이전 포스팅에서 JwtAuthenticationFilter는 OncePerRequestFilter를 확장하여 구현하였습니다.
JwtAuthenticationFilter 필터를 생성할 때 인증 작업을 AuthenticationManager에 위임해야 하나 말아야 하나 고민했습니다.
AuthenticationManager는 Filter들의 인증을 담당하는 Spring Security의 중요 컴포넌트입니다.
AuthenticationManager에 인증을 위임하게 될 경우 Spring Security의 인증 메커니즘 통합이 가능하여 확장성이나 유연성이 좋아집니다.
하지만 저는 AuthenticationManager에 위임하지 않고 직접 구현하였습니다.
- JWT는 이미 잘 정의된 표준을 가지고 있으므로 표준을 준수하면서 추가적인 확장에 대한 상황이 많지 않습니다.
- JWT는 Claim을 통해 자체적으로 정보를 가지고 있으므로 별도의 저장소 접근이 필요하지 않습니다.
- JWT는 상태를 저장하지 않는 stateless 특징을 가지므로 서버가 관리할 필요가 없습니다.
- JwtAuthenticationFilter는 다른 인증체계와의 독립성을 유지하기 위해 설계되었습니다,
- JWT Token 처리를 위한 로직이 복잡하고 다양한 작업이 포함되므로 이를 모듈화하고 재사용성을 높이려 하였습니다.
- JWT 토큰 처리는 유효성 검사 뿐만 아니라 발급부터 각종 JWT 토큰 작업이 필요합니다.
- BlackListToken 관리, RefreshToken 관리 등의 복합적인 작업을 담당합니다.
- 재사용성 있게 모듈화하고 관리하기 위해 설계적인 결정을 내렸습니다.
- 복잡성을 효과적으로 관리하고 확장성 있게 하기 위해 JWT와 관련된 핵심기능을 효율적으로 처리하도록 구현했습니다.
- JWT 토큰 처리는 유효성 검사 뿐만 아니라 발급부터 각종 JWT 토큰 작업이 필요합니다.
- 필터는 JwtProvider를 직접 호출하여 효과적으로 JWT 작업을 수행합니다.
1. JwtProvider
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtProvider {
private final JwtProperties jwtProperties;
public String generateAccessToken(JwtVo jwtVo) {
Map<String, Object> payloads = new HashMap<>();
payloads.put("email", jwtVo.getEmail());
payloads.put("role", jwtVo.joinRolesToString());
return generateJwtBuilder(payloads)
.setSubject("Access Token (" + jwtVo.getEmail() + ")")
.setExpiration(calculateExpiryDate(jwtProperties.getAccessValidity()))
.compact();
}
public String generateRefreshToken(JwtVo jwtVo) {
Map<String, Object> payloads = new HashMap<>();
payloads.put("email", jwtVo.getEmail());
return generateJwtBuilder(payloads)
.setSubject("Refresh Token (" + jwtVo.getEmail() + ")")
.setExpiration(calculateExpiryDate(getValidityDay(jwtProperties.getRefreshValidity())))
.compact();
}
public void validateToken(String token) {
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(jwtProperties.getSecret());
try {
Jwts.parser()
.setSigningKey(apiKeySecretBytes)
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
throw new JwtException("expired_token");
} catch (MalformedJwtException e) {
throw new JwtException("malformed_token");
} catch (SignatureException e) {
throw new JwtException("signature_invalid_token");
} catch (UnsupportedJwtException e) {
throw new JwtException("format_invalid_token");
}
}
private JwtBuilder generateJwtBuilder(Map<String, Object> payloads) {
Map<String, Object> headers = new HashMap<>();
headers.put("typ", "JWT");
headers.put("alg", jwtProperties.getAlgorithm());
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forName(jwtProperties.getAlgorithm());
// 서명에 담을 데이터
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(jwtProperties.getSecret());
Key signKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
return Jwts.builder()
.setHeader(headers)
.setClaims(payloads)
.setIssuedAt(Date.from(Instant.now()))
.signWith(signatureAlgorithm, signKey);
}
private Date calculateExpiryDate(long validityHour) {
return Date.from(Instant.now().plus(validityHour, ChronoUnit.HOURS));
}
private long getValidityDay(long refreshValidity) {
return refreshValidity * 24;
}
}
- JWT 토큰의 생성과 유효성 검사 등의 핵심 기능을 제공하는 컴포넌트입니다.
- 주요 메서드와 도우미 메서드로 나누어 응집도를 높였습니다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security][KoLiving] 4-4. RefreshToken Rotation (0) | 2023.10.03 |
---|---|
[Spring Security][KoLiving] 4-3. RefreshToken (2) | 2023.10.03 |
[Spring Security][KoLiving] 4-1. JwtAuthenticationFilter (0) | 2023.10.03 |
[Spring Security][KoLiving] 4. JWT 토큰 기반 인증 (0) | 2023.10.03 |
[JWT] 2. AccessToken과 RefreshToken (0) | 2023.10.03 |