[Spring Security + OAuth2 + JWT Getting Started to Practice] 24. Custom token configuration

brief introduction

Previously acquired tokens were generated based on the Spring Security OAuth2 default configuration. Spring Security allows us to customize token configuration, such as different client_id s corresponding to different tokens, token validity time, token storage policy, etc.

Custom token configuration

Let authentication server HkAuthorizationServerConfig inherit AuthorizationServerConfigurerAdapter and override its configure(ClientDetailsServiceConfigurer clients) method:

package com.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

/**
 * Authentication Server
 */
@Configuration
@EnableAuthorizationServer
public class HkAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailService;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //Save in memory
        clients.inMemory()
                .withClient("myhk1")
                .secret("myhk111")
                //Token token expiration time seconds
                .accessTokenValiditySeconds(3600)
                //Refresh token time
                .refreshTokenValiditySeconds(864000)
                //Jurisdiction
                .scopes("all", "read", "write")
                //Authorization mode
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                //Configure Multiple Client s
                .and()
                .withClient("myhk2")
                .secret("myhk222")
                .accessTokenValiditySeconds(7200);
    }
}

After inheriting the AuthorizationServerConfigurerAdapter adapter, the authentication server needs to override the configure(AuthorizationServerEndpointsConfigurer endpoints) method, specifying AuthenticationManager and UserDetailService.

Modify the authentication server configuration class WebSecurityConfigurer to register the Authentication ManagerBean we need:

package com.spring.security;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Authentication-related configuration
 */
@Primary
@Order(90)
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

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

In addition, the override configure(ClientDetailsServiceConfigurer clients) method is mainly configured:

  1. Define two client_ids, and the client can obtain different tokens through different client_ids;

  2. The token with client_id of myhk1 has a valid time of 3600 seconds and that with client_id of myhk2 has a valid time of 7200 seconds.

  3. The refresh_token with client_id myhk1 (described below) has a valid time of 864000 seconds, or 10 days, during which time new tokens can be exchanged through refresh_token;

  4. When acquiring a token with client_id myhk1, scope can only be specified as a value in "all", "read", or "write", otherwise acquisition will fail.

  5. Tokens with client_id test1 can only be obtained through password (authorization_code), while myhk2 has no restrictions.

Start the project and demonstrate several effects.Use password mode to get a token for myhk1 after starting the project:

The console output an alert for Encoded password does not look like BCrypt.

Encryption is required when specifying client_secret in the new version of spring-cloud-starter-oauth2:

.secret(new BCryptPasswordEncoder().encode("myhk111"))

In the previous section, Custom Logon Authentication Acquire Tokens, we determined whether the value of client_secret is correct in HkAuthentication SuccessHandler.Since client_secret is encrypted here, the judgment logic needs to be adjusted as follows:

        // 3. Verify the correctness of ClientId and ClientSecret
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "The corresponding information does not exist");
        } else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret Incorrect");
        } else {
            // 4. Generate TokenRequest through the TokenRequest constructor
            tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
        }

Restart the project after modification and reuse password mode to get the token:

{
    "access_token": "4051d591-4927-40e1-aae0-8d1b0d982618",
    "token_type": "bearer",
    "refresh_token": "76192780-121d-499d-a07f-630af95da58a",
    "expires_in": 3599,
    "scope": "all read write"
}

The time to see expires_in is 3600 seconds as defined by us.

As a highly available framework, we extract ClientId to the system configuration:

package com.spring.security.properties;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class OAuth2ClientProperties {

    private String clientId;

    private String clientSecret;

    //tpken token expiration time
    private int  accessTokenValiditySeconds;

    //Token refresh time
    private int refreshTokenValiditySeconds;
}
package com.spring.security.properties;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class OAuth2Properties {

    private OAuth2ClientProperties[] clients = {};
}
package com.spring.security.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * Security Attributes
 */
@Data
@ConfigurationProperties(prefix = "hk.security")
public class SecurityProperties {

    private BrowserProperties browser = new BrowserProperties();

    private ValidateCodeProperties code = new ValidateCodeProperties();

    private SocialProperties social = new SocialProperties();

    private OAuth2Properties oauth2 = new OAuth2Properties();

}

To configure:

hk:
  security:
    oauth2:
      clients[0]:
        clientId: myhk1
        clientSecret: myhk111
        accessTokenValiditySeconds: 3600
        refreshTokenValiditySeconds: 3600
      clients[1]:
        clientId: myhk2
        clientSecret: myhk222
        accessTokenValiditySeconds: 7200
        refreshTokenValiditySeconds: 7200

Modify the HkAuthorizationServerConfig class:

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //Save in memory
        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
        //Determine whether the system is configured
        if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
            //loop
            for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
                builder.withClient(config.getClientId())
                        .secret(new BCryptPasswordEncoder().encode(config.getClientSecret()))
                        //Token token expiration time seconds
                        .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds())
                        //Refresh token time
                        .refreshTokenValiditySeconds(config.getRefreshTokenValiditySeconds())
                        //Jurisdiction
                        .scopes("all", "read", "write")
                        //Authorization mode
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token");
            }

        }

    }

 

Token Storage

The default token is stored in memory, and we can save it to third-party storage, such as Redis.

Create TokenStoreConfig:

package com.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
public class TokenStoreConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}

Then specify the token storage policy in the authentication server.Override the configure(AuthorizationServerEndpointsConfigurer endpoints) method:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore redisTokenStore;

     @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailService);
    }

    ......
}

After restarting the project to acquire a token, check to see if information about the token is stored in Redis:

Tags: Spring Lombok Redis

Posted on Fri, 20 Mar 2020 23:52:18 -0400 by Ang3l0fDeath