Programming/Spring Boot

Spring Boot OAuth2-Client 내부적인 동작 과정

Jan92 2023. 3. 25. 21:37

스프링 부트 OAuth2-Client 내부적인 동작 과정

 

스프링 부트 2.x 버전으로 올라오며 oauth를 연동하는 방법이 크게 변경되었는데요.

아래 내용은 spring-boot-starter-oauth2-client 라이브러리를 적용하여 소셜 로그인(google, naver, kakao)을 구현하는 과정에서 내부적인 동작 과정에 대한 궁금증으로 찾아본 내용입니다.

 

/*

기존(1.5 버전)에서는 'org.springframework.security.oauth:spring-security-oauth2' 라이브러리를 사용하였다면, 2.x 버전부터는 'org.springframework.boot:spring-boot-starter-oauth2-client' 라이브러리를 사용합니다.

*/

 

OAuth 2.0의 경우 여러 가지 인증 방식이 존재하는데요.

아래 내용은 소셜 로그인에서 가장 일반적으로 사용되는 '권한 부여 코드 승인 방식(Authorization Code Grant)'의 인증 과정이 'spring-boot-starter-oauth2-client 라이브러리의 코드 상에서 어떻게 흘러가는지 내부적인 동작 과정'을 찾아본 것입니다.

 

 

OAuth 2.0의 개념과 동작 방식에 대한 더 자세한 내용이 궁금하시다면 아래 포스팅을 참고하시면 좋을 것 같습니다.

2023.03.22 - [Programming/Web] - OAuth, OAuth2 개념과 동작 방식 정리

 

 


Authorization Code Grant / 권한 부여 코드 승인 방식

Authorization Code Grant

위 이미지는 OAuth2에서 가장 기본이 되는 방식이며, 간편 로그인에 사용되는 'Authorization Code Grant' 방식입니다.

해당 방식의 인증 플로우를 살펴보면 아래와 같은데요.

 

  1. Client는 Authorization Server로 접근 권한을 요청합니다.
    이때 요청에 보내는 파라미터에는 client_id, redirect_uri, response_type=code가 포함되어 있는데요.
    (여기서 Client는 우리가 개발하는 Application을 이야기합니다.)
  2. Client로부터 접근 권한 요청을 받은 Authorization Server는 소셜 로그인을 할 수 있는 로그인 창을 띄워 줍니다.
  3. User(Resource Owner)는 해당 소셜 로그인 창을 통해 로그인을 진행합니다.
  4. Authorization Server는 User가 입력한 회원 정보가 맞는지 여부를 판단한 뒤, 권한 승인 코드(Authorization Code)를 반환하게 됩니다.
  5. Client는 Authorization Code를 통해 Resource Server에 보호된 자원을 요청할 수 있는 Access Token을 요청합니다.
  6. Access Token을 전달받은 Client는 해당 토큰을 통해 Resource Server에 필요한 요청을 보내게 됩니다.

***

User가 로그인 후 Authorization Server에서 Access Token을 바로 발급받는 것이 아니라 Authorization Code를 반환하는 과정이 한번 더 들어간 이유는 바로 보안 때문인데요.

Access Token을 바로 반환받게 된다면 redirect_uri를 통해 받는 방법 밖에 없는데, 이렇게 되는 경우 중요한 데이터인 액세스 토큰이 브라우저를 통해 바로 노출된다는 위험이 있습니다.

 

(해당 내용에 대해서는 간략하게만 설명하였으며, 자세한 내용이 궁금하신 경우 위 OAuth 2.0 개념과 동작 방식에 대한 포스팅을 참고해 주시면 좋을 것 같습니다.)

 

 


내부적인 동작과정

@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
        OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);

        ...
    }
}

(CustomOAuth2UserService)

 

spring boot 프로젝트에서 간편 로그인을 구현하다 보면 OAuth2UserService 인터페이스의 커스텀 구현체인 CustomOAuth2UserService를 구현하게 됩니다.

 

이때 @Override하는 loadUser() 메서드의 매개변수로 OAuth2UserRequest 객체를 받는 것을 볼 수 있는데요.

이 oAuth2UserRequest 객체에는 소셜에서 사용자 인증을 마친 최종 access token을 가지고 있습니다.

 

즉, 소셜 로그인 이후 loadUser() 메서드를 타기 전에, 위에서 본 권한 부여 코드 승인 방식의 4번과 5번 과정이 내부적으로 진행된다는 것인데요. 아래 내용을 통해서는 이 과정이 진행되는 곳을 살펴보도록 하겠습니다.

 

 

Default Login Page

@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class WebSecurityConfigure {
	
    ...
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        ... 
        
        //oauth2Login
        http.oauth2Login()
                .authorizationEndpoint()
                .baseUri("/oauth2/authorize")
                .authorizationRequestRepository(cookieAuthorizationRequestRepository)
                .and()
                .redirectionEndpoint()
                .baseUri("/oauth2/callback/*")
                .and()
                .userInfoEndpoint()
                .userService(customOAuth2UserService)

        ...

        return http.build();
    }
}

(WebSecurityConfigure)

 

http.oauth2Login() 설정 시 체이닝 되는 loginPage() 메서드로 커스텀 로그인 페이지를 설정하지 않는다면, OAuth2LoginConfigurer 클래스의 init() 메서드에 의해 DefaultLoginPageGeneratingFilter 클래스의 DEFAULT_LOGIN_PAGE_URL = "/login"이 로그인 페이지로 설정되게 되는데요.

 

 

DefaultLoginPageGeneratingFilter

DefaultLoginPageGeneratingFilter는 이름 그대로 spring security에서 따로 설정이 없을 때 기본 로그인 페이지를 생성해 주는 클래스로 generateLoginPageHtml() 메서드가 동작하며 아래와 같은 로그인 페이지를 생성하게 됩니다.

 

 

Login with OAuth 2.0

(application-oauth.properties 설정으로 인해 google, naver, kakao 소셜 로그인이 생성되었습니다.)

 

 

DefaultRedirectStrategy sendRedirect() method

이후 각각의 소셜 로그인 버튼을 클릭하면 OAuth2AuthorizationRequestRedirectFilter의 sendRedirectForAuthorization() 메서드가 동작하는데요.

이때 내부적으로 RedirectStrategy 인터페이스의 구현체인 DefaultRedirectStrategy 클래스의 sendRedirect() 메서드가 동작하며 해당 소셜 로그인의 로그인 페이지를 클라이언트에게 반환하게 됩니다.

 

 

Authorization Code

OAuth2LoginAuthenticationFilter attemptAuthentication() method

이후 Resource Owner가 해당 소셜 로그인 진행하고 나면 OAuth2LoginAuthenticationFilter 클래스의 attemptAuthentication() method가 동작하며 Authorization Server로부터 전달된 code를 HttpServletRequest로부터 가져오게 됩니다.

 

 

Access Token

OAuth2AuthorizationCodeAuthenticationProvider autheenticate() method

이어지는 과정은 OAuth2AuthorizationCodeAuthenticationProvider 클래스의 authenticate() 메서드에서 살펴볼 수 있는데요.

 

해당 메서드의 중간쯤 있는 this.accessTokenResponseClient.getTokenResponse()를 살펴보면, 이때는 acceeTokenResponseClient의 구현체인 DefaultAuthorizationCodeTokenResponseClient의 getTokenResponse() 메서드가 동작하며 AuthorizationCode를 통해 Access Token을 요청하게 됩니다.

 

 

DefaultAuthorizationCodeTokenResponseClient getTokenResponse() method

요청 과정은 this.requestEntityConverter.convert(authorizationCodeGrantRequest)를 통해 진행되는데, 이때 requestEntityConverter의 구현체는 OAuth2AuthorizationCodeGrantRequestEntityConverter가 사용되며, convert() 메서드의 경우 추상 클래스인 AbstractOAuth2AuthorizationGrantRequestEntityConverter 클래스에 구현되어 있습니다.

 

디버깅을 통해 convert() 메서드를 살펴보면 client_id, redirect_uri, grant_type, code 등을 가지고 Authorization Server로 POST 요청을 보내는 것을 확인할 수 있습니다.

 

이후 나머지 과정을 거쳐 생성된 OAuth2UserRequest가 OAuth2 로그인을 위해 직접 구현한 CustomOAuth2UserService의 loadUser() 메서드에 매개변수로 들어오게 되는 것입니다.