It would be nice if Spring Security supported REST APIs issuing self-signed JWTs.
One use case that is quite common is for a REST API to first exchange a username and password for a JWT. That JWT is then used to authenticate subsequent requests.
Comment From: jgrandja
Before we add this support, we should assess the implications of this pattern.
The initial questions I have are:
- How do tokens get revoked (when compromised)?
- How are tokens verified (where is the Public Key)?
- Who is the Issuer
iss? - Are tokens short-lived or long-lived?
- Are refresh tokens supported?
I'm curious, is this pattern being used out there today? Which libraries have implemented this?
Comment From: cromefire
Here are some examples: ASP: https://devblogs.microsoft.com/aspnet/jwt-validation-and-authorization-in-asp-net-core/ AdonisJS: https://adonisjs.com/docs/4.1/authentication#_jwt Ktor: https://ktor.io/docs/jwt.html
Comment From: jgrandja
@cromefire
The ASP link uses an ASP.NET Core authentication server, so this is not equivalent to the proposed pattern.
I discussed a customer scenario I ran into recently that required issuing bearer tokens from an ASP.NET Core authentication server and then validating those tokens in a separate ASP.NET Core web service which may not have access to the authentication server.
AdonisJs acts as an OAuth provider.
AdonisJs is a modular framework that consists of multiple service providers
ktor acts as an OAuth provider.
All 3 act as OAuth providers (Authorization Server's) so they are not equivalent to the proposed pattern.
Comment From: cromefire
Well ktor is one that I used personally without oauth and just plain jwt. So not it doesn't and I know so for sure.
The ASP link uses an ASP.NET Core authentication server
There's no reference at all to a ASP.NET Core authentication server in the article altogether, where did you find that.
As for Adonis, I have an application deployed that uses JWT, also with out oauth at all.
Comment From: cromefire
The links you provided for ktor and Adonis both don't have anything to do with the JWT feature at all. Ktor does also support oauth in parallel to plain JWT and the Adonis link doesn't even mention oauth at all.
Comment From: cromefire
AdonisJs is a modular framework that consists of multiple service providers
This is by the way just a reference to it using dependency injection (provider != OAuth provider)
Comment From: cromefire
How are tokens verified (where is the Public Key)?
The application has the private key in this case, so the public key can be derived from that.
Are tokens short-lived or long-lived?
That can usually be configured by the developer in what I have seen.
Are refresh tokens supported?
With the implementations I've seen no, except ASP.NET if I remember correctly. I think that'd be a nice feature.
Who is the Issuer iss?
I think this was determined by the developers in most cases I've seen.
How do tokens get revoked (when compromised)?
For this I only know what ktor provides and in that case you verify the JWT yourself, so you'd have to implement it yourself, but that'd be a nice addition I guess.
Comment From: jgrandja
@cromefire Let me restate my viewpoint on this ticket and hopefully this will clarify things.
This ticket essentially proposes a Security Token Service (STS) from a conceptual design standpoint.
At a bare minimum, an STS must provide the capability of revoking a token and verifying a token. If it can mint a token then it should also be able to revoke and verify a token. However, a production-ready STS will deliver much more security features on top of this minimum.
An STS is a security provider and you can consider OAuth / OIDC as specialized versions of an STS. The main advantage to using an OAuth 2.0 and/or OpenID Connect 1.0 provider is they are implemented to industry standards and therefore allow for interoperability and provide implementation (security) recommendations to protect against attacks.
When I read the issue comment:
One use case that is quite common is for a REST API to first exchange a username and password for a JWT
This looks very similar to the Resource Owner Password Credentials Grant. NOTE: This is deprecated in OAuth 2.1
We need to be careful that we don't reinvent the wheel and provide a proprietary solution.
Comment From: cromefire
Well it's from the standpoint of spring security basically just an API token that is verified using a public key (a characteristic that's good for HA for example without the need of a storage backend). To my knowledge none of the implementations listed above are providing capabilities to revoke tokens.
To combat the issues with revoking tokens I've often seen a combination of refresh tokens and short expiry dates
We need to be careful that we don't reinvent the wheel and provide a proprietary solution.
It's a feature provided by seemingly every major framework that provides authentication, so I guess it's a little too late. Your alternative to use oidc has basically 3 major Design flaws (opposed to JWT or API Token authentication): - You have to deploy a separate oidc provider - There seems to be next to no open source oidc IDP so it relies on Google, octa, etc. - You have to split user management from the application because it now lives inside the IDP
This makes oidc a totally different complexity and use case from plain JWT auth, which is just a more specific implementation of API tokens.
Comment From: yijianguanzhu
Woooo... JWT is already supported in the current version
Comment From: cromefire
Can you elaborate on that?
Comment From: yijianguanzhu
Can you elaborate on that?
I recommend you to check out the JWT doc here. I use version 5.2.5 of Spring Security Reactive
Talk is cheap. Show you the code.
@Configuration
@EnableWebFluxSecurity
@EnableConfigurationProperties(JwtProperties.class)
public class GatewaySecurityConfiguration {
@Autowired
private JwtProperties jwtProperties;
@Bean
public SecurityWebFilterChain springSecurityFilterChain( ServerHttpSecurity http ) {
final String[] skipUrls = new String[] { "/user-account/user/login" };
// Swagger path
final String[] skipSwaggerUrls = new String[] {
"/favicon.ico",
"/doc.html",
"/webjars/**",
"/swagger-resources/**",
"/**/v2/api-docs" };
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter( new JwtTokenGrantedAuthoritiesConverter() );
ReactiveJwtAuthenticationConverterAdapter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverterAdapter(
authenticationConverter );
// Skipped path
ServerWebExchangeMatcher pathMatchers = ServerWebExchangeMatchers
.pathMatchers( ArrayUtils.addAll( skipUrls, skipSwaggerUrls ) );
http.securityMatcher( new NegatedServerWebExchangeMatcher( pathMatchers ) )
.authorizeExchange()
.pathMatchers( "/**" ).access( new UserAuthorityReactiveAuthorizationManager() )
.anyExchange().authenticated()
.and().csrf().disable()
.addFilterAfter( new AuthWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION )
.oauth2ResourceServer()
.bearerTokenConverter( new JwtExtractTokenAuthenticationConverter() )
// The authentication failed handler
.authenticationEntryPoint( new UserAuthenticationEntryPoint() )
// The authorization failed handler
.accessDeniedHandler( new UserAccessDeniedHandler() )
.jwt()
.jwtAuthenticationConverter( jwtAuthenticationConverter );
return http.build();
}
@Bean
@RefreshScope
public ReactiveJwtDecoder jwtDecoder() {
byte[] data = jwtProperties.getSecretKey().getBytes( StandardCharsets.UTF_8 );
SecretKeySpec secretKey = new SecretKeySpec( data, JWSAlgorithm.HS256.getName() );
return NimbusReactiveJwtDecoder.withSecretKey( secretKey ).build();
}
}
Comment From: cromefire
Yes that can be used sort of as a workaround, but it's not quite a "proper" solution I think (although it shows the internals are already there)
See #9423
Comment From: yijianguanzhu
fine
Comment From: cromefire
Looking at the code it seems like everything is pretty much in place and the only thing needed would be some simple API to make it easy and keep you from needing to spend a lot of time to figure it out. Seems like basically one method to me (if at all) plus a guide or something to get people started and maybe something to generate the tokens (I not very deep into spring security, so it might just need some docs).
Comment From: yijianguanzhu
Yes, it is indeed a bit complicated, custom processing needs to look at some internal logic, which may not be friendly.