본문 바로가기

백엔드/Spring Security

Spring Security 표준 OAuth2 소셜 로그인 매핑 필터 2가지

 

스프링 시큐리티는 OAuth2와 통합된 아주 편리한 기능들을 제공한다. 그중 하나가, 소셜 로그인 서비스를 호출하는 URI와 Redirect URI를 캐치해서 외부 서비스 로그인 주소를 리턴하거나, Access 토큰 관련 처리등을 스프링 시큐리티 내에서 직접 처리해 준다. 즉, OAuth2 관련 URI를 매핑하는 컨트롤러를 만들 필요도 없고, 내부 로직도 직접 설계할 필요가 없다는 뜻이다.

 

소셜 로그인 관련 블로그 글들을 보면, URI를 컨트롤러로 매핑해서 관련 동작을 설계하는 글들이 많이 보인다. OAuth2를 이해하는 데에 있어서 관련 기능을 직접 구현하는 것은 좋은 방법이지만, 실제로 사용할 때는 스프링 시큐리티에서 제공하는 기능을 사용하는 게 훨씬 편리하고, 코드가 간결해진다.

 

본론으로 들어가서, 스프링 시큐리티가 제공하는 OAuth2 필터 2가지는 "OAuth2AuthorizationRequestRedirectFilter", "

OAuth2LoginAuthenticationFilter"이다. 

 

 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 RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();

	private OAuth2AuthorizationRequestResolver authorizationRequestResolver;

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

	private RequestCache requestCache = new HttpSessionRequestCache();

	/**
	 * Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided
	 * parameters.
	 * @param clientRegistrationRepository the repository of client registrations
	 */
	public OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository) {
		this(clientRegistrationRepository, DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
	}

	/**
	 * Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided
	 * parameters.
	 * @param clientRegistrationRepository the repository of client registrations
	 * @param authorizationRequestBaseUri the base {@code URI} used for authorization
	 * requests
	 */
	public OAuth2AuthorizationRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository,
			String authorizationRequestBaseUri) {
		Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
		Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
		this.authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
				authorizationRequestBaseUri);
	}
    
    // 이하 코드 생략
}

 

먼저, OAuth2AuthorizationRequestRedirectFilter이다. 이 필터는 클라이언트에서 소셜 로그인 버튼을 눌렀을 때, 해당 URI를 캐치해서 서비스 로그인 주소를 리턴해서 관련 창을 띄워주는 기능을 수행한다. 예를 들어, 아래는 클라이언트에서 로그인 버튼을 눌렀을 경우 스프링의 "oauth2/authorization/goolge" 경로로 요청을 보내는 리액트 코드의 일부이다. 

 

const onGoogleLogin = () => {
      window.location.href = 'http://localhost:8080/oauth2/authorization/google';
  };

 

원래는 위의 URI를 매핑하는 컨트롤러를 만들고 소셜 로그인 서비스 창을 띄우기 위해, 관련 주소를 조합해서 리턴하는 로직을 구성해야 하지만, OAuth2AuthorizationRequestRedirectFilter 필터의 첫 번째 필드값이  "oauth2/autorization"인 것을 보면 알 수 있듯이, 이 필터가 해당 URI를 캐치해서 모든 기능을 대신 수행해 준다. 

 

 

 OAuth2LoginAuthenticationFilter

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;

	/**
	 * Constructs an {@code OAuth2LoginAuthenticationFilter} using the provided
	 * parameters.
	 * @param clientRegistrationRepository the repository of client registrations
	 * @param authorizedClientService the authorized client service
	 */
	public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository clientRegistrationRepository,
			OAuth2AuthorizedClientService authorizedClientService) {
		this(clientRegistrationRepository, authorizedClientService, DEFAULT_FILTER_PROCESSES_URI);
	}
    
    // 이하 코드 생략
}

 

OAuth2LoginAuthenticationFilter 필터는 앞의 과정에서 소셜 로그인이 성공적으로 수행되어 호출되는 Redirect URI를 캐치해서 OAuth2LoginAuthenticationProvider에 위임하고, 이 Provider 내에서 소셜 로그인 서버로부터 Access 토큰을 발급받고 유저 정보를 획득한다. 이 필터도 마찬가지로 첫 번째 필드값을 보면 알 수 있듯이, "login/oauth2/code/"로 시작하는 URI를 캐치한다. 예를 들어, 구글 로그인 과정에서는 "login/oauth2/code/google"의 URI를 캐치하는 것이다.

 

OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
				new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
		authenticationRequest.setDetails(authenticationDetails);
		OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
			.getAuthenticationManager()
			.authenticate(authenticationRequest);

 

위의 코드는 OAuth2LoginAuthenticationFilter 클래스의 일부인데, OAuth2LoginAuthenticationToken을 생성하고 AuthenticationManager의 authenticate 메서드를 호출해서, 생성한 Authentication과 일치하는 Provider를 실행시키는 코드이다.

 

 

 

public class OAuth2UserRequest {

	private final ClientRegistration clientRegistration;

	private final OAuth2AccessToken accessToken;

	private final Map<String, Object> additionalParameters;
    
    // 이하 코드 생략
}

 

위의 모든 과정을 거치고 나면, 최종적으로 스프링 시큐리티에서 제공하는 OAuth2UserRequest 객체를 전달받는다.