본문 바로가기
Spring

[스프링부트] Oauth2.0 클라이언트 구현

by seeker00 2023. 9. 2.

1. Oauth2.0 이란?

2. 스프링부트 및 시큐리티의 Oauth2.0 

OAuth2AuthorizationRequestRedirectFilter

public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter {

	/**
	 * The default base {@code URI} used for authorization requests.
	 */
	public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";

	private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

	private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();

	private OAuth2AuthorizationRequestResolver authorizationRequestResolver;

	private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();

	private RequestCache requestCache = new HttpSessionRequestCache();

// 생략


	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		try {
			OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
			if (authorizationRequest != null) {
				this.sendRedirectForAuthorization(request, response, authorizationRequest);
				return;
			}
		}
		catch (Exception ex) {
			this.unsuccessfulRedirectForAuthorization(request, response, ex);
			return;
		}
// 생략
}
  • 리소스 서버에 인증 요청을 수행할 때, 가장 먼저 Oauth2.0 인증과 관련된 동작을 수행하는 필터이다.
    • 이 때 oauth2 기능을 트리깅하는 엔드포인트 url 은 '/oauth2/authorization/{provider_id}' 로 해당 클래스의  DEFAULT_AUTHORIZATION_REQUEST_BASE_URI 로 설정되어 있음을 위 코드로 확인할 수 있을 것이다.
  • 아래의 DefaultOAuth2AuthorizationRequestResolver 클래스를 통해 요청을 리졸브(build)하고, DefaultRedirectStrategy에 정의된 정해진 전략에 따라 (리디렉션을 이용하여) 인증 과정 동작을 수행한다.

DefaultOAuth2AuthorizationRequestResolver

  • 정해진 패턴에 따라 인증 프로바이더(구글, 네이버, 카카오 등)를 구분하고, oauth2 로직을 수행할 request 객체를 생성한다.
  • 생성된 OAuth2AuthorizationRequest 는 아래와 같다.
    • 여기서 인증 프로바이더 관련된 정보는 InmemoryClientRegistrationRepository 를 통해 가져온다. (스프링 설정 파일에 관련 정보를 세팅해두면, 해당 Repository 클래스에 정보가 저장될 것이다.)

DefaultRedirectStrategy

  • 인증 과정에 수반되는 리다이렉트 동작 로직을 갖고 있는 클래스

OAuth2LoginAuthenticationFilter

  • AbstractAuthenticationProcessingFilter 를 상속받아 구현된다.
  • 프로바이더 서버에서 authorization_code를 포함해 특정 url(/login/oauth2/code)로 리디렉션을 수행하여 나의 서버로 다시 요청이 도착하면 거치게 되는 필터이다.
    • 즉, authorization_code 를 활용해 실제 로그인 과정을 수행하는 파트를 담당하는 필터이다. 
    • 주입된 authenticationManager를 이용해 authenticate 과정을 수행하고 토큰 결과값을 리턴한다.
public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	/**
	 * The default {@code URI} where this {@code Filter} processes authentication
	 * requests.
	 */
	public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*";

	private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found";

	private static final String CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE = "client_registration_not_found";

	private ClientRegistrationRepository clientRegistrationRepository;

	private OAuth2AuthorizedClientRepository authorizedClientRepository;

	private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();

	private Converter<OAuth2LoginAuthenticationToken, OAuth2AuthenticationToken> authenticationResultConverter = this::createAuthenticationResult;
    
    // 생략
    
    	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
		if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
			OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
		}
		OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
				.removeAuthorizationRequest(request, response);
		if (authorizationRequest == null) {
			OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
		}
        // 생략
        this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
		return oauth2Authentication;
    }
    
//...

}

3. 커스텀해보기

@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfig {
	//...생략
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
        		//...생략
				.oauth2Login()
                .authorizationEndpoint()
                .baseUri("/api/oauth2/authorization")
                .authorizationRequestRepository(oAuth2CookieAuthorizationRequestRepository)
                .and()
                .redirectionEndpoint()
                .baseUri("/api/login/oauth2/code/*")
                .and()
                .userInfoEndpoint()
                .and()
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)	// 401
                .accessDeniedHandler(jwtAccessDeniedHandler);		// 403

        return http.build();
    }
}

 

OAuth2AuthorizationCodeAuthenticationProvider