Spring OAuth 401 exception - spring

I'm building a Spring Boot application with OAuth 2.0 and OpenID. Application is an API client. When the user opens the app, he is redirected to Authorization URI (that works). After login and permission grants, he should be redirected to a callback URI (that works).
Before the next page app should take some info from API, but I get org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from GET https(URL of resource).
My application yaml.
spring:
security:
oauth2:
client:
registration:
localhost-for-development-3:
provider: spring
client-id: myId
client-secret: mySecret
client-authentication-method: basic
authorization-grant-type: authorization_code
scope:
- pr.pro
- pr.act
- openid
- offline
redirect-uri: http://localhost:8080/login/oauth2/code/{registrationId}
client-name: localhost-for-development-3
provider:
spring:
authorization-uri: https://someurl.com/oauth2/auth
token-uri: https://someurl.com/oauth2/token
issuer-uri: https://someurl.com/
logging:
level:
'[org.springframework.web]': DEBUG
OAuthClientConfiguration class
#Configuration
public class OAuthClientConfiguration
{
#Bean
ReactiveClientRegistrationRepository clientRegistrations(
#Value(value = "${spring.security.oauth2.client.provider.spring.token-uri}") String tokenUri,
#Value(value = "${spring.security.oauth2.client.registration.localhost-for-development-3.client-id}") String clientId,
#Value(value = "${spring.security.oauth2.client.registration.localhost-for-development-3.client-secret}") String clientSecret,
#Value(value = "${spring.security.oauth2.client.registration.localhost-for-development-3.authorization-grant-type}") String authorizationGrantType,
#Value(value = "${spring.security.oauth2.client.registration.localhost-for-development-3.redirect-uri}") String redirectUri,
#Value(value = "${spring.security.oauth2.client.provider.spring.authorization-uri}") String authorizationUri
)
// #Value(value = "${spring.security.oauth2.client.provider.spring.issuer-uri}") String issuerUri
{
ClientRegistration registration = ClientRegistration
.withRegistrationId("localhost-for-development-3")
.tokenUri(tokenUri)
.clientId(clientId)
.clientSecret(clientSecret)
.scope("m3p.f.pr.pro", "m3p.f.pr.act", "openid", "offline")
.authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
.redirectUri(redirectUri)
.authorizationUri(authorizationUri)
// .issuerUri(issuerUri)
// .jwkSetUri("https://someurl.com")
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations)
{
InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, clientService);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultClientRegistrationId("localhost-for-development-3");
return WebClient.builder().filter(oauth).build();
}
}
I'm not sure what I need to look for in the Google console network tab. I see I got the "code" parameter. It's probably an authorization code. I don't see that I've received a token.
Please, if I need to provide you with more details, inform me what I need to look for, and I'll edit the question.
Also, the API provider mentioned that for issuer-uri I MUST write URL WITHOUT trailing "/" at the end. But when I do so, Spring Security will throw an exception. Is that connected to my problem maybe? I've already mentioned this problem in more detail in this post:
ClientRegistration.Builder adds trailing "/" at the end of issuer uri. How to prevent that?

Related

Authentication with Azure AAD Oauth2 using OBO flow in spring boot

