학습 목표
1. JWT 를 스프링 시큐리티에 적용하여 인증 인가 처리하는 방법 이해하기
2. 정규표현식 이해하기
1. 스프링 시큐리티 : JWT 로그인
스프링 시큐리티에서 기본적으로 제공하는 세션방식이 아니라 JWT를 어떻게 시큐리티에 적용할 수 있을까?
JWT는 스프링 시큐리티의 기본 인증 방식과 다르게 동작하기 때문에, 스프링 시큐리티의 기본 동작을 커스텀해야 한다.
🔍 그렇다면 Spring Security의 어떤 부분을 왜 커스텀하는 걸까?
Spring Security는 기본적으로 세션/폼 기반 인증을 제공한다.
사용자가 HTML 폼에 아이디와 비밀번호를 입력하고 서버에 제출하면, Spring Security가 이 폼 데이터를 받아서 인증을 처리하는 방식이다.를
JWT(JSON Web Token)는 API(Application Programming Interface)기반 인증으로, 서버로 JSON 형식의 데이터를 보내서 로그인하고, 서버는 상태를 저장하지 않는(Stateless) 방식으로 인증을 처리해야 한다. 따라서 세션과 쿠키를 사용하지 않는 JWT를 Spring Security의 기본 인증 처리방식으로 처리할 수 없으므로 커스텀 필터와 설정이 필요하다.
| 특징 | JWT | Spring Security |
| 상태 관리 | Stateless (무상태) | Stateful (상태유지) |
| 인증 수단 | 토큰 (Token) | 세션 (Session) |
| 동작 방식 | 요청마다 토큰을 헤더에 담아 전송 | 로그인 시 서버에 세션 생성 후 유지 |
| 확장성 | 분산 환경에 유리 (서버 확장 용이) | 분산 환경에 불리 (세션 공유 문제) |
// 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
커스텀 필터 등록 예시 코드
package com.sparta.springauth.config;
import com.sparta.springauth.jwt.JwtAuthorizationFilter;
...(생략)
@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
public class WebSecurityConfig {
private final JwtUtil jwtUtil; //Filter에 넣어줄 객체들
private final UserDetailsServiceImpl userDetailsService; //Filter에 넣어줄 객체들
private final AuthenticationConfiguration authenticationConfiguration; //Authentiication Manager 생성에 필요
public WebSecurityConfig(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService, AuthenticationConfiguration authenticationConfiguration) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
this.authenticationConfiguration = authenticationConfiguration;
}
//Authentication 매니저 만들고 등록하기
//AuthenticationManager는 바로 만들어지는 것이 아닌, AuthenticationConfiguration을 통해 만들어지므로 Bean을 직접수동등록 해야함
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
//인증 필터 등록 메서드
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
//시큐리티 필터 체인에 우리가 만든 필터 끼워넣기!
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable());
// 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
.requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);
http.formLogin((formLogin) ->
formLogin
.loginPage("/api/user/login-page").permitAll()
);
// 필터 관리
//인가를 먼저
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
//인증필터 전에 우리가 만든 필터(jwtAuthentication)를 끼워넣을 것
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
스프링 시큐리티 / JWT를 이용한 로그인 인증의 전체 흐름
- 서버는 JWT 발급 + 검증 역할
- 클라이언트는 페이지 이동 및 토큰 전달 역할을 맡는 구조.
1. 클라이언트의 로그인 요청
- 이벤트 발생: 사용자가 로그인 버튼을 누르면, 프론트엔드 자바스크립트 함수가 실행됨.
- 요청 전송: AJAX를 통해 아이디/비밀번호를 담은 JSON 데이터를 POST 요청으로 백엔드에 전달.
2. 서버의 인증 및 JWT 발급
(1) 필터 체인 진입
- 서버에 요청이 도착하면 Spring Security의 필터 체인이 먼저 실행됨.
- 로그인 요청을 처리하는 커스텀 인증 필터가 요청을 가로채고, 요청 본문의 JSON 데이터를 로그인 요청 DTO 객체로 변환(역직렬화)함.
💡 역직렬화: JSON → 자바 객체로 변환하는 과정
(2) 인증 시도
- 변환된 로그인 요청 데이터를 바탕으로 인증 토큰 객체를 생성하고, 이 인증 정보를 인증 관리자(Authentication Manager) 에게 전달하여 실제 사용자 인증을 수행.
(3) 인증 성공 → JWT 발급
- 인증이 성공하면, 필터의 인증 성공 처리 메서드가 실행됨.
- 이 메서드는 JWT 생성 유틸리티 클래스를 사용하여 사용자 정보(username, role 등)를 담은 JWT 토큰을 생성.
- 생성된 토큰은 HTTP 응답 헤더(Set-Cookie 등) 에 담겨 클라이언트로 전송됨.
3. 클라이언트의 페이지 이동
(1) AJAX 응답 처리
- 클라이언트는 서버로부터 200 OK 응답을 받음.
- AJAX의 성공 콜백 함수가 실행됨.
(2) 페이지 이동
- 콜백 함수 내부에서 window.location.href = ... 코드로 브라우저 URL을 메인 페이지로 변경.
- 브라우저는 새로운 페이지를 요청하면서, 이전에 받은 JWT 쿠키를 자동으로 포함해 서버로 전송.
4. 서버의 인가 및 자원 반환
(1) JWT 인증 필터 실행
- 새로운 요청은 Spring Security의 JWT 검증 필터(인가 담당 필터) 에 의해 가로채짐.
- 이 필터는 요청당 한 번만 실행되는 구조로 동작.
(2) 토큰 검증
- JWT 검증 필터는 요청에 포함된 토큰을 꺼내고, JWT 검증 유틸리티 클래스를 통해 서명 및 만료 여부를 확인.
(3) 인증 객체 설정
- 토큰이 유효하다면, 사용자 정보를 기반으로 인증 객체(Authentication) 를 생성하고, 이를 보안 컨텍스트(SecurityContextHolder) 에 저장해 "이 사용자는 인증됨"을 프레임워크에 알림.
(4) 요청 처리 및 자원 반환
- 인증이 완료된 요청은 필터 체인을 통과하여 컨트롤러에 도달.
- 컨트롤러는 메인 페이지에 해당하는 리소스를 반환.
- 최종적으로 사용자는 로그인된 상태로 페이지를 보게 됨.
2. 정규 표현식
정규표현식(Regular Expression)이란?
- 정규표현식은 특정 규칙을 가진 문자열의 집합을 표현하는 데 사용되는 형식 언어
- 복잡한 문자열 패턴을 간결하게 표현하고, 문자열에서 원하는 패턴을 탐색, 추출, 대체할 때 사용
정규표현식의 핵심 요소들
1. 메타 문자(Meta Characters)
| . | 모든 단일 문자 (줄바꿈 문자 제외) | a.c → abc, adc (o) ac (x) |
| ^ | 문자열의 시작 | ^abc → abcde (o) xyzabc (x) |
| $ | 문자열의 끝 | abc$ → xyzabc (o) abcde (x) |
| [] | 괄호 안의 문자 중 하나 | [abc] → a, b, c 중 하나 |
| [^] | 괄호 안의 문자를 제외한 모든 문자 | [^abc] → d, e, f 등 |
| () | 그룹화 | (ab)c → abcabc에서 (ab) 패턴을 찾을 수 있음 |
| | | OR (또는) | a|b → a 또는 b |
| \ | 이스케이프 (특수 문자를 일반 문자로) | a\.b → a.b (o) acb (x) |
2. 수량자(Quantifiers)
| * | 0회 이상 | a* → "", a, aa... |
| + | 1회 이상 | a+ → a, aa... (o) "" (x) |
| ? | 0회 또는 1회 | a? → "", a |
| {n} | 정확히 n회 | a{3} → aaa |
| {n,} | n회 이상 | a{2,} → aa, aaa... |
| {n,m} | n회 이상 m회 이하 | a{2,4} → aa, aaa, aaaa |
3. 자주 사용되는 패턴(Shorthands)
| \d | Digit. 모든 숫자와 동일 ([0-9]) | \d+ → 123, 456 |
| \D | 숫자를 제외한 모든 문자 | \D+ → abc, !@# |
| \w | Word. 모든 알파벳, 숫자, 언더스코어 ([a-zA-Z0-9_]) | \w+ → a1_b2 |
| \W | \w를 제외한 모든 문자 | \W+ → !@#$% |
| \s | Space. 모든 공백 문자 ([ \t\n\r\f]) | a\sb → a b |
| \S | \s를 제외한 모든 문자 | a\Sb → a_b |
사용 예시
(1) 전화번호
// "010-"로 시작하고, [숫자4자리][-][숫자4자리] 끝
"^010-\\d{4}-\\d{4}$"
(2) 이메일
//영어 대소문자 혹은 숫자 혹은 특수문자(_!#$%&'*+/=?{|}~^.-`) 1개 이상 + @ + 영어대소문자/숫자/특수문자(.-) 1개 이상
"^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"
오늘의 코멘트
스프링 시큐리티와 JWT는 서로의 기능을 대체하는 관계인 줄 알았는데, 알고보니 사실 다른 역할을 하는 보완적 관계라는 것을 알게 되었다. 또 필터를 커스텀하고, 중간에 필터들을 끼워넣다 보니 헷갈린 부분이 많았는데, http 통신의 흐름과 인증 인가의 흐름을 정확히 이해해야 원하는 기능을 정확히 구현할 수 있다는 것을 깨달았다.
'SPRING > Security' 카테고리의 다른 글
| JWT 토큰 생성 과정 - Claims에 관하여 (0) | 2025.11.19 |
|---|---|
| JWT 기본구조 알아보기 (0) | 2025.10.29 |
| 스프링 시큐리티 흐름 분석: DelegatingFilterProxy와 인증 체계 이해하기 (0) | 2025.10.28 |
| Spring Security 세션 Redis 저장 시 SerializationException 발생 원인과 해결 (0) | 2025.10.27 |
| 250918(목) 스프링 시큐리티의 폼 로그인 인증 이해하기 (0) | 2025.09.18 |