[SpringCloud learning notes] authentication and authorization - based on OAuth2

Posted by MaxD on Wed, 17 Nov 2021 15:14:07 +0100

Authentication and authorization - based on OAuth2

1, Establishment of OAuth2 authentication service - based on memory

1. Create project import dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

The above dependencies are based on Cloud-Hoxton.SR12, Boot-2.3.12.RELEASE. Dependencies are very important. Please refer to the official documents when building.

2. Configure authentication server

/**
 * OAuth2 Authentication policies client access related policies
 */
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    /**
     * First, get the authentication manager from the container. What is it to be queried
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * Intuitively, the service for obtaining user details from the container is the class for obtaining user information
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * Rewrite the configure method. Note that the authentication and authorization policies for client access are configured here
     * So the parameter is ClientDetailsServiceConfigurer
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
        // Reading information from memory is of course more rigorous. You should get the configuration information from the database or configuration file
        clients.inMemory()
                // The id of the client is equivalent to the registration id of the client
                .withClient("eate")
                // password
                .secret("zzc")
                // Authorization type token password client certificate
                .authorizedGrantTypes(
                        "refresh_token",
                        "password",
                        "client_credentials")
                // Authority function or scope of authorization
                .scopes("webclient","mobileclient");
    }

    /**
     * Define the container components to be used for authentication authorization, that is, the components automatically injected from above
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
        // Set authentication authorization component
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
}

Create the configuration class OAuth2Config to inherit the AuthorizationServerConfigurerAdapter class.

3. Configure user authentication method and information

/**
 * Configure the policy of OAuth2 to configure the policy of accessing the authentication server
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * The old rule is that the encryptor will report an error if it is not configured
     *
     * @return
     */
    @Bean
    public PasswordEncoder encoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * The default authentication manager of Security is selected here
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManager();
    }

    /**
     * Select the default user details service
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception{
        return super.userDetailsServiceBean();
    }

    /**
     * Security The three configuration methods overload the configuration method to configure user related information
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.inMemoryAuthentication()
                .withUser("john")
                .password("password")
                .roles("admin")
                .and()
                .withUser("mix")
                .password("123")
                .roles("user")
                .and()
                .passwordEncoder(encoder());
    }
}

4. Add annotations and callback endpoints

@RestController
@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
public class AuthticationServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthticationServerApplication.class, args);
    }

    @GetMapping(value = "/user", produces = {"application/json"})
    public Map<String,Object> user(OAuth2Authentication user){
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("user",user.getUserAuthentication().getPrincipal());
        userInfo.put("authroizaties", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
        return userInfo;
    }
}

5. Access test

To access this path: http://localhost:8080/oauth/token

Get the following Token:

{
    "access_token": "5e03b5c5-8c2a-4d4a-95fe-be358c4303a0",
    "token_type": "bearer",
    "refresh_token": "c48d4964-ee22-46d2-b010-9fae65ea7d45",
    "expires_in": 43030,
    "scope": "webclient"
}

When accessing, pay attention to adding client authentication and user authentication information.

6. Summary on the establishment of authentication and authorization services

i. Customize two sets of authentication schemes for the client and users using the client:

The client authentication policy configuration class needs to implement the AuthorizationServerConfigurerAdapter, which is in oauth2 dependency.

The class structure is as follows:

public class AuthorizationServerConfigurerAdapter implements 	                     AuthorizationServerConfigurer {
    public AuthorizationServerConfigurerAdapter() {
    }

    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    }

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    }

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    }
}

At present, only the second and third configuration methods are used to configure the client access policy.

The authentication policy configuration class of the user using the client needs to implement WebSecurityConfigurerAdapter, which is the same as ordinary security in the security dependency.

ii. Add relevant notes:

@EnableAuthorizationServer

This annotation declares that this class is an authentication authorization server.

@EnableResourceServer

This annotation declares that this class is also a resource provider. It enforces a filter to check whether the incoming request has an OAuth2 access token. Then use the callback to check whether the token is valid (explained in detail when configuring the resource server).

2, Establishment of OAuth2 resource service - based on memory

1. Create project import dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2. Configure access policy

/**
 * Implement resource server access policy configuration
 */
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    /**
     * The access policy is simple, authenticating all requests
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests().anyRequest().authenticated();
    }
}

3. Add annotation

@SpringBootApplication
@EnableResourceServer
public class ResourceServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServiceApplication.class, args);
    }

}

4. Write configuration

server:
  port: 8881

security:
  oauth2:
    resource:
      user-info-uri: http://localhost:8080/user

Configure the callback url, which points to the specified endpoint of the authentication and authorization service.

5. Summary of resource services

i. Configure resource service access policy:

The resource service access policy configuration class needs to implement the ResourceServerConfigurerAdapter class, which is in oauth2 dependency.

The class structure is as follows:

public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {
    public ResourceServerConfigurerAdapter() {
    }

    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    }

    public void configure(HttpSecurity http) throws Exception {
        ((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
    }
}

At present, only the second configuration class is used to restrict the interception of http requests.

3, Using spring cloud oauth2 + JWT to implement authentication - authentication server

OAuth2 is a token based authentication framework, but ironically, it does not provide any standard for how to define tokens in its specification. In order to correct the defects of OAuth2 token standard, a new standard named JSON Web Token stands out.

JWT features:

1. Compact – JWT tokens are Base64 encoded and can be easily passed through URL, HTTP header or POST parameters.

2. Password signature – the JWT token is issued in front of his authentication server to ensure that it has not been tampered with.

3. Self contained – because there is a signature, there is no need to confirm the token content through the authentication server, and the content can be checked by the service.

4. Extensible – when the authentication server generates a token, additional information can be placed before it is sealed, and the receiving server can decrypt and obtain the payload.

1. Add dependency

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.4.RELEASE</version>
</dependency>

2. Add configuration (the steps are quite complicated =.)

I. JWTTokenStoreConfig configuration class:

/**
 * Configure security and use jwt as authentication means
 */
