Changgou mall VI: Micro service gateway and jwt token

Posted by cahva on Wed, 23 Feb 2022 14:10:09 +0100

Microservice gateway

Basic concepts

Each service of microservice generally has different addresses. A service of an external client may call many interfaces, which will bring the following problems

  • The client will request different micro services many times, and the address is complex
  • There are cross domain requests and the processing is complex
  • Complex certification
  • Difficult to reconstruct
  • Some micro services may have browser unfriendly communication protocols and complex interactions

The system architecture after joining the microservice gateway is shown in the figure

The advantages are as follows:

  • Security, only the gateway system is exposed externally, and the micro service can be hidden in the intranet and protected through the firewall.
  • Easy to monitor. The monitoring data can be collected at the gateway and pushed to the external system for analysis.
  • Easy to certify. You can authenticate on the gateway and then forward the request to the back-end microservices without authenticating in each microservice.
  • It reduces the number of interactions between the client and each micro service
  • Easy to unify authorization.

Summary: the microservice gateway is a system. By exposing the microservice gateway system, it is convenient for us to carry out relevant functions such as authentication, security control, unified log processing and easy monitoring.

Gateway Technology

Common gateway technologies

  • nginx Nginx (tengine x) is a high-performance HTTP and Reverse proxy The web server also provides IMAP/POP3/SMTP services
  • Zuul, zuul is a load balancer based on JVM routing and server produced by Netflix.
  • Spring cloud gateway is a spring based Gateway project produced by spring. It integrates circuit breaker and path rewriting, and its performance is better than Zuul.

Comparison between Zuul and Gateway

  • The Gateway is developed by the spring cloud team, which can be well integrated. Zuul has experienced maintenance for a period of time, and Gateway was developed during this period. Now, although zuul has released 2 There is no planned integration of the spring version of cloud X
  • Gateway adopts asynchronous non blocking model AIO, which has great advantages in high concurrency and non blocking response
  • The three core functions of Gateway are routing, assertion and filtering

Gateway use

requirement analysis

Because the system we developed includes foreground system and background system, the background system is used by administrators. Then we also need to call various micro services, so we build a gateway system for system management. The analysis is as follows:

Build the background gateway system

Build analysis

It can be seen from the above that since multiple gateways are required, it is convenient for management. We will create a new project, which is packaged in pom, in which we can establish various gateway system modules. As shown in the figure:

(1) Introduce dependency

Modify the Changgou Gateway project, and the packaging method is pom

pom. The XML is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>changgou-parent</artifactId>
        <groupId>com.changgou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>changgou-gateway</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>changgou-gateway-web</module>
    </modules>

    <!--Gateway dependency-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

</project>

In the Changgou Gateway project, create a Changgou gateway web project. The gateway is mainly used to make a call to the background microservices and connect multiple microservices together.

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>changgou-gateway</artifactId>
        <groupId>com.changgou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>changgou-gateway-web</artifactId>
    <description>
        ordinary web Request gateway
    </description>
</project>

(2) Boot class

Create a boot class com. In Changgou gateway web changgou. Gatewaywebapplication, the code is as follows:

@SpringBootApplication
@EnableEurekaClient
public class GatewayWebApplication {

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

(3)application.yml configuration

Create the application. Under the resources of Changgou gateway web YML, the code is as follows:

spring:
  application:
    name: gateway-web
server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

Cross domain configuration

Sometimes, if we need to process all microservice cross domain requests, we can provide cross domain support in the gateway. Modify application YML, add the following code:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # Match all requests
              allowedOrigins: "*" #Cross domain processing allows all domains
              allowedMethods: # Supported methods
                - GET
                - POST
                - PUT
                - DELETE

Gateway filtering configuration


Routing filters allow you to modify incoming HTTP requests or outgoing HTTP responses in some way. The scope of the path filter is limited to specific paths. Spring Cloud Gateway includes many built-in GatewayFilter factories. As shown in the figure above, route to different micro services according to the request path, which can be realized by using the route filtering function of Gateway.

The filter has more than 20 implementation classes, including header filter, path filter, Hystrix filter and change request URL Filter, as well as other types of filters such as parameters and status codes.

The built-in filter factory has 22 implementation classes, including Header filter, Path filter, Hystrix filter, request URL change filter, and other types of filters such as parameters and Status codes. According to the purpose of the filter factory, it can be divided into the following categories: Header, Parameter, Path, Body, Status, Session, Redirect, Retry, ratelimit and Hystrix.

Host routing

For example, the user requests cloud itheima. COM, you can route the request to http://localhost:18081 Service processing, configured as follows:

The above figure is configured as follows:

      routes:
            - id: changgou_goods_route
              uri: http://localhost:18081
              predicates:
              - Host=cloud.itheima.com**

Test request http://cloud.itheima.com:8001/brand , the effect is as follows:

Note: at this time, you want cloud itheima. Com to access the local computer, configure the C:\Windows\System32\drivers\etc\hosts file. The mapping configuration is as follows:

127.0.0.1 cloud.itheima.com

Path matching filter configuration

We can also implement the corresponding route filtering operation according to the request path. For example, the requests starting with / brand / path in the request are directly handed over to the user http://localhost:180801 Service processing, configured as follows:

The above figure is configured as follows:

      routes:
            - id: changgou_goods_route
              uri: http://localhost:18081
              predicates:
              - Path=/brand**

Test request http://localhost:8001/brand , the effect is as follows:

PrefixPath filtering configuration

Every time a user requests a path, we can add a unified prefix to the real request, such as user request http://localhost:8001 We ask it to ask for the real address when we get there http://localhost:8001/brand , as follows:

The above figure is configured as follows:

      routes:
            - id: changgou_goods_route
              uri: http://localhost:18081
              predicates:
              #- Host=cloud.itheima.com**
              - Path=/**
              filters:
              - PrefixPath=/brand

Test request http://localhost:8001/ The effects are as follows:

StripPrefix filter configuration

Many times, there will be such a request. The user's request path is / api/brand and the real path is / brand. At this time, we need to remove / api to be the real path. At this time, we can use SttripPrefix function to filter the path, as shown in the following configuration:

The above figure is configured as follows:

      routes:
            - id: changgou_goods_route
              uri: http://localhost:18081
              predicates:
              #- Host=cloud.itheima.com**
              - Path=/**
              filters:
              #- PrefixPath=/brand
              - StripPrefix=1

Test request http://localhost:8001/api/brand , the effect is as follows:

LoadBalancerClient routing filter (client load balancing)

The above routing configuration will process the request to the specified URL every time, but if there is a large amount of concurrency in the future production environment, we need to judge the load balancing operation according to the service name. We can use LoadBalancerClientFilter to realize the load balancing call. LoadBalancerClientFilter will act on the route whose URL starts with lb, and then use loadBalancer to obtain the service instance, construct the target requestUrl and set it to GATEWAY_REQUEST_URL_ATTR attribute, used by NettyRoutingFilter.

Modify application YML configuration file, the code is as follows:

The above figure is configured as follows:

      routes:
            - id: changgou_goods_route
              #uri: http://localhost:18081
              uri: lb://goods
              predicates:
              #- Host=cloud.itheima.com**
              - Path=/**
              filters:
              #- PrefixPath=/brand
              - StripPrefix=1

Test request path http://localhost:8001/api/brand

Gateway current limiting

The gateway can do a lot of things, such as limiting the flow. When our system is frequently requested, it may crush the system. Therefore, in order to solve this problem, we need to limit the flow in each micro service. However, if there is a gateway, we can limit the flow in the gateway system, Because all requests need to pass through the gateway system before they can be routed to the micro service.

Train of thought analysis

Token Bucket

Token bucket algorithm is one of the more common current limiting algorithms, which is roughly described as follows:

  1. All requests need to get an available token before being processed;
  2. According to the current limit, set a certain rate to add tokens to the bucket;
  3. The bucket sets the maximum limit for placing tokens. When the bucket is full, the newly added tokens are discarded or rejected;
  4. After the request is reached, first obtain the token in the token bucket, and then carry out other business logic with the token. After processing the business logic, delete the token directly;
  5. The token bucket has a minimum limit. When the token in the bucket reaches the minimum limit, the token will not be deleted after the request is processed, so as to ensure sufficient current limit

As shown below:

There are many technologies for the implementation of this algorithm. Guaua is one of them, and redis client also has its implementation. Four common current limiting algorithms are mentioned in the part of advertising caching implemented by Lua and canal, the third of Changgou mall

Limit the number of requests using the token bucket

The spring cloud gateway is implemented by using the RateLimter current limiting algorithm of redis by default. Therefore, we need to introduce the dependency of redis before using it

(1) Introducing redis dependency

POM at Changgou gateway Introducing redis dependency into XML

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

(2) Define KeyResolver

Add the following code to the applicationin boot class. KeyResolver is used to calculate the key of a certain type of current limit, that is, the key of current limit can be specified through KeyResolver.

We can limit the flow according to the IP. For example, each IP can only be requested once per second. Define the key acquisition in the gateway webapplication, obtain the client IP, and use the IP as the key, as shown in the following code:

/***
 * IP Current limiting
 * @return
 */
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
    return new KeyResolver() {
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            //Get remote client IP
            String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            System.out.println("hostName:"+hostName);
            return Mono.just(hostName);
        }
    };
}

