Spring Security 5 Replacement for OAuth2RestTemplate - java

In spring-security-oauth2:2.4.0.RELEASE classes such as OAuth2RestTemplate, OAuth2ProtectedResourceDetails and ClientCredentialsAccessTokenProvider have all been marked as deprecated.
From the javadoc on these classes it points to a spring security migration guide that insinuates that people should migrate to the core spring-security 5 project. However I'm having trouble finding how I would implement my use case in this project.
All of the documentation and examples talk about integrating with a 3rd part OAuth provider if you want incoming requests to your application to be authenticated and you want to use the 3rd party OAuth provider to verify the identity.
In my use case all I want to do is make a request with a RestTemplate to an external service that is protected by OAuth. Currently I create an OAuth2ProtectedResourceDetails with my client id and secret which I pass into an OAuth2RestTemplate. I also have a custom ClientCredentialsAccessTokenProvider added to the OAuth2ResTemplate that just adds some extra headers to the token request that are required by the OAuth provider I'm using.
In the spring-security 5 documentation I've found a section that mentions customising the token request, but again that looks to be in the context of authenticating an incoming request with a 3rd party OAuth provider. It is not clear how you would use this in combination with something like a ClientHttpRequestInterceptor to ensure that each outgoing request to an external service first gets a token and then gets that added to the request.
Also in the migration guide linked above there is reference to a OAuth2AuthorizedClientService which it says is useful for using in interceptors, but again this looks like it relies on things like the ClientRegistrationRepository which seems to be where it maintains registrations for third party providers if you want to use that provide to ensure an incoming request is authenticated.
Is there any way I can make use of the new functionality in spring-security 5 for registering OAuth providers in order to get a token to add to outgoing requests from my application?

OAuth 2.0 Client features of Spring Security 5.2.x do not support RestTemplate, but only WebClient. See Spring Security Reference:
HTTP Client support
WebClient integration for Servlet Environments (for requesting
protected resources)
In addition, RestTemplate will be deprecated in a future version. See RestTemplate javadoc:
NOTE: As of 5.0, the non-blocking, reactive
org.springframework.web.reactive.client.WebClient offers a modern
alternative to the RestTemplate with efficient support for both sync
and async, as well as streaming scenarios. The RestTemplate will be
deprecated in a future version and will not have major new features
added going forward. See the WebClient section of the Spring Framework
reference documentation for more details and example code.
Therefore, the best solution would be to abandon RestTemplate in favor of WebClient.
Using WebClient for Client Credentials Flow
Configure client registration and provider either programmatically or using Spring Boot auto-configuration:
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
…​and the 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;
}
Configure the WebClient instance to use ServerOAuth2AuthorizedClientExchangeFilterFunction with the provided OAuth2AuthorizedClientManager:
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
Now, if you try to make a request using this WebClient instance, it will first request a token from the authorization server and include it in the request.

Hi maybe it's too late however RestTemplate is still supported in Spring Security 5, to non-reactive app RestTemplate is still used what you have to do is only configure spring security properly and create an interceptor as mentioned on migration guide
Use the following configuration to use client_credentials flow
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
Configuration to 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;
}
}
Interceptor
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();
}
};
}
}
This will generate access_token in the first call and whenever the token is expired. OAuth2AuthorizedClientManager will manage all this to you

I found #matt Williams answer quite helpful. Though I would like add in case someone would like to programatically pass clientId and secret for WebClient configuration. Here is how it can be Done.
#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);
}
}