Currently I'm following this guide to enable application type web_application_and_resource_server (reactjs app and spring boot in one application).
And then I have 2 apps called WebApiA and WebApiB, my web_application_and_resource_server need to call to WebApiA which then make a call to WebApiB using OBO flow.
I enabled OBO and expose nessessary APIs exactly the same as this guide for WebApp, WebApiA and WebApiB.
But when I make an api call from my web_application_and_resource_server I get this error in the console:
java.lang.IllegalStateException: Unsupported token implementation class org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
at com.azure.spring.cloud.autoconfigure.aad.implementation.webapi.AadOboOAuth2AuthorizedClientProvider.getOboAuthorizedClient(AadOboOAuth2AuthorizedClientProvider.java:115) ~[spring-cloud-azure-autoconfigure-4.1.0.jar:4.1.0]
at com.azure.spring.cloud.autoconfigure.aad.implementation.webapi.AadOboOAuth2AuthorizedClientProvider.authorize(AadOboOAuth2AuthorizedClientProvider.java:92) ~[spring-cloud-azure-autoconfigure-4.1.0.jar:4.1.0]
at org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider.authorize(DelegatingOAuth2AuthorizedClientProvider.java:71) ~[spring-security-oauth2-client-5.6.5.jar:5.6.5]
at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:176) ~[spring-security-oauth2-client-5.6.5.jar:5.6.5]
at org.springframework.security.oauth2.client.web.method.annotation.OAuth2AuthorizedClientArgumentResolver.resolveArgument(OAuth2AuthorizedClientArgumentResolver.java:137) ~[spring-security-oauth2-client-5.6.5.jar:5.6.5]
This is the code in the controller that cause the error:
#PostMapping("/webApiA")
public ResponseEntity<String> createDeployment(
#RegisteredOAuth2AuthorizedClient("webApiA") OAuth2AuthorizedClient client,
#RequestBody Object body
) {
return new ResponseEntity<>(callWebApiA(client, body), HttpStatus.OK);
}
private String callWebApiA(OAuth2AuthorizedClient client, Object reqBody) {
if (null != client) {
String body = this.webClient
.post()
.uri("http://localhost:8181/webApiA")
.body(BodyInserters.fromValue(JSONStringUtils.toJSONString(reqBody.toString())))
.attributes(oauth2AuthorizedClient(client))
.retrieve()
.bodyToMono(String.class)
.block();
return "webapiB response " + (null != body ? "success." : "failed.") + body;
} else {
return "webapiB response failed.";
}
}
Here is my application.yaml for my web_application_and_resource_server:
spring:
cloud:
azure:
active-directory:
enabled: true
post-logout-redirect-uri: 'http://localhost:8080/'
profile:
tenant-id: ${mytennantID}
credential:
client-id: ${myappid}
client-secret: ${mysecret}
app-id-uri: ${myappidurl}
application-type: web_application_and_resource_server # This is required.
authorization-clients:
graph:
authorizationGrantType: authorization_code # This is required.
scopes:
- https://graph.microsoft.com/.default
webApiA:
authorization-grant-type: on_behalf_of
scopes:
- api://${WEB_API_A_APP_ID_URL}/Obo.WebApiA.ExampleScope
Can someone help me, thank you very much, I have been stuck on this for 2 days.

Role based authorization: Oauth with OneLogin and Spring Security

I have a spring boot application which is using Oauth with OneLogin as the authorisation server.
Now, I want to implement role based authorisation to expose certain APIs only to users with certain privileges.
I have users belonging to groups. Say user A belongs to "admin" group and user B does not belong to the admin group. My question is how can I use these groups to enable only user A to access certain APIs.
This is the information about the authenticated user for reference:
authorities
0
authority "ROLE_USER" **//says ROLE_USER even when the user belongs to the admin group**
attributes
at_hash "xxxxx"
sub "xxxx"
iss "https://******/oidc/2"
groups
0 "Group A"
1 "Group B"
2 **"DEMO"**
3 **"DEMO Admin"** **//presence in this group should be considered for authorisation**
preferred_username "xxx"
given_name "xxxx"
nonce "xxxxxxx"
sid "xxxxxxx"
aud
0 "xxxxxxx"
updated_at "xxxxxx"
name "xxxxxx"
exp "xxxxxxx"
family_name "xxxxxx"
iat "xxxxxxxx"
email "xxxxxxxx"
idToken {…}
userInfo {…}
1
authority "SCOPE_email"
2
authority "SCOPE_groups"
3
authority "SCOPE_openid"
4
authority "SCOPE_profile"
I want to secure my rest controllers something like this:
#PreAuthorize("Belongs to group admin")
#RequestMapping(value = "/delete", method = RequestMethod.GET)
public string delete() {
System.out.println("delete");
}
This is my application.yaml file
server:
servlet:
context-path: /demo
spring:
security:
oauth2:
client:
registration:
onelogin:
client-id: *****
client-secret: *******
scope: openid,profile,email,groups
provider: onelogin
provider:
onelogin:
issuer-uri: https://******/oidc/2
Since your application is also a resource server, you can use a custom JwtAuthenticationConverter to configure how the JWT gets converted to an Authentication object. Specifically for this case, you can configure how the JWT gets converted to the list of GrantedAuthorities.
By default, the resource server populates the GrantedAuthorities based on the "scope" claim.
If the JWT contains a claim with the name "scope" or "scp", then Spring Security will use the value in that claim to construct the authorities by prefixing each value with "SCOPE_". That is why you see the authorities "SCOPE_email", "SCOPE_groups" etc
If you want to populate the GrantedAuthorities based on the "groups" claim instead, you can do so like this:
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter());
return converter;
}
public class CustomJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
#Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String group : getGroups(jwt)) {
grantedAuthorities.add(new SimpleGrantedAuthority(group));
}
return grantedAuthorities;
}
}
private Collection<String> getGroups(Jwt jwt) {
Object groups = jwt.getClaim("groups");
// Convert groups to Collection of Strings based on your logic
}
Then you can use the expression "hasAuthority('YOUR_CUSTOM_GROUP_NAME')" to restrict access to certain endpoints.