(3) Modify application The configuration item in YML specifies the configuration of limiting traffic and the configuration of REDIS, as shown in the figure

The modification is as follows:


The configuration code is as follows:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # Match all requests
              allowedOrigins: "*" #Cross domain processing allows all domains
              allowedMethods: # Supported methods
                - GET
                - POST
                - PUT
                - DELETE
      routes:
            - id: changgou_goods_route
              uri: lb://goods
              predicates:
              - Path=/api/brand**
              filters:
              - StripPrefix=1
              - name: RequestRateLimiter #The request stream name cannot be written casually, and the default factor is used
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1

  application:
    name: gateway-web
  #Redis configuration
  redis:
    host: 192.168.211.132
    port: 6379

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

Explanation:

redis-rate-limiter.replenishRate is how many requests you want to allow users to execute per second without discarding any requests. This is the rate at which the token bucket is filled

redis-rate-limiter.burstCapacity is the capacity of the instruction card bucket and the maximum number of requests allowed to be completed in one second. Setting this value to zero will block all requests.

Key resolver: "#{@ipkeyresolver}" is used to specify which KeyResolver to use through SPEL expression

Configured as above:

Indicates that a request is allowed to pass in one second, and the filling rate of the token bucket is also one token added in one second.

The maximum emergency condition only allows one request per second, which can be adjusted according to the service.

Multiple requests can occur as follows

User login

User information table tb_user

