2

JWT

JWT (JSON Web Token) is an open standard based on JSON that is implemented to transfer claims between web application environments.

Example login process



personally organize some information here, friends in need can click to receive it directly.

Basic knowledge of Java

22 core Java architect books

Java learning routes and materials from 0 to 1

1000+ 2021 latest

composition

What exactly does JWT look like?
JWT is composed of three pieces of information, and these three pieces of information text are combined with a link to form a JWT string. like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

element

header

The header of the JWT carries two parts of information:

  • Declaration type, here is JWT;
  • Declare the encryption algorithm, usually HMAC SHA256 directly;
  • The complete header looks like the following JSON:
{
  'typ': 'JWT',
  'alg': 'HS256'
}

Use base64 encryption to form the first part.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload (emphasis)

The payload is where the valid information is stored. The valid information consists of three parts:

  • Declaration registered in the standard;
  • Public statement
  • Private statement

Among them, the statement registered in the standard (recommended but not mandatory) includes the following parts:

  • iss: jwt issuer;
  • sub: the user targeted by jwt;
  • aud: the party receiving jwt;
  • exp: the expiration time of jwt, this expiration time must be greater than the issuance time;
  • nbf: Define the time before the jwt is unavailable;
  • iat: the issuance time of jwt;
  • The unique identity of jwt is mainly used as a one-time token to avoid replay attacks;

Public statement part: Any information can be added to the public statement, generally adding user-related information or other necessary information required by the business, but it is not recommended to add sensitive information, because this part can be decrypted on the client.

Private declaration part: A 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.

Define a payload:

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

Then encrypt it with base64 to get the second part of Jwt:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

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

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
 
 
var signature = HMACSHA256(encodedString, '密钥');
加密之后,得到signature签名信息。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

jwt final format

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

The secret is used for the issuance of jwt and the verification of jwt, so it should not be exposed in any scene.

element

SpringBoot integrates JWT [positive film]

Introduce dependencies

<!--token-->
    <!-- jwt支持 -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
    </dependency>

Create JWT tool class

Pay attention to the configuration file injection method of static properties:

package com.neuq.common.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.neuq.common.exception.ApiException;
import com.neuq.common.response.ResultInfo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author: xiang
 * @Date: 2021/5/11 21:11
 * <p>
 * JwtToken生成的工具类
 * JWT token的格式:header.payload.signature
 * header的格式(算法、token的类型),默认:{"alg": "HS512","typ": "JWT"}
 * payload的格式 设置:(用户信息、创建时间、生成时间)
 * signature的生成算法:
 * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 */

@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTUtils {

    //定义token返回头部
    public static String header;

    //token前缀
    public static String tokenPrefix;

    //签名密钥
    public static String secret;

    //有效期
    public static long expireTime;

    //存进客户端的token的key名
    public static final String USER_LOGIN_TOKEN = "USER_LOGIN_TOKEN";

    public void setHeader(String header) {
        JWTUtils.header = header;
    }

    public void setTokenPrefix(String tokenPrefix) {
        JWTUtils.tokenPrefix = tokenPrefix;
    }

    public void setSecret(String secret) {
        JWTUtils.secret = secret;
    }

    public void setExpireTime(int expireTimeInt) {
        JWTUtils.expireTime = expireTimeInt*1000L*60;
    }

    /**
     * 创建TOKEN
     * @param sub
     * @return
     */
    public static String createToken(String sub){
        return tokenPrefix + JWT.create()
                .withSubject(sub)
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
                .sign(Algorithm.HMAC512(secret));
    }


    /**
     * 验证token
     * @param token
     */
    public static String validateToken(String token){
        try {
            return JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getSubject();
        } catch (TokenExpiredException e){
            throw new ApiException(ResultInfo.unauthorized("token已经过期"));
        } catch (Exception e){
            throw new ApiException(ResultInfo.unauthorized("token验证失败"));
        }
    }

    /**
     * 检查token是否需要更新
     * @param token
     * @return
     */
    public static boolean isNeedUpdate(String token){
        //获取token过期时间
        Date expiresAt = null;
        try {
            expiresAt = JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getExpiresAt();
        } catch (TokenExpiredException e){
            return true;
        } catch (Exception e){
            throw new ApiException(ResultInfo.unauthorized("token验证失败"));
        }
        //如果剩余过期时间少于过期时常的一般时 需要更新
        return (expiresAt.getTime()-System.currentTimeMillis()) < (expireTime>>1);
    }
}

yaml attribute configuration

jwt:
  header: "Authorization" #token返回头部
  tokenPrefix: "Bearer " #token前缀
  secret: "qwertyuiop7418520" #密钥
  expireTime: 1 #token有效时间 (分钟) 建议一小时以上

The login method saves the user information in the token and returns

  @Override
  public Map<String,Object> login(User user) {
      //phone是除id外的唯一标志 需要进行检查
      if (user.getPhone() == null || user.getPhone().equals(""))
          throw new ApiException("手机号不合法");
      User selectUser = userDao.selectUserByPhone(user.getPhone());
      if (selectUser == null) {
          //注册用户
          int count = userDao.insertUser(user);
          if (count < 1) throw new ApiException(ResultInfo.serviceUnavailable("注册异常"));
      }
      //将userId存入token中
      String token = JWTUtils.createToken(selectUser.getUserId().toString());
      Map<String,Object> map = new HashMap<>();
      map.put("user",selectUser);
      map.put("token",token);
      return map;
  }

Note that the token is saved to the Http header

    @GetMapping("/login")
    public ResultInfo login(User user, HttpServletResponse response) {
        Map<String, Object> map = userService.login(user);
        //将token存入Http的header中
        response.setHeader(JWTUtils.USER_LOGIN_TOKEN, (String) map.get("token"));
        return ResultInfo.success((User)map.get("user"));
    }

The interceptor verifies the token of each request

/**
 * @Author: xiang
 * @Date: 2021/5/7 20:56
 * <p>
 * 拦截器:验证用户是否登录
 */
public class UserLoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //http的header中获得token
        String token = request.getHeader(JWTUtils.USER_LOGIN_TOKEN);
        //token不存在
        if (token == null || token.equals("")) throw new ApiException("请先登录");
        //验证token
        String sub = JWTUtils.validateToken(token);
        if (sub == null || sub.equals(""))
            throw new ApiException(ResultInfo.unauthorized("token验证失败"));
        //更新token有效时间 (如果需要更新其实就是产生一个新的token)
        if (JWTUtils.isNeedUpdate(token)){
            String newToken = JWTUtils.createToken(sub);
            response.setHeader(JWTUtils.USER_LOGIN_TOKEN,newToken);
        }
        return true;
    }
}
@Configuration
@ComponentScan(basePackages = "com.neuq.common") //全局异常处理类需要被扫描才能
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserLoginInterceptor())
                .addPathPatterns("/user/**")
                .addPathPatterns("/userInfo/**")
                .excludePathPatterns("/user/login");//开放登录路径
    }

}

sign in

Store the token or a unique identifier UUID=UUID.randomUUID().toString() in the Cookie (do not store it in the Http header), and set the path to the root path of the entire project/*;
This unique identifier is often used as the key, and the user information is cached in the server as the value! ! !

At last

Thanks to the big guys for seeing the end, think the article is helpful to you, remember to like it!


前程有光
936 声望618 粉丝