Introduction and use of JWT

1. JWT implements stateless web services

1. What is stateful

Stateful services, that is, the server needs to record the client information of each session to identify the client's identity and process requests based on the user's identity. A typical design is such as session in tomcat.

For example, login: After the user logs in, we save the login information in the server session, and give the user a cookie value to record the corresponding session. Then in the next request, if the user brings the cookie value, we can identify the corresponding session and find the user's information.

What are the disadvantages?

  • The server saves a large amount of data, increasing the pressure on the server.
  • The server saves user status and cannot expand horizontally.
  • Client requests depend on the server, and multiple requests must access the same server.

2. What is stateless?

The server does not need to record the client's status information, that is:

  • The server does not save any client requester information
  • Each request from the client must have self-describing information through which the client's identity can be identified.

What are the benefits?

  • Client requests do not rely on server information, and any multiple requests do not need to access the same service.
  • The cluster and status of the server are transparent to the client
  • The server can be migrated and scaled arbitrarily
  • Reduce server storage pressure

3. How to achieve statelessness

Stateless login process:

  • When the client requests the service for the first time, the server authenticates the user's information (login)
  • After the authentication is passed, the user information is encrypted to form a token and returned to the client as a login credential.
  • For each subsequent request, the client will carry the authentication token.
  • The service decrypts the token and determines whether it is valid.

flow chart:

​The client requests login, and the credentials are issued after login.

Please add image description

What is the most critical point in the entire login process?

Token security

The token is the only identifier to identify the client. If the encryption is not tight enough and it is forged, it will be over.

What encryption method is safe and reliable?

We will use: JWT + RSA asymmetric encryption

4. Introduction to JWT

The full name of JWT is Json Web Token, which is a JSON-style lightweight authorization and identity authentication specification that can realize stateless and distributed web application authorization; official website: https://jwt.io

JWT contains three parts of data:

  • Header: Header, usually the header has two parts of information:

    • Declaration type, here is the JWT self-describing information

    We will base64 encode the header to get the first part of the data base64 encoded and decoded

  • Payload: The payload is valid data, generally containing the following information:

    • User identity information (note that because base64 encoding is used here, decoding is reversible, so do not store sensitive information)
    • Registration statement: Such as the token issuance time, expiration time, issuer, etc. This part of the content is like the information on the ID card.

    This part will also be encoded using base64 to get the second part of the data.

  • Signature: Signature is the authentication information of the entire data. Generally, based on the data in the first two steps, plus the secret (do not leak it, it is best to change it periodically), a signature is generated through an encryption algorithm (irreversible). Used to verify the completeness and reliability of the entire data.

Generated data format:

Please add image description

You can see that it is divided into 3 segments, each segment is a part of the data above

5. JWT interaction process

Step translation:

  • 1. User login
  • 2. Service authentication, jwt is generated after passing
  • 3. Return the generated jwt to the browser
  • 4. Users carry jwt every time they request
  • 5. The server uses the public key to interpret the JWT signature. After judging that the signature is valid, it obtains the user information from the Payload.
  • 6. Process the request and return the response result

2. nimbus-jose-jwt library

1. Enter dependencies

nimbus-jose-jwt, jose4j, and java-jwt are several common libraries for operating JWT in Java.

nimbus-jose-jwt official website: https://connect2id.com/products/nimbus-jose-jwt

Required coordinates

    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>9.11.1</version>
    </dependency>

2. Core API

