Spring Boot 환경에서 Security를 적용하고 JWT(JSON Web Token)를 이용한 인증/인가 시스템을 구축하는 전반적인 과정을 정리
초기 설정부터 주요 컴포넌트 구현, AOP와 예외 처리까지 실무적인 내용을 중심으로 다룰 것임
가장 먼저 start.spring.io 또는 IDE를 통해 프로젝트를 생성하고, 필요한 의존성을 추가
프로젝트에 필요한 핵심 라이브러리 목록
| 구분 | 라이브러리 이름 | 주요 용도 |
|---|---|---|
| 핵심 | Spring Web | 스프링 MVC, REST API 개발을 위한 필수 프레임워크 |
| Spring Boot DevTools | 코드 변경 시 자동 재시작 등 개발 편의 기능 | |
| Lombok | @Getter, @Builder 등 보일러플레이트 코드 자동 생성 | |
| 보안 | Spring Security | 인증(Authentication)과 인가(Authorization) 처리 |
| DB | Spring Data JPA / MyBatis | 데이터베이스 연동 및 ORM / SQL Mapper |
| MySQL Driver / H2 Driver | 사용할 데이터베이스에 맞는 드라이버 | |
| JWT | Java JWT (jjwt) | JWT 생성 및 검증을 위한 라이브러리 |
| 기타 | Validation | @Valid를 이용한 데이터 유효성 검사 |
| Spring AOP | 관점 지향 프로그래밍, 공통 기능 모듈화 |
WebSecurityConfigurerAdapter가 **Deprecated** 되었습니다. 이 글에서는 최신 트렌드에 맞는 컴포넌트 기반(SecurityFilterChain Bean 등록) 방식을 기준으로 설명
application.yml 기본 설정데이터베이스 연결 정보와 JWT Secret Key를 설정
spring:
# --- 데이터베이스 연결 설정 ---
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://YOUR_DATABASE_ENDPOINT:3306/YOUR_DB
username: YOUR_USERNAME
password: YOUR_PASSWORD
# --- MyBatis Mapper 위치 설정 ---
mybatis:
mapper-locations:
- /mappers/*.xml
# --- JWT Secret Key 설정 (반드시 복잡하고 긴 문자열 사용) ---
jwt:
secret: afcc85d110b13b17778ae91687bec4c479936475f18a5fa74dfd8b9058488eb32fdb0c972331e36e169b8a39e52db462c628bcb1b9cdeef80fbfbc7a25979155cb55eb7de35d226a5b78e5f59ffa6421204fd53fe67f62c9c73e58eaf6e1c63cac965c602ee59e6710185217b391e100091417bf122826182c5582f75f71fc2ebbc3695438250cd3223161333589c59fae85736afa6d83276eec071b1ef1490c97521d79417ea27944d021859465ccdcfb5b3248eb43a467e243109c7665deb55145269980fddc447b8d0c9ab4a2220da605ec7dbc932137f536fe0e7b7d2aabf64f2e418833c05ad18a1fb2c775f993e2069a9c216c2376790cef15621a5ace
Config)서버가 실행될 때 IoC 컨테이너에 등록되어 동작하는 설정 클래스들
@Configuration: 해당 클래스가 설정 파일임을 명시.
@EnableWebSecurity: Spring Security 활성화.
핵심 설정 내용:
CSRF, Form/HttpBasic 로그인 비활성화: JWT 토큰 방식을 사용하므로 csrf().disable(), formLogin().disable(), httpBasic().disable() 설정.
세션 관리: 세션을 사용하지 않는 무상태(Stateless) 방식으로 설정 (sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)).
요청별 인가 규칙: authorizeRequests()를 통해 특정 URL 패턴별 접근 권한 설정.
.antMatchers("/auth/**").permitAll(): /auth/로 시작하는 경로는 인증 없이 허용.
.anyRequest().authenticated(): 그 외 모든 요청은 인증 필요.
* 필터 추가: JWT 인증을 위한 커스텀 필터(JwtFilter)를 특정 필터(UsernamePasswordAuthenticationFilter) 앞에 추가.
핵심 설정: addCorsMappings() 메소드를 오버라이드.
allowedOrigins("*"): 모든 출처에서의 요청 허용.
`allowedMethods(""): 모든 HTTP 메소드(GET, POST 등) 허용. * allowedHeaders("*")`: 모든 HTTP 헤더 허용.
무한 루프(순환 참조) 위험이 있는 객체들(e.g., BCryptPasswordEncoder)은 별도의 @Configuration 파일에서 @Bean으로 등록하여 관리하는 것이 안전함
Stateless 환경을 위해 세션 대신 JWT를 사용한 인증 방식을 구현
| 구분 | 세션(Session) 기반 인증 | 토큰(Token) 기반 인증 (JWT) |
|---|---|---|
| 상태 저장 | 서버에 사용자 상태 저장 (Stateful) | 서버에 상태 저장 안 함 (Stateless) |
| 인증 정보 | 서버의 세션 저장소 | 클라이언트 측 (로컬 스토리지, 쿠키) |
| 확장성 | 수평적 확장이 상대적으로 어려움 | 수평적 확장이 용이 |
| 보안 | 세션 ID만 전송되어 상대적으로 안전 | 토큰 자체에 정보 포함, 탈취 시 위험 |
| 주요 사용처 | 전통적인 웹 애플리케이션 | REST API, 모바일, MSA 환경 |
Authorization 필드에 토큰을 "Bearer " 접두사와 함께 실어 보냄.JwtFilter는 요청을 받으면 헤더에서 토큰을 추출하여 유효성을 검증.SecurityContext에 인증 정보를 등록. 이후 컨트롤러는 인증된 사용자로 간주하고 요청을 처리. JwtProvider: secret 키를 이용해 토큰을 생성하고, 들어온 토큰의 유효성을 검증하고, 토큰에서 Payload를 추출하는 핵심 로직 담당.
JwtFilter: 모든 요청에 앞서 실행되는 필터. Authorization 헤더에서 Bearer 토큰을 추출하여 JwtProvider에게 검증을 위임하고, 검증 성공 시 SecurityContextHolder에 인증 정보를 등록하는 역할.
* PrincipalUser: Spring Security가 인증 과정에서 사용하는 사용자 정보 객체. UserDetails 인터페이스를 구현하며, 사용자의 ID, PW(사용 안 함), 권한(Role) 정보 등을 가짐.
DTO(Data Transfer Object)에 @Pattern, @NotBlank 등 어노테이션을 붙여 데이터 형식을 강제하고, Controller 메소드에서 @Valid와 BindingResult를 함께 사용하여 유효성 검사 결과를 처리.
@Aspect: AOP 클래스임을 명시.
@Pointcut: Aspect를 적용할 지점(메소드 등)을 표현식으로 정의.
* @Around: Pointcut으로 지정된 메소드의 실행 전과 후에 원하는 로직을 추가. ProceedingJoinPoint.proceed()를 기준으로 전/후 처리 로직을 작성.
@ExceptionHandler(CustomException.class): 특정 예외가 발생했을 때 실행될 메소드를 지정.
이를 통해 서비스 로직에서는 throw new CustomException(...)으로 예외를 발생시키기만 하면, Advice 클래스가 이를 잡아 지정된 ResponseEntity를 반환.
토큰 저장: 로그인 성공 시 서버로부터 받은 JWT를 로컬 스토리지(Local Storage)에 저장.
인증 헤더 추가: axios와 같은 HTTP 클라이언트 라이브러리에서 인터셉터(Interceptor)를 사용하여, 모든 API 요청 헤더에 로컬 스토리지의 토큰을 Authorization: Bearer ${token} 형식으로 자동으로 추가.
* 상태 관리: React-Query나 Redux 등을 사용하여 사용자 로그인 상태 및 정보를 관리.