💻문제점
JwtAuthenticationFilter.java
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String extractToken = request.getHeader(JwtUtils.AUTH_TOKEN_HEADER);
jwtUtils.extractBearerToken(extractToken)
.ifPresent(
jwt -> {
jwtUtils.validateToken(jwt);
if (SecurityContextHolder.getContext().getAuthentication() == null) {
String email = getUserEmailFromJwt(jwt);
UserDetails userDetails =
userDetailsServiceImpl.loadUserByUsername(email);
setAuthenticationInSecurityContext(request, userDetails);
}
});
filterChain.doFilter(request, response);
}
JwtUtils.java
public void validateToken(String token) {
try {
extractAllClaims(token);
} catch (SecurityException | MalformedJwtException e) {
throw new InvalidJwtException(ErrorCode.INVALID_TOKEN_SIGNATURE);
} catch (ExpiredJwtException e) {
throw new InvalidJwtException(ErrorCode.EXPIRED_TOKEN);
} catch (UnsupportedJwtException e) {
throw new InvalidJwtException(ErrorCode.UNSUPPORTED_TOKEN);
} catch (IllegalArgumentException e) {
// token claim is blank or token is null
throw new InvalidJwtException(ErrorCode.INVALID_TOKEN);
} catch (Exception e) {
log.warn("처리되지 않은 JWT 오류입니다.");
throw new InvalidJwtException(ErrorCode.UNEXPECTED_TOKEN);
}
}
ErrorCode.java
@Getter
public enum ErrorCode {
// auth
EXPIRED_TOKEN("AUTH_001", "JWT 토큰이 만료되었습니다."),
INVALID_TOKEN("AUTH_002", "JWT 토큰이 유효하지 않습니다."),
INVALID_TOKEN_SIGNATURE("AUTH_003", "JWT 서명이 유효하지 않습니다."),
UNSUPPORTED_TOKEN("AUTH_004", "지원되지 않는 JWT 토큰입니다."),
UNEXPECTED_TOKEN("AUTH_005", "JWT 처리 중 예상치 못한 오류가 발생했습니다."),
//... 다른 ErrorCode
private final String code;
private final String description;
ErrorCode(String code, String description) {
this.code = code;
this.description = description;
}
}
JWT 예외가 발생하면 InvalidJwtException이라는 커스텀 예외를 던진다.
하지만, 이에 대한 핸들링이 아직 되어있지 않다. 때문에 클라이언트 측에서는 JWT 오류가 발생했는지에 대해 알 수 없다.
ControllerAdvice는 Controller단에서만 적용되기 때문에 JwtAuthenticationFilter에서 발생하는 예외에 대해서는 핸들링되지 않는다.
때문에 이에 대한 핸들링을 따로 해줄 필요가 있었다.
🔍해결
AuthenticationEntryPoint를 사용하여 인증에서 발생한 에러를 처리할 수 있다.
ErrorResponse.java
public record ErrorResponse(String errorCode, String description) {
public static ErrorResponse from(ErrorCode errorCode) {
return new ErrorResponse(errorCode.getCode(), errorCode.getDescription());
}
public static ErrorResponse from(ErrorCode errorCode, String description) {
return new ErrorResponse(errorCode.getCode(), description);
}
}
JwtAuthenticationEntryPoint.java
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
ErrorCode errorCode = (ErrorCode) request.getAttribute(JwtUtils.EXCEPTION_ATTRIBUTE);
log.error("[Exception] - JwtAuthenticationEntryPoint.commence - uri: {} - {}", request.getRequestURI(), errorCode);
setErrorResponse(response, errorCode);
}
private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode)
throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("utf-8");
ErrorResponse errorResponse = ErrorResponse.from(errorCode);
String result = new ObjectMapper().writeValueAsString(errorResponse);
response.getWriter().write(result);
}
}
JwtAuthenticationFilter.java
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String extractToken = request.getHeader(JwtUtils.AUTH_TOKEN_HEADER);
jwtUtils.extractBearerToken(extractToken)
.ifPresent(
jwt -> {
try {
jwtUtils.validateToken(jwt);
if (SecurityContextHolder.getContext().getAuthentication()
== null) {
String email = getUserEmailFromJwt(jwt);
UserDetails userDetails =
userDetailsServiceImpl.loadUserByUsername(email);
setAuthenticationInSecurityContext(request, userDetails);
}
} catch (InvalidJwtException e) {
request.setAttribute(JwtUtils.EXCEPTION_ATTRIBUTE, e.getErrorCode());
}
});
filterChain.doFilter(request, response);
}
SecurityConfig.java
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
//...
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(
config -> config.authenticationEntryPoint(jwtAuthenticationEntryPoint)) //추가
.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers(
new AntPathRequestMatcher(
"/api/v1/members/oauth/*/login"))
.permitAll()
.anyRequest()
.authenticated())
.sessionManagement(
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
728x90
'TIL' 카테고리의 다른 글
[TIL - 20231228] Interface의 default 메서드를 활용한 Enum 확장 (0) | 2023.12.28 |
---|---|
[TIL - 20231226] jacoco 패키지, 클래스 Report에서 제외 (0) | 2023.12.26 |
[TIL - 20231222] searchWeeklyIntakeCalories 로직 개선 (0) | 2023.12.22 |
[TIL-20231222] Illegal pop() with non-matching JdbcValuesSourceProcessingState (0) | 2023.12.22 |
[TIL-20231220] 리플렉션을 사용한 private 필드 접근 및 값 설정 (0) | 2023.12.20 |