Spring Boot integrates JWT to achieve front-end and back-end authentication

Get into the habit of writing together! This is the 10th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event

foreword

The rapid development of small programs and H5 applications has made the front-end and back-end separation a trend. However, system authentication is an important part of the system. This article will explain how JWT realizes front-end and back-end authentication.

Introduction to JWT

JWT (full name: Json Web Token) is an open standard (RFC 7519) that defines a compact, self-contained way to securely transmit information between parties as JSON objects.

Why use JWT

2.1 What are the disadvantages of traditional session authentication?

  • Each user's login information will be stored in the server's session. As the number of users increases, the server overhead will increase significantly.

  • The session information is stored in the server's memory, which will lead to failure for distributed applications. Although the session information can be stored in the Redis cache uniformly, this may increase the complexity.

  • Since session authentication is implemented based on cookies, it is not applicable to non-browser and mobile mobile terminals.

  • The front-end and back-end separation system, because the front-end and back-end are cross-domain, and the cookie information cannot be crossed, so the use of session authentication is also unable to continue cross-domain authentication.

2.2 Advantages of JWT Authentication

  • Concise: JWT Token has a small amount of data and a fast transmission speed.

  • Cross-language: JWT Token is stored on the client side in JSON encrypted form, so JWT is cross-language and supported by any web form. Cross-platform: It does not depend on cookies and sessions, and does not need to store session information on the server side. It is very suitable for distributed applications and for expansion.

Data structure of JWT

picture.png

Header

The first part of the JWT is the header part, which is a Json object that describes the JWT metadata, usually as follows.

{
    "alg": "HS256",
    "typ": "JWT"
}
复制代码

The alg attribute indicates the algorithm used for the signature, the default is HMAC SHA256 (written as HS256), the typ attribute indicates the type of the token, and the JWT token is uniformly written as JWT.

Payload

The second part of JWT is Payload, which is also a Json object. In addition to the data that needs to be passed, there are seven default fields for selection. iss: issuer exp: expiration time sub: subject aud: user nbf: unavailable until then iat: issue time jti: JWT ID used to identify this JWT

{
    //默认字段
    "sub":"主题123",
    //自定义字段
    "name":"java",
    "isAdmin":"true",
    "loginTime":"2021-12-05 12:00:03"
}
复制代码

It should be noted that JWT is unencrypted by default, and anyone can interpret its content, so if some sensitive information is not stored here, it will prevent information leakage. JSON objects are also saved as strings using the Base64 URL algorithm.

Signature

The signature hash part is to sign the above two parts of data. It is necessary to use the base64-encoded header and payload data to generate the hash through the specified algorithm to ensure that the data will not be tampered with.

Spring Boot integrates JWT

Import the Jwt package

<dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
  </dependency>
复制代码

Write jwt tool class


public class JwtUtil
{
//创建jwt
public static String createJWT(String subject, String issue, Object claim,
            long ttlMillis)
    {
       //当前时间
        long nowMillis = System.currentTimeMillis();
        //过期时间
        long expireMillis = nowMillis + ttlMillis;
        String result = Jwts.builder()
                .setSubject(subject) //设置主题
                .setIssuer(issue) //发行者
                .setId(issue)//jwtID
                .setExpiration(new Date(expireMillis)) //设置过期日期
                .claim("user", claim)//主题,可以包含用户信息
                .signWith(getSignatureAlgorithm(), getSignedKey())//加密算法
                .compressWith(CompressionCodecs.DEFLATE).compact();//对载荷进行压缩

        return result;
    }
    
    // 解析jwt
    public static Jws<Claims> pareseJWT(String jwt)
    {
        Jws<Claims> claims;
        try
        {
            claims = Jwts.parser().setSigningKey(getSignedKey())
                    .parseClaimsJws(jwt);
        }
        catch (Exception ex)
        {
            claims = null;
        }
        return claims;
    }