The above answer from #Anar Sultanov helped me get to this point, but as I had to add some additional headers to my OAuth token request I thought I would provide a full answer for how I solved the issue for my use case.
Configure provider details
Add the following to 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:}
Implement custom ReactiveOAuth2AccessTokenResponseClient
As this is server-to-server communication we need to use the ServerOAuth2AuthorizedClientExchangeFilterFunction. This only accepts a ReactiveOAuth2AuthorizedClientManager, not the non-reactive OAuth2AuthorizedClientManager. Therefore when we use ReactiveOAuth2AuthorizedClientManager.setAuthorizedClientProvider() (to give it the provider to use to make the OAuth2 request) we have to give it a ReactiveOAuth2AuthorizedClientProvider instead of the non-reactive OAuth2AuthorizedClientProvider. As per the spring-security reference documentation if you use a non-reactive DefaultClientCredentialsTokenResponseClient you can use the .setRequestEntityConverter() method to alter the OAuth2 token request, but the reactive equivalent WebClientReactiveClientCredentialsTokenResponseClient does not provide this facility, so we have to implement our own (we can make use of the existing WebClientReactiveClientCredentialsTokenResponseClient logic).
My implementation was called UaaWebClientReactiveClientCredentialsTokenResponseClient (implementation omitted as it only very slightly alters the headers() and body() methods from the default WebClientReactiveClientCredentialsTokenResponseClient to add some extra headers/body fields, it does not change the underlying auth flow).
Configure WebClient
The ServerOAuth2AuthorizedClientExchangeFilterFunction.setClientCredentialsTokenResponseClient() method has been deprecated, so following the deprecation advice from that method:
Deprecated. Use ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager) instead. Create an instance of ClientCredentialsReactiveOAuth2AuthorizedClientProvider configured with a WebClientReactiveClientCredentialsTokenResponseClient (or a custom one) and than supply it to DefaultReactiveOAuth2AuthorizedClientManager.
This ends up with configuration looking something like:
#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();
}
Use WebClient as normal
The oAuth2WebClient bean is now ready to be used to access resources protected by our configured OAuth2 provider in the way you would make any other request using a WebClient.

This is a simple alternative to OAuth2RestTemplate. The following snippet has been tested using Spring Boot 3.0.0-M4 and there is no application.yml configuration is needed.
SecurityConfig.java
#Bean
public ReactiveClientRegistrationRepository getRegistration() {
ClientRegistration registration = ClientRegistration
.withRegistrationId("custom")
.tokenUri("<token_URI>")
.clientId("<client_id>")
.clientSecret("<secret>")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId("custom");
return WebClient.builder()
.filter(oauth)
.filter(errorHandler()) // This is an optional
.build();
}
public static ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new IllegalAccessException(errorBody)));
} else {
return Mono.just(clientResponse);
}
});
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0-M4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependencies>

Related

How do I get Spring Security OpenID Connect to work from behind a corporate firewall?

