Practical tutorial of using JWT token in OAuth2.0 series

Keywords: Spring Database SpringBoot Java

Practical tutorial of using JWT token in OAuth2.0 series (8)

OAuth2.0 blog series:

1. Introduction to the preface

stay Previous articles We learned some basic concepts of OAuth2, had a basic understanding of OAuth2, and also stored the tokens of OAuth2.0 in the database, corresponding to the blog: jdbc data storage And then what if you don't want to store tokens?

In IDEA, Ctrl+Alt+B, you can see the implementation of TokenStore as follows:

ok, in fact, there are the above ways for token storage, which are introduced respectively:

  • InMemoryTokenStore, default store, saved in memory
  • JdbcTokenStore,access_token stored in database
  • JwtTokenStore, JWT is a special way. It is a stateless way of storage. It does not store memory or database. It only carries comprehensive user information in JWT, which can be saved in JWT with past verification
  • RedisTokenStore, access_ The token is saved in redis.
  • JwkTokenStore, access_ The token is saved to the JSON Web Key.

2. Example experiment verification

Preparation of experimental environment:

  • IntelliJ IDEA
  • Maven 3. + version
    Create a new SpringBoot Initializer project, which can be named oauth2 JWT


The following configurations are mainly added:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 <!-- Spring Cloud Oauth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- Spring Cloud Security-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

TokenStore:

   @Bean
    public TokenStore jwtTokenStore() {
        //Implementation of Access Token based on jwt
        return new JwtTokenStore(accessTokenConverter());
    }

JwtAccessTokenConverter :

 @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                //Only authorization code and password mode can customize token information
                if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) {
                    String userName = authentication.getUserAuthentication().getName();
                    // Customize some token information
                    Map<String, Object> additionalInformation = new HashMap<String, Object>(16);
                    additionalInformation.put("user_name", userName);
                    additionalInformation = Collections.unmodifiableMap(additionalInformation);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                }
                OAuth2AccessToken token = super.enhance(accessToken, authentication);
                return token;
            }
        };
        // Set signing key
        converter.setSigningKey("signingKey");
        return converter;
    }

Configure accessTokenConverter

 @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager)
                //Custom accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                //Support to obtain token Mode
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }

General configuration class reference:

package com.example.springboot.oauth2.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 *  OAuth2.0 Configuration class
 * </pre>
 *
 * <pre>
 * @author mazq
 * Modification record
 *    Revised version: modified by: modified on: 11:44, June 17, 2020
 * </pre>
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

    private static final String CLIENT_ID = "cms";
    private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
    private static final String SCOPE_READ = "read";
    private static final String SCOPE_WRITE = "write";
    private static final String TRUST = "trust";
    private static final String USER ="user";
    private static final String ALL = "all";
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 2*60;
    private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60;
    // Password mode authorization mode
    private static final String GRANT_TYPE_PASSWORD = "password";
    //Authorization code mode
    private static final String AUTHORIZATION_CODE = "authorization_code";
    //refresh token Mode
    private static final String REFRESH_TOKEN = "refresh_token";
    //Simplified licensing model
    private static final String IMPLICIT = "implicit";
    //Specify which resources require authorization authentication
    private static final String RESOURCE_ID = "resource_id";

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // Using memory storage
                .inMemory()
                //Tag client id
                .withClient(CLIENT_ID)
                //Client security code
                .secret(SECRET_CHAR_SEQUENCE)
                //Direct automatic authorization for true returns code successfully
                .autoApprove(true)
                .redirectUris("http://127.0.0.1:8084/cms/login") //Redirect uri
                //Authorized scope
                .scopes(ALL)
                //token time seconds
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                //Refresh token time seconds
                .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
                //Allowed authorization type
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD , AUTHORIZATION_CODE , REFRESH_TOKEN , IMPLICIT);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager)
                //Custom accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                //Support to obtain token Mode
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }

    /**
     * Security configuration of authentication server
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // Open / oauth/token_key authentication port authentication permission access
                .tokenKeyAccess("isAuthenticated()")
                //  Open / oauth/check_token authentication port authentication permission access
                .checkTokenAccess("isAuthenticated()")
                //Allow form authentication
                .allowFormAuthenticationForClients();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                //Only authorization code and password mode can customize token information
                if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) {
                    String userName = authentication.getUserAuthentication().getName();
                    // Customize some token information
                    Map<String, Object> additionalInformation = new HashMap<String, Object>(16);
                    additionalInformation.put("user_name", userName);
                    additionalInformation = Collections.unmodifiableMap(additionalInformation);
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                }
                OAuth2AccessToken token = super.enhance(accessToken, authentication);
                return token;
            }
        };
        // Set signing key
        converter.setSigningKey("signingKey");
        return converter;
    }

    @Bean
    public TokenStore jwtTokenStore() {
        //Implementation of Access Token based on jwt
        return new JwtTokenStore(accessTokenConverter());
    }

}

Spring security configuration class:

package com.example.springboot.oauth2.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * <pre>
 *      Spring Security Configuration class
 * </pre>
 *
 * <pre>
 * @author mazq
 * Modification record
 *    Revised version: modified by: modified on: 10:39, June 15, 2020
 * </pre>
 */
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {    //auth.inMemoryAuthentication()
        auth.inMemoryAuthentication()
                .withUser("nicky")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //Solve the problem of static resources being intercepted
        web.ignoring().antMatchers("/asserts/**");
        web.ignoring().antMatchers("/favicon.ico");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // Configure landing page and allow access
                .formLogin().permitAll()
                // to configure Basic Sign in
                //.and().httpBasic()
                // Configure logout page
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
                // All other requests need authentication
                .anyRequest().authenticated()
                // Turn off cross domain protection;
                .and().csrf().disable();
    }


}

3. Simple function test

Access the authorization link, and access it in the browser. The authorization code mode response_type parameter to code:
http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code

Because there is no login, the default login page of spring security will be returned. The specific code is http. Formlogin(). Permitall(); if you want to pop-up login, you can configure http.httpBasic();, there is no login page for this configuration. Custom login page can be configured in this way http.formLogin().loginPage("/login").permitAll()

As shown in the figure, enter the database password configured by spring security

Login succeeded, return to redirect_uri, get the authorization code

Redirect back to redirect_uri, http://localhost:8084/cms/login?code=???

Configure the authorization parameters of the request header. In Basic Auth mode, username is the client_id, password is the client_secret

After getting the authorization code, get the token. This tutorial uses the authorization code method


JWT token

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTIzNzYwNjEsInVzZXJfbmFtZSI6Im5pY2t5IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9hZG1pbiJdLCJqdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJjbGllbnRfaWQiOiJjbXMiLCJzY29wZSI6WyJhbGwiXX0.TpIBd9Gtb4M7sC1MSQsxsn8mwnhAm59CUBZPU7jwdnE",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJuaWNreSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJleHAiOjE1OTIzNzYwNjEsImF1dGhvcml0aWVzIjpbIlJPTEVfYWRtaW4iXSwianRpIjoiODVhYTlmMGYtNDliNS00NDg4LTk4MTQtNmM0MmZjMjZkYTc2IiwiY2xpZW50X2lkIjoiY21zIn0.TU8ZD_5AxRGbgbOWZSuWAxwWjMJ4HLHniA46M-dnChE",
    "expires_in":119,
    "scope":"all",
    "user_name":"nicky",
    "jti":"b3b0da15-f2d2-4e7c-a504-b389b9123403"
}

Example code download: code download

Posted by jstarkey on Wed, 17 Jun 2020 00:18:06 -0700