JWT实践原理以及自己生成token案例

JWT实践

jwt教程网站 https://jwt.io/

JWT引入依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

JWT生成和解析工具类

package com.asiainfo.framework.util;

import cn.hutool.core.bean.BeanUtil;
import com.asiainfo.framework.dto.LoginUseInfo;
import com.asiainfo.framework.enums.ExceptionEnums;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Calendar;
import java.util.Map;


@Slf4j
public class JwtUtil {
    private static final String KEY = "aaa";

    public static String generateToken(LoginUseInfo loginUseInfo) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 1);
        Map<String, Object> userInfo = BeanUtil.beanToMap(loginUseInfo);
        JwtBuilder builder = Jwts.builder()
                .setClaims(userInfo)
                .setExpiration(calendar.getTime())
                .signWith(SignatureAlgorithm.HS256, KEY);
        return builder.compact();
    }

    public static LoginUseInfo verifyToken(String tokenToBeVerify) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(KEY) // 使用签名用到的key
                    .parseClaimsJws(tokenToBeVerify).getBody();
        } catch (SignatureException e) {
            log.error("签名异常:{}", e.getMessage());
            ExceptionEnums.TOKEN_PARSE_ERROR.error();
        } catch (ExpiredJwtException e) {
            log.error("令牌过期:{}", e.getMessage());
            ExceptionEnums.TOKEN_PARSE_ERROR.error();
        } catch (MalformedJwtException e) {
            log.error("token格式不对:{}", e.getMessage());
            ExceptionEnums.TOKEN_PARSE_ERROR.error();
        } catch (Exception e) {
            log.error("token校验异常", e.getMessage());
            ExceptionEnums.TOKEN_PARSE_ERROR.error();
        }
        return BeanUtil.mapToBean(claims, LoginUseInfo.class, true);
    }
}

生成token时可以设置Claim来设置token传输中的数据,并且设置过期时间,指定签名算法和盐,加密的盐和解密的盐要一样

在application.yml里面配置拦截器的白名单

# jwt 配置相关
aif:
  auth:
    jwt:
      uncheckUrls:
        - /openapi/**
        - /feign/**

设置加载类

@Configuration
@ConfigurationProperties("aif.auth.jwt")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtSsoProperties {
    private List<String> uncheckUrls ;
    private boolean checkIp = false;

    @PostConstruct
    public void init() {
        for (String uncheckUrl:uncheckUrls){
            System.out.println(uncheckUrl);
        }
    }
}

设置jwt的拦截器校验请求头里面的token

package com.asiainfo.framework.interceptor;

import com.asiainfo.framework.config.JwtSsoProperties;
import com.asiainfo.framework.dto.LoginUseInfo;
import com.asiainfo.framework.util.JwtUtil;
import com.asiainfo.framework.util.UserInfoManager;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @description:
 * @author: Mr.Wang
 * @create: 2021-11-03 18:06
 **/
