This may be the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network. Mom no longer has to worry about me being beaten by the interviewer!

OAuth 2 has four authorization modes, namely authorization code mode (authorization code), simplified mode (implicit), password mode (resource owner password credentials), and client mode (client credentials). For details about OAuth2, please refer to this article article. (http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

Probably the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network

 

In this article, we will use authorization code mode and password mode to achieve user authentication and authorization management.

OAuth2 is actually a network standard about authorization. It sets out design ideas and operating procedures. Using this standard, we can actually implement the OAuth2 authentication process by ourselves. The spring-cloud-starter-oauth2 to be introduced today is actually a specific implementation of Spring Cloud encapsulated in accordance with the OAuth2 standard and combined with spring-security.

Under what circumstances need to use OAuth2

First of all, what everyone is most familiar with is what almost everyone has used, such as logging in with WeChat, logging in with QQ, logging in with Weibo, logging in with Google account, logging in with github authorization, etc. These are typical OAuth2 usage scenarios. Suppose we have made our own service platform. If we do not use OAuth2 login method, then we need the user to complete the registration first, and then log in with the account password of the registration number or with the mobile phone verification code. After using OAuth2, I believe that many people have used or even developed official account web services and mini programs. When we enter the web page and mini program interface, we don’t need to register for the first use, and we can directly use WeChat authorization to log in, which greatly improves The use efficiency. Because everyone has a WeChat account, you can immediately use third-party services with WeChat. This experience should not be too good. For our service, we don't need to store the user's password, just store the unique ID and user information returned by the authentication platform.

The above is to use the authorization code mode of OAuth2, and use a third-party authoritative platform to achieve user identity authentication. Of course, if your company has a lot of services, you can extract a certification center specifically, and this certification center will act as the authoritative certification platform mentioned above, and all services must go to this certification center for certification.

In this way, I did not find out, this is actually a single sign-on function. This is another usage scenario. For a multi-service platform, you can use OAuth2 to implement single sign-on for services. With only one login, you can freely travel through multiple services, of course, only limited to services and interfaces within the scope of authorization.

Realize unified authentication function

This article first introduces the single sign-on implemented by the password mode, and the next article will continue to talk about the authorization code mode.

Today, when microservices are rampant, who would dare to say that they don't have a few microservices. Microservices reduce the coupling between services, while also increasing the complexity of the system in certain aspects, such as user authentication. Assuming that we have implemented an e-commerce platform here, what users see is an APP or a web site, which is actually composed of multiple independent services, such as user service, order service, product service, etc. As long as the user enters the user name and password for the first time and completes the login, for a period of time, they can access various pages at will, such as the product list page, my order page, and my attention pages.

We can imagine that we can naturally think of what credentials must be carried when requesting each service and each interface, and then each service will know which user is requesting the interface, otherwise there must be a problem. In fact, the credentials inside are simple It is a Token, the Token that identifies the user's identity.

System architecture description

Authentication center : oauth2-auth-server, the main implementation end of OAuth2. Token generation, refresh, and verification are all done in the authentication center.

Order service : oauth2-client-order-server, one of the microservices, will go to the certification center for verification after receiving the request.

User service : oauth2-client-user-server, the second microservice, after receiving the request, it will go to the certification center for verification.

Client terminal: such as APP terminal, web terminal, etc.

Probably the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network

 

The figure above describes the request process between the client and the microservice using OAuth2. The general process is that the client exchanges the user name and password to the authentication server for a token, and returns it to the client. The client takes the token to each microservice to request the data interface. Generally, this token is placed in the header. When the microservice receives the request, it first takes the token to authenticate the server to check the legitimacy of the token. If it is legal, it will dynamically return data based on the user's role and permissions.

Create and configure authentication server

The most configured is the authentication server. Verifying the account and password, storing the token, checking the token, refreshing the token, etc. are all the work of the authentication server.

1. Import the required maven package

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

spring-cloud-starter-oauth2 includes spring-cloud-starter-security, so there is no need to introduce it separately. The redis package is introduced because the following will introduce a way to store tokens with redis.

2. Configure application.yml

Set the basic configuration of the project and add the configuration about redis, which will be used later.

spring:
  application:
    name: auth-server
  redis:
    database: 2
    host: localhost
    port: 32768
    password: 1qaz@WSX
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
    timeout: 100ms

server:
  port: 6001

management:
  endpoint:
    health:
      enabled: true

3. Basic configuration of spring security

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    /**
     * 允许匿名访问所有接口 主要是 oauth 接口
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll();
    }
}

Use @EnableWebSecurity annotation decoration, and inherit from WebSecurityConfigurerAdapter class.

The focus of this class is to declare two Beans, PasswordEncoder and AuthenticationManager. Will be used later. Among them, BCryptPasswordEncoder is a password encryption tool class, which can realize irreversible encryption. AuthenticationManager is an authorization management Bean that must be specified in order to realize the password mode of OAuth2.

4. Implement UserDetailsService

If you have used Security before, you must be familiar with this class. It is a way to implement user authentication, and it is also the simplest and most convenient one. In addition, there is a way to combine AuthenticationProvider. Let's expand when we have the opportunity to talk about Security.

The core of UserDetailsService is the loadUserByUsername method, which receives a string parameter, which is the user name passed in, and returns a UserDetails object.

@Slf4j
@Component(value = "kiteUserDetailsService")
public class KiteUserDetailsService implements UserDetailsService {


    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("usernameis:" + username);
        // 查询数据库操作
        if(!username.equals("admin")){
            throw new UsernameNotFoundException("the user is not found");
        }else{
            // 用户角色也应在数据库中获取
            String role = "ROLE_ADMIN";
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority(role));
            // 线上环境应该通过用户名查询数据库获取加密后的密码
            String password = passwordEncoder.encode("123456");
            return new org.springframework.security.core.userdetails.User(username,password, authorities);
        }
    }
}

In order to demonstrate here, the user name, password, and role are all written in the code. In a formal environment, here should be the encrypted password and role to be found from the database or other places based on the user name. The account is admin and the password is 123456, which will be used later when exchanging tokens. And set the "ROLE_ADMIN" role for this user.

5. OAuth2 configuration file

Create a configuration file inherited from AuthorizationServerConfigurerAdapter.

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public UserDetailsService kiteUserDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore redisTokenStore;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /**
         * redis token 方式
         */
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(kiteUserDetailsService)
                .tokenStore(redisTokenStore);

    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("order-client")
                .secret(passwordEncoder.encode("order-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .and()
                .withClient("user-client")
                .secret(passwordEncoder.encode("user-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated()");
        security.tokenKeyAccess("isAuthenticated()");
    }
}

There are three rewrites of the configure method.

Rewriting of AuthorizationServerEndpointsConfigurer parameters

endpoints.authenticationManager(authenticationManager)
                .userDetailsService(kiteUserDetailsService)
                .tokenStore(redisTokenStore);
复制代码

AuthenticationManage() calls this method to support password mode.

userDetailsService() Set up user authentication service.

tokenStore() specifies the storage method of the token.

The definition of redisTokenStore Bean is as follows:

@Configuration
public class RedisTokenStoreConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

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

ClientDetailsServiceConfigurer parameter rewriting, here defines the constraints of each end. include

ClientId, Client-Secret: These two parameters correspond to the cleint-id and client-secret defined by the requester

authorizedGrantTypes can include one or more of the following settings:

  • authorization_code: authorization code type.
  • implicit: implicit authorization type.
  • password: The password type of the resource owner (ie, user).
  • client_credentials: The type of client credentials (client ID and Key).
  • refresh_token: Obtain a new token through the refresh token obtained through the above authorization.

accessTokenValiditySeconds: the validity period of the token

Scopes: Used to limit the client's access rights. The scope parameter will be included when exchanging tokens. Only those in the scope definition can be exchanged for tokens normally.

The above code is stored in inMemory mode, and the configuration is saved in the memory, which is equivalent to hard coding. The practice in the formal environment is to persist to the database, such as mysql.

The specific approach is as follows:

  1. Add a table to the database and insert data
create table oauth_client_details (
    client_id VARCHAR(256) PRIMARY KEY,
    resource_ids VARCHAR(256),
    client_secret VARCHAR(256),
    scope VARCHAR(256),
    authorized_grant_types VARCHAR(256),
    web_server_redirect_uri VARCHAR(256),
    authorities VARCHAR(256),
    access_token_validity INTEGER,
    refresh_token_validity INTEGER,
    additional_information VARCHAR(4096),
    autoapprove VARCHAR(256)
);
INSERT INTO oauth_client_details
    (client_id, client_secret, scope, authorized_grant_types,
    web_server_redirect_uri, authorities, access_token_validity,
    refresh_token_validity, additional_information, autoapprove)
VALUES
    ('user-client', '$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq', 'all',
    'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

INSERT INTO oauth_client_details
    (client_id, client_secret, scope, authorized_grant_types,
    web_server_redirect_uri, authorities, access_token_validity,
    refresh_token_validity, additional_information, autoapprove)
VALUES
    ('order-client', '$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW', 'all',
    'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

Note: The client_secret field cannot be directly the original value of secret, it needs to be encrypted. Because BCryptPasswordEncoder is used, the final inserted value should be the value after BCryptPasswordEncoder.encode().

  1. Then add the configuration about the database in the configuration file application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_cloud?characterEncoding=UTF-8&useSSL=false
    username: root
    password: password
    hikari:
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      maximum-pool-size: 9   

After Spring Boot 2.0, hikari is used as the database connection pool by default. If you use other connection pools, you need to import related packages, and then increase the configuration accordingly.

  1. Add DataSource injection in OAuth2 configuration class (OAuth2Config)
@Autowired
private DataSource dataSource;
  1. Modify the rewriting method of public void configure(ClientDetailsServiceConfigurer clients) as follows:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
	JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
	jcsb.passwordEncoder(passwordEncoder);
}

There is also an overridden method public void configure(AuthorizationServerSecurityConfigurer security), which restricts the client's access to the authentication interface.

security.allowFormAuthenticationForClients();
security.checkTokenAccess("isAuthenticated()");
security.tokenKeyAccess("isAuthenticated()");

The first line of code is to allow the client to access the OAuth2 authorization interface, otherwise 401 will be returned when the token is requested.

The second and third lines are to allow authorized users to access the checkToken interface and obtain the token interface, respectively.

After completion, start the project. If you are using IDEA, you will see the RESTful interface related to oauth2 in the Mapping window below.

Probably the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network

 

The main ones are as follows:

POST /oauth/authorize  授权码模式认证授权接口
GET/POST /oauth/token  获取 token 的接口
POST  /oauth/check_token  检查 token 合法性接口

Create user client project

The authentication server is created above, and a client is created below, which corresponds to the business-related microservices in our system. We assume that this microservice project manages user-related data, so it is called a user client.

1. Reference related maven packages

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. Application.yml configuration file

spring:
  application:
    name: client-user
  redis:
    database: 2
    host: localhost
    port: 32768
    password: 1qaz@WSX
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
    timeout: 100ms
server:
  port: 6101
  servlet:
    context-path: /client-user

security:
  oauth2:
    client:
      client-id: user-client
      client-secret: user-secret-8888
      user-authorization-uri: http://localhost:6001/oauth/authorize
      access-token-uri: http://localhost:6001/oauth/token
    resource:
      id: user-client
      user-info-uri: user-info
    authorization:
      check-token-access: http://localhost:6001/oauth/check_token

The above is the general configuration information and redis configuration. The focus is on the security configuration below. If you do not pay attention to the configuration here, 401 or other problems will occur.

The client-id and client-secret must be consistent with the configuration in the authentication service, if inMemory or jdbc is used.

User-authorization-uri is required for authorization code authentication, which will be discussed in the next article.

The access-token-uri is the interface to obtain the token required by the password mode.

authorization.check-token-access is also key information. When the server receives the request from the client, it needs to take the token in the request to the authentication server for token verification, which is the requested interface

3. Resource configuration file

In the concept of OAuth2, all interfaces are called resources, and the permissions of the interfaces are the permissions of the resources. Therefore, Spring Security OAuth2 provides the annotation @EnableResourceServer for resources, which is similar to @EnableWebSecurity.

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Value("${security.oauth2.client.client-id}")
    private String clientId;

    @Value("${security.oauth2.client.client-secret}")
    private String secret;

    @Value("${security.oauth2.authorization.check-token-access}")
    private String checkTokenEndpointUrl;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

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

    @Bean
    public RemoteTokenServices tokenService() {
        RemoteTokenServices tokenService = new RemoteTokenServices();
        tokenService.setClientId(clientId);
        tokenService.setClientSecret(secret);
        tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
        return tokenService;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(tokenService());
    }
}

Because redis is used as the storage of the token, a special configuration of a bean called tokenService is required, through which token verification can be achieved.

4. Finally, add a RESTful interface

@Slf4j
@RestController
public class UserController {

    @GetMapping(value = "get")
    //@PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public Object get(Authentication authentication){
        //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        authentication.getCredentials();
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
        String token = details.getTokenValue();
        return token;
    }
}

A RESTful method, which can only be accessed when the accessing user has ROLE_ADMIN permission, otherwise it returns 401 Unauthorized.

You can get the authorization information for viewing through the Authentication parameter or SecurityContextHolder.getContext().getAuthentication().

Test certification function

1. Start the authentication server and start the port as 6001

2. Start the user service client and start port 6101

3. Request the authentication server to obtain the token

I use REST Client to do access request, the request format is as follows:

POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=all
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Assuming that we are using it on a web side, grant_type is password, indicating that this is a password mode using OAuth2.

username=admin and password=123456 are equivalent to the username and password entered in the web login interface. In the authentication server configuration, the username is fixed as admin and the password is 123456. In the online environment, it should be obtained by querying the database. .

Scope=all is related to permissions. The scope is all specified in OAuthConfig of the authentication service.

Authorization should be added to the request header, the format is Basic space base64(clientId:clientSecret), the client-id of this microservice client is user-client, and client-secret is user-secret-8888. Connect these two values ​​through a colon , And use base64 encoding (user-client:user-secret-8888) to obtain the value of dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==, which can be obtained through https://www.sojson.com/base64.html online encoding.

Probably the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network

 

After running the request, if the parameters are correct, the returned content obtained is as follows, which is a json format

{
  "access_token": "9f958300-5005-46ea-9061-323c9e6c7a4d",
  "token_type": "bearer",
  "refresh_token": "0f5871f5-98f1-405e-848e-80f641bab72e",
  "expires_in": 3599,
  "scope": "all"
}

access_token: is the token that needs to be brought with the request later, and is also the main purpose of this request. token_type: bearer, which is the most commonly used form of access token refresh_token: this value can be used later to exchange for a new token without entering an account Password expires_in: token expiration time (seconds)

4. Use the obtained token to request resource interface

We have defined an interface http://localhost:6101/client-user/get in the user client, and now we take the token obtained in the previous step to request this interface.

GET http://localhost:6101/client-user/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer ce334918-e666-455a-8ecd-8bd680415d84

The request header Authorization is also required, and the format is bearer + space + token. Normally, according to the logic of the interface, the token will be returned as it is.

5. After the token expires, exchange refresh_token for access_token

Generally, the expiration time of access_token is set to be less than the expiration time of refresh_token, so that after the access_token expires, the user can obtain a new access_token without logging in again.

### 换取 access_token
POST http://localhost:6001/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

grant_type is set to refresh_token.

refresh_token is set to the value of refresh_token returned when the token is requested.

Authorization is added to the request header, and the format is still Basic + space + base64 (client-id: client-secret)

After the request is successful, the same data format as the request token will be returned.

Replace redisToken with JWT

The above token storage uses the redis solution, Spring Security OAuth2 also provides support for jdbc and jwt, jdbc is not considered for the time being, now we will introduce the JWT way to implement token storage.

With JWT, there is no need to store the token on the server. JWT has its own special encryption method, which can effectively prevent data from being tampered with. As long as you don't put key information such as user passwords in JWT, you can ensure security.

Authentication server transformation

First remove the redis configuration.

Add JwtConfig configuration class

@Configuration
public class JwtTokenConfig {

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("dev");
        return accessTokenConverter;
    }
}

