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);
}
}
运行结果