How to get the refresh token in a spring OAuth2 client

I'm developing a Spring application which acts as an OAuth2 client and Spotify is the resource server.
This is my configuration:
spring:
security:
oauth2:
client:
registration:
spotify:
client-id: ...
client-secret: ...
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: user-read-private, user-read-email
client-name: Spotify
client-alias: spotify
provider:
spotify:
authorization-uri: https://accounts.spotify.com/authorize
token-uri: https://accounts.spotify.com/api/token
user-info-uri: https://api.spotify.com/v1/me
user-name-attribute: display_name
My problem is that I just can't find how to get the refresh token that is sent by Spotify in the response of /api/token
This is how the Spotify response looks like:
(Source: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow)
I tried to implement my own CustomUserService like this:
.and()
.userInfoEndpoint()
.userService(customUserService)
inside my CustomUserService I tried to overload the following method: public OAuth2User loadUser(OAuth2UserRequest userRequest)
In this OAuth2UserRequest object I can find the access token but there is absolutely no information about the refresh token:
I'm thinking about I need some additional config to put the refresh_token in the additionalParameters object but I can't find anything like this.
Is there any way I can get the refresh token in my code and do stuff with that?
So I figured out a way to overcome this. The first thing needed is to include the accessTokenResponseClient in the security config with a custom implementation.
Security Config:
...
.and()
.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient())
...
And the key part here is to set our CustomTokenResponseConverter:
#Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomTokenResponseConverter());
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
accessTokenResponseClient.setRestOperations(restTemplate);
return accessTokenResponseClient;
}
In this converter it is possible to access the refresh token and for example put it in the additionalParameters map that is mentioned in the question:
public class CustomTokenResponseConverter implements
Converter<Map<String, String>, OAuth2AccessTokenResponse> {
#Override
public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);
String refreshToken = tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN);
long expiresIn = Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
Set<String> scopes = Collections.emptySet();
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " "))
.collect(Collectors.toSet());
}
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken);
return OAuth2AccessTokenResponse.withToken(accessToken)
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(expiresIn)
.scopes(scopes)
.refreshToken(refreshToken)
.additionalParameters(Collections.unmodifiableMap(additionalParameters))
.build();
}
}
This way it can be accessed among the additionalParameters in the custom user service the following way:
String refreshToken = (String) userRequest.getAdditionalParameters().get(OAuth2ParameterNames.REFRESH_TOKEN);

Spring Boot add additional attribute to WebClient request in ServerOAuth2AuthorizedClientExchangeFilterFunction

I am trying to implement the client_credentials grant to get a token in my spring boot resource server.
I am using Auth0 as an Authorization server. They seem to require an extra parameter in the request body to be added called audience.
I have tried to do the request through postman and it works. I am now trying to reproduce it within Spring. Here is the working postman request
curl -X POST \
https://XXX.auth0.com/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&audience=https%3A%2F%2Fxxxxx.auth0.com%2Fapi%2Fv2%2F&client_id=SOME_CLIENT_ID&client_secret=SOME_CLIENT_SECRET'
The problem I am facing is that i have no way to add the missing audience parameter to the token request.
I have a configuration defined in my application.yml
client:
provider:
auth0:
issuer-uri: https://XXXX.auth0.com//
registration:
auth0-client:
provider: auth0
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
auth0:
client-id: Client
client-secret: Secret
I have the web client filter configured like this.
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
oauth2.setDefaultClientRegistrationId("auth0");
return WebClient.builder()
.filter(oauth2)
.build();
}
I am injecting the instance and trying to do a request to get the user by email
return this.webClient.get()
.uri(this.usersUrl + "/api/v2/users-by-email?email={email}", email)
.attributes(auth0ClientCredentials())
.retrieve()
.bodyToMono(User.class);
The way i understand it, the filter intercepts this userByEmail request and before it executes it it tries to execute the /oauth/token request to get JWT Bearer token which it can append to the first one and execute it.
Is there a way to add a parameter to the filter? It has been extremely difficult to step through it and figure out where exactly the parameters are being appended since its reactive and am quite new at this. Even some pointers to where to look would be helpful.
I was having the same problem where access token response and request for it wasn't following oAuth2 standards. Here's my code (it's in kotlin but should be understandable also for java devs) for spring boot version 2.3.6.RELEASE.
Gradle dependencies:
implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
After adding them you have to firstly create your custom token request/response client which will implement ReactiveOAuth2AccessTokenResponseClient interface:
class CustomTokenResponseClient : ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private val webClient = WebClient.builder().build()
override fun getTokenResponse(
authorizationGrantRequest: OAuth2ClientCredentialsGrantRequest
): Mono<OAuth2AccessTokenResponse> =
webClient.post()
.uri(authorizationGrantRequest.clientRegistration.providerDetails.tokenUri)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(CustomTokenRequest(
clientId = authorizationGrantRequest.clientRegistration.clientId,
clientSecret = authorizationGrantRequest.clientRegistration.clientSecret
))
.exchange()
.flatMap { it.bodyToMono<NotStandardTokenResponse>() }
.map { it.toOAuth2AccessTokenResponse() }
private fun NotStandardTokenResponse.toOAuth2AccessTokenResponse() = OAuth2AccessTokenResponse
.withToken(this.accessToken)
.refreshToken(this.refreshToken)
.expiresIn(convertExpirationDateToDuration(this.data.expires).toSeconds())
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build()
}
As you can see above, in this class you can adjust token request/response handling to your specific needs.
Note: authorizationGrantRequest param inside getTokenResponse method. Spring is passing here data from you application properties, so follow the standards when defining them, e.g. they may look like this:
spring:
security:
oauth2:
client:
registration:
name-for-oauth-integration:
authorization-grant-type: client_credentials
client-id: id
client-secret: secret
provider:
name-for-oauth-integration:
token-uri: https://oauth.com/token
The last step is to use your CustomTokenResponseClient inside oAuth2 configuration, it may look like this:
#Configuration
class CustomOAuth2Configuration {
#Bean
fun customOAuth2WebWebClient(clientRegistrations: ReactiveClientRegistrationRepository): WebClient {
val clientRegistryRepo = InMemoryReactiveClientRegistrationRepository(
clientRegistrations.findByRegistrationId("name-for-oauth-integration").block()
)
val clientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistryRepo)
val authorizedClientManager =
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistryRepo, clientService)
val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider()
authorizedClientProvider.setAccessTokenResponseClient(CustomTokenResponseClient())
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
val oauthFilter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauthFilter.setDefaultClientRegistrationId("name-for-oauth-integration")
return WebClient.builder()
.filter(oauthFilter)
.build()
}
}
Right now, this is possible, but not elegant.
Note that you can provide a custom ReactiveOAuth2AccessTokenResponseClient to ServerOAuth2AuthorizedClientExchangeFilterFunction.
You can create your own implementation of this - and thereby add any other parameters you need - by copying the contents of WebClientReactiveClientCredentialsTokenResponseClient.
That said, it would be better if there were a setter to make that more convenient. You can follow the corresponding issue in Spring Security's backlog.
Here is what i found out after further investigation. The code described in my question was never going to call the client_credentials and fit my use-case. I think (not 100% sure on this) it will be very useful in the future if i am trying to propagate the user submitted token around multiple services in a micro-service architecture. A chain of actions like this comes to mind:
User calls Service A -> Service A calls Service B -> Service B responds -> Service A responds back to user request.
And using the same token to begin with through the whole process.
My solution to my use-case:
What i did was create a new Filter class largely based on the original and implement a step before executing the request where i check if i have a JWT token stored that can be used for the Auth0 Management API. If i don't i build up the client_credentials grant request and get one, then attach this token as a bearer to the initial request and execute that one. I also added a small token in-memory caching mechanism so that if the token is valid any other requests at a later date will just use it. Here is my code.
Filter
public class Auth0ClientCredentialsGrantFilterFunction implements ExchangeFilterFunction {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
/**
* Required by auth0 when requesting a client credentials token
*/
private String audience;
private String clientRegistrationId;
private Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore;
public Auth0ClientCredentialsGrantFilterFunction(ReactiveClientRegistrationRepository clientRegistrationRepository,
String clientRegistrationId,
String audience) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.audience = audience;
this.clientRegistrationId = clientRegistrationId;
this.auth0InMemoryAccessTokenStore = new Auth0InMemoryAccessTokenStore();
}
public void setAuth0InMemoryAccessTokenStore(Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore) {
this.auth0InMemoryAccessTokenStore = auth0InMemoryAccessTokenStore;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return auth0ClientCredentialsToken(next)
.map(token -> bearer(request, token.getTokenValue()))
.flatMap(next::exchange)
.switchIfEmpty(next.exchange(request));
}
private Mono<OAuth2AccessToken> auth0ClientCredentialsToken(ExchangeFunction next) {
return Mono.defer(this::loadClientRegistration)
.map(clientRegistration -> new ClientCredentialsRequest(clientRegistration, audience))
.flatMap(request -> this.auth0InMemoryAccessTokenStore.retrieveToken()
.switchIfEmpty(refreshAuth0Token(request, next)));
}
private Mono<OAuth2AccessToken> refreshAuth0Token(ClientCredentialsRequest clientCredentialsRequest, ExchangeFunction next) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
String tokenUri = clientRegistration
.getProviderDetails().getTokenUri();
ClientRequest clientCredentialsTokenRequest = ClientRequest.create(HttpMethod.POST, URI.create(tokenUri))
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.body(clientCredentialsTokenBody(clientCredentialsRequest))
.build();
return next.exchange(clientCredentialsTokenRequest)
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.map(OAuth2AccessTokenResponse::getAccessToken)
.doOnNext(token -> this.auth0InMemoryAccessTokenStore.storeToken(token));
}
private static BodyInserters.FormInserter<String> clientCredentialsTokenBody(ClientCredentialsRequest clientCredentialsRequest) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
return BodyInserters
.fromFormData("grant_type", AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.with("client_id", clientRegistration.getClientId())
.with("client_secret", clientRegistration.getClientSecret())
.with("audience", clientCredentialsRequest.getAudience());
}
private Mono<ClientRegistration> loadClientRegistration() {
return Mono.just(clientRegistrationId)
.flatMap(r -> clientRegistrationRepository.findByRegistrationId(r));
}
private ClientRequest bearer(ClientRequest request, String token) {
return ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(token))
.build();
}
static class ClientCredentialsRequest {
private final ClientRegistration clientRegistration;
private final String audience;
public ClientCredentialsRequest(ClientRegistration clientRegistration, String audience) {
this.clientRegistration = clientRegistration;
this.audience = audience;
}
public ClientRegistration getClientRegistration() {
return clientRegistration;
}
public String getAudience() {
return audience;
}
}
}
Token Store
public class Auth0InMemoryAccessTokenStore implements ReactiveInMemoryAccessTokenStore {
private AtomicReference<OAuth2AccessToken> token = new AtomicReference<>();
private Clock clock = Clock.systemUTC();
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
public Auth0InMemoryAccessTokenStore() {
}
#Override
public Mono<OAuth2AccessToken> retrieveToken() {
return Mono.justOrEmpty(token.get())
.filter(Objects::nonNull)
.filter(token -> token.getExpiresAt() != null)
.filter(token -> {
Instant now = this.clock.instant();
Instant expiresAt = token.getExpiresAt();
if (now.isBefore(expiresAt.minus(this.accessTokenExpiresSkew))) {
return true;
}
return false;
});
}
#Override
public Mono<Void> storeToken(OAuth2AccessToken token) {
this.token.set(token);
return Mono.empty();
}
}
Token Store Interface
public interface ReactiveInMemoryAccessTokenStore {
Mono<OAuth2AccessToken> retrieveToken();
Mono<Void> storeToken(OAuth2AccessToken token);
}
And finally defining the beans and using it.
#Bean
public Auth0ClientCredentialsGrantFilterFunction auth0FilterFunction(ReactiveClientRegistrationRepository clientRegistrations,
#Value("${auth0.client-registration-id}") String clientRegistrationId,
#Value("${auth0.audience}") String audience) {
return new Auth0ClientCredentialsGrantFilterFunction(clientRegistrations, clientRegistrationId, audience);
}
#Bean(name = "auth0-webclient")
WebClient webClient(Auth0ClientCredentialsGrantFilterFunction filter) {
return WebClient.builder()
.filter(filter)
.build();
}
There is a slight problem with the token store at this time as the client_credentials token request will be executed multiple on parallel requests that come at the same time, but i can live with that for the foreseeable future.
Your application.yml is missing one variable:
client-authentication-method: post
it should be like this:
spring:
security:
oauth2:
client:
provider:
auth0-client:
token-uri: https://XXXX.auth0.com//
registration:
auth0-client:
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
client-authentication-method: post
Without it I was getting "invalid_client" response all the time.
Tested in spring-boot 2.7.2

