Spring/Spring Security

[Spring Security] 7-4. OAuth2 Client: Authorized Client Features

noahkim_ 2025. 7. 20. 11:03

1. Resolving an Authorized Client

@RegisteredOAuth2AuthorizedClient

  • 컨트롤러 메서드에서 OAuth2AuthorizedClient를 주입받을 수 있음
  • OAuth2AuthorizedClientArgumentResolver에 의해 주입됨
  • OAuth2AuthorizedClientArgumentResolver는 OAuth2AuthorizedClientManager를 사용하여 가져옴
  • OAuth2AuthorizedClientManager는 Repository와 Provider에게 위임하여 OAuth2AuthorizedClient를 반환함
    • Provider의 전략 객체별 조건을 만족해야 동작함

 

예시) OAuth2ClientController

더보기
@Controller
public class OAuth2ClientController {

    @GetMapping("/")
    public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
        // accessToken으로 외부 API 호출 등 수행

        return "index";
    }
}

 

2. RestClient Integration

OAuth2ClientHttpRequestInterceptor

  • 보호된 자원 서버에 접근할 때 Bearer 토큰을 자동으로 Authorization Header에 넣어주는 인터셉터 설정
  • 인증 토큰이 없을 경우, 내부적으로 OAuth2AuthorizedManager를 사용해서 인증 토큰 얻어온 후, 요청함

 

설정) RestClient

더보기
@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        OAuth2ClientHttpRequestInterceptor requestInterceptor =
            new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);

        return RestClient.builder()
            .requestInterceptor(requestInterceptor)
            .build();
    }
}

 

Providing the clientRegistrationId

  • 요청 시, 어떤 provider에 요청할지는 clientRegistrationIdResolver가 담당함
  • 기본적으로 RequestAttributeClientRegistrationIdResolver를 사용함
    • request의 'clientRegistrationId' attribute에 의해 결정됨

 

 

커스터마이징) clientRegistrationIdResolver

더보기
@Bean
public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    OAuth2ClientHttpRequestInterceptor interceptor =
        new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);

    interceptor.setClientRegistrationIdResolver((request) -> {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return (authentication instanceof OAuth2AuthenticationToken principal)
            ? principal.getAuthorizedClientRegistrationId()
            : null;
    });

    return RestClient.builder()
        .requestInterceptor(interceptor)
        .build();
}

 

Providing the principal

  • OAuth2ClientHttpRequestInterceptor는 기본적으로 SecurityContextHolder에서 현재 로그인한 사용자(principal)를 읽음
  • clientRegistrationId + principal로 그 사용자의 OAuth2AuthorizedClient를 찾거나 새로 토큰을 받음

 

커스터마이징) principleResolver

더보기
requestInterceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
  • HttpRequest의 attributes()에 명시적으로 principle을 지정할 수 있음

 

Handling Failure

  • Access Token이 실패했을 때, 해당 OAuth2AuthorizedClient를 자동으로 삭제하는 방식이 필요 (예: 만료됨)
  • OAuth2ClientHttpRequestInterceptor는 OAuth2AuthorizedClient를 저장하는 두가지 컴포넌트를 주입받아 삭제함
    • Repository: http request 흐름일 경우
    • Service: 서버 사이드 동작 경우

 

설정) Repository 기반

더보기
OAuth2AuthorizationFailureHandler failureHandler =
    OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientRepository);
requestInterceptor.setAuthorizationFailureHandler(failureHandler);

 

설정) Service 기반

더보기
OAuth2AuthorizationFailureHandler failureHandler =
    OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientService);
requestInterceptor.setAuthorizationFailureHandler(failureHandler);

 

 


출처