@Configuration
public class JWTTokenStoreConfig {
    /**
     * A common configuration class is used to obtain the key from the configuration file
     */
    @Autowired
    private ServiceConfig serviceConfig;

    /**
     * Define the token storage type as JwtTokenStore
     * This class is responsible for the encapsulation of tokens in CloudSecurity
     * It is used to save the token (encapsulated in OAuth2AccessToken)
     * Details Baidu
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * Set default TokenServices
     * Inject the above JwtTokenStore
     * This class is responsible for generating, obtaining and refreshing tokens according to the type of TokenStore
     *
     * @return
     */
    @Bean
    @Primary
    public DefaultTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices
                = new DefaultTokenServices();
        // Set the type of Token to be generated
        defaultTokenServices.setTokenStore(tokenStore());
        // Set allow refresh Token
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    /**
     * Create JWT token converter
     *
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());
        return converter;
    }

    /**
     * This class is responsible for adding additional data to the JWT
     *
     * @return
     */
    @Bean
    public TokenEnhancer jwtTokenEnhancer(){
        return new JWTTokenEnhancer();
    }
}

This class is a configuration class responsible for the creation, signature and translation of JWT tokens.

Important classes:

// JWT token converter
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(serviceConfig.getJwtSigningKey());
// Class used to set token type
TokenStore = new JwtTokenStore(jwtAccessTokenConverter());
// The default class used to generate token in oauth2
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());

After the above steps, you can convert the OAuth2 default token into a JWT token.

II. Create a class to read the configuration:

@Component
@Configuration
public class ServiceConfig {
    @Value("${{signing.key}")
    private String jwtSigningKey = "";

    public String getJwtSigningKey() {
        return jwtSigningKey;
    }
}

III. create token enhancer implementation class:

public class JWTTokenEnhancer implements TokenEnhancer {

    private String getOrgId(String userName){
        return "ZZC";
    }

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        String orgId =  getOrgId(authentication.getName());

        additionalInfo.put("organizationId", orgId);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

The source code of TokenEnhancer interface is as follows:

package org.springframework.security.oauth2.provider.token;

import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;

public interface TokenEnhancer {
    OAuth2AccessToken enhance(OAuth2AccessToken var1, OAuth2Authentication var2);
}

You can rewrite the enhance method by implementing the interface to add information to the Token. The Authentication object is stored in the second parameter oauthauthentication of the method, which can be used to obtain user information. The first parameter is the Token of OAuth2.

DefaultOAuth2AccessToken implements the interface OAuth2AccessToken and provides a method to add additional information to the token.

Ⅳ. OAuth2Config configuration class:

/**
 * OAuth2 Authentication policies client access related policies
 */
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DefaultTokenServices defaultTokenServices;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    
    /**
     * First, get the authentication manager from the container. What is it to be queried
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * Intuitively, the service for obtaining user details from the container is the class for obtaining user information
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * Rewrite the configure method. Note that the authentication and authorization policies for client access are configured here
     * So the parameter is ClientDetailsServiceConfigurer
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
        // Reading information from memory is of course more rigorous. You should get the configuration information from the database or configuration file
        clients.inMemory()
                // The id of the client is equivalent to the registration id of the client
                .withClient("eate")
                // password
                .secret("zzc")
                // Authorization type token password client certificate
                .authorizedGrantTypes(
                        "refresh_token",
                        "password",
                        "client_credentials")
                // Authority function or scope of authorization
                .scopes("webclient","mobileclient");
    }

    /**
     * Define the container components to be used for authentication authorization, that is, the components automatically injected from above
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
		
        // Create an enhancer chain and add two enhancers to the chain
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));

        // Set authentication authorization component
        endpoints
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancerChain)
                .accessTokenConverter(jwtAccessTokenConverter)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
}

Most of the configuration contents are the same as before. Let's take a look at the differences:

① , * * an enhancer chain is declared in the configure(AuthorizationServerEndpointsConfigurer endpoints) * * method

TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer,jwtAccessTokenConverter));

Why declare it in this configuration method? Because this configuration method is responsible for setting the authentication endpoint, the AuthorizationServerEndpointsConfigurer parameter is provided to customize the authentication scheme.

② JwtAccessTokenConverter can be directly added to the enhancer chain

public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean

This is a class declaration. In essence, JwtAccessTokenConverter is also a TokenEnhancer.

③ Inject all the previously declared configurations and tell the authorization server endpoints configurator to issue the token of the custom rule.

3. Write yaml configuration key

signing:
	key: zzc123456

Other parts do not need to be changed.

4, Using spring cloud oauth2 + JWT to implement authentication - resource side

Well, it doesn't seem to need any changes for the time being.

Topics: Java Spring Boot