Expected Behavior The OAuth2 Resource server should have a way to cache tokens (such as JWT) to avoid validating the tokens for every requests.
Current Behavior Currently, there is no way to cache the tokens which has some life time (or expiry).
Context We are using OAuth2 for server to server authorization/authentication. Our authorization server leverages JWT type of access token and it has a limited life time. So, for accessing target server, the caller should get the token from Authorization server and it uses that token during the token lifetime. Currently, the Spring OAuth2 client has a way to cache the token for a given life time, for OAuth2 resource server, there is no way to set such configuration.
Comment From: jzheaux
Hi, @Kieun, thanks for the suggestion, but I don't think this is something that we want to do as it is important to validate the JWT on each request. Like Basic Authentication, Bearer Token Authentication is designed as a stateless protocol.
the Spring OAuth2 client has a way to cache the token
The reason that OAuth 2.0 Client remembers the token is that a logged-in user session is being created. In other words, Client is for applications and Resource Server is for APIs.
EDIT: Can you elaborate on why you don't want your resource server to verify the JWT on each request?
Comment From: Kieun
@jzheaux I'm thinking that this is common approach to leverage JWT for the authorization between the server to server.
Can you elaborate on why you want your resource server to verify the JWT on each request?
As you've mentioned, our resource server is somehow stateless since we don't have service account (client id/secret and other attributes) in the storage. This request is more about for high performance. Our example use cases are
- Gather metrics of the resources server per every seconds from the prometheus
- Other servers (Spring boot admin, back office tool) send requests for specific resources
- External and internal RP uses the resource server's API
Those are protected by OAuth2 JWT token including specific scope and etc.
For your information, we leverage client_credentials grant type and the user is not involved at all.
Again, I'm thinking that this is common use cases. e.g., Google OAuth 2.0 for Server to Server Applications
Without such cache or similar mechanisms, the resource server needs to verify JWT token (including signature verification) even if the JWT token is reused during the lifetime and its verification was done before. For high performing applications (low latency), it's good to have such configuration or options for caching token verification results.
As a way to improve the performance, we might introduce more light JWT signature algorithm such ES256 or something. But, that's not the scope for this feature requests. And, we are not using introspection API for this reason and we do not revoke the token since it introduces additional layer and it does not give much about the security. In stead, our issued token has short lifetime.
Comment From: jzheaux
Thank you for the extra information.
Regarding the Google article you linked, can you help me see the part where it is saying that APIs wouldn't verify the JWT on each request? This section talks about using JWT so that the authorization server isn't pinged, and that is what Resource Server does OOTB with its JWT support already.
I'm not clear on whether it is common to have the Resource Server cache JWT validation results. Further, since it is not part of the OAuth spec, I think it would be important to gather more evidence that this practice is quite common. Even if it is quite common, I believe further evidence would be needed to demonstrate that the performance benefits outweigh the security implications before adding such a component to Spring Security.
If you insist on caching the verification results, I think you can use Spring Cache with JwtDecoder like so:
public class MyCachingJwtDecoder implements JwtDecoder {
private final JwtDecoder delegate;
@Override
@Cacheable(... your caching configuration ...)
Jwt decode(String jwt) {
return this.delegate.decode(jwt);
}
}
And then publish it as a @Bean:
@Bean
JwtDecoder jwtDecoder() {
JwtDecoder delegate = JwtDecoders.fromIssuerLocation(issuerUri).build();
return new MyCachingJwtDeoder(delegate);
}
Your caching rules may be more sophisticated than this, in which case I'd recommend taking a look at Spring Cache's feature set.
At this point, I'm going to close this ticket as declined, but please let me know if it seems like I've misunderstood. We can reopen if a Spring Security feature is identified.
Comment From: Kieun
@jzheaux Thanks for the comments. Apparently, I might try with Spring cache feature and let me try.
Regarding the cache, I still don't understand why the resource server needs to verify same JWT access token for every request. As I've mentioned, there is a case where the token revocation (or invalidation) is not introduced and static JWT token used for the authorization. Performing such verification and validation to the JWT will always have same result and the token is valid before the expiry. So, what's the benefits of verifying the same token again and agin for every requests? Is there any security benefits in this case? I'm still close to leverage caches in this case if possible.
Additional information in #12868
I would like to reopen the issue https://github.com/spring-projects/spring-security/issues/12830 which is closed with denial. About the comment from the original issue
I'm not clear on whether it is common to have the Resource Server cache JWT validation results. Further, since it is not part of the OAuth spec, I think it would be important to gather more evidence that this practice is quite common. Even if it is quite common, I believe further evidence would be needed to demonstrate that the performance benefits outweigh the security implications before adding such a component to Spring Security.
Performance sensitive applications caches various of data to provide low-latency APIs. In micro service architecture, there might be tons of API calls between the server (machine) and sometimes the authorization and authentication is provided OAuth2. Regarding security implication without any revocation mechanism, validating same JWT token (access token) multiple times from the same clients does not sacrifice much about the security. You might say that it would introduce some chances for hackers to modify and add invalid (or unverified) tokens to the cache so that they can bypass OAuth2 JWT token validation. But, I'm thinking it's not so critical and that's the policy of the resource server and resource server can take the own risk.
For your information, there are cases to introduce such cache mechanism.
Amazon Cognito user pools Related Stack overflow issue I suggest that keep opening this ticket to gather more feedbacks or requests.
Comment From: jzheaux
I still don't understand why the resource server needs to verify same JWT access token for every request.
Because the signature needs verification to ensure that nothing is forged. Because the verification is performed locally on locally-cached public keys, it's often an acceptable tradeoff between performance and security.
Performing such verification and validation to the JWT will always have same result and the token is valid before the expiry.
It's a consequence of using a stateless protocol like HTTP. The creds are given on every request because the application doesn't remember anything from the previous request.
To be clear, though, caching may be reasonable in your use case. It depends on your performance and security modeling together.
Is there any security benefits in this case?
It's a matter of having one more thing to secure. Once you introduce a cache, you need to ask yourself who has access to that cache, who can write to it, etc. If local JWT validation is fast enough for your needs, it would be nice to have as few moving pieces to secure as possible.
Based on what you said in #12868, I think there is confusion about why I declined the feature request. The reason is not that caching is unreasonable; it's that the community needs are unclear. Until more clarity arrives (perhaps in the form of an RFC), a general-purpose cache API like Spring Cache is going to suit you better.
Comment From: Kieun
@jzheaux I clearly understand why you declined the request. However, to hear some feedbacks from the community, I suggest you to keep this issue open.
Comment From: jzheaux
That sounds fair, @Kieun. I'll leave it open, and we can see where the conversation goes.
Comment From: uniquejava
We only set spring cloud gateway as resource server. Each API request pass through the gateway, the gateway resolve JWT and pass the resolved payload(JWT claims) via http header(as a json string) to each micro-service.
In each spring boot project(as a micro-service), there is not even spring security dependency, and each controller get user info directly from http header
This way the performance and complexity reduces considerably.
I think to further improve the performance, you can do some cache in the gateway. It can be memory or redis deppending how many gateway instances you have.
Comment From: Kieun
@uniquejava we could do similar jobs. But, the requirements is about providing the way for introducing caching mechanism for the access token verification results.
Comment From: samueldlightfoot
@Kieun @jzheaux
For what it's worth, we implemented the caching of raw tokens as described earlier to great success. We were spending 10% of our CPU time decoding (mostly) and validating the tokens on each call. This overhead completely disappeared with the caching.
Comment From: rpsandiford
It's been a while since this post was last active, but I'll pitch in here.
I'm in the midst of trying to upgrade our OAuth2 handling from the old 2.5.2 release to the newer 5.7.7 version. (Just happens that's what we're on for the rest of spring security - we'll get to 6.x at some point).
I'm having major problems with this upgrade, of which this caching of OAuth2 tokens for service to service access (i.e. 'Client Credentials Grant'). We cache the tokens with a class that implements ResourceServerTokenServices - for which I find no corresponding capability in OAuth2 3.0 and higher. So - this was a capability that was not brought forward. This change plus others (e.g. no 'ClientDetails') has me tearing out my hair trying to figure out how to do our upgrade.
We have our own authorization server - in fact, several, since we deploy around the world. Any given service can call any other given service either in a local deployment (authenticating / authorizing to the authentication server in the local deployment) or to a remote deployment (authenticating / authorizing to the auth server in the remote deployment). Authorizing to a geographically remote server on every REST request is really expensive in terms of time. So, we cache locally, and manage the timeout ourselves to ensure we have current tokens.
Anyways - its going to be too late for us (unless we decide the pain of upgrading right now is too great, and we stay on 2.5.2 for a while longer) - but please consider at least adding the OPTIONAL capability of being able to more readily handle aspects from the old 2.x OAuth2 handling that hasn't (yet) made it into the 3.x and later rewrite...
Comment From: jzheaux
Thanks for the update, @rpsandiford, and I'm sorry to hear that you are in the middle of a painful upgrade. That's always such a pain, and I feel for you.
I'm having major problems with this upgrade, of which this caching of OAuth2 tokens for service to service access (i.e. 'Client Credentials Grant').
Since you want to store tokens that one service would use to talk to another service, then please take a look at OAuth2AuthorizedClientManager, which is the component that Spring Security's WebClient filters use to keep the token up to date. The manager uses OAuth2AuthorizedClientRepository to store tokens.
Please also take a look at our sample application that configures an application as with Client Credentials Grant.
Comment From: Clemens85
Hi, I read this issue with great interest and here is one feedback from me (maybe I didn't quite understand everything however):
We have a scenario in which we store data from users and/or other services temproarily and then process it asynchronously later. We also store the access tokens temporarily for this purpose, as these are later required asynchronously for authentication / authorization.
It may however happen, that our authorization server (Keycloak) is not always reachable, due to for maintenance reasons etc. Now, of course, you can say that this must not happen and that this is a critical component that must always be running. And of course that should be the case - but unfortunately this is sometimes not the case, especially when dealing with self-operated servers. And at the end of the day, we have to deal with this on the application side.
If our Keycloak is now unavailable for a short time, nobody can authenticate themselves either. This is obvious and not the problem. However, one problem is that data we have already stored contains valid tokens from the time it was stored, as our Keycloak was still running before. But this data can no longer be processed, as a request has to be sent to the Keycloak again to validate the token.
I hope that I was able to explain the issue clearly. From my understanding, a cache as proposed by the issue creator would be one possible solution to circumvent such issues (when the auth server is (temporarily) unavailable). In our case, I think it would even be sufficient if Spring Security stores the certificate stuff (from our Keycloak) for token validation locally for a defined period of time as a fallback, as I think that this is sufficient for successful JWT validation. But I am not aware of the complexity for doing so and/or if this causes other problems.
I just wanted also to give some feedback to the original issue. We can solve our issue also by the proposed delegate/cache pattern from jzheaux (respectively in our case maybe also by a retry pattern in our asynchronous processing). But I think that there exist really some (rare) cases (like maybe our case) in which it might be helpful to have such a feature, although I doubt that it will be implemented by Spring Security, due to you would have also to provide something like a distributed cache when dealing with several app instances, like in our scenario.