-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Resource Server - Multi-Tenant Jwt Decoder by Issuer #6778
Comments
Related to #5385 |
Thanks for reaching out! It's good to see you again, @gburboz. This ticket is one that we've been discussing a bit internally. We'll get back to you as soon as possible. |
@gburboz I think a Looking at |
@jgrandja these were my exact thoughts if we should make it more generic to support mapping based off any user specified claim. However, it was trade off between simplicity and flexibility so I decided to keep it simpler unless we see some demand for such use cases. Note that claims in JWT need not be plain String, they can be complex objects as well. |
On a separate unrelated note
Drawback is that issuer format is expected to be an URL and not any generic URI. This can be taken up as separate issue if there is interest in discussing this further. |
@gburboz I am working on a similar problem at my company where I need to be able to connect to multiple identity providers with unique issuer URLs. Your solution is promising but the underlying To address this, I came up with a solution that involves a registry of I like your idea of parsing the iss from the token to identify the tenant. It is preferable to enforcing a tenant-identifying header or some other request attribute instead. |
@xsreality your use case and solution is very interesting but I am not sure how common this use case is. I would suggest @jgrandja and @jzheaux to comment on if we want to bring this feature into JwtDecoder itself or not. Alternative-1: Share instance of Alternative-2: Refactor Also note that as per current architecture, there is more to decoders than just decoding the token. They also have delegates for authentication converter and token validators. |
Yes, I agree that there is an advantage to using a contract where the
Can you clarify @xsreality which you are stating is preferable and why? Subdomains and paths are often used for tenant identification, and for good reason. By the way, in cases where request material is used to identify tenants,
I think the double-parsing is only a mild annoyance. It's common in multi-tenancy scenarios to need to parse something - the URL, headers, etc. - in order to identify the tenant. That said, performance is important, and we want to avoid duplicate work, if possible. We could do that by instead introducing a factory method to static JwtDecoder fromJwtDecoderResolver(Function<JWT, NimbusJwtDecoder> jwtDecoderResolver) {
return token -> {
JWT jwt = parse(token);
return jwtDecoderResolver.apply(jwt).decode(token, jwt);
}
}
Good point, @gburboz. It can be tricky to know where to simplify with multi-tenancy. Intrinsic to a lot of multi-tenancy scenarios is the idea that tenants can be added and removed at runtime. You might be able to achieve both simplicity and flexibility by separating the concerns: Map<URI, NimbusJwtDecoder> jwtDecoders = // ...
JwtDecoder multitenant = NimbusJwtDecoder.fromJwtDecoderResolver(
jwt -> jwtDecoders.get(URI.create(jwt.getIssuer().toExternalForm()))); |
I am not very close to the spring oauth code, so perhaps treat the comment as user feedback ;). If multi-tenancy can't easily be added, maybe the current implementation is not aligned to the token processing stages of a resource server. We had this need a year ago (#5351) and ended up implementing a bunch of components including an authentication filter along these lines:
The parts to make 1-4 work in a default implementation are already present in the nimbus library. We basically implemented a custom nimbus Note: I have the locator facade present the entire JWK set of the resolved issuer to the processor. That way the processor can make the final JWK selection based on the Seems to me the missing link in your design centres around the processing context (or lack thereof) passed from parse token stage to the validation stage and the delegation of the key selection to a configurable component which has access to parse stage information to offer the correct JWKs to the processor. Step 5 gets a bit more tricky as in multi-tenant environments you will have to normalise the claims of all issuers to something that your spring app understands (as in hard codes in the auth annotations). But claims mapper configuration could be attached to the JWK selector. Not sure if that makes sense, but sometimes less is more. |
@bertramn these are good points - often times, less is more. I agree that key selection by issuer is likely the primary use case. @gburboz do you have a concrete use case for composing of completely custom For key selection, I think a simpler route would be to enhance the Nimbus API so that For more complex scenarios, I lean towards an implementation of To @jgrandja's point, while it would be nice to be more generic, I think this is something we could pretty easily add later on in another ticket - maybe a resolver that takes any claim name. |
@jzheaux , we have a need to be able to handle incoming JWTs from different OAuth Providers to be handled by single resource server. As off today claims mapping and token validations are also tied to |
@gburboz Possibly, but if you don't mind, let's explore another option first. Nimbus 7.3 ships with the ability to resolve a JWK set by the issuer claim, e.g.: Map<String, JWSKeySelector> keySelectors = ...;
JWTClaimSetAwareJWSKeySelector selector = (header, claims, context) -> {
String issuer = claims.getIssuer();
return Optional.ofNullable(keySelectors.get(issuer))
.map(keySelector -> keySelector.selectJWSKeys(header, context))
.orElseThrow(() ...);
};
ConfigurableJWTProcessor<?> processor = new DefaultJWTProcessor<?>();
processor.setJWTClaimSetAwareJWSKeySelector(selector); In Spring Security 5.2, you can supply your own NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor); As for the validator and claims mapper, do you need tenant isolation (for example, can each tenant describe their own unique validation rules)? If so, you could use a similar strategy as above, just for decoder.setJwtValidator(multiTenantJwtValidator);
decoder.setClaimSetConverter(multiTenantClaimSetConverter); This is more verbose, but it will be more efficient since the JWT won't be parsed multiple times. Would the above give you the tenant isolation that you need? |
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed. |
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue. |
@gburboz @xsreality @bertramn Would you comment on how well this implementation would address your multi-tenancy use cases? The essence of the idea is: JwtAuthenticationManagerResolver resolver =
new JwtAuthenticationManagerResolver("whitelist", "of", "issuers"); or Converter<String, AuthenticationManager> issuerManagerConverter = // ...
JwtAuthenticationManagerResolver resolver =
new JwtAuthenticationManagerResolver(issuerManagerConverter); Where said UPDATE - Actually, let's move the discussion about this particular idea over to the PR that implements it - #7733 |
Hi @jzheaux I have similar use case. Project setup Details - org.springframework.boot" version "2.3.1.RELEASE" I have gone through this support documentation but need some help to implement this. |
Hi, @RavindraSengar. Spring Boot does not at this time support listing multiple issuers as application properties. Spring Security does support multiple issuers, though, with To address your other question, we prefer Stack Overflow questions since it's a common forum for usage questions. If you've got further questions, please feel free to ask them there and paste the SO link here, if you like. |
Summary
This is related to Issue #5351 but takes different approach to support multi-tenant Jwt Decoders by issuer
Actual Behavior
Currently Resource Server with jwt is configured as shown below which is then configured with underlying
NimbusJwtDecoder
to decode tokens.OR
Expected Behavior
Proposal is to add
MultiTenantDelegatingJwtDecoder
which is composed of multipleNimbusJwtDecoder
indexed by a mandatoryissuer-uri
(Map<URL, NimbusJwtDecoder>
) .Use following configuration:
The multi tenant decoder does initial parsing (
JWT jwt = parse(token);
) and lookup the issuer claim from parsed JWT.Based off issuer claim, it will look up underlying
NimbusJwtDecoder
and delegates further processing to it.NimbusJwtDecoder
can optionally be modified so that it will have additionalJwt decode(JWT token)
to avoid double parsing (JWT jwt = parse(token);
).Version
Spring Security 5.1.x
Sample
If
NimbusJwtDecoder
is not to be modified,MultiTenantDelegatingJwtDecoder
can be implemented with pull request #6779The text was updated successfully, but these errors were encountered: