Build SpringCloud microservice framework from scratch -- Auth service integration JWT

In the previous article, we established the Auth service, which is specially used to handle user authentication and authorization. Here, we integrate JWT as the authentication ticket.

What JWT

What is JWT

Json web token (JWT) is a JSON based open standard (RFC 7519) implemented to transfer declarations between network application environments A concise and self-contained method is defined to safely transfer information between communication parties in the form of JSON objects. Because of the existence of digital signature, this information is trusted. JWT can use HMAC algorithm or RSA public-private secret key to sign.

JWT request process

 

1. The user sends a post request using the account and face;
2. The server uses the private key to create a jwt;
3. The server returns this jwt to the browser;
4. The browser sends the jwt string in the request header to the server like a request;
5. The server verifies the jwt;
6. Return the response resource to the browser.

Main application scenarios of JWT

Identity authentication in this scenario, once the user completes the login, JWT is included in each subsequent request, which can be used to verify the user's identity and the access rights to routes, services and resources. Because its cost is very small, it can be easily transmitted in systems with different domain names. At present, this technology is widely used in single sign on (SSO). Information exchange using JWT to encode data between the two sides of communication is a very secure way. Because its information is signed, it can ensure that the information sent by the sender is not forged.

advantage

1. Compact: it can be sent through URL, POST parameter or HTTP header, because the amount of data is small and the transmission speed is fast
2. Self contained: the load contains the information required by all users, avoiding multiple queries to the database
3. Because tokens are stored on the client in the form of JSON encryption, JWT is cross language and is supported in any web form in principle.
4. There is no need to save session information on the server, which is especially suitable for distributed microservices.