   //获取主题信息
    public static Claims getClaims(String jwt)
    {
        Claims claims;
        try
        {
            claims = Jwts.parser().setSigningKey(getSignedKey())
                    .parseClaimsJws(jwt).getBody();
        }
        catch (Exception ex)
        {
            claims = null;
        }
        return claims;
    }
  }
  
   /**
     * 获取密钥
     * 
     * @return Key
     */
    private static Key getSignedKey()
    {
        byte[] apiKeySecretBytes = DatatypeConverter
                .parseBase64Binary(getAuthKey());
        Key signingKey = new SecretKeySpec(apiKeySecretBytes,
                getSignatureAlgorithm().getJcaName());
        return signingKey;
    }
    
    private static SignatureAlgorithm getSignatureAlgorithm()
    {
        return SignatureAlgorithm.HS256;
    }
  
  //获取密钥,可以动态配置
  public static String getAuthKey()
  {
        String auth = "123@#1234";
  }
      
复制代码

Token Authentication Interceptor

 Component
public class TokenInterceptor extends HandlerInterceptorAdapter
{
    public static Log logger = LogManager.getLogger(TokenInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception
    {
        String uri = request.getRequestURI();
        logger.info("start TokenInterceptor preHandle.." + uri);
         
		//需要过滤特殊请求
        if (SystemUtil.isFree(uri) || SystemUtil.isProtected(uri))
        {
            return true;
        }
        
		
        String metohd=request.getMethod().toString();
        logger.info("TokenInterceptor request method:"+metohd);
        //options 方法需要过滤
        if("OPTIONS".equals(metohd))
        {
            return true;   
        }
        
		//是否开启token认证
        boolean flag = SystemUtil.getVerifyToken();
        ResponseResult result = new ResponseResult();
		//从请求的head信息中获取token
        String token = request.getHeader("X-Token");

        if (flag)
        {
            if(StringUtils.isEmpty(token))
            {
                token=request.getParameter("X-Token");    
            }
            // token不存在
            if (StringUtils.isEmpty(token))
            {
                result.setCode(ResultCode.NEED_AUTH.getCode());
                result.setMsg(ResultCode.NEED_AUTH.getMsg());
                WebUtil.writeJson(result, response);
                return false;
            }
            else
            {
                Claims claims = JwtUtil.getClaims(token);
                String subject = "";
                if (claims != null)
                {
                    subject = claims.getSubject();
                    // 验证主题
                    if (StringUtils.isEmpty(subject))
                    {
                        result.setCode(ResultCode.INVALID_TOKEN.getCode());
                        result.setMsg(ResultCode.INVALID_TOKEN.getMsg());
                        WebUtil.writeJson(result, response);
                        return false;
                    }								
                }
                else
                {
                    result.setCode(ResultCode.INVALID_TOKEN.getCode());
                    result.setMsg(ResultCode.INVALID_TOKEN.getMsg());
                    WebUtil.writeJson(result, response);
                    return false;
                }
            }
        }
        return true;
    }

}
复制代码

Configure the interceptor

@Configuration
public class WebConfig implements WebMvcConfigurer
{
    @Resource
    private TokenInterceptor tokenInterceptor;

    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
 }
复制代码

Login verification process

picture.png

sample code

@RequestMapping("login")
public Result login(HttpServletResponse response)
{
    Map<String, Object> map = new HashMap<>();
    //
    Result result = loginAuth(user);
    int code = result.getCode();
            //登录认证成功
    if (code ==ResultCode.SUCCESS)
    {
        //默认为7天
        Long ttlMillis = 7*1000 * 60 * 60 * 24;
        //过期时间
        long expreTime = System.currentTimeMillis() + ttlMillis;
        String tokenKey = UUID.randomUUID().toString();
        String tokenId = JwtUtil.createJWT(user.getUserId(), tokenKey,
                user.getPassword(), expreTime);                   
        map.put("expreTime", expreTime);				
        map.put("tokenId", tokenId);           
    }
    else
    {
        logger.error("login error:" +FastJsonUtil.toJSONString(result));
    }
    return result;
}
复制代码

Summarize

The times are advancing and technology is constantly being updated. In the past, Session+Redis was used to implement single sign-on, but now it can be replaced by jwt+Redis implementation. Only by constantly updating our technology stack can we avoid being ruthlessly eliminated. Regarding the use of annotation methods Implementation and token refresh will be explained in subsequent articles.

Guess you like

Origin juejin.im/post/7085364569916309535