Four stages of Java learning-04 JWT technology analysis and Practice

Introduction to JWT

background

In traditional stateful service applications, the server needs to record the client information of each session, so as to identify the client identity and process the request according to the user identity. A typical design is session in Tomcat. For example, login: after the user logs in, we save the user's information in the server session, give the user a cookie value, record the corresponding session, and then make the next request, When the user brings the cookie value (this step is automatically completed by the browser), we can identify the corresponding session and find the user's information. This method is the most convenient at present, but in distributed applications, it is not a good choice for the server to save the user's state, so JWT was born

JWT overview

JWT(JSON WEB Token) is a standard. With the help of JSON format data as the token in the WEB application request, the self-contained design of data is carried out to realize the safe information transmission of all parties. In the process of data transmission, data can also be encrypted, signed and other related processing. At the same time, JWT is also the most popular cross domain authentication solution (its official website is: https://jwt.io/ ). It is very convenient to realize user identity authentication in distributed system.

JWT data structure

JWT is usually composed of three parts: header, payload and signature. The format is as follows:

xxxxx.yyyyy.zzzzz

For example:

eyJhbGciOiJIUzI1NiJ9.eyJwZXJtaXNzaW9ucyI6InN5czpyZXM6Y3JlYXRlLHN5czpyZXM6cmV0cmlldmUiLCJleHAiOjE2MjY5MzIyNTksImlhdCI6MTYyNjkzMDQ1OSwidXNlcm5hbWUiOiJqYWNrIn0.SQrRS5nuID1Xv5GMvUgnr7xrVzB7GcRFrkNak-x16Mw

Header section

The Header part is a JSON object that describes the metadata of JWT, usually as follows.

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

In the above code, the alg attribute represents the signature algorithm, and the default is HMAC SHA256 (HS256); the typ attribute represents the type of the token, and the JWT token is uniformly written as JWT. Finally, convert the JSON object into a string using the Base64URL algorithm (see later).

Payload section

The Payload part is also a JSON object, which is used to store the actual data to be transferred. Seven official fields are specified in the JWT specification for selection.
 iss (issuer): issuer
 exp (expiration time): expiration time
 subject (subject): subject
 aud (audience): audience
 nbf (Not Before): effective time
 iat (Issued At): issuing time
 jti (JWT ID): No
In addition to official fields, you can also define private fields in this section. The following is an example.

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

Note that JWT is not encrypted by default and can be read by anyone, so don't put secret information in this part.

The JSON object should also be converted into a string using the Base64URL algorithm.

Signature section

The Signature part is the Signature of the first two parts. Its purpose is to prevent data from being tampered with.

First, you need to specify a secret. This key is only known to the server and cannot be disclosed to the user. Then, use the signature algorithm specified in the Header (HMAC SHA256 by default) to generate a signature according to the following formula.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

After the Signature is calculated, the Header, Payload and Signature are combined into a string. Each part is separated by a dot (.) and can be returned to the user.

JWT quick start

Environmental preparation

Step 1: create a project, for example:


Step 2: add the dependency and its POM The contents of the XML file are 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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
       <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.2.RELEASE</version>
    </parent>
    <groupId>com.cy</groupId>
    <artifactId>03-jt-security-jwt</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <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>
        <!--add to jwt rely on-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
</project>

Step 3: create the configuration file application YML (don't write anything for the time being)
Step 4: define the startup class. The code is as follows

package com.cy.jt;
@SpringBootApplication
public class SecurityJwtApplication {
    public static void main(String[] args) {
        SpringApplication.run(
                SecurityJwtApplication.class,
                args);
    }
}

Step 4: run the startup class to check whether it can be started successfully

Create and parse token s

Write unit tests to practice the creation and parsing of Token objects, such as:

@Test
void testCreateAndParseToken(){
    //1. Create token
    //1.1 define load information
    Map<String,Object> map=new HashMap<>();
    map.put("username", "jack");
    map.put("permissions", "sys:res:create,sys:res:retrieve");
    //1.2 define expiration practices
    Calendar calendar=Calendar.getInstance();
    calendar.add(Calendar.MINUTE, 30);
    Date expirationTime=calendar.getTime();
    //1.3 define key
    String secret="AAABBBCCCDDD";
    //1.4 generating tokens
    String token= Jwts.builder()
            .setClaims(map)
            .setIssuedAt(new Date())
            .setExpiration(calendar.getTime())
            .signWith(SignatureAlgorithm.HS256,secret)
            .compact();
    System.out.println(token);
    //2. Parse token
    Claims claims = Jwts.parser().setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    System.out.println("claims="+claims);
}

Create JWT tool class

In order to simplify the application of JWT in the project, we usually build a tool class to encapsulate the creation and parsing of token, for example:

package com.cy.jt.security.util;
public class JwtUtils {
    /**
     * Secret key
     */
    private static String secret="AAABBBCCCDDDEEE";
    /**
     * Validity period in seconds
     * Default 30 minutes
     */
    private static Long expirationTimeInSecond=1800L;
    /**
     * Get claim from token
     *
     * @param token token
     * @return claim
     */
    public static Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            throw new IllegalArgumentException("Token invalided.");
        }
    }
    /**
     * Judge whether the token has expired
     * @param token token
     * @return true if expired, false if not expired
     */
    private static Boolean isTokenExpired(String token) {
        Date expiration = getClaimsFromToken(token).getExpiration();
        return expiration.before(new Date());
    }
    /**
     * Generate a token for the specified user
     * @param claims User information
     * @return token
     */
    public static String generateToken(Map<String, Object> claims) {
        Date createdTime = new Date();
        Date expirationTime = new Date(System.currentTimeMillis() + expirationTimeInSecond * 1000);
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
    }
}

