
아래 SecurityConfig에서 설명할 테지만 Filter는 사용자 인증/인가 과정인데, Spring Security에서 자동으로 거치는 UsernamePasswordAuthenticationFilter를 거칠 때 JwtAuthenticationFilter를 먼저 거쳐 인증된 사용자인지 확인하도록 한다.
dependencies {
// Spring Security
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.security:spring-security-test")
// JWT
implementation("io.jsonwebtoken:jjwt:0.9.1")
// javax.xml.bind.DatatypeConverter 오류 해결
implementation("com.sun.xml.bind:jaxb-impl:4.0.1")
implementation("com.sun.xml.bind:jaxb-core:4.0.1")
implementation("javax.xml.bind:jaxb-api:2.4.0-b180830.0359")
}
@Getter @Setter
@NoArgsConstructor
@ToString
public class LoginDTO {
private String userID;
private String pwd;
}
@Component
@RequiredArgsConstructor
public class JwtProvider {
private String secretKey = "ERA jwt secret key";
private final long tokenValidTime = 60 * 60 * 1000L; // 유효 시간 60분
private final UserDetailsService userDetailsService;
// 객체 초기화 시 secretKey를 Base64로 encoding
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
// JWT 생성
public String createToken(String userID) { // List<String> roles
Claims claims = Jwts.claims().setSubject(userID); // JWT payload에 저장되는 정보 단위
// claims.put("roles", roles);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenValidTime))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// 인증 정보 조회
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserID(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// token에서 UserID 뽑기
public String getUserID(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
// 토큰 유효성, 만료 일자 확인
public boolean validateToken(String jwt) {
try {
if (!jwt.substring(0, "BEARER ".length()).equalsIgnoreCase("BEARER ")) {
return false;
} else {
jwt = jwt.split(" ")[1].trim();
}
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt);
return !claims.getBody().getExpiration().before(new Date());
} catch(Exception e) {
return false;
}
}
// request의 header에서 token 가져오기
public String resolveToken(HttpServletRequest request) {
return request.getHeader("Authorization");
}
}
SecurityFilterChain에 추가STATELESS로 설정@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtProvider jwtProvider;
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // token을 사용하므로 csrf disable
.sessionManagement((sessionManagement) -> // Spring Security가 Session을 아예 배재(생성, 사용 X)
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
// Http 요청이 들어오면 가장 먼저 거치는 filter
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// Header에서 Token 가져오기
String token = jwtProvider.resolveToken(request);
// 토큰이 유효하다면,
if (token != null && jwtProvider.validateToken(token)) {
// 토큰으로부터 인증 정보를 받아
Authentication authentication = jwtProvider.getAuthentication(token);
// SecurityContext 객체에 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response); // 다음 filter 실행
}
}