인증과 인가 개념 및 보안 흐름
1. 인증과 인가
1) 인증
- 사용자가 누구인지 확인하는 절차
- ex) 아이디 비밀번호, 스마트폰 잠금해제, 신분증 출입
- 인증 = 로그인
- 신원 확인 (로그인)
2) 인가
- 인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지 확인하는 절차
- 예시) 일반 사용자와 관리자 권한, 유료 구독자, 특정 사무실 출입권한
- 인가 = 권한
- 권한 부여 (접근 제어)
2. 세션과 JWT
1) 세션
- 서버가 사용자의 로그인 상태를 기억하는 방식
- 동작 흐름
1. 사용자 로그인
사용자가 아이디/비밀번호로 로그인을 시도
2. 세션 정보 생성 및 저장:
서버는 로그인 정보가 유효하면, 사용자를 위한 고유한 세션 ID를 생성하고
이 정보를 서버 메모리나 데이터베이스에 저장(의미없는 값)
3. 세션 ID 전송:
서버는 생성된 세션 ID를 클라이언트(브라우저)에게 보내고, 브라우저는 보통 이 ID를 쿠키에 저장
4. 요청과 검증:
이후 클라이언트는 서버에 요청을 보낼 때마다 쿠키에 담긴 세션 ID를 함께 보냄
서버는 이 세션 ID를 받아 저장된 세션 정보와 비교하여 사용자를 식별하고 요청을 처리
- 장점: 서버에서 모든 세션 정보를 관리하므로 보안에 유리 / 특정 사용자를 강제로 로그아웃시키는 등 제어가 쉬움
쿠키에 세션 ID만 저장하므로 클라이언트에 민감한 정보가 남지 X - 단점: 사용자가 많아질수록 서버의 메모리나 DB 부하 커짐 / 확장성 문제
2) JWT
- 서버가 로그인 상태를 저장하지 않는(stateless) 인증 방식 / 서버 상태 X = stateless
- 서버가 상태를 저장하지 않으므로 JWT 자체에 모든 인증 정보가 담겨있음
- 동작 흐름
1. 사용자 로그인:
사용자가 아이디/비밀번호로 로그인을 시도
2. 토큰 생성 및 전송:
서버는 로그인 정보가 유효하면, 사용자의 정보와 권한, 만료 시간 등을 담은
암호화된 토큰(Access Token)을 생성하여 클라이언트에게 보냄
서버는 이 토큰을 저장X
3. 토큰 저장:
클라이언트는 전달받은 토큰을 로컬 스토리지나 쿠키에 저장
4. 요청과 검증:
이후 클라이언트는 서버에 요청을 보낼 때마다 HTTP 헤더(쿠키포함)에 토큰을 실어 보냄
서버는 이 토큰의 서명을 검증하여 유효성을 확인하고 요청을 처리
- 장점
1. 무상태(Stateless) 및 확장성
서버가 토큰을 저장하지 않으므로 서버의 부하가 줄고, 여러 서버로 확장하기 용이
서버가 토큰을 저장하지 않아도 유저를 식별할 수 있는 이유는,
JWT 자체에 모든 인증 정보가 담겨있기 때문
2. 유연성:
웹뿐만 아니라 모바일 앱 등 다양한 클라이언트 환경에서 사용하기 편리
- 단점: 토큰이 탈취되면 만료될 때까지 악용 / 세션 ID보다 토큰 크기가 더 큼
3. 웹 애플리케이션 보안 흐름
1) 서버 > 발급
- 세션방식: 유니크한 세션키
- JWT: JWT(토큰)
2) 서비스 이용 시나리오로 보는 보안 흐름(마이페이지 확인)
- 인증 (로그인)
1. 사용자
서비스에 접속하여 아이디, 비번 입력 후 로그인
2. 처리
1) 입력된 아이디, 비번이랑 회원 정보가 일치하는지 확인
2) 신원 확인
3) 로그인 성공 증표로 '출입증' 발급 / 세션키 or JWT
3. 결과
로그인 성공
- 인가 (권한 확인)
1. 사용자
로그인 후 마이페이지 클릭
2. 서비스 처리
1) 마이페이지는 개인정보가 담긴 페이지
2) 출입증을 보고 본인이 맞는지 다시 확인
3) 본인이 아니면 접근 제한 = 볼 권한 X
3. 결과
본인이 맞으므로 페이지 볼 권한이 있다고 판단
- 응답 (결과 보여주기)
1. 사용자
페이지 로딩 기다리기
2. 처리
1) 모든 권한 확인이 끝났으므로 데이터 베이스에서 개인정보 가져옴
2) 데이터를 사용자 컴퓨터로 전송
3. 결과
사용자 화면에 이메일, 연락처 등 개인정보가 담긴 마이페이지 표시
HTTP 헤더와 쿠키
1. HTTP 복습

