什么是JWT
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全传输信息。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用****RSA或ECDSA的公钥/私钥对进行签名。
JWT的结构
JWT是由三段字符串组成的,中间使用点分隔,第一部分为头部(header),第二部分为载荷(payload),第三部分为签证(signture)。
头部主要有俩部分组成:令牌的类型和签名算法,使用Basement4Url编码作为令牌的第一部分。
载荷主要是存放主要信息的地方,官方定义有三种声明,注册声明、公共声明、私人声明
- 注册声明:这些是一组预定义的声明,它们不是强制性的,但建议使用,以提供一组有用的、可互操作的声明。其中一些是:** iss(发行人)、 exp(到期时间)、 sub(主题)、 aud**(受众)等。
- 公共声明:这些可以由使用 JWT 的人随意定义。但是为了避免冲突,它们应该在IANA JSON Web Token Registry中定义,或者定义为包含抗冲突命名空间的 URI。
-
私人声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。
其实也就是用户的信息保存到载荷中,使用Basement4Url编码作为令牌的第二部分。
签名
就是将头部和载荷编码之后的字符串一起加上一个盐值(secret,主要是放在服务器端,用来颁发token)再用头部中的签名算法进行加密形成第三部分。
JWT的简单使用
对应依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
一个简单的Jwt工具类
public class JwtUtil {
/**
* 设置token的超时时间为一小时,方便测试
*/
private final static int TOKEN_TIME_OUT = 3600 * 1000;
/**
* jwt第三部分加密的时候使用的盐值
*/
private final static String TOKEN_SECRET = "oneyi";
/**
* 获取token
* @param map
* @return
*/
public static String getToken(Map map){
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512,TOKEN_SECRET) // 加密算法 盐值
.setClaims(map) // 需要的数据 (载荷)
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_TIME_OUT)) // 有效时间
.compact();
}
/**
* 获得token中的claims信息
* @param token
* @return
*/
public static Claims getClaims(String token){
return Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token)
.getBody();
}
/**
* 验证token是否有效
* 当token失效,取claims会抛出异常,即可证明失效
* @param token
* @return
*/
public static boolean verifyToken(String token){
//如果为空直接返回false
if(StringUtils.isEmpty(token)){
return false;
}
try{
Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
return false;
}
return true;
}
}
JWT衍生出的问题
token是设置了有效时间,如果客户注销登录,这个token怎么处理。如果仅仅删除客户端的token那当然不行,因为token一旦泄露,别人带着这个token来访问服务器一样也会通过。
- 方案一:修改密码或者退出登录修改一下盐值(secret),这样上一次的token就没用了,所以在写JWT工具类的时候,盐值尽量可以与用户相关同时有要是动态的。
- 方案二:使用数据库,存放一个修改或者注销的时间,在创建token时,带上创建时间,如果创建时间小于修改或注销的时间,就表示他是修改或者注销的token,即为无效token。
关于token的续期问题,也是俩种方案
- 在token中不设置过期时间,在redis中设置过期时间,redis存储用户信息和过期时间,用来和token中的用户信息作比较。在有效期内用户进行访问服务端成功,就增加过期时间,当然这个可以在一定范围内刷新每次都刷新会有性能问题。
- 使用sccess_token和refresh_token方案,refresh_token主要就是用来刷新sccess_token,为了保证refresh_token可以刷新sccess_token,设置时间是seccess_token的俩倍。