Article from Itboyhub.com/

Authorization code mode

Create a New Maven parent project and then create three SpringBoot subprojects

Auth-server Indicates the authorization server

Rely on

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.11. RELEASE</version>
    <relativePath/> <! -- lookup parent from repository -->
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR11</spring-cloud.version>
</properties>
<dependencies>
    <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.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
Copy the code

Set the port number to 8080

Create a New SecurityConfig configuration class to configure our user information

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("southyin")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().formLogin(); }}Copy the code

Create the AccessTokenConfig class and configure the location for storing tokens. In this case, memory is used first

@Configuration
public class AccessTokenConfig {

    /** * Where is the configuration token stored *@returnInMemoryTokenStore means saved in memory */
    @Bean
    TokenStore tokenStore(a) {
        return newInMemoryTokenStore(); }}Copy the code

Create the AuthorizationServer class and configure information such as the client, token, and authorization code

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer  extends AuthorizationServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Autowired
    ClientDetailsService detailsService;

    / * * * AuthorizationServerSecurityConfigurer used to configure the token endpoint security constraints, namely the endpoint who can visit * q, who can not access. * checkTokenAccess is a Token endpoint that we set to be accessible * (later, when the resource server receives the Token and needs to verify the Token, it will access this endpoint) *@param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    / * * * ClientDetailsServiceConfigurer used to configure the client details * authorization server to do two aspects of the inspection, on the one hand is to check the client, on the other hand is to check the user, check the user, before we * have configured, here is the configuration check the client. The client information can be stored in the database, which is also relatively easy, similar to the user information stored in the database, but in order to simplify the code, I will save the client information in memory * * here we configure the client ID respectively, Secret, resource ID, authorization type, authorization scope, and redirection URI. In the previous article, THERE were four types, none of which included refresh_token, but in real * operations, refresh_token is also counted as an *@param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("southyin")
                .secret(new BCryptPasswordEncoder().encode("123"))
                .resourceIds("res1")
                .authorizedGrantTypes("authorization_code"."refresh_token")
                .scopes("all")
                .redirectUris("http://localhost:8082/index.html");
    }

    / * * * AuthorizationServerEndpointsConfigurer here used to configure the access token endpoint and token service. * * authorizationCodeServices used to configure the authorization code of the store, here we are in memory * * tokenServices used to configure the token of the storage, namely access_token storage location, What's the difference between an authorization code and a token? Authorization codes are used to get tokens, which expire once used, and tokens are used to get resources *@param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authorizationCodeServices(authorizationCodeServices())
                .tokenServices(tokenServices());
    }

    / * * * authorizationCodeServices used to configure the authorization code of the store, here we are * in the memory@return* /
    @Bean
    AuthorizationCodeServices authorizationCodeServices(a) {
        return new InMemoryAuthorizationCodeServices();
    }

    /** * tokenServices this Bean is used to configure some basic information about the Token, such as whether the Token supports refreshing, the storage location of the Token, the expiration date of the Token, and the expiration date of the refreshed Token. When a Token is about to expire, we need to obtain a new Token. When we obtain a new Token, we need to obtain a certificate information. This certificate information is not the old Token. Instead, there is another * refresh_token, which also has an expiration date *@return* /
    @Bean
    AuthorizationServerTokenServices tokenServices(a) {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(detailsService);
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore);
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        returnservices; }}Copy the code

Start the

User-server Indicates the resource server

Rely on

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.11. RELEASE</version>
    <relativePath/> <! -- lookup parent from repository -->
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR11</spring-cloud.version>
</properties>
<dependencies>
    <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.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
Copy the code

Start port 8081

Create the ResourceServerConfig class to configure the resource server information

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    /** * tokenServices we configure an instance of RemoteTokenServices, this is because the resource server and authorization server are separate, the resource server and authorization server are placed together, RemoteTokenServices = access_token = client_id; client_secret = client_id; When a user requests resources from the resource server, he/she carries an access_token with him/her. With this configuration, he/she can verify whether the token is correct. *@return* /
    @Bean
    RemoteTokenServices tokenServices(a) {
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        services.setClientId("southyin");
        services.setClientSecret("123");
        return services;
    }

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated(); }}Copy the code

Creating a Resource Interface

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello1(a) {
        return "/hello";
    }

    @GetMapping("/admin/hello")
    public String hello2(a) {
        return "/admin/hello"; }}Copy the code

Start the

Client-app Indicates a third-party application

This item is not required and can also be tested using Postman

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
Copy the code

Start port 8082 and create index.html in the Templates folder to simulate third-party web pages

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http//www/thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="http://localhost:8080/oauth/authorize? client_id=southyin&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html">Third-party Login</a>
    <h1 th:text="${msg}"></h1>
</body>
</html>
Copy the code

Start the class

@SpringBootApplication
public class ClientAppApplication {

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

    @Bean
    RestTemplate restTemplate(a) {
        return newRestTemplate(); }}Copy the code

Access page interface

@Controller
public class HelloController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/index.html")
    public String hello(String code, Model model) {
        if(code ! =null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("client_id"."southyin");
            map.add("client_secret"."123");
            map.add("redirect_uri"."http://localhost:8082/index.html");
            map.add("grant_type"."authorization_code");
            Map<String, String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
            String access_token = resp.get("access_token");
            System.out.println(access_token);
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization"."Bearer " + access_token);
            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
            ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
            model.addAttribute("msg", entity.getBody());
        }
        return "index"; }}Copy the code

If there is no code in the parameter, the index page is directly returned. If there is code (code is the authorization code, not the token), the user applies for the token from the authorization server, and then applies for resources from the resource server with the returned Access_token. Then put it into Model, return to index page, index page gets the resource information, display it on the page, start the test

  • 1. Accesshttp://localhost:8082/index.html

  • 2. Click the hyperlink, login user to apply for authorization, user name and password are in the previousAuthorization serverConfigured insouthyin123

Password mode

Password mode requires a high degree of trust in third parties, such as the authorizer and the third party are their own applications, which is ok

Auth-server Indicates the authorization server

Modify the authorization server to support the password mode

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("southyin")
            .secret(new BCryptPasswordEncoder().encode("123"))
            .resourceIds("res1")
            .authorizedGrantTypes("authorization_code"."password"."refresh_token")
            .scopes("all")
            .redirectUris("http://localhost:8082/index.html");
}
Copy the code

Everything else remains the same here, except the addition of password mode to authorizedGrantTypes

Since the user logs in after using the Password mode, we need to configure an AuthenticationManager, again in the AuthorizationServer class, as follows

@Autowired
AuthenticationManager authenticationManager;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws
Exception {
    endpoints
           .authenticationManager(authenticationManager)
           .tokenServices(tokenServices());
}
Copy the code

Note that the authorization code mode, we configure AuthorizationCodeServices don’t need it, now replaced by the authenticationManager

Add the AuthenticationManager object in SecurityConfig

Client-app Indicates a third-party application

Modified index. HTML

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http//www/thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post"> 
    <table>       
        <tr>         
            <td>User name:</td>       
            <td><input name="username"></td>       
        </tr>       
        <tr>           
            <td>Password:</td>           
            <td><input name="password"></td>       
        </tr>
        <tr>
            <td><input type="submit" value="Login"></td>
        </tr>
    </table>
</form>
<h1 th:text="${msg}"></h1>
</body>
</html>
Copy the code

Login interface /login,Post request

@Controller
public class HelloController {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/index.html")
    public String index(a) {
        return "index";
    }
    
    @PostMapping("/login")
    public String hello(String username,String password, Model model) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", username);
        map.add("password", password);
        map.add("client_secret"."123");
        map.add("client_id"."southyin");
        map.add("grant_type"."password");
        Map<String,String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
        String access_token = resp.get("access_token");
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization"."Bearer " + access_token);
        HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
        ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET,httpEntity, String.class);
        model.addAttribute("msg", entity.getBody());
        return "index"; }}Copy the code

In the login interface, after receiving a username and password, we send a POST request via the RestTemplate. Note that in the POST request, the value of grant_type is password. We can get the access_token returned by auth-server

After configuration is complete, start the client – app, visit http://localhost:8082/index.html page for testing. After authorization is completed, we can see the following content on the project home page

The refresh token

Take the previous password mode as an example, run the Postman test

localhost:8080/oauth/token? client_id=southyin&client_secret=123&grant_type=password&username=southyin&password=123

The data is as follows

{
    "access_token": "3d3b5333-67fa-41b2-934a-0063a0f3eb8c"."token_type": "bearer"."refresh_token": "b0fb6c4e-31b2-4dd5-b049-3bbd15e2890b"."expires_in": 7199."scope": "all"
}
Copy the code

In addition to issuing tokens, the /oauth/token endpoint can also be used to refresh tokens. Refresh_token is used to refresh tokens

Again, Postman sends the request

localhost:8080/oauth/token? client_id=southyin&client_secret=123&grant_type=refresh_token&refresh_token=b0fb6c4e-31b2-4dd5-b049-3bbd15e2890b

Get a new token

{
    "access_token": "d5e88e07-0c28-4677-9f28-6f0e7c6f7e63"."token_type": "bearer"."refresh_token": "b0fb6c4e-31b2-4dd5-b049-3bbd15e2890b"."expires_in": 7199."scope": "all"
}
Copy the code

The previous token will become invalid

The token is stored

When we configured the authorization code pattern, two things were in memory:

  • InMemoryAuthorizationCodeServices this table authorization code memory.
  • InMemoryTokenStore indicates that the generated token is in memory

The authorization code is invalid once it’s used, so it’s okay to store it in memory, but the token, what other storage schemes are there

InMemoryTokenStore implements the TokenStore interface. Let’s take a look at the TokenStore implementation class:

  1. InMemoryTokenStore, which we used before and which is the system default, stores access_token in memory. This is fine for standalone applications, but not recommended for distributed environments

  2. JdbcTokenStore, in which tokens are stored in data so that they can be easily shared with other applications

  3. JwtTokenStore, this is not actually a store, because after using JWT, all the user’s information is in the generated JWT, the server does not need to save, this is also stateless login

  4. RedisTokenStore, this is obviously an access_token store in Redis.

  5. JwkTokenStore, which saves the access_token to the JSON Web Key

RedisTokenStore and JwtTokenStore are common usage schemes

RedisTokenStore scheme

First we start a Redis service and then add a Redis dependency to auth-server:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code

After the dependency is successfully added, add the Redis configuration in application.properties

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=12345
Copy the code

After the configuration is complete, we modify the TokenStore instance

@Configuration
public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Bean
    TokenStore tokenStore(a) {
        return newRedisTokenStore(redisConnectionFactory); }}Copy the code

Then we started auth-server, client-app and user-server respectively and went through the third-party login process. Then we found that there was also a copy of access_token distributed in Redis

The validity period of access_token in Redis is the validity period of the authorization code. It is this expiration mechanism in Redis that gives it a natural advantage when storing access_tokens

Jwt scheme

Put behind

Client information is stored in the database

The previous client information is stored directly in memory

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
           .withClient("southyin")
           .secret(new BCryptPasswordEncoder().encode("123"))
           .resourceIds("res1")
           .authorizedGrantTypes("authorization_code"."refresh_token")
           .scopes("all")
           .redirectUris("http://localhost:8082/index.html");
}
Copy the code

In actual project, this way is not an option, then the client information in the code to write dead, bad after maintenance, and to our client is a large amount of information possible, WeChat, for example, there are a large number of third-party applications access the WeChat login, WeChat could not write all the client information to die in the code, so we will give the client information in a database

The main interface involved in client information entry is ClientDetailsService, which has two implementation classes

InMemoryClientDetailsService exist in memory. If you want to store in a database, it is obviously JdbcClientDetailsService. Take a look at the source code of JdbcClientDetailsService and you can analyze the structure of the database

public class JdbcClientDetailsService implements ClientDetailsService.ClientRegistrationService {
 private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
 + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
 + "refresh_token_validity, additional_information, autoapprove";
 private static final String CLIENT_FIELDS = "client_secret, " +
CLIENT_FIELDS_FOR_UPDATE;
 private static final String BASE_FIND_STATEMENT = "select client_id, " +
CLIENT_FIELDS
 + " from oauth_client_details";
 private static final String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
 private static final String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT +
" where client_id = ?";
 private static final String DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (" + CLIENT_FIELDS
 + ", client_id) values (? ,? ,? ,? ,? ,? ,? ,? ,? ,? ,?) ";
 private static final String DEFAULT_UPDATE_STATEMENT = "update oauth_client_details " + "set "
 + CLIENT_FIELDS_FOR_UPDATE.replaceAll(","."=?, ") + "=? where client_id = ?";
 private static final String DEFAULT_UPDATE_SECRET_STATEMENT = "update oauth_client_details "
 + "set client_secret = ? where client_id = ?";
 private static final String DEFAULT_DELETE_STATEMENT = "delete from oauth_client_details where client_id = ?";
Copy the code

Analysis results in table structure scripts

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
 `client_id` varchar(48) NOT NULL.`resource_ids` varchar(256) DEFAULT NULL.`client_secret` varchar(256) DEFAULT NULL.`scope` varchar(256) DEFAULT NULL.`authorized_grant_types` varchar(256) DEFAULT NULL.`web_server_redirect_uri` varchar(256) DEFAULT NULL.`authorities` varchar(256) DEFAULT NULL.`access_token_validity` int(11) DEFAULT NULL.`refresh_token_validity` int(11) DEFAULT NULL.`additional_information` varchar(4096) DEFAULT NULL.`autoapprove` varchar(256) DEFAULT NULL,
 PRIMARY KEY (`client_id`))ENGINE=InnoDB DEFAULT CHARSET=utf8;
Copy the code

Add client information to the table

Since the database is used, the dependency should also provide corresponding support, we add the following dependency to auth-server

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
Copy the code
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///userserver? useUnicode=true&characterEncoding=UTF-8&useSSL=true
spring.datasource.password=root
spring.datasource.username=root
spring.main.allow-bean-definition-overriding=true
Copy the code

The last one is added to the configuration because we are going to create our own ClientDetailsService, and the system already has one, allowing our own instance to override the system default instance

Next, provide your own instance

@Autowired
DataSource dataSource;
@Bean
ClientDetailsService clientDetailsService(a) {
    return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService());
}
Copy the code

After the configuration is complete, restart auth-server and go through the third-party login process

Token expiration dates can also be configured in the database so that they are not configured in the code

Authorization code mode, the modified AuthorizationServerTokenServices instances:

@Bean
AuthorizationServerTokenServices tokenServices(a) {
    DefaultTokenServices services = new DefaultTokenServices();
    services.setClientDetailsService(clientDetailsService());
    services.setSupportRefreshToken(true);
    services.setTokenStore(tokenStore);
    return services;
}
Copy the code

Client-app third-party application optimization

First, define a special class TokenTask to solve the problem of Token management

@Component
public class TokenTask {
    @Autowired
    RestTemplate restTemplate;
    public String access_token = "";
    public String refresh_token = "";
    public String getData(String code) {
        if ("".equals(access_token) && code ! =null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("client_id"."southyin");
            map.add("client_secret"."123");
            map.add("redirect_uri"."http://localhost:8082/index.html");
            map.add("grant_type"."authorization_code,password");
            Map<String, String> resp =
restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
            access_token = resp.get("access_token");
            refresh_token = resp.get("refresh_token");
            return loadDataFromResServer();
       } else {
            returnloadDataFromResServer(); }}private String loadDataFromResServer(a) {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization"."Bearer " + access_token);
            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
            ResponseEntity<String> entity =
restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, 
httpEntity, String.class);
            return entity.getBody();
       } catch (RestClientException e) {
            return "Not loaded"; }}Scheduled @scheduled (cron = "0 50.5 /1 * *?" )
    public void tokenTask(a) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id"."southyin");
        map.add("client_secret"."123");
        map.add("refresh_token", refresh_token);
        map.add("grant_type"."refresh_token");
        Map<String, String> resp =
restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
        access_token = resp.get("access_token");
        refresh_token = resp.get("refresh_token"); }}Copy the code
    1. First of all ingetDataMethod, ifaccess_tokenIs an empty string, andcodeDon’t fornull, indicating that this is just get the authorization code, ready to apply for the token, the token after getting, willaccess_tokenrefresh_tokenAssign values to global variables, and then callloadDataFromResServerMethod to load data from the resource server
    1. There’s another onetokenTaskMethod, this is a timed task to refresh every 115 minutesaccess_token(Access_token is valid for 120 minutes)

After the transformation is complete, go to HelloController for adjustment

@Controller
public class HelloController {
    @Autowired
    TokenTask tokenTask;
    @GetMapping("/index.html")
    public String hello(String code, Model model) {
        model.addAttribute("msg", tokenTask.getData(code));
        return "index"; }}Copy the code

This way you can refresh index. HTML without an error

OAuth2 + JWT

JWT contains three parts of data:

The Header usually has two parts of information:

  • Declare type, in this case JWT
  • Encryption algorithm, custom

We base64URL-encode the header (decidable) to get the first part of the data

The official document, RFC7519, gives seven examples of the Payload.

  • Iss (issuer) : indicates the issuer
  • Exp (expiration Time) : indicates the expiration time of the token
  • Sub (subject) : indicates the topic
  • Aud (audience) : Audience
  • NBF (Not Before) : indicates the effective time
  • Iat (Issued At) : time of issue
  • Jti (JWT ID) : indicates the ID

This part will also be Base64Url encoded to get the second part of the data

The Signature is the authentication information of the entire data. Generally, the encryption algorithm configured in the Header is generated based on the data obtained in the previous two steps and the secret key of the service (the secret key is stored on the server and cannot be disclosed to the client). Used to verify the integrity and reliability of the whole data

transformAccessTokenConfig

@Configuration
public class AccessTokenConfig {
    private String SIGNING_KEY = "southyin";
    
    @Bean
    TokenStore tokenStore(a) {
        return new JwtTokenStore(tokenConverter());
    }

    @Bean
    JwtAccessTokenConverter tokenConverter(a) {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        returnconverter; }}Copy the code
  1. TokenStore uses the JwtTokenStore instance. Whether the access_token is stored in memory or in Redis, it must be saved. After the client sends the access_token, it needs to check whether it is correct. However, if JWT is used, access_token does not actually need to be stored (stateless login, server does not need to store information), because all the user information is in JWT, so JwtTokenStore configured here is not a store per se

  2. JwtAccessTokenConverter converts user information to JWT (convert user information to JWT string or extract user information from JWT string)

  3. When the JWT string is generated, we need a signature, which needs to be saved by itself

The default user information generated by JWT is mainly user roles, user names, etc. If we want to add additional information to the generated JWT, we can add it as follows

@Component
public class CustomAdditionalInformation implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> map = accessToken.getAdditionalInformation();
        map.put("author"."southyin");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
        returnaccessToken; }}Copy the code

Custom class CustomAdditionalInformation TokenEnhancer interface is implemented, and enhance the method of implementing an interface. The OAuth2AccessToken argument in the Enhance method is the generated access_token information, and we can pull the generated additional information from OAuth2AccessToken, You then append your own information to this, and the previously configured JwtAccessTokenConverter is also an instance of TokenEnhancer

Also need to change in AuthorizationServer AuthorizationServerTokenServices instance

@Autowired
JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
CustomAdditionalInformation customAdditionalInformation;
@Bean
AuthorizationServerTokenServices tokenServices(a) {
    DefaultTokenServices services = new DefaultTokenServices();
    services.setClientDetailsService(clientDetailsService());
    services.setSupportRefreshToken(true);
    services.setTokenStore(tokenStore);
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter, 
customAdditionalInformation));
    services.setTokenEnhancer(tokenEnhancerChain);
    return services;
}
Copy the code

Here is mainly in DefaultTokenServices configuration TokenEnhancer, before JwtAccessTokenConverter and CustomAdditionalInformation two instances into come in

Resource Server Transformation

Copy the AccessTokenConfig class from Auth-Server to user-server, and then configure a TokenStore instead of a remote verification address in the resource server configuration

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    TokenStore tokenStore;
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws
Exception {
        resources.resourceId("res1").tokenStore(tokenStore);
   }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
               .antMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated(); }}Copy the code

Test, here took a password mode localhost: 8080 / request/token? client_id=southyin&client_secret=123&grant_type=password&username=southyin&password=123

Take the returned access_token to localhost: 8080 / request/analytic check_token see can see JWT, save the user details.

You can also use the access_token to access the resource server