1. pom.xml : 라이브러리 의존성
- Spring Security 적용을 위해 spring-boot-starter-security를 추가
- JWT 생성/파싱을 위해 jjwt 라이브러리를 추가
- JJWT는 최신 버전에서 jjwt-api, jjwt-impl, jjwt-jackson으로 분리된 구조를 사용
- 과거에는 jjwt 단일 의존성을 사용할 수 있었지만, 최신 환경(Spring Boot 3 이상)에서는 분리된 모듈 방식 사용을 권장
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JJWT (최신 버전은 3개 모듈로 분리) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>
| 의존성 |
설명 |
| spring-boot-starter-security |
Spring Security 기본 기능 제공 |
| jjwt-api |
JWT 생성/파싱 API |
| jjwt-impl |
JWT 실제 구현체 |
| jjwt-jackson |
JWT Payload JSON 처리 |
2. application.properties 설정
- JWT 서명에 사용할 비밀키와 만료시간을 properties에 설정
jwt.secret=abcdefghijklmnopqrstuvwxyz123456
jwt.expiration=3600000
| 속성 |
설명 |
| jwt.secret |
JWT 서명에 사용할 비밀키 |
| jwt.expiration |
토큰 만료시간(밀리초), 예: 3600000 = 1시간 |
3. JwtTokenProvider 클래스
- 토큰 관련 공통 기능을 모아두는 역할
- JWT 생성, JWT에서 사용자 정보 추출, JWT 유효성 검사, HTTP 요청 헤더에서 토큰 추출 등
@Component
public class JwtTokenProvider {
private final Key key;
private final long tokenValidTime;
private static final String AUTH_HEADER = "Authorization";
private static final String TOKEN_PREFIX = "Bearer ";
public JwtTokenProvider(
@Value("${jwt.secret}") String secretKey,
@Value("${jwt.expiration}") long tokenValidTime
) {
this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
this.tokenValidTime = tokenValidTime;
}
// JWT 생성
public String createToken(String username, String role) {
Date now = new Date();
Date expiry = new Date(now.getTime() + tokenValidTime);
return Jwts.builder()
.subject(username)
.claim("role", role)
.issuedAt(now)
.expiration(expiry)
.signWith(key)
.compact();
}
// JWT에서 Authentication 생성
public Authentication getAuthentication(String token) {
String username = getUsername(token);
String role = getRole(token);
return new UsernamePasswordAuthenticationToken(
username,
null,
List.of(new SimpleGrantedAuthority(role))
);
}
// 토큰에서 username 추출
public String getUsername(String token) {
return getClaims(token).getSubject();
}
// 토큰에서 role 추출
public String getRole(String token) {
return getClaims(token).get("role", String.class);
}
// 토큰 유효성 검사
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// HTTP 요청 헤더에서 토큰 추출
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTH_HEADER);
if (bearerToken != null && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
// Claims 추출
private Claims getClaims(String token) {
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
}
| 구분 |
코드 / 메서드 |
설명 |
| Key 생성 |
Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)) |
문자열 형태의 secretKey를 JWT 서명에 사용할 Key 객체로 변환 |
| 토큰 생성 |
createToken(String username, String role) |
사용자 ID와 권한을 담아 JWT 생성 (subject, claim, issuedAt, expiration 포함) |
| 인증 객체 생성 |
getAuthentication(String token) |
JWT에서 사용자 정보 추출 후 Authentication 객체 생성 |
| 토큰 추출 |
resolveToken(HttpServletRequest request) |
Authorization: Bearer ... 헤더에서 실제 토큰 문자열만 추출 |
3. JwtAuthenticationFilter 클래스
- 매 요청마다 실행되응 필처
- 토큰을 확인하고 유효하면 사용자 정보를 꺼내서 Authentication 객체 생성해 SecurityContextHolder에 저장
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsername(token);
String role = jwtTokenProvider.getRole(token);
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
| 구분 |
SecurityContext |
Session |
| 저장 위치 |
서버 메모리 (ThreadLocal) |
서버 메모리/스토리지 |
| 유지 범위 |
현재 요청 동안만 |
여러 요청 동안 유지 |
| 목적 |
지금 요청의 인증 정보 |
로그인 상태 유지 |
| JWT에서 사용 |
⭕ |
❌ (JWT는 세션 불필요) |
4. Securityconig
- 구현한 JwtAuthenticationFilter 등록
- JWT 방식은 보통 서버 세션을 사용하지 않으므로, 세션 정책을 STATELESS로 설정(JSESSIONID 생성되지 않음)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
| 구분 |
설정 코드 |
설명 |
| CSRF 비활성화 |
csrf.disable() |
JWT는 세션 기반 인증이 아니므로 CSRF 방어가 필요 없어 비활성화 |
| 세션 정책 |
SessionCreationPolicy.STATELESS |
서버에 인증 정보를 저장하지 않고, 매 요청마다 JWT로 인증 처리 |
| 필터 등록 |
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) |
JWT 필터를 Security 필터 체인에 등록하고, 기존 인증 필터보다 먼저 실행 |
| [Spring] REST API 구현 (Spring Boot, JPA, H2)과 Content Negotiation(콘텐츠 협상) (0) |
2026.04.07 |
| [ Spring ] REST API란? – 설계 원칙, HTTP 메서드, 상태코드 정리 (0) |
2026.04.07 |
| [JWT] JWT(JSON Web Token)란? 구조(Header, Payload, Signature)와 인증 흐름, Access Token & Refresh Token, 토큰 저장 방식 (0) |
2026.04.06 |
| [Spring Security] Spring Security 구현 - FilterChain, UserDetailsService, PasswordEncoder, CSRF/CORS (1) |
2026.04.06 |
| [Spring Security] Spring Security란? SecurityFilterChain과 인증 흐름 (0) |
2026.04.06 |