JWT (Principle + Process + Example)

1. Introduction

JSON Web Tokens (JWT), jwt is widely used in user authentication of the system, especially in current front-end and back-end separation projects

1. JWT authentication process

What are the differences in authentication between traditional token methods and jwt?

  • Traditional token method
    After the user successfully logs in, the server generates a random token for the user,and saves a token in the server (database or cache). The user needs to bring the token when he visits again in the future, and the server receives the token. After that, go to the database or cache to verify whether the token has timed out and whether it is legal.

  • jwt way
    After the user successfully logs in, the server generates a random token to the user through jwt (the server does not need to retain the token). The user needs to bring the token when visiting again in the future. After the server receives the token, it uses jwt to compare the token Verify whether it times out and whether it is legal.

Advantages of JWT

  • Compact: It can be sent through URL, POST parameters or in HTTP header. The data volume is small and the transmission speed is fast.
  • Self-contained: The payload contains the information required by the user, avoiding multiple queries to the database
  • Because the Token is stored on the client in JSON encrypted form, JWT is cross-language and in principle supported by any web form.
  • No need to save session information on the server side, especially suitable for distributed microservices

2. Cross-domain authentication issues

Internet projects are inseparable from the interaction between servers and clients. Interaction is inseparable from authentication, and JWT came into being.
The token model is not bad, the problem is that the scaling is not good. Of course, there is no problem with a single machine. If it is a server cluster, or a cross-domain service-oriented architecture, session data sharing is required, and each server can read the session.

For example, website A and website B are related services of the same company. Now it is required that as long as the user logs in on one of the websites, he will automatically log in when he visits another website. How can this be achieved?

One solution is to persist session data and write it to the database or other persistence layer. After receiving the request, various services request data from the persistence layer. The advantage of this solution is that the structure is clear, but the disadvantage is that the amount of work is relatively large. In addition, if the persistence layer fails, it will be a single point of failure.

Another solution is for the server to simply not save session data. All data is saved on the client and sent back to the server for each request. JWT is a representative of this solution.

2. JWT principle

The principle of JWT is that after the server authenticates, it generates a JSON object and sends it back to the user, as shown below.

{
  "Name": "Zhang San",
  "role": "admin",
  "Expiration time": "July 1, 2018 0:00"
}

In the future, when the user communicates with the server, this JSON object will be sent back. The server relies solely on this object to identify the user. In order to prevent users from tampering with data, the server will add a signature when generating this object.

The server does not save any session data, which means that the server becomes stateless, making it easier to expand.

1. JWT data structure

It is a long string separated into three parts by dots (.).

The three parts of a JWT are as follows.

  • Header
  • Payload
  • Signature

Header.Payload.Signature

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

In the above code, the alg attribute represents the signature algorithm (algorithm), the default is HMAC SHA256 (written as HS256); the typ attribute represents the type of this token (token) ), JWT tokens are uniformly written as JWT.

Finally, convert the above JSON object into a string using the Base64URL algorithm (see below for details).

Payload

The Payload part is also a JSON object, used to store the actual data that needs to be transferred. JWT specifies 7 official fields for selection.

iss (issuer): issuer
exp (expiration time): expiration time
sub (subject):subject
aud (audience): audience
nbf (Not Before): Effective time
iat (Issued At): issuance time
jti (JWT ID): number

In addition to official fields, you can also define private fields in this section. Here 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 do not put secret information in this section.

This JSON object must also be converted into a string using the Base64URL algorithm.

Signature

The Signature part is a signature of the first two parts to prevent data tampering.

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

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

After calculating the signature, combine the three parts of Header, Payload, and Signature into a string, and separate each part with a “dot” (.) before returning it to the user.

Base64URL

As mentioned earlier, the algorithm for serializing Header and Payload is Base64URL. This algorithm is basically similar to the Base64 algorithm, but there are some minor differences.

As a token, JWT may be placed in the URL in some cases (such as api.example.com/?token=xxx). Base64 has three characters +, / and =, which have special meanings in URLs, so they must be replaced: = is omitted, + is replaced with -, and / is replaced with _. This is the Base64URL algorithm.

2. Common exception information in JWT

AlgorithmMismatchException Algorithm mismatch exception (that is, the algorithms used are inconsistent)
SignatureVerificationException Signature inconsistency exception (that is, the signature used is inconsistent)
InvalidClaimException Invalid payload exception (that is, the data has been tampered with)
TokenExpiredException Token expiration exception