Application of JWT in project

AuthController authentication service

AuthController is defined to handle login authentication business. The code is as follows:

package com.cy.jt.security.controller;
@RestController
public class AuthController {
    @RequestMapping("/login")
    public Map<String,Object> doLogin(String username,
                          String password){
        Map<String,Object> map=new HashMap<>();
        if("jack".equals(username)&&"123456".equals(password)){
            map.put("state","200");
            map.put("message","login ok");
            Map<String,Object> claims=new HashMap<>();//Load information
            claims.put("username",username);
            map.put("Authentication", JwtUtils.generatorToken(claims));
            return map;
        }else{
            map.put("state","500");
            map.put("message","login failure");
            return map;
        }
    }
}

ResourceController resource service

Define a resource service object. After successful login, you can access the methods in this object, for example:

package com.cy.jt.security.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResourceController {
    @RequestMapping("/retrieve")
    public String doRetrieve(){
        //Check whether the user is logged in
        //Execute business query
        return "do retrieve resource success";
    }
    @RequestMapping("/update")
    public String doUpdate(){
        //Check whether the user is logged in
        //Execute business query
        return "do update resource success";
    }
}

TokenInterceptor interceptor and configuration

If the legitimacy of user identity is verified in each method, the code redundancy will be large. We can write a Spring MVC interceptor,
Perform user identity detection in the interceptor, for example:

package com.cy.jt.security.interceptor;
import com.cy.jt.security.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Token (token:ticker pass) interceptor
 * HandlerInterceptor is the interceptor in Spring MVC,
 * Some actions can be performed before and after the Controller method is executed
 * 1)Handler Processor (the class described by @ RestController is regarded as a processor in Spring MVC)
 * 2)Interceptor Interceptor
 */
public class TokenInterceptor implements HandlerInterceptor {
    /**
     * preHandle Execute before the target Controller method executes
     * @param handler Target Controller object
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        //http://localhost:8080/retrieve?Authentication=
        //String token= request.getParameter("Authentication");
        String token=request.getHeader("Authentication");
        //Determine whether there is a token in the request
        if(token==null||"".equals(token))
            throw new RuntimeException("please login");
        //Determines whether the token has expired
        boolean flag=JwtUtils.isTokenExpired(token);
        if(flag)
        throw new RuntimeException("login timeout,please login");
        return true;//true indicates release, and false indicates that after the request is intercepted, the delivery will not continue
    }
}

After the interceptor is written, you need to add the interceptor to the spring mvc execution chain and set the requests to be intercepted. This process can be completed through the configuration class. The code is as follows:

package com.cy.jt.security.config;
/**
 * Define Spring Web MVC configuration classes
 */
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
    /**Add interceptors to the execution chain of spring mvc
     * @param registry This object provides a list collection to which interceptors can be added
     * */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new TokenInterceptor())
                 //Configure the url to intercept
                 .addPathPatterns("/retrieve","/update");
    }
}

Postman access test

Step 1: login access test

Step 2: conduct resource access test, for example:

Summary

This chapter focuses on the background of JWT, its composition and basic application in the project, which need to be analyzed and understood in practice

Analysis of key and difficult points

  • The background of the birth of JWT? (in case of stateless session under distributed architecture application platform, standardize the token (pass ticket) data format)
  • Data format defined by JWT specification? (header, load - details, signature, thinking about the composition of an article,)
  • Application of JAVA related API s under JWT specification? (jjwt dependency - Jwts)
  • Create and parse tokens based on JAVA API under JWT specification

FAQ analysis

  • What is JWT? (a standard data format)
  • What are the data formats in the JWT specification? (3 parts, the first two parts will be Base64 encoded, and the most will be encrypted based on the signature algorithm)
  • Can the Payload part of JWT be customized? (Claims)
  • Where are JWT token objects typically created? (the server can respond to the client after creating a token)
  • If JWT tokens were to be stored on the client, where would you store them? (Cookie,localStorage,sessionStorage)
  • How does the JWT token pass from the client to the server? (request parameter, request header)

Bug analysis

  • The same key must be used when creating and parsing a token
  • The Token has expired

Keywords: Java jwt

Added by zunebuggy on Wed, 29 Dec 2021 13:31:49 +0200