spring-security
spring-security copied to clipboard
Provide support for OAuth 2.0 Token Exchange for client
Hi,
This feature is removed from the 5.2.x milestone. May I know if any plan about this feature? Many thanks.
Thanks and regards, William
@William1104 We are planning on implementing this feature but it may be too early at the moment until the spec goes through the review process further.
Instead we replaced this feature with #6053. As an FYI, you can also exchange a JWT token for another JWT using the JWT Bearer grant.
I'm curious, are you aware of any providers that have implemented OAuth 2.0 Token Exchange?
@jgrandja
I'm curious, are you aware of any providers that have implemented OAuth 2.0 Token Exchange?
One example would be Keycloak (https://www.keycloak.org/):
Token exchange in Keycloak is a very loose implementation of the OAuth Token Exchange specification at the IETF (https://www.keycloak.org/docs/6.0/securing_apps/#_token-exchange).
@jgrandja I am facing this problem where spring security isn't sending the scopes to auth server (azure). I traced it back to OAuth2AuthorizationCodeGrantRequestEntityConverter.java
which is ignoring the "scope" and "resource" parameters that are present in the request parameter in that class.
Is there a way I can override this behavior?
@kdhindsa The issue you are having is not related to this issue (Token Exchange). Please post this question on StackOverflow or log a new issue if you believe this is a bug. Please see guidelines on using GitHub Issues.
spring security isn't sending the scopes to auth server (azure)
Have you configured the scopes
property for the ClientRegistration
?
Please see the reference doc for more details. I suspect there is a misconfiguration.
@jgrandja, yes, I had configured the scopes correctly:
spring.security.oauth2.client.registration.azure.scope=openid,user.read,offline_access,files.read.all
but that didn't work. Eventually I found this configuration:
http.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(aadAccessTokenResponseClient());
So I ended up creating my custom response client service which manually injects scopes:
public class AADOAuth2AuthorizationCodeGrantRequestEntityConverter
extends OAuth2AuthorizationCodeGrantRequestEntityConverter {
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
RequestEntity requestEntity = super.convert(authorizationCodeGrantRequest);
LinkedMultiValueMap<String, String> params = (LinkedMultiValueMap<String, String>)requestEntity.getBody();
// FIXME: read from config
params.put("scope", Arrays.asList("openid user.read offline_access files.read.all"));
params.put("resource", Arrays.asList("https://graph.microsoft.com"));
return requestEntity;
}
}
and that worked.
Hi @jgrandja, after lots of draft versions, the corresponding RFC 8693 standard for token exchange has finally been published this week (https://tools.ietf.org/html/rfc8693). So it would be great if you could schedule this in one of the next milestones.
Thanks for the heads up @andifalk. I don't think we'll be able to get this into 5.3 (due Mar 4) as we have other priority tasks that need to be completed. We'll likely target 5.4
This issue seems quite old now... Is this feature still in the roadmap for Spring Security?
https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16
@emedina RFC 8693 was just published in Jan 2020, as mentioned in this comment. Now that it's published, we will see which providers implement to determine the appropriate time to implement on our end.
At the same time, features will get implemented quicker by the community via PR's as our team only has so much bandwidth. As of now, this feature is not scheduled for 5.4 but if a PR comes in then we will consider it then.
@jgrandja Does this issue get resolved now?
@ZxShirley It's not scheduled as of yet. As mentioned in my previous comment...
...features will get implemented quicker by the community via PR's as our team only has so much bandwidth. As of now, this feature is not scheduled for 5.4 but if a PR comes in then we will consider it then.
We'll be prioritizing features when we plan for 5.5, which will be towards end of this month.
Any update on when this will be prioritized as RFC 8693 was been defined for over a year now? Many providers are supporting this now such as keycloak, ping federate, etc.
@muskiehunter1985 We won't be able to get this into 5.5.0
.
However, I've scheduled it for 5.6.0
, which should be released around Nov 2021.
We'll aim to get it into the 5.6.0-M1
release.
Does this issue cover the urn:ietf:params:oauth:token-type:access_token
subject token type ? If so, the current implementation of JwtBearerOAuth2AuthorizedClientProvider
would be near the same. Only the Converter would map assertion to subject_token parameters. And subject_token_type and grant_type should be given accordingly.
Jwt Bearer may works well but, as per the RFC, the AS must validate the audience claim value is its token endpoint uri. This block all use cases where a microservice would request a new token on behalf of the current request token (audience is the microservice itself not the AS), which, IMHO is a far more common use case than requiring the microservice to forged/signed a Jwt Bearer for a given subject.
Unfortunaletly, some classes are not public and make difficult to write its own provider (OAuth2AuthorizationGrantRequestEntityUtils
or AbstractOAuth2AuthorizationGrantRequestEntityConverter
). As an example, https://github.com/jgrandja/oauth2-protocol-patterns/blob/main/microservice-b/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java won't compile anymore.
This is not specific to this issue as OAuth2 is an extensible framework and it's not an unusual use case to implement its own grant type.
@scrocquesel
Does this issue cover the
urn:ietf:params:oauth:token-type:access_token
Yes. It's defined in Section 3. Token Type Identifiers
As an example,
...JwtBearerGrantRequestEntityConverter.java
won't compile anymore
The oauth2-protocol-patterns
sample has not been upgraded to Spring Security 5.5 as of yet and this class will be removed when updated which will resolve the compile error.
Is this still scheduled it for 5.6.0, which should be released around Nov 2021?
@mmo21 Unfortunately, this won't make it into 5.6
. We've been quite busy with Spring Authorization Server this year and there hasn't been anyone from the community to contribute to this.
Hi, @kdhindsa, @jgrandja.
@jgrandja, yes, I had configured the scopes correctly:
spring.security.oauth2.client.registration.azure.scope=openid,user.read,offline_access,files.read.all
but that didn't work. Eventually I found this configuration:
http.oauth2Login() .tokenEndpoint() .accessTokenResponseClient(aadAccessTokenResponseClient());
So I ended up creating my custom response client service which manually injects scopes:
public class AADOAuth2AuthorizationCodeGrantRequestEntityConverter extends OAuth2AuthorizationCodeGrantRequestEntityConverter { @Override public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { RequestEntity requestEntity = super.convert(authorizationCodeGrantRequest); LinkedMultiValueMap<String, String> params = (LinkedMultiValueMap<String, String>)requestEntity.getBody(); // FIXME: read from config params.put("scope", Arrays.asList("openid user.read offline_access files.read.all")); params.put("resource", Arrays.asList("https://graph.microsoft.com")); return requestEntity; } }
and that worked.
About this topic, I created an new issue: https://github.com/spring-projects/spring-security/issues/10452
My humble attempt at a workaround : https://github.com/paulceli/spring-token-exchange-keycloak
I'm looking at this and I'd like a little bit of guidance as to how to interpret the specification and how to follow Spring Security's conventions.
My uncertainties revolve around the two flavors of examples seen in the spec, under 2.3 Example Token Exchange, and under Appendix A. Additional Token Exchange Examples, respectively.
- In the first example, the
subject_token_type
isurn:ietf:params:oauth:token-type:access_token
and the access token from the authenticated user is being passed directly to thesubject_token
request parameter. - In the second set of examples, it seems as though the resource server has issued a JWT and is sending this as the
subject_token
, along with asubject_token_type
equal tourn:ietf:params:oauth:token-type:jwt
. - In the very last example, there's an
actor_token
andactor_token_type
(delegation, rather than impersonation.)
My problem is figuring out how to choose which one of these methods to use for subject_token
, and also, when to send the actor_token
/actor_token_type
request parameters, and when not to. I can only assume that all of this is up to the resource server, but how should Spring Security decide which strategy to use?
- Do we choose a default and then let the rest be configurable? For example, always get the
subject_token
from theAuthorization
header, and don't send anyactor_token
. - Do we introduce
subjectTokenType
andactorTokenType
in theClientRegistration
class, and based on their values, choose a strategy? For example, ifClientRegistration.subjectTokenType
isurn:ietf:params:oauth:token-type:access_token
, we pass on the token we got in theAuthorization
header, and if it'surn:ietf:params:oauth:token-type:jwt
, we generate our own JWT.
Hopefully this hasn't been too much rambling from my part.
(PS: I can't guarantee that my efforts will actually amount to anything in the end, but either way, the questions I raise here will maybe help others working on this issue.)
I'd like some feedback as to whether or not I'm going in the completely wrong direction here:
Is it okay to introduce new static methods in ServletOAuth2AuthorizedClientExchangeFilterFunction
for easily adding audience
and resource
values to the request context, just like the pre-existing ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(String)
and ServletOAuth2AuthorizedClientExchangeFilterFunction.authentication(Authentication)
methods?
For example, something along the lines of this:
public static Consumer<Map<String, Object>> audience(String audience) {
return (attributes) -> attributes.put(OAuth2AuthorizationContext.AUDIENCE_ATTR_NAME, audience);
}
And just to give an example of how it might look when setting up the WebClient
call:
webClient
.get()
.uri("/rest/v1/lastname")
.accept(MediaType.APPLICATION_JSON)
.attributes(clientRegistrationId("lastname-api-client"))
.attributes(audience("lastname-api")) // <- new
.attributes(resource(URI.create("http://localhost:8083"))) // <- new
// ...
If not, what's the preferred way of passing additional information into the token exchange request? Reading between the lines, I've come to the conclusion that adding anything at all to ClientRegistration
is probably not desirable, so this was the best alternative I could find so far.
@ThomasKasene I haven't had a chance to re-review the spec lately. I appreciate that users are looking for this feature but we've been very busy with many other priorities and we only have so much bandwidth. No one has offered to take this feature on so this slows things down as well.
Is it okay to introduce new static methods in
ServletOAuth2AuthorizedClientExchangeFilterFunction
for easily addingaudience
andresource
values to the request context
No. This would not be the approach we would use.
We would most definitely introduce a new OAuth2AccessTokenResponseClient
and OAuth2AuthorizedClientProvider
that ultimately would be composed in OAuth2AuthorizedClientManager
.
I've come to the conclusion that adding anything at all to
ClientRegistration
is probably not desirable
Correct.
I'm slowly chipping away at the spec/implementation as I find the time, and I recently discarded the idea of manipulating the ExchangeFilterFunction
. I have added such methods to my OAuth2AuthorizedClientProvider
as there seems to be a definite need for them, however. But we'll see how it all turns out in the end. Anyway, I appreciate you taking the time to let me know!
Currently, my biggest obstacle is figuring out how to make it create and sign the actor token. I couldn't find any easily re-usable code which does this, so I was forced to draw inspiration (mostly copy) from NimbusJwtClientAuthenticationParametersConverter
.
@ThomasKasene Please reach out to me in January as we should have our 6.1
plan in place. If this is scheduled for 6.1
then contributions would be very helpful.
I'll do that. In the meantime, I've got a couple more questions to ponder:
- Considering that the acting party can appear in several token exchanges (for example, an admin or a support user), and the fact that a principal may choose to delegate access to multiple such actors, is it even possible to manage all those
OAuth2AuthorizedClient
s with the current APIs (OAuth2AuthorizedClientRepository
, etc)? - Adding to that, what do we do if a client chooses to obtain an authorized client both with the
client_credentials
grant, and with thetoken-exchange
grant, where it uses itself as the actor? Those principals would be identical (I think), and with matchingclientRegistrationId
s they would be difficult for theOAuth2AuthorizedClientRepository
implementations to distinguish. - Is it acceptable to add a new constant,
org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.N_A
, to represent the value registered as part of RFC8693?
@ThomasKasene I won't be able to answer your questions until I review the spec in detail, which won't happen until after the new year.
Any news on this topic? in which version, we could expect this?
There's no news from me, life happened and I've been somewhat absent these last few months.
The specification is a very flexible one, and it's difficult to shoehorn the entire thing into Spring Security without making some changes to several of the existing APIs, at least from my limited understanding of Spring Security. The main complexity comes from the fact that a user should be able to authorize up to several other users (or applications) to act on their behalf, so the model becomes something like this:
principal + client registration ID + actor1 -> OAuth2AuthorizedClient
(1)
principal + client registration ID + actor2 -> OAuth2AuthorizedClient
(2)
principal + client registration ID + actor3 -> OAuth2AuthorizedClient
(3)
Whereas the APIs within Spring Security only allows for this model:
principal + client registration ID -> OAuth2AuthorizedClient
(I'm hoping somebody smarter than me will correct me if I've misunderstood these APIs!)
@jgrandja, Any news regarding adding it into the upcoming milestones? Almost 3 years left since the time you promised to include it..