
CSRF/세션 설정, CSRF가 필요한 JWT 케이스, AuthenticationManager vs SecurityContextHolder 기준 정리
JWT 기반 인증을 붙이기 전에 SecurityConfig로 보안 기본 뼈대를 잡았다.
그리고 “JWT인데도 CSRF를 켜야 하는 경우”가 존재한다는 기준을 정리했고,
마지막으로 헷갈리기 쉬운 포인트인 AuthenticationManager/Provider는 언제 타고, 언제 SecurityContextHolder에 직접 넣는지를 정리했다.
JWT 기반 인증을 붙이기 위한 최소한의 SecurityFilterChain 구성은 아래처럼 시작했다.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm ->
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
CSRF 비활성화: JWT를 Authorization 헤더(Bearer) 로 보내는 전형적인 API 구조에서는 CSRF 리스크가 크게 줄어들어 disable을 많이 한다.
STATELESS: 서버가 세션에 인증 상태를 저장하지 않고 요청마다 JWT로 인증하는 “무상태 API”를 선언한다.
/api/auth/** 허용: 회원가입/로그인/재발급 같은 인증 진입점은 인증 없이 접근 가능해야 한다.
나머지 요청은 인증 필요: 결국 JWT 필터(또는 인증 로직)가 SecurityContext에 인증 정보를 넣어줘야 통과된다.
오늘 정리한 핵심 결론은 이거였다.
JWT라고 해서 CSRF가 항상 불필요한 건 아니다.
브라우저가 인증 정보를 “자동으로 전송”하는 구조(쿠키 기반)라면 JWT여도 CSRF를 고려해야 한다.
JWT(특히 Refresh Token)를 HttpOnly 쿠키로 저장하는 경우
Access는 헤더로 보내더라도 Refresh는 쿠키로 운영하는 경우
/auth/refresh 같은 재발급 엔드포인트가 CSRF 타겟이 될 수 있어 Origin/Referer 체크, SameSite 정책, CSRF 토큰 등을 함께 고려하는 팀이 많다.반대로,
여기가 오늘 가장 헷갈렸던 포인트라 기준을 명확히 잡았다.
AuthenticationManager/Provider를 탄다
→ “인증 검증(자격 증명 확인)을 시큐리티 표준 파이프라인에 맡긴다”
SecurityContextHolder에 직접 담는다
→ “내가 이미 검증했으니 결과만 컨텍스트에 꽂는다”
가장 전형적인 사용처다.
AuthenticationManager.authenticate(...) 호출
내부 Provider(예: DaoAuthenticationProvider)가
UserDetailsService로 사용자 로딩
PasswordEncoder로 비밀번호 검증
계정 잠금/비활성 같은 정책 처리
성공 시 authenticated Authentication 반환
✅ 정리: “비밀번호 같은 자격 증명을 검증해야 하면 AuthenticationManager를 타는 게 자연스럽다.”
JWT도 Provider로 처리하도록 설계할 수 있다.
JwtAuthenticationToken 생성 → AuthenticationManager.authenticate(token)
JwtAuthenticationProvider에서 검증 + 권한 구성 → authenticated 반환
인증 방식이 여러 개로 늘어날 가능성이 크고(확장성)
인증 로직/예외 처리/감사로그를 중앙화하고 싶을 때
JWT 기반 API에서 흔히 이렇게 한다.
토큰 추출
서명/만료 검증
(필요시) 사용자/권한 로딩
Authentication 생성 후 SecurityContextHolder에 set
JWT는 “비밀번호 확인”이 아니라 “서명/만료 검증” 중심
매 요청마다 Provider 체인을 태우지 않아도 되고 구현이 단순하다
JWT + Stateless API에서는 보통 아래 조합이 가장 흔하고 무난하다.
로그인(아이디/비밀번호): AuthenticationManager 사용
로그인 이후 요청(JWT): 필터에서 검증 후 SecurityContextHolder 직접 세팅
필터에서 예외가 터졌을 때 401/403 응답이 지저분해짐
→ EntryPoint/AccessDeniedHandler 또는 필터 예외 흐름 정리 필요
매 요청마다 사용자 조회로 DB 부하가 커질 수 있음
→ 토큰 클레임에 role 포함/캐싱 고려
비동기/스레드 전환 시 컨텍스트 유지 문제
→ 요청 스레드 밖으로 넘어가면 별도 처리 필요