본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 기반으로 작성되었습니다.
실습 위주의 이해를 목표로 하기 때문에 다소 과장이 많고 생략된 부분이 많을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 대해 유의하시기 바랍니다.
또한, 본 시리즈는 ChatGPT의 도움을 받아 작성되었습니다.
수 차례의 질문을 통해 도출된 여러가지 다양한 방식의 코드를 종합하여
작성자의 이해와 경험을 바탕으로 가장 정석으로 생각되는 코드를 재정립하였습니다.
Provider
를 통해Token
의 생성, 분석, 유효성 검사 등을 수행하고,
Filter
를 통해Spring Security
의 접근 권한 검사를 수행한다.
SpringBoot WebSecurity
에서JWT(JSON Web Token)
의 데이터 흐름은 다음과 같습니다.
CustomUserDetails
->CustomUserDetailsService
->
JwtTokenProvider
->JwtTokenFilter
->WebSecurityConfig
application.properties
# JWT, 1HOUR=3600000 / 24HOUR=86400000
jwt.secret=your-secret-key
jwt.accessTokenExpirationTime=3600000
jwt.refreshTokenExpirationTime=86400000
JwtTokenProvider.java
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecretKey;
@Value("${jwt.accessTokenExpirationTime}")
private Long jwtAccessTokenExpirationTime;
@Value("${jwt.refreshTokenExpirationTime}")
private Long jwtRefreshTokenExpirationTime;
public String generateAccessToken(Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
Date expiryDate = new Date(new Date().getTime() + jwtAccessTokenExpirationTime);
return Jwts.builder()
.setSubject(customUserDetails.getUsername())
.claim("user-id", customUserDetails.getId())
.claim("user-email", customUserDetails.getEmail())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecretKey)
.compact();
}
public String generateRefreshToken(Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
Date expiryDate = new Date(new Date().getTime() + jwtRefreshTokenExpirationTime);
return Jwts.builder()
.setSubject(customUserDetails.getUsername())
.claim("user-id", customUserDetails.getId())
.claim("user-email", customUserDetails.getEmail())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecretKey)
.compact();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Long getUserIdFromToken(String token) {
return Jwts.parser()
.setSigningKey(jwtSecretKey)
.parseClaimsJws(token)
.getBody()
.get("user-id", Long.class);
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(jwtSecretKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public String getUserEmailFromToken(String token) {
return Jwts.parser()
.setSigningKey(jwtSecretKey)
.parseClaimsJws(token)
.getBody()
.get("user-email", String.class);
}
public Date getExpirationFromToken(String token) {
return Jwts.parser()
.setSigningKey(jwtSecretKey)
.parseClaimsJws(token)
.getBody()
.getExpiration();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecretKey).parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
System.out.println("Invalid JWT signature");
} catch (MalformedJwtException ex) {
System.out.println("Invalid JWT token");
} catch (ExpiredJwtException ex) {
System.out.println("Expired JWT token");
} catch (UnsupportedJwtException ex) {
System.out.println("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
System.out.println("JWT claims string is empty.");
}
return false;
}
}
JwtTokenProvider
: 위에서부터 구분선을 기준으로 각각
JWT(JSON Web Token)
의 생성, 분석, 유효성 검사 기능을 담당합니다.
JwtTokenFilter.java
@Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final CustomUserDetailsService customUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String accessToken = getTokenFromRequest(request);
if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) {
UsernamePasswordAuthenticationToken authentication = getAuthenticationFromToken(accessToken);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private UsernamePasswordAuthenticationToken getAuthenticationFromToken(String token) {
Long userId = jwtTokenProvider.getUserIdFromToken(token);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
Filter
를 통해Spring Security
의 접근 권한 검사를 수행한다.
Request
로부터Token
을 추출하고,Token
으로부터 권한 정보를 추출한다.
추출한Token
이 유효한지 검사하고, 추출한Authority
를 통해
Request
,Response
간FilterChain
을 수행한다.