Skip to content

Add JWT Authentication support #9424

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

Open
jzheaux opened this issue Feb 10, 2021 · 16 comments
Open

Add JWT Authentication support #9424

jzheaux opened this issue Feb 10, 2021 · 16 comments
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement

Comments

@jzheaux
Copy link
Contributor

jzheaux commented Feb 10, 2021

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.

@jzheaux jzheaux added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Feb 10, 2021
@jgrandja
Copy link
Contributor

jgrandja commented Feb 22, 2021

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?

@jgrandja
Copy link
Contributor

jgrandja commented Mar 19, 2021

@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.

@cromefire
Copy link

cromefire commented Mar 19, 2021

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.

@cromefire
Copy link

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.

@cromefire
Copy link

cromefire commented Mar 19, 2021

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)

@cromefire
Copy link

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.

@jgrandja
Copy link
Contributor

@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.

@cromefire
Copy link

cromefire commented Mar 26, 2021

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.

@yijianguanzhu
Copy link

Woooo... JWT is already supported in the current version

@cromefire
Copy link

Can you elaborate on that?

@yijianguanzhu
Copy link

yijianguanzhu commented Apr 4, 2021

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();
    }
}

@cromefire
Copy link

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

@yijianguanzhu
Copy link

fine

@cromefire
Copy link

cromefire commented Apr 4, 2021

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).

@yijianguanzhu
Copy link

Yes, it is indeed a bit complicated, custom processing needs to look at some internal logic, which may not be friendly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants