TIL

2023.12.10 TIL - oauth2-client 인증 실패 핸들러

Srping oauth2-client 모듈을 활용하여 소셜로그인을 구현하였다.

그 중 인증처리가 실패할 경우 에러 응답을 주기 위해 oauth2 failureHandler를 구현하였다.

 


인증 실패 커스텀 핸들러 구현

OAuth2 인증실패 시 기본적으로 SimpleUrlAuthenticationFailureHandler가 실패처리를 하게된다.

 

SimpleUrlAuthenticationFailureHandler은 설정 때 받은 url로 (기본값: /login?error) 리다이렉트 시켜 응답을 반환한다. 

 

하지만 구현하는 프로젝트는 REST API 서버이므로 리다이렉트를 시키지 않고 Json 포맷으로 적절한 에러응답을 보내야한다.

그래서 커스텀 핸들러를 구현하고 주입해주어야한다. 

 

OAuth2AuthenticationFailureHandler

핸들러를 만드는 방법은 간단하다. 

AuthenticationFailureHandler의 구현체를 만들어주고

onAuthenticationFailure 메소드를 재정의해줘서

security config에서 주입해주면된다.

 

REST API 서버이므로 JSON 포맷으로 응답을 주기 위해 ObjectMapper로 resonse body를 만들어주었다. 

@Component
public class OAuth2AuthenticationFailureHandler implements AuthenticationFailureHandler {
   private final ObjectMapper objectMapper;

   public OAuth2AuthenticationFailureHandler(ObjectMapper objectMapper) {
      this.objectMapper = objectMapper;
   }

   @Override
   public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException exception)
      throws IOException {
      response.setContentType(MediaType.APPLICATION_JSON_VALUE);
      response.setCharacterEncoding(StandardCharsets.UTF_8.name());
      response.setStatus(INTERNAL_SERVER_ERROR.getHttpStatus());
      response.getWriter().write(
         objectMapper.writeValueAsString(
            BaseResponse.of(
               INTERNAL_SERVER_ERROR.getMessage(),
               INTERNAL_SERVER_ERROR.getHttpStatus(),
               ""
            )
         )
      );
   }
}

 

 

SecurityConfig

http
    .oauth2Login(oauth2 -> oauth2
        // 추가 설정 코드들..
        .failureHandler(oauth2AuthenticationFailureHandler)
    );

 


발생한 문제

OAuth2 실패 핸들러를 구현하고 주입하여 설정한 후에 실패 시 에러응답이 잘나오는지 테스트를 하니

에러응답이 원하는대로 나오질 않았다.

 

@Override
public OAuth2User loadUser(OAuth2UserRequest oauth2Userrequest) throws
   OAuth2AuthenticationException {
   OAuth2User oauth2User = super.loadUser(oauth2Userrequest);

   try {
      return processOAuth2User(oauth2Userrequest, oauth2User);
   } catch (Exception exception) {
      log.error("OAuth2 Authentication process error");
      throw new RuntimeException();
   }
}

private OAuth2User processOAuth2User(
   OAuth2UserRequest oauth2Userrequest, OAuth2User oauth2User) throws Exception {
   String registrationId = oauth2Userrequest.getClientRegistration().getRegistrationId().toUpperCase();
   SocialType socialType = valueOf(registrationId);

   OAuth2UserDetails oAuth2UserDetails = OAuth2UserInfoFactory.getOAuth2UserInfo(socialType,
      oauth2User.getAttributes());
      
   //의도적으로 예외 발생 -> 실패 테스트
   throw new Exception();
   //return new CustomUserDetails(findUser(socialType, oAuth2UserDetails), oauth2User.getAttributes());
}

 

설정한 500 에러로 반환되어야 할 응답이

 

accessDeniedHandler로 빠진 것이다.

 

원인

원인은 Oauth2FailureHandler 가 잡는 예외가 다르기 때문이다.

테스트에서는 RuntimeException 으로 예외를 발생시키고 있다. 

 

failurehandler가 잡는 예외는 AuthenticationException  계열이므로 핸들링을 할 수가 없었고

SecurityContext 안에 Authentication이 없으므로 AccessDeniedHandler가 잡게되는 것이다.

 

해결

해결방법은 간단하다.

인증 처리 로직에서 예외 발생시 Authentication Exeption 계열로 예외를 바꿔 발생시켜주면 된다.

@Override
public OAuth2User loadUser(OAuth2UserRequest oauth2Userrequest) throws
   OAuth2AuthenticationException {
   OAuth2User oauth2User = super.loadUser(oauth2Userrequest);

   try {
      return processOAuth2User(oauth2Userrequest, oauth2User);
   } catch (Exception exception) {
      log.error("OAuth2 Authentication process error");
      //Exception 을 catch하여 AuthenticationException 계열로 변경
      throw new InternalAuthenticationServiceException(exception.getMessage(), exception.getCause());
   }
}

 

다시 테스트를 해보면 원하는 failure handler가 처리하는 것을 확인할 수 있다.

 

728x90