저번 게시글에서 아무리 Security단에서 예외가 발생해도 절대 Spring의 Dispatcher Servlet 뒤에 있는 Handler Intercepter 쪽에서 에러를 처리할 수 없음을 알게되었다.
그래서, jwt 토큰이 부적절하게 들어오는 경우 해당 에러 처리를 위해 필터를 하나 더 작성하여 INVALID_TOKEN
에러로 처리했다.
그런데, 문제는 토큰 자체가 요청에 포함되지 않는 경우는 처리할 수 없었다.
일단 급한대로 찾아본 결과, 인증이 되지않은 유저가 요청을 했을때 동작하는 AuthenticationEntryPoint
를 상속받아 구현하면 된다고 한다.
사실 이 부분은 좀 더 공부해봐야 알겠지만 AuthenticationEntryPoint
을 상속한 CustomAuthenticationEntryPoint
에서 모든 토큰에 대한 예외 처리를 할 수 있을 것 같은데, 그 부분은 일단 추추에 진행해보도록 하고, 지금은 CustomAuthenticationEntryPoint
에서 토큰 자체가 없는 요청에 대한 예외처리만을 해보겠다.
CustomAuthenticationEntryPoint.java
@RequiredArgsConstructor
@Component
@Slf4j
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.error("토큰이 존재하지 않습니다.");
setErrorResponse(response,ErrorCode.TOKEN_NOT_FOUND);
}
private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode) throws IOException {
response.setStatus(errorCode.getStatus().value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
ObjectMapper objectMapper = new ObjectMapper();
ErrorResponse errorResponse = new ErrorResponse(errorCode);
response.getWriter().write(objectMapper.writeValueAsString(Response.error("ERROR",errorResponse)));
}
}
AuthenticationEntryPoint
클래스의 commence
메서드를 오버라이딩 하여 구현한다.
ErrorCode
클래스에서 토큰이 존재하지 않을 시에 다음과 같은 에러 처리 상황을 추가했다.
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "토큰이 존재하지 않습니다."),
setErrorResponse
메서드를 통해 응답값을 내가 의도한 TOKEN_NOT_FOUND
예외처리 형식으로 반환한다.
SecurityConfig.java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig {
@Value("${jwt.token.secret}")
private String secretKey;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic().disable() //rest api 이므로 기본설정 사용안함. 기본설정은 비인증시 로그인폼 화면으로 리다이렉트
.csrf().disable() //rest api 이므로 csrf 보안이 필요없음
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //jwt token으로 인증시 세션 필요없으므로 생성안함
.and()
.authorizeRequests()
.antMatchers("/api/v1/users/login","/api/v1/users/join", "/swagger-ui").permitAll() // join, login 은 언제나 가능
.antMatchers(HttpMethod.GET,"/api/v1/**").permitAll() // 모든 get 요청 허용
.antMatchers(HttpMethod.POST,"/api/v1/**").authenticated() // 순서대로 적용이 되기 때문에 join, login 다음에 써주기
.antMatchers(HttpMethod.PUT, "/api/v1/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/v1/**").authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())// 토큰 없을 시 에러 처리
.and()
.addFilterBefore(new JwtTokenFilter(secretKey), UsernamePasswordAuthenticationFilter.class) // UserNamePasswordAuthenticationFilter 적용하기 전에 JwtTokenFilter 적용한다는 의미
.addFilterBefore(new JwtTokenExceptionFilter(),JwtTokenFilter.class) // 인증
.build();
}
}
마지막으로, Security 설정 정보에 CustomAuthenticationEntryPoint
를 추가한다.
다음과 같은 부분이 추가되었다.
.and().exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
Leave a comment