에서 spring-security-oauth2:2.4.0.RELEASE
같은 클래스 OAuth2RestTemplate
, OAuth2ProtectedResourceDetails
및 ClientCredentialsAccessTokenProvider
사용되지 않는 모든 표시되었습니다.
이 클래스의 javadoc에서 사람들이 핵심 스프링 보안 5 프로젝트로 마이그레이션해야 함을 알려주 는 스프링 보안 마이그레이션 안내서 를 가리 킵니다 . 그러나이 프로젝트에서 사용 사례를 구현하는 방법을 찾는 데 문제가 있습니다.
응용 프로그램에 대한 들어오는 요청을 인증하고 타사 OAuth 공급자를 사용하여 ID를 확인하려는 경우 모든 설명서 및 예제는 타사 OAuth 공급자와의 통합에 대해 설명합니다.
유스 케이스 RestTemplate
에서 OAuth로 보호되는 외부 서비스를 요청 하기 만하면됩니다. 현재 나는 OAuth2ProtectedResourceDetails
고객 ID와 비밀을 사용하여로 전달합니다 OAuth2RestTemplate
. 나는 또한 사용자는 한 ClientCredentialsAccessTokenProvider
에 추가 된 OAuth2ResTemplate
단지 내가 사용하고있어 OAuth를 제공에 필요한 토큰 요청에 몇 가지 추가 헤더를 추가하는.
spring-security 5 문서 에서 토큰 요청 사용자 정의 를 언급하는 섹션을 찾았 지만 다시 타사 OAuth 제공자와 수신 요청을 인증하는 맥락에서 보입니다. ClientHttpRequestInterceptor
외부 서비스에 대한 각 발신 요청이 먼저 토큰을 얻은 다음 요청에 추가되도록하기 위해 이것을 어떻게 a와 결합하여 사용하는지는 확실하지 않습니다 .
또한 위에 링크 된 마이그레이션 안내서에는 OAuth2AuthorizedClientService
인터셉터에서 사용하는 데 유용한 것으로 언급되어 있지만 다시 ClientRegistrationRepository
사용하려면 타사 공급자의 등록을 유지하는 위치 에 의존 하는 것처럼 보입니다. 수신 요청이 인증되도록합니다.
애플리케이션의 발신 요청에 토큰을 추가하기 위해 OAuth 제공자를 등록하기 위해 spring-security 5의 새로운 기능을 사용할 수있는 방법이 있습니까?
답변
OAuth는 2.0 클라이언트는 지원하지 않습니다 5.2.x 봄 보안의 기능을 RestTemplate
하지만, WebClient
. 보기 봄 보안 참조 :
HTTP 클라이언트 지원
WebClient
서블릿 환경을위한 통합 (보호 자원 요청을 위해)
또한 RestTemplate
향후 버전 에서는 더 이상 사용되지 않습니다. RestTemplate javadoc을 참조하십시오 .
참고 : 비 블로킹 반응 형 5.0에서는
스트리밍 시나리오뿐만 아니라 동기화 및 비동기 모두에 대한 효율적인 지원을org.springframework.web.reactive.client.WebClient
제공하는 현대적인 대안을 제공합니다RestTemplate
. 는RestTemplate
향후 버전에서 더 이상 사용되지 않습니다 및 향후 추가 된 새로운 주요 기능이 없습니다. 자세한WebClient
내용과 예제 코드는 Spring Framework 참조 문서 섹션을 참조하십시오 .
따라서 최선의 해결책은 RestTemplate
에 찬성 하여 포기 하는 것입니다 WebClient
.
사용 WebClient
흐름 클라이언트 자격 증명에 대해
프로그래밍 방식으로 또는 Spring Boot 자동 구성을 사용하여 클라이언트 등록 및 제공자를 구성하십시오.
spring:
security:
oauth2:
client:
registration:
custom:
client-id: clientId
client-secret: clientSecret
authorization-grant-type: client_credentials
provider:
custom:
token-uri: http://localhost:8081/oauth/token
… 그리고 OAuth2AuthorizedClientManager
@Bean
:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
제공된 것과 함께 WebClient
사용할 인스턴스를 구성하십시오 .ServerOAuth2AuthorizedClientExchangeFilterFunction
OAuth2AuthorizedClientManager
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
이제이 WebClient
인스턴스를 사용하여 요청을 시도 하면 먼저 권한 부여 서버에서 토큰을 요청하여 요청에 포함시킵니다.
답변
@Anar Sultanov의 위의 답변은이 시점까지 도달하는 데 도움이되었지만 OAuth 토큰 요청에 헤더를 추가해야 할 때 사용 사례의 문제를 해결하는 방법에 대한 완전한 답변을 제공 할 것이라고 생각했습니다.
제공자 세부 사항 구성
에 다음을 추가하십시오 application.properties
spring.security.oauth2.client.registration.uaa.client-id=${CLIENT_ID:}
spring.security.oauth2.client.registration.uaa.client-secret=${CLIENT_SECRET:}
spring.security.oauth2.client.registration.uaa.scope=${SCOPE:}
spring.security.oauth2.client.registration.uaa.authorization-grant-type=client_credentials
spring.security.oauth2.client.provider.uaa.token-uri=${UAA_URL:}
맞춤 구현 ReactiveOAuth2AccessTokenResponseClient
이것은 서버 간 통신이므로을 사용해야합니다 ServerOAuth2AuthorizedClientExchangeFilterFunction
. 이것은 ReactiveOAuth2AuthorizedClientManager
비 반응이 아닌을 허용합니다 OAuth2AuthorizedClientManager
. 따라서 ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider()
(제공자가 OAuth2 요청을하는 데 사용하도록) ReactiveOAuth2AuthorizedClientProvider
사용하는 경우 비 반응성 대신에 이를 제공해야합니다 OAuth2AuthorizedClientProvider
. 당으로 스프링 보안 참조 문서 당신이 사용하는 경우 비 반응 DefaultClientCredentialsTokenResponseClient
당신이 사용할 수있는 .setRequestEntityConverter()
으로 OAuth2 토큰 요청을 변경하는 방법을하지만, 반응 상응하는 WebClientReactiveClientCredentialsTokenResponseClient
우리가 (우리가 사용을 자신 할 수있는 우리의 구현해야하므로,이 기능을 제공하지 않습니다 기존 WebClientReactiveClientCredentialsTokenResponseClient
논리).
내 구현이 호출되었습니다 UaaWebClientReactiveClientCredentialsTokenResponseClient
( 기본적 으로 headers()
및 body()
메소드를 약간 변경하여 WebClientReactiveClientCredentialsTokenResponseClient
추가 헤더 / 본문 필드를 추가하기 때문에 구현이 생략되었으므로 기본 인증 흐름은 변경되지 않습니다).
구성 WebClient
이 ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient()
메소드는 더 이상 사용되지 않으므로 해당 메소드에서 제공되지 않는 조언을 따르십시오.
더 이상 사용되지 않습니다. 대신 사용하십시오
ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)
. (또는 사용자 정의 인스턴스)로ClientCredentialsReactiveOAuth2AuthorizedClientProvider
구성된 인스턴스를 작성하고에WebClientReactiveClientCredentialsTokenResponseClient
제공하십시오DefaultReactiveOAuth2AuthorizedClientManager
.
이것은 다음과 같은 구성으로 끝납니다.
@Bean("oAuth2WebClient")
public WebClient oauthFilteredWebClient(final ReactiveClientRegistrationRepository
clientRegistrationRepository)
{
final ClientCredentialsReactiveOAuth2AuthorizedClientProvider
clientCredentialsReactiveOAuth2AuthorizedClientProvider =
new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
clientCredentialsReactiveOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(
new UaaWebClientReactiveClientCredentialsTokenResponseClient());
final DefaultReactiveOAuth2AuthorizedClientManager defaultReactiveOAuth2AuthorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
defaultReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider(
clientCredentialsReactiveOAuth2AuthorizedClientProvider);
final ServerOAuth2AuthorizedClientExchangeFilterFunction oAuthFilter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(defaultReactiveOAuth2AuthorizedClientManager);
oAuthFilter.setDefaultClientRegistrationId("uaa");
return WebClient.builder()
.filter(oAuthFilter)
.build();
}
WebClient
정상적으로 사용
이제 oAuth2WebClient
Bean을 사용하여 다른 요청을하는 방식으로 구성된 OAuth2 제공자가 보호하는 자원에 액세스 할 수 WebClient
있습니다.
답변
@ matt Williams의 답변이 도움이된다는 것을 알았습니다. 누군가가 WebClient 구성을 위해 clientId 및 secret을 프로그래밍 방식으로 전달하려는 경우 추가하고 싶습니다. 다음은 완료 방법입니다.
@Configuration
public class WebClientConfig {
public static final String TEST_REGISTRATION_ID = "test-client";
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
var clientRegistration = ClientRegistration.withRegistrationId(TEST_REGISTRATION_ID)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientId("<client_id>")
.clientSecret("<client_secret>")
.tokenUri("<token_uri>")
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
@Bean
public WebClient testWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId(TEST_REGISTRATION_ID);
return WebClient.builder()
.baseUrl("https://.test.com")
.filter(oauth)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
}
답변
안녕하세요 아마 너무 늦었지만 RestTemplate은 Spring Security 5에서 여전히 지원됩니다.
client_credentials 플로우를 사용하려면 다음 구성을 사용하십시오.
application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
client:
registration:
okta:
client-id: ${okta.oauth2.clientId}
client-secret: ${okta.oauth2.clientSecret}
scope: "custom-scope"
authorization-grant-type: client_credentials
provider: okta
provider:
okta:
authorization-uri: ${okta.oauth2.issuer}/v1/authorize
token-uri: ${okta.oauth2.issuer}/v1/token
OauthResTemplate에 대한 구성
@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {
public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";
private final RestTemplateBuilder restTemplateBuilder;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private final ClientRegistrationRepository clientRegistrationRepository;
@Bean(OAUTH_WEBCLIENT)
RestTemplate oAuthRestTemplate() {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);
return restTemplateBuilder
.additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
.setReadTimeout(Duration.ofSeconds(5))
.setConnectTimeout(Duration.ofSeconds(1))
.build();
}
@Bean
OAuth2AuthorizedClientManager authorizedClientManager() {
var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
요격기
public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final OAuth2AuthorizedClientManager manager;
private final Authentication principal;
private final ClientRegistration clientRegistration;
public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
this.manager = manager;
this.clientRegistration = clientRegistration;
this.principal = createPrincipal();
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(principal)
.build();
OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
return execution.execute(request, body);
}
private Authentication createPrincipal() {
return new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public boolean isAuthenticated() {
return false;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return clientRegistration.getClientId();
}
};
}
}
이것은 첫 번째 호출에서 그리고 토큰이 만료 될 때마다 access_token을 생성합니다. OAuth2AuthorizedClientManager가이 모든 것을 관리합니다