什么是Jwts
JSON Web Token (JWT)是一种基于 token 的认证方案。
简单的说,JWT就是一种Token的编码算法,服务器端负责根据一个密码和算法生成Token,然后发给客户端,客户端只负责后面每次请求都在HTTP header里面带上这个Token,服务器负责验证这个Token是不是合法的,有没有过期等,并可以解析出subject和claim里面的数据。
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
为什么使用Jwts
传统的垂直架构应用,所有的代码都在一个war包中,用户的请求session通常都是存在tomcat会话中,前端用sessionid来标识本次请求的会话
在分布式架构下,用户的一个请求会跨越多个项目,显然在tomcat中存储已经不合适了
在这张情况下有几种解决方案:
使用独立缓存来储存用户的session,比如存在redis或memcached中;spring框架提供的springSession就是这种解决方案。这种方案的前提必须是所有的服务必须链接同一个缓存中心,不能跨越两个不同的系统
使用JWTs独立存储用户信息,后台使用拦截器对收到的JWTs进行解析,转成实际用户信息再分发给其他的相关服务
Jwts组成
第一部分为头部header,第二部分为载荷payload(可以理解为消息主体),第三部分为签证signature
header
jwt的头部承载两部分信息:
playload
signature
这个签证信息由三部分组成:
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
生成Token
/**
* 用户ID
*/
private static final String CLAIM_KEY_USER_ID = "userId";
/**
* 用户名称
*/
private static final String CLAIM_KEY_USERNAME = Claims.SUBJECT;
/**
* 创建时间
*/
private static final String CLAIM_KEY_CREATED = "created";
//根据用户信息生成token
public String generateToken(LoginUserVo loginUserVo) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USER_ID, loginUserVo.getId());
claims.put(CLAIM_KEY_USERNAME, loginUserVo.getLoginName());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
//从数据声明生成令牌
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
return Jwts.builder()
//注入参数
.setClaims(claims)
//设置过期时间
.setExpiration(expirationDate)
//设置算法及密钥
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
解析Token
//获取Token中的主体
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("JWT格式验证失败:{}", token);
}
return claims;
}
常用操作
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param username 从数据库中查询出来的用户名
*/
public boolean validateToken(String token, String username) {
String usernameToken = getUserNameFromToken(token);
return usernameToken.equals(username) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getClaimsFromToken(token).getExpiration();
return expiredDate.before(new Date());
}
/**
* 当原来的token没过期时是可以刷新的
*
* @param oldToken 带tokenHead的token
*/
public String refreshToken(String oldToken) {
if (StrUtil.isEmpty(oldToken)) {
return null;
}
String token = oldToken.substring(tokenHead.length());
if (StrUtil.isEmpty(token)) {
return null;
}
//token校验不通过
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return null;
}
//如果token已经过期,不支持刷新
if (isTokenExpired(token)) {
return null;
}
//如果token在30分钟之内刚刷新过,返回原token
if (tokenRefreshJustBefore(token, 30 * 60)) {
return token;
} else {
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
/**
* 判断token在指定时间内是否刚刚刷新过
*
* @param token 原token
* @param time 指定时间(秒)
*/
private boolean tokenRefreshJustBefore(String token, int time) {
Claims claims = getClaimsFromToken(token);
Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
Date refreshDate = new Date();
//刷新时间在创建时间的指定时间内
return refreshDate.after(created) && refreshDate.before(DateUtil.offsetSecond(created, time));
}
/**
* 获取请求token
*
* @param request
* @return
*/
private String getToken(HttpServletRequest request) {
String token = request.getHeader(tokenHeader);
if (StringUtils.isNotEmpty(token)) {
token = token.substring(tokenHead.length());
}
return token;
}