JwtAccessTokenConverter is for JWT data conversion. This is because JWT has its own unique data format. If you haven't learned about JWT, you can search for it first.

Change OAuthConfig configuration class

@Autowired
private TokenStore jwtTokenStore;

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /**
         * 普通 jwt 模式
         */
         endpoints.tokenStore(jwtTokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .userDetailsService(kiteUserDetailsService)
                /**
                 * 支持 password 模式
                 */
                .authenticationManager(authenticationManager);
}

Inject JWT-related Beans, and then modify the configure(final AuthorizationServerEndpointsConfigurer endpoints) method to JWT storage mode.

Transform user client

Modify the application.yml configuration file

security:
  oauth2:
    client:
      client-id: user-client
      client-secret: user-secret-8888
      user-authorization-uri: http://localhost:6001/oauth/authorize
      access-token-uri: http://localhost:6001/oauth/token
    resource:
      jwt:
        key-uri: http://localhost:6001/oauth/token_key
        key-value: dev

Note that the SigningKey set by the authentication server JwtAccessTokenConverter must be the same as the key-value in the configuration file, otherwise the JWT cannot be decoded normally and the verification fails.

Configuration of ResourceServerConfig class

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

        accessTokenConverter.setSigningKey("dev");
        accessTokenConverter.setVerifierKey("dev");
        return accessTokenConverter;
    }

    @Autowired
    private TokenStore jwtTokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(jwtTokenStore);
    }
}