I'm trying to get OIDC working from behind a corporate firewall, and I'm running into some issues.
I am trying to use the Google as my OpenID provider. I have configured an OAuth 2.0 Client ID in Google with type "Web Application".
I have updated my application.yaml and added the following properties:
security:
oauth2:
client:
registration:
google:
client-id: <client-id from google>
client-secret: <client-secret from google>
I have a WebSecurityConfigurerAdapter class with the following configure() method (shamelessly copied from Baeldung):
#Override
protected void configure(HttpSecurity http) throws Exception{
Set<String> googleScopes = new HashSet<>();
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.email");
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.profile");
OidcUserService googleUserService = new OidcUserService();
googleUserService.setAccessibleScopes(googleScopes);
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin
.userInfoEndpoint()
.oidcUserService(googleUserService)
);
}
When I attempt view the application in a browser, everything initially looks good. I get redirected to google to authenticate, and then google redirects back to my application. However it all goes south when the application attempts to exchange the authorization code for a access token.
I get the following error:
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token
Response: I/O error on POST request for "https://www.googleapis.com/oauth2/v4/token": Software caused
connection abort: recv failed; nested exception is java.net.SocketException: Software caused connection
abort: recv failed
Ok, looks like an issue with the corporate firewall. Cool, no problem I'll just use a RestTemplateCustomizer. So I declare the following beans:
#Bean
HttpHost proxyHost(#Value("${http.proxyHost}") String proxyHost) {
log.debug("Setting proxy host to: '{}'", proxyHost);
return HttpHost.create(proxyHost);
}
#Bean
DefaultProxyRoutePlanner proxyRoutePlanner(HttpHost proxyHost) {
return new DefaultProxyRoutePlanner(proxyHost);
}
#Bean
HttpClient httpClient(DefaultProxyRoutePlanner proxyRoutePlanner) {
return HttpClientBuilder.create().setRoutePlanner(proxyRoutePlanner).build();
}
#Bean
HttpComponentsClientHttpRequestFactory httpRequestFactory(HttpClient httpClient) {
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
#Bean
RestTemplateCustomizer restTemplateCustomizer(HttpComponentsClientHttpRequestFactory httpRequestFactory) {
RestTemplateCustomizer restTemplateCustomizer = (RestTemplate template)->{
log.debug("Returning a customized rest template.");
template.setRequestFactory(httpRequestFactory);
};
return restTemplateCustomizer;
}
This failed to fix the problem. After some debugging, I realized that the DefaultAuthorizationCodeTokenResponseClient is creating its restOperations by calling new RestTemplate() rather than using RestTemplateBuilder, so all of my work to create a RestTemplateCustomizer is for nothing.
So I updated my WebSecurityConfigurerAdapter:
public class OidcSecurityConfigurer extends WebSecurityConfigurerAdapter {
private RestTemplateBuilder restTemplateBuilder;
public OidcSecurityConfigurer(#Autowired RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}
#Override
protected void configure(HttpSecurity http) throws Exception{
Set<String> googleScopes = new HashSet<>();
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.email");
googleScopes.add(
"https://www.googleapis.com/auth/userinfo.profile");
OidcUserService googleUserService = new OidcUserService();
googleUserService.setAccessibleScopes(googleScopes);
http
.authorizeRequests(
authorizeRequests -> authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauthLogin -> oauthLogin
.tokenEndpoint(teCustomizer->{
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRestOperations(this.restTemplateBuilder.build());
teCustomizer.accessTokenResponseClient(client);
})
.userInfoEndpoint()
.oidcUserService(googleUserService)
);
}
}
This got a little further. Now it was getting connected to google, but it wasn't able to read the response due to a NullPointerException on line 80 in DefaultAuthorizationCodeTokenResponseClient.java.
java.lang.NullPointerException: null
at org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient.getTokenResponse(DefaultAuthorizationCodeTokenResponseClient.java:80) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient.getTokenResponse(DefaultAuthorizationCodeTokenResponseClient.java:57) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.getResponse(OidcAuthorizationCodeAuthenticationProvider.java:170) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:144) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.6.3.jar:5.6.3]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:195) ~[spring-security-oauth2-client-5.6.3.jar:5.6.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223) ~[spring-security-web-5.6.3.jar:5.6.3]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:213) ~[spring-security-web-5.6.3.jar:5.6.3]
Looking at the code, it seems that the token isn't being decoded properly because I didn't add the right messageConverters to the RestTemplate. So updated the RestTemplateCustomizer declaration as follows:
#Bean
RestTemplateCustomizer restTemplateCustomizer(HttpComponentsClientHttpRequestFactory httpRequestFactory) {
RestTemplateCustomizer restTemplateCustomizer = (RestTemplate template)->{
log.debug("Returning a customized rest template.");
template.setRequestFactory(httpRequestFactory);
template.setMessageConverters(
Arrays.asList(
new FormHttpMessageConverter(),
new OAuth2AccessTokenResponseHttpMessageConverter()
)
);
template.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
};
return restTemplateCustomizer;
}
Now the access token is being decoded correctly, but the JWT user token is failing. WTH? So I looked at the class NimbusJwtDecoder and it also is calling new RestTemplate(). So I apparently need to figure out how to change the RestTemplate being used by the NimbusJwtDecoder similar to what I did for DefaultAuthorizationCodeTokenResponseClient.
At this point I'm wondering if I'm barking up the wrong tree. This seems like so much work just get the built-in OAuth2 support working. It almost seems like it would be easier to just handle the all the OAuth2 handshake stuff manually rather than using what's built into spring-security. The kicker is that this is primarily an issue that will only occur during development. For a production deployment of the app they can easily open the firewall to allow the application to make outgoing connections to the OpenID provider.
I would appreciate any guidance anyone could give.
Thanks,
Dave

Spring WebClient with OAuth2 is not storing access token

