본문 바로가기

Backend/Spring | SpringBoot

[Spring Security] Exception Handler

반응형

보안문자를 추가하면서 보안문자 검증에 실패하면 Exception을 던져서 Hanlder에서 처리하게 하려고 했다.

 

하지만 예외는 던지는데 그대로 Break로 처리하고 Handler에서 처리하지 않고있었다.

 

사용 코드 : ⬇️

더보기
@RequiredArgsConstructor
@EnableMethodSecurity
@Configuration
@Slf4j
public class SpringSecurityConfig2{
  
  ...
  
  @Bean
  public AuthenticationSuccessHandler customSuccessHandler() {
    return new CustomLoginSuccessHandler2(cookieUtil, jwtTokenizer2);
  }

  @Bean
  public AuthenticationFailureHandler customFailureHandler() {
    return new CustomLoginFailHandler2();
  }

  // OAuth2 로그인 성공시 handler
  @Bean
  public CustomOauth2LoginSuccessHandler2 customOauth2LoginSuccessHandler() {
    return new CustomOauth2LoginSuccessHandler2(cookieUtil, jwtTokenizer2);
  }

  // OAuth2 로그인 실패시 handler
  @Bean
  public CustomAuthenticationFailureHandler2 customAuthenticationFailureHandler(){
    return new CustomAuthenticationFailureHandler2(cookieUtil);
  }
  
  ...
  
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
    http.authenticationManager(authenticationManager);

    http.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource()));

//    http.csrf(AbstractHttpConfigurer::disable);
    http.csrf((csrf) -> csrf
        .csrfTokenRepository(sessionCsrfRepository()));

    http.sessionManagement(httpSecuritySessionManagementConfigurer -> {
      httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.NEVER);
    });

    // request URL에 대한 권한 확인
    http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
        authorizationManagerRequestMatcherRegistry.anyRequest().permitAll());

    http.addFilterBefore(jwtVerifyFilter(), UsernamePasswordAuthenticationFilter.class);

    // Form 로그인 설정
    http.formLogin(httpSecurityFormLoginConfigurer -> {
      log.info("----Configuring Form Login----");
      httpSecurityFormLoginConfigurer
          .loginPage("/login")
          .successHandler(customSuccessHandler())
          .failureHandler(customAuthenticationFailureHandler())
          ;
      // 로컬에서만 Filter적용
      http.addFilterAt(this.abstractAuthenticationProcessingFilter(authenticationManager, captchaUtil), UsernamePasswordAuthenticationFilter.class);
    });
    // oauth인증
    http.oauth2Login(httpSecurityOAuth2LoginConfigurer -> {
        log.info("----Configuring Oauth2 Login----");
        httpSecurityOAuth2LoginConfigurer                              
            .loginPage("/login")
            .successHandler(customOauth2LoginSuccessHandler())
            .failureHandler(customAuthenticationFailureHandler())
            // 사용자 인증
            .userInfoEndpoint(userInfoEndpointConfig ->
                userInfoEndpointConfig.userService(customOAuth2UserService))
        ;
    });
    return http.build();
  }

  // 인증 필터
  public AbstractAuthenticationProcessingFilter abstractAuthenticationProcessingFilter(
      // 토큰 정보 추가
      final AuthenticationManager authenticationManager, final CaptchaUtil captchaUtil) {
    // 토큰 정보를 이용해 사용자 정보 식별
    LoginAuthenticationFilter loginAuthenticationFilter = new LoginAuthenticationFilter("/ajax/loginProcess", authenticationManager, captchaUtil);
    // Handler에 토큰 정보 추가
    loginAuthenticationFilter.setAuthenticationSuccessHandler(customSuccessHandler());
    loginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler());
    // Rest API 방식을 사용하기 위해 추가
    loginAuthenticationFilter.setSecurityContextRepository(
        new DelegatingSecurityContextRepository(
            new RequestAttributeSecurityContextRepository(),
            new HttpSessionSecurityContextRepository()
        ));
    return loginAuthenticationFilter;
  }

}



@Component
@Slf4j
@RequiredArgsConstructor
public class CustomLoginFailHandler2 implements AuthenticationFailureHandler {

  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException exception) throws IOException, ServletException {
    log.info("--------------------------- WebLoginFailHandler ---------------------------");

    request.getSession().setAttribute("error.message", exception.getMessage());

    String msg = exception.getMessage() == null ? "" : exception.getMessage();

    if (exception.getCause() instanceof CustomCaptchaException) {
      // 에러 메시지 추출
      CustomCaptchaException customCaptchaException = (CustomCaptchaException) exception.getCause();
      msg = customCaptchaException.getMessage();
    }

    if (!msg.equals("")) {
      if (msg.equals("INVALID_CAPTCHA")) {
        log.error("FAIL CAPTCHA VALIDATE");
//        response.sendRedirect("/error/WRONG_LOGIN");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        Map<String, String> data = new HashMap<>();
        data.put("error", msg);
        response.getWriter().write(new ObjectMapper().writeValueAsString(data));
      }

    }
  }
}

 

 

public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    ...
    String captchaAnswer = loginRequestDto.captchaAnswer;
    String captchaId = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);

    System.out.println("++++++++++++++++++");
    System.out.println(captchaAnswer);
    System.out.println(captchaId);


      if (!captchaUtil.validateCaptcha(captchaId, captchaAnswer)) {
        logger.info("Captcha Validate Fail : Provided Text : " + captchaId + " / User Text : " + captchaAnswer);
        throw new CustomCaptchaException("INVALID_CAPTCHA");
      }
      
    ...
}

 

 

➡️ 던져진 Exception을 Handler에서 가져가게 하는 방법은 간단했다...

CustomCaptchaException을 전에는 RuntimeException을 extends로 하도록 작성했었는데 이것을 AuthenticationException을 extends하도록 수정하였더니 잘 가져갔다.

 

여기서 생긴 의문은 Oauth를 이용한 로그인에 대한 FailureHandler처리에서는 RuntimeException를 extends해도 정상적으로 가져갔는데 FormLogin에서는 왜 FailureHandler가 가져가지 못한것일까 하는 것이다.

 

그래서 찾아보니 다음과 같은 결과를 알수있었다.

AuthenticationException을 상속받는 예외 클래스는 Spring Security의 인증 프로세스에서 발생한 예외로 간주되어, AuthenticationFailureHandler를 통해 적절하게 처리됩니다.
반면에 RuntimeException을 상속받는 예외는 Spring Security의 인증 프로세스 외부에서 발생한 예외로 간주될 수 있으며, 이는 인증 실패로 간주되지 않습니다.

 

 

반응형

'Backend > Spring | SpringBoot' 카테고리의 다른 글

[Oauth2] 구글 OAuth2 연동(클라이언트 ID/PW발급)  (0) 2024.07.25
[Oauth2] Implicit Grant vs Authorization Code Grant  (0) 2024.06.20
[OAuth2] 회고4  (0) 2024.06.18
[TIL] 회고 1  (0) 2024.06.17
[OAuth2] 회고3  (0) 2024.06.14