Skip to content

Use JWT as Access Token

Wuyi Chen edited this page Jul 3, 2019 · 13 revisions

Overview

JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for creating access tokens. There are some facts about JWT:

  • Encoded into Base64.
  • The authorized entity can decrypt a JWT to JSON.
  • JWT allows you to add custom fields into JSON.

For changing your authentication eco-system to use JWT token, you need to do several things:


Configure the authentication service

Set dependencies

Besides the Spring Security library and the OAuth2 library, the Spring Security JWT library needs to be added.

build.gradle

dependencies {
    // Spring Security and OAuth2
    compile group: 'org.springframework.cloud',          name: 'spring-cloud-security'
    compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2',       version: '2.1.3.RELEASE'
	
    // Spring Security JWT
    compile group: 'org.springframework.security',       name: 'spring-security-jwt',          version: '1.0.0.RELEASE'

    // For storing credentials and roles in database
    compile group: 'org.springframework.boot',           name: 'spring-boot-starter-data-jpa', version: '1.5.0.RELEASE'
    compile group: 'org.springframework.boot',           name: 'spring-boot-autoconfigure',    version: '1.5.0.RELEASE'
    compile group: 'mysql',                              name: 'mysql-connector-java',         version: '5.1.38'
}

Add signing key

Like encrypting passwords in the config server, you need set up a symmetric encryption key by adding signing.key into the configuration of the authentication service. You can either add it to the local application.yml file or the remote authenticationservice.yml file if you moved the configuration into the config server.

application.yml

signing:
  key: 345345fsdfsf5345

authenticationservice.yml

signing.key: "345345fsdfsf5345"

Define the logic of creating, signing and translating JWT token

JWTTokenStoreConfig.java

@Configuration
public class JWTTokenStoreConfig {
    @Autowired
    private ServiceConfig serviceConfig;

    /**
     * Create a new JWT token store.
     * 
     * @return  The {@code TokenStore} object.
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * Generate token services.
     * 
     * <p>This method will use the Spring security’s default token services 
     * implementation which tokens will be generated as random UUID values.
     * 
     * @return  The {@code DefaultTokenServices} object.
     */
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    /**
     * Generate the converter for translating the token.
     * 
     * @return  The {@code JwtAccessTokenConverter} object with the signing 
     *          key.
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());           // Set the signing key that will be used to sign your token (define in configuration)
        return converter;
    }

    /**
     * Create a new JWT token enhancer.
     * 
     * <p>The {@code JWTTokenEnhancer} class will help on adding custom fields 
     * into a JWT token. Currently, the organization Id field is added into 
     * the JWT token.
     * 
     * @return  The {@code TokenEnhancer} object.
     */
    @Bean
    public TokenEnhancer jwtTokenEnhancer() {
        return new JWTTokenEnhancer();
    }
}

Hook the authentication service with the logic of JWT token

JWTOAuth2Config.java

@Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DefaultTokenServices tokenServices;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));   // Spring Security allows you to hook multiple token enhancers

        endpoints.tokenStore(tokenStore)                                                                  // The JWTTokenStoreConfig.tokenStore() will be injected in here
                .accessTokenConverter(jwtAccessTokenConverter)                                            // The JWTTokenStoreConfig.jwtAccessTokenConverter() will be injected in here
                .tokenEnhancer(tokenEnhancerChain)                                                        // The chain of token enhancers will be passed into the endpoint
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {                      // Define what client applications are registered with the service
        clients.inMemory()                                                                                // Store the application information in memory
                .withClient("eagleeye")                                                                   // Specify which client application will register
                .secret("thisissecret")                                                                   // Specify the secret which will be used to get the access token
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")                  // Provide a list of the authorization grant types that will be supported by the service
                .scopes("webclient", "mobileclient");                                                     // Define the types of the client applications can get the access token from the service
    }
}

Generate the access token

We will make a same request to the endpoint of the authentication service to get a JWT token.

  • URL: http://localhost:8901/auth/oauth/token

  • Method: POST

  • Authentication

    • Type: Basic Auth
    • Username: eagleeye
    • Password: thisissecret
  • Body:

    Key Value
    grant_type password
    scope webclient
    username john.carnell
    password password1

After sending the HTTP request to the authentication service with the credential, it will return the JWT token:

{
    "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb25JZCI6ImQxODU5ZjFmLTRiZDctNDU5My04NjU0LWVhNmQ5YTZhNjI2ZSIsInVzZXJfbmFtZSI6ImpvaG4uY2FybmVsbCIsInNjb3BlIjpbIndlYmNsaWVudCJdLCJleHAiOjE1NTQ4OTQ1NTQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI1N2I3ZGVjMi1iZGQ0LTQxYzUtOWVlZC1mM2Q1MjVmMzVmODEiLCJjbGllbnRfaWQiOiJlYWdsZWV5ZSJ9.OPcJzKnDg8g1rOhU10SIgbWPOcZIEswqcsFlbIPxfiw",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb25JZCI6ImQxODU5ZjFmLTRiZDctNDU5My04NjU0LWVhNmQ5YTZhNjI2ZSIsInVzZXJfbmFtZSI6ImpvaG4uY2FybmVsbCIsInNjb3BlIjpbIndlYmNsaWVudCJdLCJhdGkiOiI1N2I3ZGVjMi1iZGQ0LTQxYzUtOWVlZC1mM2Q1MjVmMzVmODEiLCJleHAiOjE1NTc0NDMzNTQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI5NGQxNzIxNi03ZGFhLTQ4ZmMtYmJkYy0yNmU2YTZjNTBjNmYiLCJjbGllbnRfaWQiOiJlYWdsZWV5ZSJ9.UUXCaDh8kT6wip8Uuf7GRDn8szqjx5FVu8BkOfuREd4",
    "expires_in": 43199,
    "scope": "webclient",
    "organizationId": "d1859f1f-4bd7-4593-8654-ea6d9a6a626e",
    "jti": "57b7dec2-bdd4-41c5-9eed-f3d525f35f81"
}

Configure the protected service

Set dependencies

Besides the Spring Security library and the OAuth2 library, the Spring Security JWT library needs to be added.

build.gradle

dependencies {
    // Spring Security and OAuth2
    compile group: 'org.springframework.cloud',          name: 'spring-cloud-security'
    compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2',       version: '2.1.3.RELEASE'
	
    // Spring Security JWT
    compile group: 'org.springframework.security',       name: 'spring-security-jwt',          version: '1.0.0.RELEASE'
}

Add the logic of creating, signing and translating JWT token

Similar to the JWTTokenStoreConfig.java in the authentication service, the protected service needs the JWTTokenStoreConfig.java to define the logic of creating, signing and translating JWT token.

JWTTokenStoreConfig.java

@Configuration
public class JWTTokenStoreConfig {
    @Autowired
    private ServiceConfig serviceConfig;

    //JWT
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());
        return converter;
    }
}

Define permissions

For the protected service, you still need to define the permissions about who or which role can access the protected service.

  • Give permissions to all authenticated users:

ResourceServerConfiguration.java

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}
  • Give permissions to a group of users with certain role:

ResourceServerConfiguration.java

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.DELETE, "/v1/organizations/**")
                .hasRole("ADMIN")                                        // Only ADMIN role have the permission to do DELETE operations
                .anyRequest()
                .authenticated();
    }
}
Clone this wiki locally