Not getting Client Authority/Role while using RemoteTokenService

I am using Spring-Security-OAuth2 for implementing my own oauth server and resource server. I am using RemoteTokenService as my ResourceServerTokenService on my ResourceServer which will authenticate any accessToken using the CheckTokenEndpoint (/oauth/check_token) on OAuth Server.
I have added a antMatcher for an api url e.g. /data/list which will need client application Role / Authority: "ROLE_ADMIN" like this .antMatcher('/data/list').access("#oauth2.clientHasRole('ROLE_ADMIN')")
but it is not working.
I have done some trial and error on this end point and what I get is following :::
When oauth grant is client only i.e. client_credential grant.
what we get from /oauth/check_token
{
"scope":["read"],
"exp":1412955393,
"client_id":"sample_test_client_app"
}
we dont get any client authority. so how can spring security will perform above authorization check of "#oauth2.clientHasRole('ROLE_ADMIN')"
When oauth grant is user + client i.e. Authorization_code grant
what we get from /oauth/check_token
{
"aud":["resource_id"],
"exp":1412957540,
"user_name":"developer",
"authorities":["ROLE_USER"],
"client_id":"sample_test_client_app",
"scope":["read"]
}
and for authorization_code grnat we are still not getting client authority/role. so can any one tell me how can we perform clientHasRole authentication on any api url?
For "#oauth2.clientHasRole('ROLE_ADMIN')" to work we have to implemented our AccessTokenConverter and inject it into auth server and resource server.
so create a new class which extends DefaultAccessTokenConverter and override convertAccessToken and extractAuthentication methods.
In convertAccessToken method just add
Map<String, Object> response = (Map<String, Object>) super.convertAccessToken(token, authentication);
OAuth2Request clientToken = authentication.getOAuth2Request();
response.put("clientAuthorities", clientToken.getAuthorities());
and in extractAuthentication method add
Collection<HashMap<String, String>> clientAuthorities = (Collection<HashMap<String, String>>) map.get("client_authority");
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (HashMap<String, String> grantedAuthority : clientAuthorities) {
for (String authority : grantedAuthority.values()) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
}
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? (Collection<String>) map.get(AUD) : Collections.<String> emptySet());
OAuth2Request request = new OAuth2Request(parameters, clientId, grantedAuthorities, true, scope, resourceIds, null, null, null);
At auth server :
set this class in AuthorizationServerEndpointsConfigurer
At resource server :
set this class in RemoteTokenServices

Resources