🔐 Spring Security 정리 (JWT + 커스텀 필터 중심)
1) 스프링 시큐리티 개요
- 정의: Spring 기반 애플리케이션에 인증(Authentication) 과 인가(Authorization) 를 제공하는 라이브러리
- 대표 기능: 로그인/로그아웃, 소셜 로그인, CSRF 방어, URL 접근 제어, 필터 체인 기반 보안
2) 의존성
implementation 'org.springframework.boot:spring-boot-starter-security'
- 추가 즉시 기본 보안 필터가 활성화됨(모든 요청이 인증 필요 상태로 바뀜).
3) 보안 설정 — SecurityConfig
3-1. 전체 코드
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/user/info").hasAnyRole("ADMIN", "USER")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/**").permitAll()
);
// [2.2] CSRF 설정
http.csrf(csrf -> csrf.disable());
// [2.3] 세션 정책
http.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// [2.4] 커스텀 JWT 필터를 UsernamePasswordAuthenticationFilter 이전에 삽입
http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
// [2.5] 위 설정을 바탕으로 SecurityFilterChain 빌드
return http.build();
}
}
3-2. 주요 API & 매개변수 설명
| 메서드 | 설명 |
|---|
authorizeHttpRequests() | URL별 접근 권한(인가) 설정 |
.requestMatchers("/api/admin/**") | 특정 URL 패턴 지정 |
.hasRole("ADMIN") | ROLE_ADMIN 권한 보유자만 접근 허용 |
.hasAnyRole("ADMIN","USER") | 두 권한 중 하나라도 있으면 허용 |
.permitAll() | 인증 없이 접근 허용 |
csrf().disable() | 개발용: CSRF 방어 비활성화 |
sessionManagement().sessionCreationPolicy(STATELESS) | JWT 기반: 세션 미사용 |
addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) | 커스텀 JWT 필터를 인증 필터보다 먼저 실행 |
4) 커스텀 JWT 필터 — JwtAuthFilter
@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("loginUser".equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
}
if (token != null && jwtService.checkToken(token)) {
String uid = jwtService.getUid(token);
String urole = jwtService.getUrole(token);
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
uid,
null,
List.of(new SimpleGrantedAuthority("ROLE_" + urole))
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
filterChain.doFilter(request, response);
}
}
5) 주요 클래스 / 메서드 상세 설명
| 항목 | 설명 |
|---|
OncePerRequestFilter | 요청마다 한 번만 실행되는 스프링 필터 베이스 클래스 |
doFilterInternal() | 요청/응답/필터 체인을 받아 직접 인증 로직을 구현 |
HttpServletRequest | 쿠키, 헤더 등 요청 데이터 접근 |
FilterChain | 다음 필터 호출 (doFilter() 호출 필수) |
UsernamePasswordAuthenticationToken | 인증 객체로, principal(사용자), credentials(비밀번호), 권한 정보 포함 |
SecurityContextHolder | 현재 요청 스레드의 보안 컨텍스트 저장소. 인증 성공 시 Authentication을 저장 |
SimpleGrantedAuthority("ROLE_" + role) | 문자열 권한을 스프링 시큐리티 권한 객체로 변환 |
addFilterBefore() | 커스텀 필터를 기존 시큐리티 필터 체인에 삽입 |
SessionCreationPolicy.STATELESS | 세션 사용하지 않고 JWT로만 인증 상태 유지 |
6) 실무 팁
- STATELESS 정책: 세션 저장소가 없으므로 서버 확장성(Scale-out)에 유리
- CSRF 설정: 쿠키 기반 JWT 사용 시
HttpOnly + Secure + SameSite 설정 병행
- 권한 규칙: 항상 대문자(
ADMIN, USER) 사용
- @PreAuthorize("hasRole('ADMIN')"): 메서드 단위 권한 검증 시 사용
- 토큰 위치: Authorization 헤더(
Bearer <JWT>) 방식도 가능
7) 빠른 레퍼런스
| 항목 | 핵심 내용 |
|---|
| URL 인가 | authorizeHttpRequests().requestMatchers().hasRole()/permitAll() |
| CSRF | 개발 시 비활성화, 운영 시 예외 지정 |
| 세션 | SessionCreationPolicy.STATELESS |
| 필터 삽입 | addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) |
| 인증 주입 | SecurityContextHolder.getContext().setAuthentication(auth) |
| 권한 타입 | SimpleGrantedAuthority("ROLE_ADMIN") |