@Component
public class JwtInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(JwtInterceptor.class);
    final JwtSsoProperties jwtSsoProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();


    public JwtInterceptor(JwtSsoProperties jwtSsoProperties) {
        this.jwtSsoProperties = jwtSsoProperties;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("ai-jwt-token");
        if (StringUtils.isBlank(token)) {
            token = this.getCookieToken(request);
            log.info("header中没有token,尝试从cookie中获取,token ={}", token);
        }
//        System.out.println(request.getRequestURI());
//        System.out.println(request.getHeader("ai-jwt-token"));
        boolean needCheck = this.needLoginCheck(request);
        if (needCheck) {
            LoginUseInfo loginUseInfo = JwtUtil.verifyToken(token);
            UserInfoManager.getInstance().setUser(loginUseInfo);
            if (loginUseInfo != null) {
                UserInfoManager.getInstance().setUser(loginUseInfo);
                return true;
            } else {
                return false;
            }
        }
//       如果为白名单并且token非空也解析token 为了在application中配置feign调用的方式为白名单,并且解析feign之间调用token拿到当前用户
        if (!StringUtils.isBlank(token)) {
            LoginUseInfo loginUseInfo = JwtUtil.verifyToken(token);
            UserInfoManager.getInstance().setUser(loginUseInfo);
            if (loginUseInfo != null) {
                UserInfoManager.getInstance().setUser(loginUseInfo);
            }
        }
        return true;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfoManager.getInstance().clear();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    private String getCookieToken(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            Cookie[] var3 = cookies;
            int var4 = cookies.length;

            for (int var5 = 0; var5 < var4; ++var5) {
                Cookie cookie = var3[var5];
                String name = cookie.getName();
                if ("ai-jwt-token".equals(name)) {
                    String value = cookie.getValue();
                    if (Strings.isNotBlank(value)) {
                        return value;
                    }
                }
            }
        }

        return null;
    }

    private boolean needLoginCheck(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (uri.contains(".")) {
            String suffix = uri.substring(uri.lastIndexOf(".") + 1);
            List<String> resources = Arrays.asList("map,json,xml,ts,js,css,less,sass,scss,jpeg,jpg,png,gif,bmp,svg,ttf,eot,woff,woff2".split(","));
            if (StringUtils.isNotBlank(suffix) && resources.contains(suffix.toLowerCase())) {
                return false;
            }
        }

        List<String> uncheckUrls = this.jwtSsoProperties.getUncheckUrls();
        if (uncheckUrls == null) {
            return true;
        } else {
            Iterator var7 = uncheckUrls.iterator();

            String uncheckUrl;
            do {
                if (!var7.hasNext()) {
                    return true;
                }

                uncheckUrl = (String) var7.next();
            } while (!this.antPathMatcher.match(uncheckUrl, uri));

            return false;
        }
    }
}

JWT原理

jwt组成

  • Header
  • Payload
  • Signature

jwt组成形式xxxxx.yyyyy.zzzzz

head

head部分json表明类型以及签名的算法例如HMAC或者SHA256或者RSA,在使用base64加密算法进行加密组成token的第一个部分

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

payload

token里面的一些声明既claim可以存入其中,例如一些用户信息在经过base64加密组成token的第二部分

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

Signature

生成方式,将headerbase64解密,payloadbase64解密拼接使用HMACSHA256指定盐进行加密再进行base64加密返回字符串

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

自己生成token DEMO

package com.demo.jwt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.junit.Test;
import org.springframework.util.Base64Utils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;


/**
 * @description:
 * @author: Mr.Wang
 * @create: 2021-11-06 09:55
 **/

public class JwtTest {
    String charset = "utf-8";
    /*
    * 盐
    * */
    String key = "123";
    /*
    * 自己模拟生成jwt的token
    * */
    @Test
    public void testMyJwt() throws Exception{
        JSONObject originalhead = new JSONObject();
        originalhead.put("type","JWT");
        originalhead.put("alg","HS256");
        String header = Base64Utils.encodeToUrlSafeString(originalhead.toJSONString().getBytes(charset));
//      获取payload
        JSONObject originalPayload = new JSONObject();
        User user = new User();
        user.setName("aaa");
        originalPayload.put("userinfo", JSON.toJSON(user));
        String payload = Base64Utils.encodeToUrlSafeString(originalPayload.toJSONString().getBytes(charset));
//        获取signature
        String signature = hmacSha256Encode(header,payload);
        String token = String.format("%s.%s.%s",header,payload,signature);
        System.out.println(token);

    }
    public String hmacSha256Encode(String header,String payload) throws Exception {
//        获取摘要算法的对象
        Mac hmac256 = Mac.getInstance("HmacSHA256");
//        指定算法的key
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(charset),"HmacSHA256");
        hmac256.init(secretKeySpec);
        hmac256.update(header.getBytes(charset));
        hmac256.update(".".getBytes(charset));
        byte[] bytes = hmac256.doFinal(payload.getBytes(charset));
        return Base64.encode(bytes);
    }
}

运行结果
在这里插入图片描述

补充JWT使用redis续约方案

https://blog.csdn.net/weixin_39255905/article/details/115201764?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163713902416780269871323%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163713902416780269871323&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-18-115201764.pc_search_result_cache&utm_term=token%E7%BB%AD%E7%BA%A6%E4%B8%8D%E7%94%A8redis&spm=1018.2226.3001.4187

おすすめ

転載: blog.csdn.net/Persistence___/article/details/121174567