Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save abhi2495/51ad47394da2effc0fb63f97b3a78e37 to your computer and use it in GitHub Desktop.
Save abhi2495/51ad47394da2effc0fb63f97b3a78e37 to your computer and use it in GitHub Desktop.
##################################################################################
##################################################################################
######### IF YOU FOUND THIS GIST USEFUL, PLEASE LEAVE A STAR. THANKS. ############
##################################################################################
##################################################################################
spring:
security:
oauth2:
client:
provider:
<provider-name>:
issuer-uri: <issuer-uri implementing OIDC>
registration:
<provider-name>:
client-id: <client-id>
client-secret: <client-secret>
scope: <comma separated scopes>
authorization-grant-type: client_credentials
OR
spring:
security:
oauth2:
client:
provider:
<provider-name>:
token-uri: <token-uri of provider implementing OIDC>
registration:
<provider-name>:
client-id: <client-id>
client-secret: <client-secret>
scope: <comma separated scopes>
authorization-grant-type: client_credentials
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
//Relevant dependencies
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
webClient.get()
.uri(<protected resource uri which you want to access>)
.attributes(clientRegistrationId(<The Provider name specified under registration in app yaml>))
.retrieve()
.bodyToMono(String.class)
.map(string
-> "Retrieved using Client Credentials Grant Type: " + string)
.subscribe(LOGGER::info);

This gist describes the configuration required for Spring reactive WebClient to make a call to an OAuth2 protected resource through OAuth2.0 Client Credentials Grant Type Flow.

Assumption is that the Authorization Server supports OpenId Connect 1.0 specifications.

@EnableWebFluxSecurity
public class WebSecurityConfiguration {
@Bean
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder().filter(oauth).build();
}
@Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
return http
.oauth2Client()
.and()
.build();
}
}
@krnbr
Copy link

krnbr commented Jul 10, 2020

am getting redirected to /oauth2/authorization/<provider name>

This code is not working as expected for me

@krnbr
Copy link

krnbr commented Jul 10, 2020

can you please help on it

@abhi2495
Copy link
Author

abhi2495 commented Jul 12, 2020

@krnbr Hey Karanbir, I think you are using authorization code oauth flow. That's why you are getting redirected. If you can share your code (github link) or post your question in stackoverflow, I would definitely take a look .

In my code, I am trying to access a resource that supports "client credentials" flow. That's why there is no redirection. This is pure machine-to-machine communication.

@krnbr
Copy link

krnbr commented Jul 12, 2020

I think you are missing some configuration, may be..

I configured things exactly like you shared above and it was definitely client credentials

Actually the framework (Spring Oauth2 client) is internally doing some redirection to /oauth2/authorization/<provider-name>

@abhi2495
Copy link
Author

@krnbr Unfortunately, I dont see any configuration missing at this moment. It's partly copied from my working sample. As I said, if you can share your code (github repo link) or post your question in stackoverflow, I would definitely take a look .

@krnbr
Copy link

krnbr commented Jul 13, 2020

never mind i figured it out. I had to add the filter chain, for making it to work

@abhi2495
Copy link
Author

@krnbr can you please paste the code which part was missing? I would add that in the gist.

@krnbr
Copy link

krnbr commented Jul 13, 2020

sure will share that

@ractive
Copy link

ractive commented Jul 17, 2020

@krnbr I'm facing the same issue that I'm getting redirected to /oauth2/authorization/<provider> with a configured client_credentials grant. How exactly did you fix this?

@ractive
Copy link

ractive commented Jul 17, 2020

Adding this to the configuration solved it:

	@Bean
	SecurityWebFilterChain configure(ServerHttpSecurity http) {
		return http
			.oauth2Client()
			.and()
			.build();
	}

@krnbr
Copy link

krnbr commented Jul 17, 2020

Yes @ractive

that is the correct way. Only after that it will work, which @abhi2495 was missing on the gist

@abhi2495
Copy link
Author

Thanks @ractive and @krnbr for pointing out. Adding it to the gist.

@winster
Copy link

winster commented Jul 23, 2020

@abhi2495, I get an error when using the exact code.. it seems the oauth client not sending the credentials in the token request
`2020-07-24 02:46:00.960 TRACE 15272 --- [ scheduling-1] o.s.w.r.f.client.ExchangeFunctions : [6ec5c256] HTTP POST http://localhost:8780/auth/realms/ng/protocol/openid-connect/token, headers={masked}
2020-07-24 02:46:01.104 TRACE 15272 --- [ctor-http-nio-2] o.s.http.codec.FormHttpMessageWriter : [6ec5c256] Writing form fields [grant_type, client_id, scope] (content masked)
2020-07-24 02:46:01.173 TRACE 15272 --- [ctor-http-nio-2] o.s.w.r.f.client.ExchangeFunctions : [6ec5c256] Response 401 UNAUTHORIZED, headers={masked}
2020-07-24 02:46:01.216 TRACE 15272 --- [ctor-http-nio-2] o.s.http.codec.json.Jackson2JsonDecoder : [6ec5c256] Decoded [{error=unauthorized_client, error_description=Client secret not provided in request}]
2020-07-24 02:46:01.243 ERROR 15272 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task

org.springframework.security.oauth2.client.ClientAuthorizationException: [unauthorized_client] Client secret not provided in request
at org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider.lambda$authorize$0(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java:82) ~[spring-security-oauth2-client-5.3.3.RELEASE.jar:5.3.3.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to GET http://localhost:8090/app2/api [DefaultWebClient]
Stack trace:
`

@abhi2495
Copy link
Author

@winster From the logs, it looks like Client secret not provided in request. Please check your application yaml once.

@krnbr
Copy link

krnbr commented Jul 24, 2020

@winster see if you are using this bean correctly?

@Bean
  public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    return WebClient.builder().filter(oauth).build();
  }

@krnbr
Copy link

krnbr commented Jul 24, 2020

@winster probably you can refer link here

@winster
Copy link

winster commented Jul 24, 2020

Thank you guys for the quick response. I don't have a clue what went wrong last time. But now it works. I was playing with different config including @krnbr 's. But my observations are below

  • When I use DefaultReactiveOAuth2AuthorizedClientManager, I get a different error like IllegalArgumentException: serverWebExchange cannot be null. Now I use @abhi2495 's code only which is with AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.

  • I dont need a SecurityWebFilterChain bean. Do you know what is the use of it? My app has nothing but a scheduled job in which a webclient talks to another app.

  • My auth server is Keycloak. If the auth url uses https, spring throws SSLhandshakeException, though my httpclient (of webclient) uses an insecure trust factory. Do you know how to fix this?

  • And most importantly, spring oauth2 client is not reusing the token. Everytime it requests for a new token. I configured the access token with 1 minute (minimum allowed) lifespan and client session expiry time. And I can see that Keycloak is retaining the sessions (the number of sessions just increases)

This is the sample app https://github.com/winster/oauth

@krnbr
Copy link

krnbr commented Jul 24, 2020

@winster

For https please find some initial details here

@ractive
Copy link

ractive commented Jul 24, 2020

BTW: I realized that if you use spring boot you should not define the WebClient bean like this. Spring boot normally configures a WebClient.Builder instance that considers settings like spring.jackson.serialization.write-dates-as-timestamps=false etc. and creates a WebClient bean for you, using this builder. But if you create a WebClient bean like this, the settings in spring boot’s builder are lost - and so are you! 😉

Use either this:

@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
    return webClientBuilder
            .filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager))
            .build();
}

or even better this:

@Bean
public WebClientCustomizer webClientCustomizer(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
	return webClientBuilder -> webClientBuilder
		.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager));
}

And use this builder in your services to create a WebClient instance:

@PostConstruct
public void init() {
	this.webClient = webClientBuilder.build();
}

See also here: https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-webclient-customization

@krnbr
Copy link

krnbr commented Jul 24, 2020

thanks @ractive for the info

@krnbr
Copy link

krnbr commented Jul 24, 2020

@winster

the other question

[DefaultReactiveOAuth2AuthorizedClientManager ](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.html)for use within the context of ServerWebExchange, like server based stuff, etc.

In case for the scheduled job etc. standalone code -> you will get ServerWebExchange as null only

But if it is not server context based then You should use the [AuthorizedClientServiceOAuth2AuthorizedClientManager](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.html)- like in your case - in a scheduled/background thread

even if you see the packages the difference is the web package

@winster
Copy link

winster commented Jul 24, 2020

@krnbr, that was very handy. Thanks! Now sslhandshake with auth server is successful and a new token is issued every time. But resource server says 401, logs say An error occurred while attempting to decode the Jwt: The iss claim is not valid. Debugging on that.
Regarding my 4th point, what do you think? How can we make authorizedClientManager to request a new token only if Resource Server says invalid token?

@krnbr
Copy link

krnbr commented Jul 24, 2020

@winster

Do clap on that page. I actually invested time writing that one..

resource server means that the access token's issuer is not matching as per the JWT based access token you are sending, which is the issuer claim configured in the access token.

And it is not matching the issuers allowed in resource server.

I guess one minute is less for the access token..

you should try to increase the expiry, like practically i did a expiry of token for 2 hours and it was working flawlessly.. Do not know the case of AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager

@winster
Copy link

winster commented Jul 24, 2020

done! Without SSL, token is valid though. The problem is that, Resource Server uses http URL for jwt.issuer-uri and it does not match with the URL in the jwt which is https as you rightly pointed out. Simply changing the uri at Resource Server to https, throws SSLHandshakeException. Interestingly, oauth2-client-jose (5.3.3) still uses RestTemplate.
I fixed it by using a (Global) RestTemplate Customizer at Resource Server. But would have been better, if I could build a rest template only for Auth Server.
https://github.com/winster/oauth

@krnbr thanks again. Changing the access token lifespan to 5 minutes, does not generate lot of new sessions at Auth Server

@haydenrear
Copy link

Thanks so much for this! Really helpful.

@rajeevprasanna
Copy link

Can someone help me with this. it is redirecting to relative URL /authorization/{provider} without going through actual redirect link

@sohskd
Copy link

sohskd commented Jun 3, 2021

Hi do you have an example of Spring Cloud Gateway using the Webflux? I have posted a question here https://stackoverflow.com/questions/67801105/spring-cloud-gateway-with-custom-auth-server-client-credentials-flow-with-webcli

@PaoloHi
Copy link

PaoloHi commented Apr 22, 2024

hi , since .oauth2Client() its now actually depracated for Lamabda functions on spring 7 what actually would be the translataion ? or simply would it be obiate it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment