JWT 로그인은 만들었지만, "토큰이 실제로 어느 순간에 검증되고
컨트롤러는 어떻게 로그인 사용자를 아는지"가 흐릿했다.이번 과정의 핵심은 "필터를 등록하는 것"이 아니라,
요청 한 번이 필터를 거쳐 인증되는 흐름 전체를 이해하는 것이었다.
JWT 인증은 컨트롤러가 아니라 필터 단계에서 끝난다. 흐름은 이렇다.
요청 (Authorization: Bearer xxx)
│
JwtAuthenticationFilter (OncePerRequestFilter)
1) 헤더에서 토큰 추출
2) 토큰 검증 (jwtUtil.isValid)
3) 유효하면 CustomUserPrincipal 생성
4) SecurityContextHolder에 인증 객체 심기
│
filterChain.doFilter (다음 필터 → 컨트롤러)
│
Controller (@AuthenticationPrincipal 로 사용자 꺼내 씀)
즉 필터가 "이 요청은 누구다"를 SecurityContext에 등록해두면, 그 뒤의 인가 규칙과 컨트롤러가 그 정보를 그대로 활용한다.
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) {
String token = resolveToken(request); // 1) "Bearer " 떼고 토큰 추출
if (token != null && jwtUtil.isValid(token)) { // 2) 검증
Long memberId = jwtUtil.getMemberId(token);
String email = jwtUtil.getEmail(token);
CustomUserPrincipal principal = // 3) 사용자 표현 객체
new CustomUserPrincipal(memberId, email, "USER");
UsernamePasswordAuthenticationToken authentication =
UsernamePasswordAuthenticationToken.authenticated(
principal, null,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication); // 4) 인증 객체 심기
SecurityContextHolder.setContext(context);
}
filterChain.doFilter(request, response); // 다음 단계로
}
토큰이 없거나 틀리면 아무것도 심지 않고 그냥 통과시킨다. 필터가 직접 막지 않는다는 게 포인트다(뒤의 인가 규칙이 판단).
OncePerRequestFilter인가 — 이름 그대로 요청 한 번당 필터가 딱 한 번 실행되도록 보장한다. forward/include 같은 내부 디스패치로 필터가 중복 실행돼 인증을 두 번 하는 걸 막아준다.authorizeHttpRequests의 authenticated())은 SecurityContext를 보고 통과 여부를 정한다. 그래서 인가가 판단하기 전에 인증이 끝나 있어야 한다. 그래서 addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)로 이 필터를 앞쪽에 끼웠다.JwtAuthenticationEntryPoint가 401을 낸다. 역할을 분리한 것이다.| 헷갈렸던 것 | 이해한 내용 |
|---|---|
| 컨트롤러가 로그인 사용자를 어떻게 아는가 | 필터가 SecurityContext에 심은 principal을, 컨트롤러가 @AuthenticationPrincipal로 꺼내 쓰는 것 |
createEmptyContext() + setContext() 패턴 | 기존 컨텍스트를 직접 건드리지 않고 새 컨텍스트를 만들어 교체하는 게 Spring Security 권장 방식 |
authenticated(...) 정적 팩토리 | "이미 인증 완료된" 토큰을 만드는 것(자격증명은 null, 권한만 부여) |
인증은 컨트롤러에 도달하기 전에 필터에서 이미 끝나 있고, 컨트롤러는 그 결과를 받아 쓰기만 한다는 걸 알게 됐다.
JWT 인증은 OncePerRequestFilter를 상속한 필터에서, 요청당 한 번 토큰을 꺼내 검증하고
성공하면 SecurityContextHolder에 인증 객체를 심는 방식으로 동작한다.
필터는 신원만 확인하고, 실제 차단(401)은 인가 단계와 EntryPoint가 맡는다.
덕분에 세션 없이(stateless) 매 요청을 토큰만으로 인증할 수 있다.
JWT 필터는 요청당 한 번, 토큰을 검증해
SecurityContext에 사용자를 심는다.
막는 건 필터가 아니라 인가 규칙이고, 컨트롤러는 그 결과를@AuthenticationPrincipal로 받아 쓴다.