2.1. Encryption process
  • In nimbus-jose-jwt, the Header class is used to represent the head of the JWT. However, the Header class is an abstract class, and we use its subclass JWSHeader .

    Create the header object:

     @Test
     public void createToken(){
          
          
         //创建头部对象
         JWSHeader jwsHeader =
                 new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法
                         .type(JOSEObjectType.JWT) // 静态常量
                         .build();
         System.out.println(jwsHeader);
     }
    

    You can .toBase64URL()get the Base64 form of the header information (which is also the actual header information in the JWT) through the method:

  • Use the Payload class to represent the payload part of the JWT

    Create load section object:

        @Test
        public void createToken(){
          
          
            //创建头部对象
            JWSHeader jwsHeader =
                    new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                            .type(JOSEObjectType.JWT) // 静态常量
                            .build();
            System.out.println(jwsHeader);
    
            //创建载荷
            Payload payload = new Payload("hello world");
            System.out.println(payload);
        }
    

    You can .toBase64URL()obtain the Base64 form of the payload information through the method (this is also the actual payload information in the JWT):

  • Signature part

    There is no special class representation for the signature part. The signature part is not created by yourself, but 头部 + 荷载部 + 加密算法calculated by

    ​ nimbus-jose-jwt specially provides a signer JWSSigner to participate in the signing process. The key is specified when creating the signer:

JWSSigner jwsSigner = new MACSigner("Key"); //A key must be specified in MACSigner()


最终,整个 JWT 由一个 **JWSObject** 对象表示:

```java
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 进行签名(根据前两部分生成第三部分)
jwsObject.sign(jwsSigner);

What we ultimately want is a JWT string, not an object. Here we .serialize()can then call the method on the JWSObject object representing the JWT:

String token = jwsObject.serialize();

Complete example:

  @Test
    public void createToken() throws JOSEException {
    
    

        //创建头部对象
        JWSHeader jwsHeader =
                new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                        .type(JOSEObjectType.JWT) // 静态常量
                        .build();
        //创建载荷
        Payload payload = new Payload("hello world");

        //创建签名器
        JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥
        //创建签名
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
        jwsObject.sign(jwsSigner);//再+签名部分

        //生成token字符串
        String token = jwsObject.serialize();
        System.out.println(token);
    }

If an exception occurs: com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits, it is because the key length is not long enough. Just increase the key length.

2.2. Decryption

There are only two core APIs for the reverse decryption and verification process: the static method parse method of JWSObject and the verification of its JWSVerifier object.

If you want to directly verify the validity of a JWSObject object, you need to create a JWSVerifier object.

//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同

Then directly call the verify method of the jwsObject object:

if (!jwsObject.verify(jwsVerifier)) {
    
    
    throw new RuntimeException("token 签名不合法!");
}

3. Token renewal

In actual development, the token cannot always be valid. For example, if no operation is performed within 30 minutes, the authentication will expire and you need to log in again. If you have been requesting access, the token will be valid until the last visit is more than 30 seconds away from the next visit. If the time exceeds 30 minutes, the certification expires.

springsecurity integrates JWT:


@Component
public class JWTfilter extends OncePerRequestFilter {
    
    

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired(required = false)
    private SecurityLoginService securityLoginService;

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {
    
    

        //功能点1:在请求头拿到jwt
        String jwt = httpServletRequest.getHeader("jwt");
        if (jwt == null) {
    
    
            //放给security 其他过滤器,该方法不做处理
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        // 功能点2:jwt不合法
        if (!JWTUtil.decode(jwt)) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //功能点3 获取jwt的用户信息
        Map payLoad = JWTUtil.getPayload(jwt);
        String username = (String) payLoad.get("username");

        //拿到redis的jwt
        String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);

        //判断redis是否有该jwt
        if (redisJWT == null) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        if (!jwt.equals(redisJWT)) {
    
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //给redis 的jwt续期
        redisTemplate.opsForValue().set("jwt:" + username, jwt, 30,
                TimeUnit.MINUTES);

        //获取用户名,密码,权限
        UserDetails userDetails = securityLoginService.loadUserByUsername(username);

        // 获取用户信息 生成security容器凭证
        UsernamePasswordAuthenticationToken upa =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername()
                        , userDetails.getPassword(), userDetails.getAuthorities());

        //放入凭证
        SecurityContextHolder.getContext().setAuthentication(upa);

        // 本方法共功能执行完了,交给下一个过滤器
        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }
}
   //前后端项目中要禁用掉session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
在securityConfig 类注入
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);

Guess you like

Origin blog.csdn.net/lanlan112233/article/details/129762966