-
Notifications
You must be signed in to change notification settings - Fork 6k
Provide Resource Server Configurer #5226
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
Comments
So far, this is a draft on what may be the Resource Server Configuration DSL. Feedback is welcome! Issue: spring-projects/spring-security#5226
So far, this is a draft on what may be the Resource Server Configuration DSL. Feedback is welcome! Issue: spring-projects/spring-security#5226
So far, this is a draft on what may be the Resource Server Configuration DSL. Feedback is welcome! Issue: spring-projects/spring-security#5226
So far, this is a draft on what may be the Resource Server Configuration DSL. Feedback is welcome! Issue: spring-projects/spring-security#5226
First, it appears to me that OAuth2 Access Tokens can be expressed in a number of formats. From the JWT spec: JSON Web Token (JWT) is a compact claims format And in the case of an opaque token, the format is "opaque" to us until, perhaps, it is processed. For now, the DSL supports the configuration of JWT and Opaque as formats supported by this resource server instance. Roughly speaking, each format configured implies an additional AuthenticationProvider. In configuring each format, the DSL supports custom ways to process tokens of that format. Second, verification strategies stand independent of the token's original format. Once any claims have actually been made, then they can be verified. At the very least, a token's claims to its own validity are confirmed. For example, a token's iat and exp. This is not configurable. Additionally, there are two additional standard verification steps, decrypting the claims and verifying the attached signature. The DSL, then, supports configuration of these two verification steps, though they will not result in instances of OAuth2AccessTokenVerifier. The DSL also allows for the specification of custom verification on top of basic verification. Issue: spring-projects/spring-security#5226
First, it appears to me that OAuth2 Access Tokens can be expressed in a number of formats. From the JWT spec: JSON Web Token (JWT) is a compact claims format And in the case of an opaque token, the format is "opaque" to us until, perhaps, it is processed. For now, the DSL supports the configuration of JWT and Opaque as formats supported by this resource server instance. Roughly speaking, each format configured implies an additional AuthenticationProvider. In configuring each format, the DSL supports custom ways to process tokens of that format. Second, verification strategies stand independent of the token's original format. Once any claims have actually been made, then they can be verified. At the very least, a token's claims to its own validity are confirmed. For example, a token's iat and exp. This is not configurable. Additionally, there are two additional standard verification steps, decrypting the claims and verifying the attached signature. The DSL, then, supports configuration of these two verification steps, though they will not result in instances of OAuth2AccessTokenVerifier. The DSL also allows for the specification of custom verification on top of basic verification. Issue: spring-projects/spring-security#5226
First, it appears to me that OAuth2 Access Tokens can be expressed in a number of formats. From the JWT spec: JSON Web Token (JWT) is a compact claims format And in the case of an opaque token, the format is "opaque" to us until, perhaps, it is processed. For now, the DSL supports the configuration of JWT and Opaque as formats supported by this resource server instance. Roughly speaking, each format configured implies an additional AuthenticationProvider. In configuring each format, the DSL supports custom ways to process tokens of that format. Second, verification strategies stand independent of the token's original format. Once any claims have actually been made, then they can be verified. At the very least, a token's claims to its own validity are confirmed. For example, a token's iat and exp. This is not configurable. Additionally, there are two additional standard verification steps, decrypting the claims and verifying the attached signature. The DSL, then, supports configuration of these two verification steps, though they will not result in instances of OAuth2AccessTokenVerifier. The DSL also allows for the specification of custom verification on top of basic verification. Issue: spring-projects/spring-security#5226
I'm moving the discussion about the structure if the DSL here. I'll begin with a restatement of what I last "officially" proposed and then explain some revisions, which are based on team feedback. Initial ProposalA typical minimal configuration would look like this under the current proposal: http
.oauth2().resourceServer()
.accessTokens()
.formats().jwt(); And a complete configuration would look like this: http
.oauth2().resourceServer()
.bearerTokenResolver(customBearerTokenResolver())
.accessTokens()
.verifiers()
.signature().keys("file:///the-location-of-my-key")
.encryption().keys("http://a-remote-jwks-uri")
.addVerifier(customTokenVerifier()).and()
.formats()
.jwt().processor(customJwtProcessor()).and()
.opaque().processor(customOpaqueTokenProcessor()); Updated proposalA number of changes have been suggested, and rather than try and represent everyone's feedback perfectly, I'll invite them to comment on the following updated idea, which incorporates the points that, at least for me, made sense: http
.oauth2().resourceServer()
.jwt()
.signature().keys("https://jwks.json"); or http
.oauth2().resourceServer()
.bearerTokenResolver(customBearerTokenResolver())
.jwt()
.processor(customJwtProcessor())
.verifiers(oneCustomVerifier(), anotherCustomVerifier()); The above would be a highly custom circumstance where the code needs to customize the way the bearer token is resolved from the request, the way the token is processed into a verified JWT, and has custom ways for verifying the resulting JWT. Verifying now needs to be couched underneath the format because the verifier contract is typed by the token format. Do we need
|
A rough draft of Resource Server DSL support, including basic support for a JWK Set Uri and single key. Issue: spring-projects/spring-security#5226
@rwinch, @jgrandja I've had a bit more time to work with the DSL, and I've got the following as options: http.oauth2().resourceServer()
.jwt().signature().keys(url("https://jwks.json")); This is currently the simplest configuration possible. I don't recommend going simpler than this as it would involve inventing a convention for where on the filesystem to find keys or making non-spec assumptions about the value of the JWK set uri. Of course, the following should be possible: http.oauth2().resourceServer();
@Bean
public JwtDecoder decoder() {
return new MyJwtDecoder();
} in which case we could assume that the code wants to use the bearer token support based on the existence of a JwtDecoder bean. Also, this is possible: http.oauth2().resourceServer().jwt(new MyJwtDecoder()); Which is simply the DSL analog for the bean wiring. One comment that was made early on is that the DSL should make it as clear as possible what is a reasonable configuration and what isn't. To that end, I think that the key configuration should be strongly-typed and should be done in such a way that only one key configuration is possible. For example: http.oauth2().resourceServer()
.jwt().signature().keys(myKeyMap()) Would return the parent configurer (JwtConfigurer) instead of the current configurer (SignatureConfigurer). In this way, a sequence of These are the three key signatures that I am planning on (and have corresponding samples for your perusal): .keys(String kid, Key key);
.keys(Map<String, Key> keys);
.keys(UrlConfigurer url) There is a bit to unpack here. First, I don't want to deal with byte[] or String-specified keys. In the local case, folks can retrieve their keys however they want, parse them into Second, this is kid-centric. Some of the early feedback I received was that it isn't uncommon to completely ignore the kid and just iterate through the list based on custom criteria. To that end, I originally also had this in mind: .keys(myKeyProvider()) but I've left it out thus far as I'm still mulling over the contract and running into some roadblocks with Nimbus. Feedback is of course welcome. Third, is the url. I brought up a concern early on that I could not easily tweak the timeouts for the JWK set url. We need to provide this somewhere, and I'm proposing introducing a UrlConfigurer class that can be reused across the DSL to describe URLs. Some early feedback I received from Rob was a concern about the DSL becoming overly sophisticated relative to something that, in the end, isn't really Spring Security's concern (namely URL connections). What I'm proposing here is a separate class where such configuration can be isolated. And, if that is still unsettling for folks, I can see the keyProvider approach being another possibility. Here is a slightly more sophsisticated example: http.oauth2().resourceServer(a(), list(), of(), audiences())
.bearerToken(myResolver())
.jwt()
.algorithm(JwsAlgorithms.RS256)
.signature().keys(myJwkUrl())
.encryption().keys(myPrivateKeyMap())
.verifications(my(), jwt(), verifiers())
.grants(myAuthorityGranter())
.and()
.opaque()
.token(myTokenUrl())
.verifications(my(), opaque(), verifiers())
.and()
.and()
.csrf()
// .... I like the pattern Originally, there were verifiers at the Lastly, we need a way to give the resource server a list of supported audiences. I'm okay with an resourceServer().ids(a(), list(), of(), audiences()) Though placing them in the |
The minimal configuration looks good. This configuration will meet the requirements for #5130.
An alternative could be:
This naming would align with the spec language for JSON Web Signature (JWS). However, I think I also like:
For JSON Web Encryption (JWE) support...when we get to it. Alternatively:
Again, I think Either way, JWE is not in scope for 5.1 but at least we know that the DSL can grow with this design structure.
I agree with @rwinch here, we should remove the
or maybe...
In regards to this configuration:
We cannot rely on I believe the only configuration points we need for 5.1 is something like this:
This would meet the requirements for #5130 and #5131.
Can you elaborate on the roadblocks, maybe I can help?
I prefer
I'm not convinced this is the best strategy to go with. If we provide a more strongly typed configurer for the various claims, for example This simple configuration point provides all the flexibility we need to provide:
We can provide an implementation as such:
We can take this further and have Spring Boot specific property:
The auto-config would read this property and create a
This same pattern can be followed for any standard I feel like we need to keep the DSL simple as such:
And let Spring Boot do it's typical magic using the pattern as described above. For non Spring Boot users, they can configure the various Does this make sense? |
+1 to @jgrandja's comments. A couple of clarifications:
I wasn't clear here, but I think one of your examples points out my concern. Your Yes, I like the idea of audiences just being verifiers. That's clean. I can see the value in placing them at the top of the config, if you can help resolve my above concern. |
Regarding KeyProvider, Nimbus does have a hook for this (paraphrase): List<? extends Key> selectJWSKeys(JWSHeader jwsHeader, SecurityContext context) And interface KeyProvider {
List<? extends Key> provide(Map<String, Object> header)
}
(jwsHeader, context) ->
keyProvider.provide(new LinkedHashMap<>(jwsHeader.toJSONObject())) So, I guess it's not so much of a roadblock as a question. It seems like this same interface would be valuable for encryption, when retrieving keys to decrypt with. Nimbus's contract here is JWS-specific (which is fine), but it just makes me wonder if doing I do like doing a key provider as we can represent several behaviors that way, e.g. the single key configuration can use it, too. |
Let's say we have:
The user would create the verifiers as such:
And than configure it:
The signature for
This would work. However, I don't like the fact that the Audience OAuth2TokenVerifier's are duplicating logic. Either way, the However, you got me thinking if it's better to put it under |
@jzheaux I'm not sure if we need this:
The user would configure one or more keys via:
and the Dig deep into the Nimbus API's. There is a way to convert |
It seems to me that if I can provide more than one key, then I will also want to provide a heuristic for how to select which key. A hook like Also, it isn't clear to me why I would go from a |
How about we start with one key instead of multiple:
Let's work through this case first and than we can take the next step and possibly expose multiple input keys. Let's keep it simple to start. Also, I want us to take advantage of Nimbus See this in the spec for further info: 6. Key Identification
Converting from a
The JOSE framework operates on JWK's. In this way, it makes sense why you would convert a Does this make sense? |
I don't think I am proposing this. While I understand that we, Spring Security want to leverage Nimbus's sophistication, we need to be careful to not be too constrained on how we project that out to the user. A key provider allows the user the flexibility to do what he needs to do in the circumstance that we have not anticipated his need (very likely). I actually don't see this as a departure from how Nimbus is designed. When configuring a JWT processor, we specify a JWSKeySelector which simply returns a list of Keys, how ever they were obtained. One way to get this list of keys is by giving it a JWK Set Url in which case Nimbus provides ample support. Another is to simply use one key. Because the final result of all of Nimbus's support is just a List of Keys (as returned by JWSKeySelector), I don't see the benefit of going through more of Nimbus's plumbing, e.g. I give Nimbus a single key so that it can turn it into a JWK so that it can turn it back into the Key I gave it? I don't see it (though this also may not be what you are saying). In addition, the JWSKeySelector API is completely unaware of JWKs for a reason, I think--I'm not sure the intent of Nimbus is to push all keys through the JWK representation. Another is whatever way the user feels is necessary--query a custom endpoint, derive from custom headers--which I don't really need to anticipate other than to hand her the header, though I understand what you mean about addressing the single key issue first. I think I would really only argue for one official implementation at this point which is |
Here are a few more thoughts on verifiers (which I'm henceforth going to refer to as validators). Here is one way that validators could be wired: http.oauth2().resourceServer()
.jwt()
.validators(
new JwtAccessTokenValidator(Duration.ofSeconds(30)),
new AudienceValidator(...),
new JwtClaimValidator(...)); This is nice in that it is pretty easy to remain passive, e.g. we just accept a list of validators and call it good. There are several places in the DSL that, instead of accepting a list, accept a chain of individual invocations, e.g. antMatchers, authenticationProviders, etc. So, I like this: http.oauth2().resourceServer()
.jwt()
.validator(new JwtAccessTokenValidator(Duration.ofSeconds(30)))
.validator(new AudienceValidator(...))
.validator(new JwtClaimValidator(...)); I think it is more readable in addition to being potentially more familiar for those already acclimated to the Spring Security DSL. I also like this: http.oauth2().resourceServer()
.jwt()
.timestamps().areValidWithin(30).seconds()
.audience().is(...)
.issuer().is(...)
.custom("custom-claim").is(...)
.validator(new MyCustomValidator(...)); I think that this is even more readable, but it does commit the DSL to more JWT semantics, making it bigger overall. I don't think it is a problem to include support for each claim in the JWT spec, e.g. sub, aud, iss, etc., though I don't really see the value and would prefer not to--I don't really see ppl going beyond audience, issuer, and custom claims before they just create their own validator. I also pondered "and" and "or" validators, but I'm of the opinion to simply encourage folks to create their own validator if they need something that sophisticated. An alternative that I'm also playing with (which I don't really like, but am happy to get feedback on) is: http.oauth2().resourceServer()
.jwt()
.validators(
timestamps().areValidWithin(30).seconds(),
audience().in(...),
issuer().is(...)); I think in the above case, I'm trying too hard to live in both words and it comes out feeling choppy. If it inspires feedback from the community, though, I'm happy to hear it. |
The other thing that I'm just keeping an eye out for is resource servers that need to support more than one signature type. Today, the code doesn't seem to support this very well, and so it's not one of my immediate concerns, but here is a thought for the future: http.oauth2().resourceServer()
.jwt()
.having(JwsAlgorithms.RS256)
.signature().keys(publicKey())
.issuer(...).and()
.having(JwsAlgorithms.HS512)
.signature().keys(symmetricKey())
.issuer(...).and() // applies just to HS512 JWTs
.audience() // applies to everyone
.authoritiesGranter(...)
.scopeAttributeName(...); |
http.oauth2().resourceServer()
.jwt()
.validator(new JwtAccessTokenValidator(Duration.ofSeconds(30))); ...whatever the way to validate time looks like, would it be possible to provide a |
@Clockwork-Muse that sounds like it could be a helpful feature though I'd be worried about devs hanging themselves with flexibility like that. Also, note that this might not be enough to get what you want since our backing implementation (Nimbus) might not have the appropriate hook (it does not use java.time yet), but there are probably ways around that if this is the right thing to do. I've created an issue in our sandbox project that you can track: jzheaux/spring-security-oauth2-resource-server#57 |
Introducing initial support for Jwt-Encoded Bearer Token authorization with remote JWK set signature verification. High-level features include: - Accepting bearer tokens as headers and form or query parameters - Verifying signatures from a remote Jwk set And: - A DSL for easy configuration - A sample to demonstrate usage Fixes: spring-projectsgh-5128 Fixes: spring-projectsgh-5125 Fixes: spring-projectsgh-5121 Fixes: spring-projectsgh-5130 Fixes: spring-projectsgh-5226 Fixes: spring-projectsgh-5237
Summary
HttpSecurity
should support the configuration of an application to operate as a resource server, e.g.Relates to #4887
The text was updated successfully, but these errors were encountered: