package com.whyc.manager;
|
|
import com.auth0.jwt.JWT;
|
import com.auth0.jwt.JWTVerifier;
|
import com.auth0.jwt.algorithms.Algorithm;
|
import com.auth0.jwt.exceptions.TokenExpiredException;
|
import com.whyc.config.JwtProperties;
|
import com.whyc.pojo.User;
|
import com.whyc.util.CommonUtil;
|
import com.whyc.util.EncodesUtil;
|
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.JwtBuilder;
|
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
import javax.servlet.ServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.util.*;
|
|
/**
|
* JWT 管理:
|
* state-base: session,基于状态,用户登录状态 保存于Session中,server-client是关联的
|
* stateless-base: jwt,基于无状态,用户的状态 未保存在Session中,所以web请求可以由任何Server应答,发送的请求头要求包含jwt信息,以便服务器能理解状态
|
*
|
* JWT的应用场景:
|
* 优点:
|
* 1.基于jwt的特性:由于不可篡改和有效期固定,非常适合用于一次性验证
|
* 2.用户禁用cookie时,可以使用
|
* 3.在分布式会话中,某些场景可以使用jwt,因为是无状态的.
|
* 4.前后端分离,cookie在跨域访问禁止携带,jwt适用于跨域认证
|
* 缺点:
|
* 1.如果做会话管理:
|
* -无法续签,用于会话管理需要继承其他框架比如spring security auth2 jwt
|
* -做会话管理的话,如果cookie泄露,无法拒绝 jwt验证,因为是计算机计算校验合法性;而session的话清除session可以导致登录验证失败
|
* -jwt字段长,会造成http请求开销变大
|
*
|
* 本项目使用jwt,需要继承续签框架
|
*
|
*/
|
@Component
|
@Slf4j
|
public class JWTManager {
|
|
@Autowired
|
JwtProperties jwtProperties;
|
|
/**
|
* 签发令牌:
|
* jwt字符串三部分:
|
* 1.header
|
* -字符串类型,一般为"JWT"
|
* -算法加密类型,一般为"HS256"或其他加密算法
|
* 2.payload
|
* 常见以下四个标准字段:
|
* st:签发时间
|
* jti:JWT唯一标识
|
* signer:签发人,一般为username或者userId
|
* exp:过期时间
|
* 3.signature
|
* 对1和2进行header中的算法加入签证秘钥,防止令牌被篡改
|
* @param signer 签发人
|
* @param ttlMillis 令牌有效时长
|
* @param sessionId 唯一标识
|
* @param claims 声明map,可携带其他自定义信息
|
* @return
|
*/
|
public String issueToken(String signer, long ttlMillis, String sessionId, Map<String,Object> claims){
|
if(claims==null){
|
claims = new HashMap<>();
|
}
|
long mowMillis = System.currentTimeMillis();
|
//签名秘钥
|
String hexEncodedSecretKey = EncodesUtil.encodeHex(this.jwtProperties.getHexEncodedSecretKey().getBytes());
|
|
JwtBuilder jwtBuilder = Jwts.builder()
|
.setClaims(claims)
|
//唯一标识
|
.setId(sessionId)
|
//签发时间
|
.setIssuedAt(new Date(mowMillis))
|
//签发人
|
.setSubject(signer)
|
//签证秘钥
|
.signWith(SignatureAlgorithm.HS256, hexEncodedSecretKey.getBytes());
|
if(ttlMillis>=0) {
|
//过期时间
|
long expMillis = mowMillis + ttlMillis;
|
Date exp = new Date(expMillis);
|
jwtBuilder.setExpiration(exp);
|
}
|
|
return jwtBuilder.compact();
|
}
|
|
/**
|
* 解析令牌
|
* @param jwt 令牌
|
* @return
|
*/
|
public Claims decodeToken(String jwt){
|
String encodeHex = EncodesUtil.encodeHex(jwtProperties.getHexEncodedSecretKey().getBytes());
|
//得到DefaultJwtParser
|
return Jwts.parser()
|
//设置签名的秘钥
|
.setSigningKey(encodeHex.getBytes())
|
//可以偏移24h,过期1天内可以获取.超过1天会登录失败
|
.setAllowedClockSkewSeconds(86400000)
|
//设置需要解析的jwt
|
.parseClaimsJws(jwt)
|
.getBody();
|
|
}
|
|
/**
|
* 判断令牌是否合法
|
* @param jwt 令牌
|
* @return
|
*/
|
public boolean verifyToken(String jwt, ServletRequest request){
|
boolean res = false;
|
String encodeHex = EncodesUtil.encodeHex(jwtProperties.getHexEncodedSecretKey().getBytes());
|
//这是官方的校验规则,可以自定义
|
Algorithm algorithm = Algorithm.HMAC256(encodeHex);
|
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
|
try {
|
//校验不通过会抛出异常
|
//判断合法的标准:1.header和payload没有被篡改
|
// 2.没有过期
|
//增加:首先判断jwt是否有效,在内存中存在,实际上已经将jwt状态化了
|
List jwts = (List) request.getServletContext().getAttribute("jwts");
|
if(jwts!=null && jwts.contains(jwt)) {
|
jwtVerifier.verify(jwt);
|
res = true;
|
}
|
}catch (TokenExpiredException expiredException){
|
log.error("expiredException");
|
//对于空闲时间小于30分钟的,过期了也可以通过校验,变相续签
|
Claims claims = decodeToken(jwt);
|
String userJson = (String) claims.get("user");
|
User user = CommonUtil.toUser(userJson);
|
Date date = new Date();
|
//System.out.println("当前时间:"+date);
|
Date date1 = new Date((Long) request.getServletContext().getAttribute("exp_" + user.getName()));
|
//System.out.println("最新活跃时间:"+date1);
|
if (System.currentTimeMillis()-(Long) request.getServletContext().getAttribute("exp_" + user.getName()) <= 180000) {
|
//System.out.println("距离最新活跃时间小于3分钟");
|
res = true;
|
}
|
}catch (Exception e){
|
log.error("exception");
|
}
|
return res;
|
}
|
|
}
|