CREATE TABLE `tb_user` (
  `username` varchar(50) NOT NULL COMMENT 'user name',
  `password` varchar(100) NOT NULL COMMENT 'Password, encrypted storage',
  `phone` varchar(20) DEFAULT NULL COMMENT 'Registered mobile number',
  `email` varchar(50) DEFAULT NULL COMMENT 'Mailbox registration',
  `created` datetime NOT NULL COMMENT 'Creation time',
  `updated` datetime NOT NULL COMMENT 'Modification time',
  `source_type` varchar(1) DEFAULT NULL COMMENT 'Member source: 1:PC,2: H5,3: Android,4: IOS',
  `nick_name` varchar(50) DEFAULT NULL COMMENT 'nickname',
  `name` varchar(50) DEFAULT NULL COMMENT 'Real name',
  `status` varchar(1) DEFAULT NULL COMMENT 'Usage status (1 normal 0 abnormal)',
  `head_pic` varchar(150) DEFAULT NULL COMMENT 'Avatar address',
  `qq` varchar(20) DEFAULT NULL COMMENT 'QQ number',
  `is_mobile_check` varchar(1) DEFAULT '0' COMMENT 'Whether the mobile phone is verified (0 No 1 yes)',
  `is_email_check` varchar(1) DEFAULT '0' COMMENT 'Whether the mailbox is detected (0 No 1 yes)',
  `sex` varchar(1) DEFAULT '1' COMMENT 'Gender, 1 male, 0 female',
  `user_level` int(11) DEFAULT NULL COMMENT 'Membership level',
  `points` int(11) DEFAULT NULL COMMENT 'integral',
  `experience_value` int(11) DEFAULT NULL COMMENT 'Experience value',
  `birthday` datetime DEFAULT NULL COMMENT 'Date Of Birth ',
  `last_login_time` datetime DEFAULT NULL COMMENT 'Last login time',
  PRIMARY KEY (`username`),
  UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='User table';

User micro service

(1) Public API

(2) Rely on

In the POM of Changgou service user XML introduces the following dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>changgou-service</artifactId>
        <groupId>com.changgou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>changgou-service-user</artifactId>

    <!--rely on-->
    <dependencies>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou-service-user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

(3) Start class creation

Create the startup class com. In the Changgou service user micro service changgou. Userapplication, the code is as follows:

@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.changgou.user.dao")
public class UserApplication {

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

(4)application.yml configuration

Create an application in the resources of Changgou service user YML configuration, the code is as follows:

server:
  port: 18089
spring:
  application:
    name: user
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.211.132:3306/changgou_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true

Login business logic

When logging in, password verification is required. BCryptPasswordEncoder is used for encryption. BCrypt in the data needs to be imported into the common project, where BCrypt Checkpw ("plaintext", "ciphertext") is used to compare whether the passwords are consistent.

Modify the com. Of Changgou service user changgou. user. controller. Add login method to usercontroller with the following code:

/***
 * User login
 */
@RequestMapping(value = "/login")
public Result login(String username,String password){
    //Query user information
    User user = userService.findById(username);

    if(user!=null && BCrypt.checkpw(password,user.getPassword())){
        return new Result(true,StatusCode.OK,"Login succeeded!",user);
    }
    return  new Result(false,StatusCode.LOGINERROR,"Wrong account or password!");
}

Note: the password is encrypted here.

Gateway Association

In our daily work, we do not directly expose the microservices. Generally, we use gateway docking to protect the microservices. As shown in the figure above, when the user accesses / api/user /, we call the specified method of the user's microservices according to the user's request. Of course, in addition to / api/user /, / api/address /, / api/areas /, / api/cities /, / api/provinces / all need to be processed by the user micro service to modify the application of the gateway project Changgou gateway web YML configuration file, with the following code:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # Match all requests
              allowedOrigins: "*" #Cross domain processing allows all domains
              allowedMethods: # Supported methods
                - GET
                - POST
                - PUT
                - DELETE
      routes:
            - id: changgou_goods_route
              uri: lb://goods
              predicates:
              - Path=/api/goods/**
              filters:
              - StripPrefix=1
              - name: RequestRateLimiter #The request stream name cannot be written casually, and the default factor is used
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1
            #User micro service
            - id: changgou_user_route
              uri: lb://user
              predicates:
              - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
              filters:
              - StripPrefix=1

  application:
    name: gateway-web
  #Redis configuration
  redis:
    host: 192.168.211.132
    port: 6379

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

Authentication processing based on JWT

JWT token composition

A JWT is actually a string, which consists of three parts: header, payload and signature.

Header

The header is used to describe the most basic information about the JWT, such as its type and the algorithm used for signature. This can also be represented as a JSON object.

{"typ":"JWT","alg":"HS256"}

The header indicates that the signature algorithm is HS256 algorithm. We encode BASE64 http://base64.xpcha.com/ , the encoded string is as follows:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Little knowledge: BASE64 is a representation method of binary data based on 64 printable characters. Since the 6th power of 2 is equal to 64, every 6 bits are a unit corresponding to a printable character. Three bytes have 24 bits, corresponding to four BASE64 units, that is, three bytes need to be represented by four printable characters. JDK provides very convenient BASE64Encoder and BASE64Decoder, which can be used to complete BASE64 based encoding and decoding

Load (payload)

Payload is the place where valid information is stored. The name seems to refer specifically to the goods carried on the aircraft. These valid information contains three parts

(1) Declaration registered in the standard (recommended but not mandatory)

iss: jwt Issuer
sub: jwt Target users
aud: receive jwt Party of
exp: jwt The expiration time of must be greater than the issuing time
nbf: Define the time before which the jwt Are not available.
iat: jwt Date of issuance
jti: jwt The unique identification of the, which is mainly used as a one-time identification token,To avoid replay attacks.

(2) Public statement

Any information can be added to the public statement. Generally, the user's relevant information or other necessary information required by the business can be added However, it is not recommended to add sensitive information because this part can be decrypted on the client

(3) Private declaration

Private declaration is a declaration jointly defined by providers and consumers. It is generally not recommended to store sensitive information, because base64 is symmetrically decrypted, which means that this part of information can be classified as plaintext information.

This refers to the custom claim. For example, admin and name in the following face structure examples belong to self-defined claim. The difference between these claims and those specified in the JWT standard lies in: the claims specified in the JWT. After receiving the JWT, the receiver of the JWT knows how to verify the claims of these standards (whether they can be verified or not); private claims will not be verified unless the receiver is explicitly told to verify these claims and rules.

Define a payload:

{"sub":"1234567890","name":"John Doe","admin":true}

Then it is encrypted with base64 to get the second part of Jwt.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

Visa (signature)

The third part of jwt is a visa information, which consists of three parts:

Header (after Base64)

Payload (after Base64)

secret

This part needs base64 encrypted header and base64 encrypted payload The string formed by connection is then encrypted by salt secret combination through the encryption method declared in the header, and then constitutes the third part of jwt.

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Use these three parts Connected into a complete string to form the final jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

The private key is generated by jwt on the server side, so it should be signed and issued by jwt on the server side. Note that the private key is not signed and issued by jwt on the server side. Once the client knows this secret, it means that the client can sign jwt it by itself.

JJWT tool library

JJWT is a Java library that provides end-to-end JWT creation and validation. Always free and open source (Apache License, version 2.0), JJWT is easy to use and understand. It is designed as a smooth interface centered on architecture, hiding most of its complexity.

Official documents:

https://github.com/jwtk/jjwt

Create Token

(1) Dependency introduction

POM in the Changgou parent project Add dependency to XML:

<!--authentication -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

(2) Create test

Create test classes and set test methods under / test/java in Changgou common

public class JwtTest {

    /****
     * Create Jwt token
     */
    @Test
    public void testCreateJwt(){
        JwtBuilder builder= Jwts.builder()
                .setId("888")             //Set unique number
                .setSubject("Xiaobai")       //The setting theme can be JSON data
                .setIssuedAt(new Date())  //Set issue date
                .signWith(SignatureAlgorithm.HS256,"itcast");//Set the signature to use HS256 algorithm, and set the secret key (string)
        //Build and return a string
        System.out.println( builder.compact() );
    }
}

Run print results:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4

Run again, you will find that the results of each run are different, because our load includes time.

TOKEN parsing

We have just created a token. In a web application, this operation is carried out by the server and sent to the client. The client needs to carry this token when sending a request to the server next time (it's like holding a ticket). The server should parse the information in the token (such as user id) when receiving this token, Query the database according to these information and return the corresponding results.

/***
 * Parsing Jwt token data
 */
@Test
public void testParseJwt(){
    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4";
    Claims claims = Jwts.parser().
            setSigningKey("itcast").
            parseClaimsJws(compactJwt).
            getBody();
    System.out.println(claims);
}

Run print effect:

{jti=888, sub=Xiaobai, iat=1562062287}

If you try to tamper with the token or signature secret key, you will find that an error will be reported at runtime, so parsing the token is to verify the token
At the same time, the Claims class also supports setting the expiration time. If the expiration time is exceeded, the parsing will also report an error

Custom claims

Our example just now only stores id and subject information. If you want to store more information (such as roles), you can define custom claims.

Create a test class and set the test method:

Create token:

Run print effect:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjMyOTIsImFkZHJlc3MiOiLmt7HlnLPpu5Hpqazorq3nu4PokKXnqIvluo_lkZjkuK3lv4MiLCJuYW1lIjoi546L5LqUIiwiYWdlIjoyN30.ZSbHt5qrxz0F1Ma9rVHHAIy4jMCBGIHoNaaPQXxV_dk

Parse TOKEN:

/***
 * Parsing Jwt token data
 */
@Test
public void testParseJwt(){
    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjMyOTIsImFkZHJlc3MiOiLmt7HlnLPpu5Hpqazorq3nu4PokKXnqIvluo_lkZjkuK3lv4MiLCJuYW1lIjoi546L5LqUIiwiYWdlIjoyN30.ZSbHt5qrxz0F1Ma9rVHHAIy4jMCBGIHoNaaPQXxV_dk";
    Claims claims = Jwts.parser().
            setSigningKey("itcast").
            parseClaimsJws(compactJwt).
            getBody();
    System.out.println(claims);
}

Operation effect:

Authentication processing

  1. The user calls the microservice by accessing the microservice gateway and carries the header file information at the same time
  2. Intercept at the microservice gateway and obtain the path to be accessed by the user after interception
  3. Identify whether the user's access path requires login. If necessary, identify whether the user's identity can access the path [here, a set of permissions can be designed based on the database]
  4. If permission access is required and the user has logged in, it will be released
  5. If permission access is required and the user is not logged in, the user will be prompted to log in
  6. The user accesses the user's Micro service through the gateway for login authentication
  7. After the authentication is passed, the user micro service will issue a token to the gateway, and the gateway will encapsulate the user information into the header file and respond to the user
  8. The next time the user accesses, the user can identify whether to log in by carrying the token information in the header file

User login and sign TOKEN

(1) Generate token tool class

Create the class entity in Changgou common Jwtutil is mainly used to assist in generating Jwt token information. The code is as follows:

public class JwtUtil {

    //Valid for
    public static final Long JWT_TTL = 3600000L;// 60 * 60 * 1000 one hour

    //Jwt token information
    public static final String JWT_KEY = "itcast";

    public static String createJWT(String id, String subject, Long ttlMillis) {
        //Specify algorithm
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //Current system time
        long nowMillis = System.currentTimeMillis();
        //Token issuing time
        Date now = new Date(nowMillis);

        //If the token validity period is null, the default setting is 1 hour
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }

        //Token expiration time setting
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);

        //Generate secret key
        SecretKey secretKey = generalKey();

        //Encapsulate Jwt token information
        JwtBuilder builder = Jwts.builder()
                .setId(id)                    //Unique ID
                .setSubject(subject)          // Topics can be JSON data
                .setIssuer("admin")          // Issuer
                .setIssuedAt(now)             // Time filed 
                .signWith(signatureAlgorithm, secretKey) // Signature algorithm and key
                .setExpiration(expDate);      // Set expiration time
        return builder.compact();
    }

    /**
     * Generate encrypted secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * Parsing token data
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

(2) If the user logs in successfully, sign and issue TOKEN, and modify the login method:

/***
 * User login
 */
@RequestMapping(value = "/login")
public Result login(String username,String password){
    //Query user information
    User user = userService.findById(username);

    if(user!=null && BCrypt.checkpw(password,user.getPassword())){
        //Set token information
        Map<String,Object> info = new HashMap<String,Object>();
        info.put("role","USER");
        info.put("success","SUCCESS");
        info.put("username",username);
        //Generate token
        String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(info),null);
        return new Result(true,StatusCode.OK,"Login succeeded!",jwt);
    }
    return  new Result(false,StatusCode.LOGINERROR,"Wrong account or password!");
}