Run the request to request the token interface

POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=all
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

The returned results are as follows:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJleHAiOjE1NzE3NzU4OTQsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjdkMjg4NDUtMmU2ZC00ZmRjLTg1OGYtMWNiY2RlNzI1ZmMyIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQifQ.vk_msYtbrAr93h5sK4wy6EC2_wRD_cD_UBS8O6eRziw",
  "expires_in": 3599,
  "scope": "all",
  "jti": "8cca29af-ea77-4fe6-9fe1-327415dcd21d"
}

We have seen the return of the token is the JWT format, to decode the JWT online website  jwt.io/  or  jwt.calebb.net/ the token decode look

Probably the most detailed Spring Cloud OAuth2 single sign-on tutorial on the entire network

 

Have you seen it? User_name, client_id and other information are all in it.

Hold the returned token and request the user client interface

GET http://localhost:6101/client-user/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM

Enhanced JWT

What if I want to add additional fields (such as other user information) to the JWT, of course. spring security oauth2 provides a TokenEnhancer enhancer. In fact, not only JWT, but RedisToken can also be used.

Declare an enhancer

public class JWTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("jwt-ext", "JWT 扩展信息");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}

Through oAuth2Authentication, you can get user name and other information, through which we can query the database or cache here to get more information, and these information can be added as JWT extended information.

