저번 게시글에서 아무리 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())



References

Leave a comment