`

Structure of JWT

JWT is composed of three pieces of information, which are used as text Join together to form a JWT string.
Like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT consists of three parts:
Header header (the header contains the metadata of the token and contains the type of signature and / or encryption algorithm)
Payload load (similar to items carried on an aircraft)
Signature signature / visa

Header

The header of JWT carries two parts of information: token type and encryption algorithm.

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

Declaration type: jwt here
Algorithm for declaring encryption: HMAC SHA256 is usually used directly

Encryption algorithm is a one-way function hash algorithm. Common algorithms include MD5, SHA and HAMC.
MD5 (message digest algorithm 5) abbreviation, widely used in encryption and decryption technology, commonly used in file verification. Check? No matter how large the file is, a unique MD5 value can be generated after MD5
SHA (Secure Hash Algorithm), an important tool in cryptography applications such as digital signature, has higher security than MD5
HMAC (Hash Message Authentication Code), Hash message authentication code, authentication protocol of Hash algorithm based on key. The public function and key are used to generate a fixed length value as the authentication identifier, which is used to identify the integrity of the message. Commonly used for interface signature verification

Payload

Payload is the place where valid information is stored.
Valid information consists of three parts
1. Declaration registered in the standard
2. Public statements
3. Declaration of private ownership

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

iss: jwt issuer
sub: user oriented (jwt user oriented)
aud: party receiving jwt
exp: expiration time stamp (the expiration time of jwt, which must be greater than the issuing time)
nbf: define the time before which the jwt is unavailable
IAT: issuing time of JWT
JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attacks.

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

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.

Signature

The third part of jwt is a visa information
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.
The secret key is stored in the server. The server will generate and verify the token based on this key, so it needs to be protected.

reference material: https://www.jianshu.com/p/e88d3f8151db

New service springcloud_common, storing public dependencies

Establish maven project as before:

BaseApiService:Get user information, which will be used later
BaseResult:Encapsulate general return interface information
AuthApiConstant Class encapsulates some common constant values
BaseApiConstant Class encapsulates the interface constants returned by the general:

The package redis encapsulates some configuration information about redis. Redis should be configured in the configuration file for each service referenced to Common dependency

RedisConfig: redis configuration information
RedisParameter: redis parameter information
RedisUtil:  Tool class for operation redis

I won't post the code. You can copy it against the source code

Then we configure Redis related information in the Auth service

server:
  port: 8003
spring:
  application:
    name: api-auth
  redis:
    hostserver: 192.168.0.132:6379 #This node is configured with a redis stand-alone. After configuration, the following sentry mode fails
    database: 4 # Redis database index (0 by default)
    timeout: 10000 # Connection timeout (MS)
    password:  # Redis server connection password (blank by default)
    perKey: general_redis #redis prefix
    sentinel:
      master: mymaster
      nodes:
    lettuce:
      pool:
        max-active: 200 # The maximum number of connections in the connection pool (negative value indicates no limit)
        max-wait: -1 # Maximum blocking waiting time of connection pool (negative value indicates no limit)
        max-idle: 10 # Maximum free connections in connection pool
        min-idle: 0  # Minimum free connections in connection pool
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8001/eureka/

Auth integrated JWT

pom introduces JWT dependency, and jsonwebtoken is used here

  <!-- introduce jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

Encapsulating JWT tool classes:

package com.springcloud.jwt;

import cn.hutool.core.date.DateUtil;

import com.springcloud.common.constant.AuthApiConstant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

/**
 * JWT tool
 */
@Component
@Slf4j
public class JwtUtil {
    /**
     * secret key
     */
    private static final String SECRET = AuthApiConstant.AUTH_SECRET;


    /**
     * Generate user token and set token timeout
     */
    public String createToken(Map map) {
        Claims claims = Jwts.claims();
        claims.put(AuthApiConstant.AUTH_USER_INFO_NAME, map);
        return Jwts.builder().setClaims(claims).setExpiration(DateUtil.offsetSecond(new Date(), Integer.parseInt(map.get(AuthApiConstant.AUTH_USER_REFRESH_NAME).toString())))
                .signWith(SignatureAlgorithm.HS512, SECRET).compact();
    }

    /**
     * Obtain user information through Token
     *
     * @param token
     * @return
     */
    public Claims getClaimByToken(String token) {
        try {
            Jws<Claims> jws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
            return jws.getBody();
        } catch (Exception e) {
            return null;
        }

    }


    /**
     * Verify whether the token is expired
     *
     * @param expiration
     * @return
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

}

be careful:

All the methods of Auth service here are extracted into a centrally managed service:

In spring cloud_ Create a new service springcloud under Parent_ api,springcloud_ There are many sub modules under the API, which uniformly manage the entity classes and interfaces of each service. If you don't understand, you can directly look at the source code

AuthService:
package com.springcloud.service;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

public interface AuthService {
    @GetMapping("/auth/getToken")
    Map<String, Object> getToken(@RequestParam("userName") String userName, @RequestParam("password") String password);

    @GetMapping("/auth/verifyToken")
    Map<String, Object> verifyToken(@RequestParam(value = "token") String token);

    @GetMapping("/auth/refreshToken")
    Map<String, Object> refreshToken(@RequestParam(value = "refreshToken") String refreshToken);
}

Service Auth creates a new class TokenManage and implements AuthService

TokenManage:

package com.springcloud.manage;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.springcloud.common.constant.AuthApiConstant;
import com.springcloud.common.redis.RedisUtil;
import com.springcloud.jwt.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class TokenManage {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private RedisUtil redisUtil;


    /**
     * Generate token
     *
     * @param userId             User id
     * @param userName           name
     * @param expire             token Actual time of expiration
     * @param refreshTokenExpire refreshToken Expiration time
     * @return
     */
    public Map<String, Object> getTokenInfo(String userId, String userName,
                                            int expire, int refreshTokenExpire) {
        Map<String, Object> map = new HashMap();
        map.put(AuthApiConstant.AUTH_USER_USERNAME_NAME, userName); //user name
        map.put(AuthApiConstant.AUTH_USER_ID, userId);    //User number
        map.put(AuthApiConstant.AUTH_USER_REFRESH_NAME, expire);// Token expiration time
        //Generate authentication token
        String token = jwtUtil.createToken(map);
        //Generate refreshToken
        String refreshToken = redisUtil.getKeys(AuthApiConstant.AUTH_REDIS_TOKENKEY + userId + ":*").stream().findFirst().orElse(null);
        if (StrUtil.isBlank(refreshToken)) {
            refreshToken = IdUtil.simpleUUID();
        } else {
            refreshTokenExpire = Convert.toInt(redisUtil.getExpire(refreshToken));
            refreshToken = refreshToken.substring(refreshToken.lastIndexOf(":") + 1);
        }
        //Put the user information map into redis
        redisUtil.set(AuthApiConstant.AUTH_REDIS_TOKENKEY + userId + ":" + refreshToken, map, refreshTokenExpire);
        map.put(AuthApiConstant.TOKEN_NAME, token);
        map.put(AuthApiConstant.AUTH_USER_REFRESHTOKEN_NAME, refreshToken);
        map.put(AuthApiConstant.AUTH_USER_REFRESHTOKENEXPIRE_NAME, refreshTokenExpire);
        return map;
    }
}

token expiration refresh scheme

1. Single sign on
After the user logs in, the back-end validates the user successfully and generates two tokens, which are access respectively_ Token (token used by the access interface), refresh_ Token (the token used to brush the renewal after the access_token expires. Note that the expiration time of refresh_token should be longer than that of access_token). The back end stores the user information and these two tokens in redis and returns them to the front end.
After the front end obtains the two token s returned by successful login, it stores them in the local storage.
2. Interface request
The front end encapsulates the unified interface request function and token refresh function. After the request is successful, verify the returned result. If the token expires, call the token refresh function to request a new token
After receiving the token refresh request, the backend combines the user information, token and refresh stored in redis_ The token validates the request parameters, and generates a new token and refresh after passing the verification_ The token is stored in redis and returned to the front end. This completes the token refresh.


After starting the project:

Then the interface here is Swagger2, which is integrated and has a configuration file

code:

package com.springcloud.config;

import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import com.springcloud.common.constant.AuthApiConstant;
import io.swagger.annotations.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
public class SwaggerConfig {
    @Bean
    public Docket defaultApi2() {
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(AuthApiConstant.SWAGGER_TITLE)
                .version(AuthApiConstant.SWAGGER_VERSION)
                .build();
    }
}

There are a lot of things. It's inevitable to miss something when writing with a blog. Please see the source code for the missing places. Write here first. I'm tired.

Give more advice on deficiencies.

Project address:

https://gitee.com/bosszddquan/springcloudframe

Keywords: Java Redis Spring Cloud jwt

Added by gdrums on Tue, 08 Feb 2022 11:36:03 +0200