Custom global filter

Create a filter class, as shown in the figure:

The AuthorizeFilter code is as follows:

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    //Token header name
    private static final String AUTHORIZE_TOKEN = "Authorization";

    /***
     * Global filter
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //Get Request and Response objects
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //Gets the URI of the request
        String path = request.getURI().getPath();

        //If it is an open micro service such as login and goods [some of the goods here are open], it will be released directly. There is no complete demonstration here. A set of permission system needs to be designed for the complete demonstration
        if (path.startsWith("/api/user/login") || path.startsWith("/api/brand/search/")) {
            //Release
            Mono<Void> filter = chain.filter(exchange);
            return filter;
        }

        //Get token information in header file
        String tokent = request.getHeaders().getFirst(AUTHORIZE_TOKEN);

        //If not in the header file, it is obtained from the request parameters
        if (StringUtils.isEmpty(tokent)) {
            tokent = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
        }

        //If it is empty, the error code is output
        if (StringUtils.isEmpty(tokent)) {
            //The setting method is not allowed to be accessed, 405 error code
           response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
           return response.setComplete();
        }

        //Parsing token data
        try {
            Claims claims = JwtUtil.parseJWT(tokent);
        } catch (Exception e) {
            e.printStackTrace();
            //Parsing failed, response error
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //Release
        return chain.filter(exchange);
    }


    /***
     * Filter execution sequence
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

Configure filtering rules

Modify the yml file of the gateway system:

The above codes are as follows:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]': # Match all requests
              allowedOrigins: "*" #Cross domain processing allows all domains
              allowedMethods: # Supported methods
                - GET
                - POST
                - PUT
                - DELETE
      routes:
            - id: changgou_goods_route
              uri: lb://goods
              predicates:
              - Path=/api/album/**,/api/brand/**,/api/cache/**,/api/categoryBrand/**,/api/category/**,/api/para/**,/api/pref/**,/api/sku/**,/api/spec/**,/api/spu/**,/api/stockBack/**,/api/template/**
              filters:
              - StripPrefix=1
              - name: RequestRateLimiter #The request stream name cannot be written casually, and the default factor is used
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1
            #User micro service
            - id: changgou_user_route
              uri: lb://user
              predicates:
              - Path=/api/user/**,/api/address/**,/api/areas/**,/api/cities/**,/api/provinces/**
              filters:
              - StripPrefix=1

  application:
    name: gateway-web
  #Redis configuration
  redis:
    host: 192.168.211.132
    port: 6379

server:
  port: 8001
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
management:
  endpoint:
    gateway:
      enabled: true
    web:
      exposure:
        include: true

Session retention

Every time the user requests, we need to obtain the token data. There are multiple methods. We can submit the data to the header file or store the data in the Cookie every time, verify the data from the Cookie every time, and submit the token data to the gateway in the form of parameters every time. It is easier to implement using Cookie.

Login encapsulation Cookie

Modify the user micro service, add the token information to the Cookie every time you log in, and modify the com. Of Changgou service user changgou. user. controller. The login method of usercontroller is as follows

Filter get token data

Each time, obtain the token in the Cookie through the filter in the gateway, analyze the token data, and modify the AuthorizeFilter in the micro service gateway Changgou gateway web. The code is as follows:

Add Header information

We can also add the request header information in the global filter of Gateway. For example, we can add the token information to the request header and obtain the header information in the micro service, as shown in the following code:

Modify the AuthorizeFilter filter in the microservice gateway and add the token to the request header in the token information verification block, as shown in the following code:

Topics: Java Spring Cloud Microservices