I have WebClient in my Spring Boot application that connects to the external service via OAuth2, and the configuration of it looks like following:
#Configuration
#RequiredArgsConstructor
public class OAuth2ClientConfiguration {
private final OAuth2ClientProperties properties;
#Bean
ReactiveClientRegistrationRepository clientRegistration() {
ClientRegistration registration = ClientRegistration
.withRegistrationId(properties.getClientRegistrationId())
.tokenUri(properties.getTokenUri())
.clientId(properties.getClientId())
.clientSecret(properties.getClientSecret())
.authorizationGrantType(new AuthorizationGrantType(properties.getAuthorizationGrantType()))
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistration) {
var clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistration);
var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistration, clientService);
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId(properties.getClientRegistrationId());
return WebClient.builder()
.filter(oauth)
.build();
}
}
and here is an access token:
{
"access_token": "some_generated_access_token",
"token_type": "bearer",
"expires_in": 82822,
"scope": "api",
"jti": "6e1a8d7c-3909-4acf-9168-cf912fcd0c8a"
}
It is working and everything is Ok, but... it is not storing the access token after it gets it, it is getting new access token each time it is called. I figured it out when launching my integration tests and verifying Authorization Server calls. However in configuration shown above it should store in memory.
I found out in internet this kind of problem can occur with SpringBoot version up to 2.2.3 and "org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE"
But I am using newest version of the Spring Boot 2.4.9, and it uses org.springframework.security:spring-security-oauth2-client:5.4.7
How can this issue be solved?

How to set any attribute in ClientRegistration in spring oauth2

Since I cannot keep client secret in application.yml , so it's kept in vault and from there it gets resolved. However, I can see that ClientRegistration is a final class , hence it's client secret can't be set later once the bean is already initialized.
In such case how can I set secret & use new object of ClientRegistration in all the referred beans.
Something like below I am trying to achieve but don't how to set enrichedClientRegistration in webclient or other referred places.
#Slf4j
#Configuration
public class WebClientConfig {
#Bean
WebClient authWebClient(ClientRegistrationRepository clientRegistrations,
OAuth2AuthorizedClientRepository authorizedClients,
PasswordResolver passwordResolver) {
var clientRegistration = clientRegistrations.findByRegistrationId("myApp");
log.info("Before client secret is {}",clientRegistration.getClientSecret());
var clientSecret = passwordResolver.resolve(clientRegistration.getClientSecret());
log.info("Resolved client secret is {}", clientSecret);
var enrichedClientRegistration=ClientRegistration.withClientRegistration(clientRegistration)
.clientSecret(clientSecret)
.build();
log.info("After client secret is {}",clientRegistrations.findByRegistrationId("myApp").getClientSecret());
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
oauth.setDefaultClientRegistrationId("myApp");
return WebClient.builder()
.apply(oauth.oauth2Configuration())
.build();
}
}
Since ClientRegistration is a final class which in injected into ClientRegistrationRepository, so you need completely override ClientRegistrationRepository as per example given in spring documentation.
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/jc.html#jc-oauth2login-completely-override-autoconfiguration

Oauth 2 spring RestTemplate login with refresh token

What I wanna achieve
So I have a client application in java (JavaFX + Spring-boot hybrid-application). You can have a look at it here https://github.com/FAForever/downlords-faf-client . So till now we stored username/ password if the user wished to be kept logged in which is obviously a pretty bad idea. So now I wanna store the refreshtoken and then log the user in with that.
What it looks like now
See here
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
What I found so far
I found out that restTemplate.getAccessToken().getRefreshToken() will give me the refreshtoken I want to save and later so to keep the user logged in.
What I can not figure out
I can not find a way to create a OAuth2RestTemplate with an refresh token only. Is that even possible? Can someone point me in the right direction? Maybe link me some articles to read? Is this the right place to read?
I do not think this is possible with an OAuth2RestTemplate, but you can reimplement the desired parts yourself. I'd like to share an example with your for OAuth password login to Microsofts flavour of OAuth2 (Azure Active Directory). It does miss the piece of fetching a new token from an existing refresh token yet, but I added a comment where you need to add it.
A simple way to mimic OAuthRestTemplates behavior is a custom ClientHttpRequestInterceptor which delegates the token fetching to a dedicated Spring service component, that you append to your RestTemplate:
#RequiredArgsConstructor
#Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
#NotNull
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
This interceptor can be added to your primary RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
The token service used in the interceptor holds the token in a cache and on request checks for the expiry of the token and if required queries a new one.
#Service
#Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
In this code example you would once login using username and password and afterwards all further logins would be using the refresh token. If you want to use the refresh token directly, you use the public method, otherwise it will be done internally.
Since the login code is specifically written for login to Microsoft AAD, you should recheck the MultiValueMap parameters.
TokenServiceProperties are straightforward:
#Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Adapt them if needed.
The whole solution has one minor drawback: Instead of one RestTemplate that you usually fetch via depency injection, you now need a second one (a "simple" one) to fetch the OAuth token. In this example we create it in the constructor of the TokenService. However this is in general bad style as it makes it harder for unit testing etc. You could also think about using qualified beans or using a more basic http client in the TokenService.
Another important thing to note: I am using the spring-security-oauth2 package here. If you did not configure Spring Security in your project, this will trigger Spring Security auto-configuration which might not be desired - you can solve this by excluding undesired packages, e.g. in gradle:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}

Store access token in OAuth2.0 application and use it repeatedly until it expires?

I'm developing an OAuth2.0 "CLIENT" application which call some APIs(secured by oauth2.0).
I'm using OAuth2.0RestTemplate which contains CLIENT_ID, CLIENT_SECRET, username and password. The code for calling OAuth2.0 secured APIs looks like this:
#Bean
OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
List<String> Scopes = new ArrayList<String>(2);
Scopes.add("read");
Scopes.add("write");
resource.setClientAuthenticationScheme(AuthenticationScheme.header);
resource.setId("*****");
resource.setAccessTokenUri(tokenUrl);
resource.setClientId("*****");
resource.setClientSecret("*****");
resource.setGrantType("password");
resource.setScope(Scopes);
resource.setUsername("*****");
resource.setPassword("*****");
return resource;
}
#Autowired
private OAuth2RestTemplate restTemplate;
Map<String, String> allCredentials = new HashMap<>();
allCredentials.put("username", "***");
allCredentials.put("password", "***");
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAll(allCredentials);
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() { };
ResponseEntity<List<MyObject>> response = restTemplate.exchange("https://***.*****.com/api/*****/*****",
HttpMethod.GET,
null,
responseType);
AllCities all = new AllCities();
all.setAllCities(response.getBody());
As you can see everytime I want to call a service the code get a new ACCESS TOKEN which is wildly wrong!!! My question is how can I automatically receive and store the issued token in my application an use it until it expires and then automatically get a new one?
On the other hand my token only contains access token and doesn't contain refresh token(I don't know why!!! this is so weird!!!)
Hello you can design like google client library.
First step you need to create the datastore for store the token in your directory like C:/User/soyphea/.token/datastore.
Before you load your function retrieve access_token_store. Your access token should have expired_in.
if(access_token_store from your datastore !=null && !expired){
access_token = access_token_store.
} else {
access_token = Your RestTemplate function for retrieve access_token.
}
finally you can retrieve access_token.
In spring security oauth2 if you want to support refresh_token you need to set,
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("resource-serv")
.scopes("read")
.resourceIds("my-resource")
.secret("secret123")
.and()
.withClient("app")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.scopes("read")
.resourceIds("my-resource")
.secret("appclientsecret");
}
First of all you have define that your app is a Oaut2App for this in Spring boot you can use the annotation #EnableOAuth2Client in your code and configure the client application metadata in your applicaition.yml. A skeleton client app can be like below:
#EnableOAuth2Client
#SpringBootApplication
public class HelloOauthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloOauthServiceApplication.class, args);
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource){
return new OAuth2RestTemplate(resource);
}
}
application.yml
security:
oauth2:
client:
clientId: client
clientSecret: secret
accessTokenUri: http://localhost:9090/oauth/token
userAuthorizationUri: http://localhost:9090/oauth/authorize
auto-approve-scopes: '.*'
registered-redirect-uri: http://localhost:9090/login
clientAuthenticationScheme: form
grant-type: passwordR
resource:
token-info-uri: http://localhost:9090/oauth/check_token
in this way you have guarantee that the OAuth2RestTemplate of spring will use and upgrade the token

Categories