I want to move from spring-security-oauth to spring-security now, but I cannot find any way of doing so. I searched a lot and all I could find are examples about providing an OAuth Endpoint.
My current OAuth2RestTemplate is a bit complicated, as the oauth server is not using the standard way for identification, inspired by the post here.
This is my OAuth2RestTemplate:
fun createOAuthRestTemplate(resourceDetails: OAuth2ProtectedResourceDetails): OAuth2RestTemplate {
val clientCredentialsAccessTokenProvider = ClientCredentialsAccessTokenProvider()
clientCredentialsAccessTokenProvider.setAuthenticationHandler(<myClientAuthenticationHandler extends ClientAuthenticationHandler>))
clientCredentialsAccessTokenProvider.setRequestFactory(requestFactory)
val oauthTemplate = OAuth2RestTemplate(resourceDetails)
oauthTemplate.setAccessTokenProvider(clientCredentialsAccessTokenProvider)
return oauthTemplate
}
The spring migration guide unfortunately didn't help me much as it just mentions the RestTemplate, but doesn't go into detail.
[...]
A Simplified RestTemplate and WebClient
Spring Security OAuth extends RestTemplate, introducing OAuth2RestTemplate.
This class needs to be instantiated and exposed as a @Bean.
Spring Security chooses to favor composition and instead exposes an
OAuth2AuthorizedClientService, which is useful for creating RestTemplate interceptors
[...]
My question is now:
How do I get the same functionality in a rest template using spring-security?
OAuth 2.0 with Spring Security 5 and RestTemplate
Spring Security 5.2 does not have direct support for RestTemplate
, it has beans that simplify the work, though. The recommendation is to use WebClient
, if you can, instead of RestTemplate
.
However, if you need to use RestTemplate
, then you first want to create an OAuth2AuthorizedClientManager
:
@Bean
OAuth2AuthorizedClientManager authorizeClientManager(
ClientRegistrationRepository clients,
OAuth2AuthorizedClientRepository authorizedClients) {
DefaultOAuth2AuthorizedClientManager manager =
new DefaultOAuth2AuthorizedClientManager(clients, authorizedClients);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
manager.setAuthorizedClientProvider(authorizedClientProvider);
return manager;
}
This manager bean is where you make decisions on the kinds of grant types that you want your interceptor to negotiate for you. It is similar to the ClientCredentialsAccessTokenProvider
in your post.
Second, you'll want to create a RestTemplate
interceptor. It will ask the manager to get it a token and then add that token to the Authorization
header:
@Component
public class OAuth2AuthorizedClientInterceptor implements ClientHttpRequestInterceptor {
OAuth2AuthorizedClientManager manager;
public OAuth2AuthorizedClientInterceptor(OAuth2AuthorizedClientManager manager) {
this.manager = manager;
}
public ClientHttpResponse intercept(
HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
Authentication principal = // ...
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("foo-client")
.principal(principal)
.build();
OAuth2AuthorizedClient authorizedClient =
this.manager.authorize(authorizedRequest);
HttpHeaders headers = httpRequest.getHeaders();
headers.setBearerAuth(authorizedClient.getAccessToken().getValue());
return execution.execute(request, body);
}
}
In Spring Security 5, each client is represented by a registration id. The registration id is what goes in place of foo-client
.
The principal
will depend a bit on your situation, but it is typically sufficient to use SecurityContextHolder.getContext().getAuthentication()
. If there is no user in context, you might consider AnonymousAuthenticationToken
, e.g.
Authentication principal = new AnonymousAuthenticationToken
("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
Finally, you can add your interceptor to your RestTemplate
:
@Bean
public RestTemplate rest(OAuth2AuthorizedClientInterceptor interceptor) {
RestTemplate rest = new RestTemplate();
rest.getInterceptors().add(interceptor);
return rest;
}
JWT Client Authentication
As for the post you referred to, it is doing a JWT for client authentication. For this support, you'll want to look at configuring a DefaultClientCredentialsTokenResponseClient
. This would be part of the manager that you built in the first step:
@Bean
OAuth2AuthorizedClientManager authorizeClientManager(
ClientRegistrationRepository clients,
OAuth2AuthorizedClientRepository authorizedClients) {
DefaultOAuth2AuthorizedClientManager manager =
new DefaultOAuth2AuthorizedClientManager(clients, authorizedClients);
DefaultClientCredentialsTokenResponseClient tokenClient =
new DefaultClientCredentialsTokenResponseClient();
tokenClient.setRequestEntityConverter(fooConverter);
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(cc -> cc
.accessTokenResponseClient(tokenClient))
.build();
manager.setAuthorizedClientProvider(authorizedClientProvider);
return manager;
}
fooConverter
is any converter that you specify for creating the appropriate RequestEntity
instance. Something like:
// ...
tokenClient.setRequestEntityConverter(grantRequest -> {
ClientRegistration client = grantRequest.getClientRegistration();
// ... formulate JWT
// ... create `RequestEntity`, including `Authorization` header
// that includes JWT as the bearer token
});
This setter, setRequestEntityConverter
is the functional equivalent of ClientAuthenticationHandler
in the legacy project.