OAuthConfig configuration class modification

Injection enhancer

@Autowired
private TokenEnhancer jwtTokenEnhancer;

@Bean
public TokenEnhancer jwtTokenEnhancer(){
    return new JWTokenEnhancer();
}

修改 configure(final AuthorizationServerEndpointsConfigurer endpoints)方法

@Override
public void configure( final AuthorizationServerEndpointsConfigurer endpoints ) throws Exception{
	/**
	 * jwt 增强模式
	 */
	TokenEnhancerChain	enhancerChain	= new TokenEnhancerChain();
	List<TokenEnhancer>	enhancerList	= new ArrayList<>();
	enhancerList.add( jwtTokenEnhancer );
	enhancerList.add( jwtAccessTokenConverter );
	enhancerChain.setTokenEnhancers( enhancerList );
	endpoints.tokenStore( jwtTokenStore )
	.userDetailsService( kiteUserDetailsService )
	/**
	 * 支持 password 模式
	 */
	.authenticationManager( authenticationManager )
	.tokenEnhancer( enhancerChain )
	.accessTokenConverter( jwtAccessTokenConverter );
}

Request the token again, and there is an additional jwt-ext field in the returned content

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImE0NTUxZDllLWI3ZWQtNDc1OS1iMmYxLWYwYjliMjFjYTQyYyIsImV4cCI6MTU3MTc3NzU3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJmNTI3ODJlOS0wOGRjLTQ2NGUtYmJhYy03OTMwNzYwYmZiZjciLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.UQMf140CG8U0eWh08nGlctpIye9iJ7p2i6NYHkGAwhY",
  "expires_in": 3599,
  "scope": "all",
  "jwt-ext": "JWT 扩展信息",
  "jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c"
}

The user client parses the JWT data

If we add additional information to the JWT, we may use this information. After receiving the token in the JWT format, the user client must parse the JWT.

Import JWT package

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

Add a RESTful interface to parse JWT

@GetMapping(value = "jwt")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public Object jwtParser(Authentication authentication){
    authentication.getCredentials();
    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
    String jwtToken = details.getTokenValue();
    Claims claims = Jwts.parser()
                .setSigningKey("dev".getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(jwtToken)
                .getBody();
    return claims;
}

Also note that the signature settings should be the same as the authentication server.

Use the token from the previous step to request the above interface

### 解析 jwt
GET http://localhost:6101/client-user/jwt
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ

The returned content is as follows:

{
  "user_name": "admin",
  "jwt-ext": "JWT 扩展信息",
  "scope": [
    "all"
  ],
  "exp": 1571745178,
  "authorities": [
    "ROLE_ADMIN"
  ],
  "jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c",
  "client_id": "user-client"
}

Guess you like

Origin blog.csdn.net/weixin_48612224/article/details/109231485