3. Several characteristics of JWT

(1) JWT is not encrypted by default, but it can also be encrypted. After generating the original Token, it can be encrypted again with the key.

(2) If JWT is not encrypted, secret data cannot be written to JWT.

(3) JWT can be used not only for authentication, but also for exchanging information. Effective use of JWT can reduce the number of times the server queries the database.

(4) The biggest disadvantage of JWT is that because the server does not save the session state, it cannot revoke a token or change the permissions of the token during use. That is, once a JWT is issued, it remains valid until it expires, unless the server deploys additional logic.

(5) JWT itself contains authentication information. Once leaked, anyone can obtain all permissions of the token. To reduce theft, the validity period of JWT should be set relatively short. For some more important permissions, users should be authenticated again when using them.

(6) In order to reduce theft, JWT should not be transmitted in plain code using HTTP protocol, but should be transmitted using HTTPS protocol.

3. Use of JWT

The client receives the JWT returned by the server, which can be stored in Cookie or in localStorage.

From then on, every time the client communicates with the server, it must bring this JWT. You can put it in a cookie and send it automatically, but this cannot cross domains,so a better approach is to put it in the Authorization field of the HTTP request header.

Authorization: Bearer <token>

Another approach is to put the JWT in the data body of the POST request when crossing domains.

Examples of takeaway items:

JwtProperties (encapsulates two tokens;)
 /**
     * Related configurations for management-side employees to generate jwt tokens
     */
    private String adminSecretKey;
    private long adminTtl;
    private String adminTokenName;

    /**
     * Configuration related to jwt token generation by WeChat users on the client side
     */
    private String userSecretKey;
    private long userTtl;
    private String userTokenName;
JwtUtil (jwt encryption and decryption tool class, expiration time and other settings)
public class JwtUtil {<!-- -->
   /**
    * Generate jwt
    * Use Hs256 algorithm, use fixed secret key for private key
    *
    * @param secretKey jwt secret key
    * @param ttlMillis jwt expiration time (milliseconds)
    * @param claims information set
    * @return
    */
   public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {<!-- -->
       // Specify the signature algorithm used when signing, that is, the header part
       SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

       //Time to generate JWT
       long expMillis = System.currentTimeMillis() + ttlMillis;
       Date exp = new Date(expMillis);

       //Set the body of jwt
       JwtBuilder builder = Jwts.builder()
               // If there is a private statement, you must first set the private statement you created. This is to assign a value to the builder's claim. Once it is written after the standard statement assignment, it will overwrite those standard statements.
               .setClaims(claims)
               //Set the signature algorithm used for signature and the secret key used for signature
               .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
               //Set expiration time
               .setExpiration(exp);

       return builder.compact();
   }

   /**
    * Token decryption
    *
    * @param secretKey jwt secret key This secret key must be kept on the server side and cannot be exposed.
    * Otherwise, the sign can be forged. If you connect to multiple clients, it is recommended to change it to multiple
    * @param token encrypted token
    * @return
    */
   public static Claims parseJWT(String secretKey, String token) {<!-- -->
       // Get DefaultJwtParser
       Claims claims = Jwts.parser()
               //Set the signature key
               .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
               //Set the jwt that needs to be parsed
               .parseClaimsJws(token).getBody();
       return claims;
   }

Interceptor
 @Autowired
    private JwtProperties jwtProperties;

    /**
     * Verify jwt
     *
     * @param request
     * @param response
     * @param handler target method
     * @return true, means release, false, means release is prohibited
     * @throwsException
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {<!-- -->
        //Determine whether the currently intercepted method is the Controller's method or other resources
        if (!(handler instanceof HandlerMethod)) {<!-- -->
            //The currently intercepted method is not a dynamic method, so let it go directly.
            return true;
        }

        //1. Obtain the token from the request header because the token is stored in the request header.
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2. Verification token
        try {<!-- -->
            log.info("jwt verification:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("Current employee id:", empId);
            //3. Pass, release
            return true;
        } catch (Exception ex) {<!-- -->
            //4. If it fails, respond with 401 status code
            response.setStatus(401);
            return false;
        }
    }
Configure the jwt interceptor into a custom interceptor
 @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

    /**
     * Register custom interceptor
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {<!-- -->
        log.info("Start registering custom interceptor...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
    }
controller implementation
 @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {<!-- -->
        log.info("Employee login: {}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //After successful login, generate jwt token
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

Reference: https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

syntaxbug.com © 2021 All Rights Reserved.