1) 시작 줄 (Start Line)
- 첫 줄
- 요청의 종류(GET, POST 등)
- 응답의 성공 여부(`200 OK`, `404 Not Found` 등)
2) 헤더
- 보내는 사람, 받는 사람, 내용물의 종류 등 메시지를 처리하는 데 필요한 각종 설정과 데이터가 담김
3) 본문(body)
- HTML 문서, 이미지 파일, JSON 데이터 등이 여기에 담김
4) HTTP의 특징 - Stateless
- 로그인 필요
- 인증방식: 세션, JWT
- 세션키 or JWT를 매 요청마다 포함해서 보내야 특징 극복하고 로그인 상태 유지 가능
2. HTTP Header
1) 구성: Key: Value의 형태
- 예시) name: hello 라는 데이터를 Header로 보내기

- 세션 키나 JWT 헤더에 보내기 > 바디의 데이터와 분리하여 구분 가능

3. HTTP Cookie
1) HTTP Cookie
- 헤더
- key: Cookie 혹은 Set-Cookie
- Key=Value 형태 / 구분자: ;
- ex) Cookie = auth_token=xyz987654321;preferences=dark_mode
2) 요청과 응답
- 요청: Client / Key 이름이 Cookie
- 응답: Server / Key 이름이 Set-Cookie
3) Set-Cookie의 보안 설정
- Secure
- 목적: 쿠키가 HTTPS 통신일 때만 서버로 전송되도록 강제
- 해결 문제: 암호화되지 않은 HTTP 연결에서 쿠키가 탈취되는 것을 막음
- 예시) Set-Cookie: session_id=...; Secure
- HttpOnly
- 목적: 자바스크립트(document.cookie)가 쿠키에 접근하는 것을 막음
- 해결 문제: 악성 스크립트가 쿠키를 훔쳐가는 것을 막음
- 예시) Set-Cookie: session_id=...; HttpOnly
- SameSite
- 목적: 다른 도메인에서 요청을 보낼 때 쿠키 전송 여부를 결정
- 해결 문제: 사용자가 의도하지 않은 요청(ex. 비밀번호 변경)에 쿠키가 자동으로 실려 가는 것을 막음
- 예시) Set-Cookie: session_id=...; SameSite=Lax
Session과 JWT
1. Session
1) 개념
- 세션 인증은 서버가 사용자의 로그인 상태를 기억하고 관리하는 방식
2) 핵심 특징: Stateful (상태 유지)
- 로그인 > 서버는 무작위의 유니크한 Session ID(=Session Key)를 생성 > 키 값으로 하여 로그인 정보 저장
/ Session ID를 클라이언트에게 반환 - 클라이언트 요청 > Session ID를 헤더(or 쿠키)에 넣어 서버에 요청
> 서버는 Session ID를 키로 하여 로그인 정보를 가져와 유저를 식별
2. JWT
1) 개념
- JWT는 인증에 필요한 정보들을 암호화시킨 JSON 객체를 사용하여 인증을 처리하는 방식
2) 핵심 특징: Stateless (무상태)
- 로그인을 하면 서버는 JWT에 생성하여 이를 클라이언트에게 반환
- 클라이언트는 요청 시 JWT를 헤더(or 쿠키)에 넣어 서버에 요청하면, 서버는 JWT에 담겨있는 정보로 유저를 식별
3) 구성 요소
- aaaa.bbbb.cccc와 같이 점(.)으로 구분된 세 부분으로 구성
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- 헤더(헤더): JSON
- 토큰의 유형(typ: JWT)과 서명(Signature)을 생성할 때 사용할 알고리즘(alg: HS256, RS256 등) 정보가 담겨있음
- JSON 객체를 Base64Url 방식으로 인코딩한 값
{
"alg": "HS256",
"typ": "JWT"
}
- 페이로드 (Payload):JSON
- 토큰에 담을 실제 정보 / 클레임(Claim) 이 들어 있음
클레임은 사용자의 정보(이름, 아이디, 권한 등)나 토큰의 속성(만료 시간, 발급자 등)을 나타냄 - 페이로드 역시 Base64Url 방식으로 인코딩 / 암호화가 아니므로 누구나 디코딩하여 내용을 볼 수 있다는 것
(비밀번호 정보 X)
- 토큰에 담을 실제 정보 / 클레임(Claim) 이 들어 있음
{
"sub": "user123", // 토큰 제목 (사용자 ID)
"name": "Spartan Kim", // 사용자 이름
"iat": 1516239022, // 토큰 발급 시간
"exp": 1516242622 // 토큰 만료 시간
}
- 서명 (Signature)
- JWT의 무결성을 보장하는 가장 중요한 부분
- Header와 Payload를 인코딩한 값과 서버만 알고 있는 비밀 키(Secret Key)를 헤더에 지정된 알고리즘(예: HS256)으로 암호화하여 생성
- 서버는 클라이언트로부터 받은 토큰의 헤더와 페이로드를 동일한 비밀 키와 알고리즘으로 암호화하여 서명 값을 다시 만듬 > 토큰의 기존 서명 값과 일치하는지 확인하여 토큰의 위변조 여부를 판단
3. Access Token과 Refresh Token
1) 문제점
- 토큰의 유효기간을 짧게 하면 > 보안에는 좋지만, 사용자가 로그인을 너무 자주 해야 해서 불편
- 토큰의 유효기간을 길게 하면 > 사용자는 편하지만, 한 번 탈취되면 오랜 시간 동안 악용될 수 있어 위험
- 문제 해결을 위해 Access Token과 Refresh Token 토큰 함께 사용
2) Access Token
- 실제 자원에 접근할 때 사용하는 토큰 / JWT
- 유효기간을 짧게 설정
- 탈취되더라도 짧은 시간만 유효하기 때문에 피해를 최소화
3) Refresh Token
- 새로운 Access Token을 발급받기 위해 사용하는 토큰
- Access Token보다 유효기간을 길게 설정
- 평소에는 사용하지 않고, Access Token이 만료되었을 때만 사용
- 보통 데이터베이스와 같은 안전한 곳에 저장하여 관리
ArgumentResolver
1. 개념
- 컨트롤러의 메서드 파라미터에 들어오는 데이터를 자동으로 처리하고 바인딩해주는 도구
2. ArgumentResolver 구현 방법
1) 1단계: HandlerMethodArgumentResolver 인터페이스 구현
- 인터페이스를 구현하는 클래스 생성 / 두 개의 핵심 메소드 정의
2) 2단계: supportsParameter() 메소드 구현 (언제 실행할지 결정)
- ArgumentResolver의 실행 조건을 정의하는 부분
- 스프링은 컨트롤러의 모든 파라미터를 하나씩 이 메소드에 넘겨보면서 "이 파라미터를 네가 처리할 수 있니?"라고 물어봄
- true: 해당 파라미터는 이 ArgumentResolver가 처리해야 할 대상으로 지정
- false: 스프링은 다른 ArgumentResolver에게 다시 물어봄
3) 3단계: resolveArgument() 메소드 구현 (무엇을 만들어줄지 결정)
- true를 반환했을 때만 이 메소드가 실행
- 파라미터에 실제로 주입될 값을 만들어서 반환하는 로직을 작성
- HttpServletRequest에 접근하여 세션이나 쿠키, 헤더 정보를 가져올 수 있음
- 가져온 정보를 바탕으로 DB를 조회하거나 객체를 생성하는 등의 작업을 수행
- 최종적으로 만들어진 객체를 반환하면, 스프링이 해당 컨트롤러 파라미터에 그 값을 넣어줌
4) 4단계: WebMvcConfigurer에 등록
- ArgumentResolver를 스프링이 인식하고 사용할 수 있도록 설정 파일에 등록
- WebMvcConfigurer 인터페이스를 구현한 설정 클래스에서 addArgumentResolvers 메소드를 오버라이드하여 우리가 만든 리졸버를 추가
3. Custom @RequestParam과 @PathVariable 구현 실습
1) @CustomRequestParam 구현
- @CustomRequestParam 어노테이션 생성
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomRequestParam {
String DEFAULT_NONE = ValueConstants.DEFAULT_NONE;
String value() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
- 1~3단계
public class CustomRequestParamArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CustomRequestParam.class);
}
@Override @SuppressWarnings("all")
public Object resolveArgument(
@NonNull MethodParameter parameter,
ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
CustomRequestParam annotation = parameter.getParameterAnnotation(CustomRequestParam.class);
String paramName = annotation.value().isEmpty() ? parameter.getParameterName() : annotation.value();
String value = webRequest.getParameter(paramName);
if (value == null && !CustomRequestParam.DEFAULT_NONE.equals(annotation.defaultValue())) {
value = annotation.defaultValue();
}
return value == null ? null : new SimpleTypeConverter().convertIfNecessary(value, parameter.getParameterType());
}
}
- 4단계
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CustomRequestParamArgumentResolver());
}
}
2) CustomPathVariable
- 준비
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomPathVariable {
String value() default "";
boolean required() default true;
}
- 1~3단계
public class CustomPathVariableArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CustomPathVariable.class);
}
@Override @SuppressWarnings("all")
public Object resolveArgument(
@NonNull MethodParameter parameter,
ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
CustomPathVariable annotation = parameter.getParameterAnnotation(CustomPathVariable.class);
String variableName = annotation.value().isEmpty() ? parameter.getParameterName() : annotation.value();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Map<String, String> uriTemplateVars =
(Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
String value = uriTemplateVars.get(variableName);
return value == null ? null : new SimpleTypeConverter().convertIfNecessary(value, parameter.getParameterType());
}
}
- 4단계
@Configuration
public class CustomWebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CustomRequestParamArgumentResolver());
resolvers.add(new CustomPathVariableArgumentResolver());
}
}'🔌 SPARTA > Courses' 카테고리의 다른 글
| JWT와 Filter (0) | 2026.03.03 |
|---|---|
| [숙련 Spring] Spring Data JPA (0) | 2026.02.18 |
| [숙련 Spring] Spring Boot 활용하기 (0) | 2026.02.10 |
| 스탠다드반 3회차: 실전 SQL (0) | 2026.02.09 |
| 스탠다드반 2회차: 데이터 베이스 모델링 (0) | 2026.02.05 |