@TOC

OAuth2.0 series of blogs:

  • Basic Concept and Operation Process of OAuth2.0 Series (1)
  • OAuth2.0 Authorization Mode Tutorial (part 2)
  • OAuth2.0 Simplified Mode Tutorial (Part 3)
  • OAuth2.0 Series of Password mode practice tutorial (4)
  • OAuth2.0 Client Mode Tutorial (5)
  • OAuth2.0 Series information database storage tutorial (6)
  • OAuth2.0 Redis Information Storage Tutorial (7)
  • OAuth2.0 JWT Token Tutorial (8)
  • OAuth2.0 series integrated JWT for single sign-on

1. Introduction to the preface of the article

In the previous article we learned some basic concepts of OAuth2, have a basic understanding of OAuth2, also for OAuth2.0 token database storage, corresponding blog: JDBC way of data storage, and then if you do not want to store token can be realized?

IDEA, Ctrl+Alt+B, you can see the implementation of TokenStore, there are several:

  • InMemoryTokenStore, default storage, stored in memory
  • JdbcTokenStore, access_token is stored in the database
  • JwtTokenStore and JWT are special. It is a kind of stateless storage that does not carry memory or database storage, but carries comprehensive user information in JWT, which can be saved in JWT with past verification
  • RedisTokenStore, store access_token in redis.
  • JwkTokenStore, which saves the access_token to the JSON Web Key.

2. Example experiment verification

Preparation for experimental environment:

  • IntelliJ IDEA
  • Maven3.+ creates a New SpringBoot Initializer project, which can be named oAuth2-jwt

 <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>
Copy the code

TokenStore:

   @Bean
    public TokenStore jwtTokenStore(a) {
        // Access Token based on JWT
        return new JwtTokenStore(accessTokenConverter());
    }
Copy the code

JwtAccessTokenConverter:

 @Bean
    public JwtAccessTokenConverter accessTokenConverter(a){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                // You can customize token information only in authorization code and password mode
                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);
                returntoken; }};// Set the signature key
        converter.setSigningKey("signingKey");
        return converter;
    }

Copy the code

Configuration accessTokenConverter

 @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager)
                // Customize accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                // The token can be obtained
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }
Copy the code

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> *@authorMazq * Modified Record * Modified by: Date: 2020/06/17 11:44 Modified Content: * </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";
    // Simplify the authorization mode
    private static final String IMPLICIT = "implicit";
    // Specify which resources require authorization validation
    private static final String RESOURCE_ID = "resource_id";

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // Use memory storage
                .inMemory()
                // Mark the client ID
                .withClient(CLIENT_ID)
                // Client security code
                .secret(SECRET_CHAR_SEQUENCE)
                // Return code for true direct automatic authorization success
                .autoApprove(true)
                .redirectUris("http://127.0.0.1:8084/cms/login") // Redirect the URI
                // Allow the scope of authorization
                .scopes(ALL)
                / / token time seconds
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                // Refresh the token time in 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)
                // Customize accessTokenConverter
                .accessTokenConverter(accessTokenConverter())
                // The token can be obtained
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS);
    }

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

    @Bean
    public JwtAccessTokenConverter accessTokenConverter(a){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String grantType = authentication.getOAuth2Request().getGrantType();
                // You can customize token information only in authorization code and password mode
                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);
                returntoken; }};// Set the signature key
        converter.setSigningKey("signingKey");
        return converter;
    }

    @Bean
    public TokenStore jwtTokenStore(a) {
        // Access Token based on JWT
        return newJwtTokenStore(accessTokenConverter()); }}Copy the code

SpringSecurity 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 > * * *@authorMazq * Modified Record * Modified by: Date: 2020/06/15 10:39 Modified Content: * </pre> */
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean(a) 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 {
        // Resolve static resource interception
        web.ignoring().antMatchers("/asserts/**");
        web.ignoring().antMatchers("/favicon.ico");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http   // Configure the login page and allow access
                .formLogin().permitAll()
                // Configure Basic login
                //.and().httpBasic()
                // Configure the logout page
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and().authorizeRequests().antMatchers("/oauth/**"."/login/**"."/logout/**").permitAll()
                // All other requests require authentication
                .anyRequest().authenticated()
                // Disable cross-domain protection;.and().csrf().disable(); }}Copy the code

3. Simple function test

Access can access authorization link, the browser, authorization code mode payload parameters transfer code: http://localhost:8888/oauth/authorize? client_id=cms&client_secret=secret&response_type=code

Because there is no login, the default login page for SpringSecurity is returned with http.formLogin ().permitall (); Http.httpbasic (); Http.formlogin ().loginPage(“/login”).permitall ()

As shown, enter the database password configured for SpringSecurity

If login succeeds, return redirect_URI and obtain the authorization code

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

Configure the authorization parameters for the request header in Basic Auth mode, username = client_id, password = client_secret

{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTIzNzYwNjEsInVzZXJfbmFtZSI6Im5pY2t5IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9hZG 1pbiJdLCJqdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJjbGllbnRfaWQiOiJjbXMiLCJzY29wZSI6WyJhbGwiXX0.TpIBd9 Gtb4M7sC1MSQsxsn8mwnhAm59CUBZPU7jwdnE"."token_type":"bearer"."refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJuaWNreSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJiM2IwZGExNS1mMmQyLTRlN2 MtYTUwNC1iMzg5YjkxMjM0MDMiLCJleHAiOjE1OTIzNzYwNjEsImF1dGhvcml0aWVzIjpbIlJPTEVfYWRtaW4iXSwianRpIjoiODVhYTlmMGYtNDliNS00ND g4LTk4MTQtNmM0MmZjMjZkYTc2IiwiY2xpZW50X2lkIjoiY21zIn0.TU8ZD_5AxRGbgbOWZSuWAxwWjMJ4HLHniA46M-dnChE"."expires_in":119."scope":"all"."user_name":"nicky"."jti":"b3b0da15-f2d2-4e7c-a504-b389b9123403"
}
Copy the code

Example code download