Spring Boot는 Java 기반의 애플리케이션 개발을 간소화하는 프레임워크로, 인증과 권한 부여(Authentication and Authorization)에서도 자주 사용됩니다. JSON Web Token(JWT)은 인증 및 정보 교환 목적으로 사용되는 컴팩트하고 독립적인 토큰입니다. Spring Boot에서 JWT를 사용하는 것은 인증/인가를 처리하는데 매우 효과적입니다.
JWT는 JSON 포맷으로 데이터를 포함하며, 주로 다음 세가지 파트로 구성됩니다.
Authorization: Bearer <JWT>
다음은 Spring Boot로 JWT 인증 시스템을 구현하는 기본단계입니다.
pom.xml
에 필요한 의존성을 추가합니다.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
JWT 토큰을 생성하는 유틸리티 클래스를 작성합니다.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtTokenUtil {
private static final String SECRET_KEY = "your_secret_key";
private static final long EXPIRATION_TIME = 86400000; // 1 day
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public static boolean isTokenValid(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
JWT를 검증하는 필터를 구현합니다.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwtToken = authorizationHeader.substring(7);
username = JwtTokenUtil.extractUsername(jwtToken);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// UserDetailsService에서 사용자 정보를 로드 (필요시 구현)
UserDetails userDetails = /* load user from DB */;
if (JwtTokenUtil.isTokenValid(jwtToken)) {
// SecurityContext에 인증 정보 설정
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
Spring Security에 JWT 필터를 추가합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/authenticate", "/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SECRET_KEY
는 반드시 안전하게 보호하고, 필요시 키 로테이션을 고려하세요.