JWT的介绍、代码实现与解决方案
简介
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
载荷就是存放有效信息的地方
签证信息由3部分组成
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
优点
体积小,因而传输速度更快
多样化的传输方式,可以通过URL传输、POST传输、请求头Header传输(常用)
简单方便,服务端拿到jwt后无需再次查询数据库校验token可用性,也无需进行redis缓存校验
在分布式系统中,很好地解决了单点登录问题
很方便的解决了跨域授权问题,因为跨域无法共享cookie
缺点
因为JWT是无状态的,因此服务端无法控制已经生成的Token失效,是不可控的,这一点对于是否使用jwt是需要重点考量的
获取到jwt也就拥有了登录权限,因此jwt是不可泄露的,网站最好使用https,防止中间攻击偷取jwt
在退出登录 / 修改密码时 token仍生效。下面有解决方案
核心代码
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
package com.mtgg.utils;
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
@Slf4j
public class JwtUtils {
public static void main(String[] args) throws InterruptedException {
String x = createJWT("mtgg", "", "1", 120 * 1000);
Claims claims = parseJWT(x);
System.out.println(JSON.toJSON(claims));
claims.setIssuer("lskdfjl");
Claims claims1 = parseJWT(x);
System.out.println(JSON.toJSON(claims1));
}
/**
* 签发JWT
*
* @param
* @param subject 可以是JSON数据 尽可能少
* @param ttlMillis
* @return String
*/
public static String createJWT(String subject, String roles, String version, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
// .setId(id) //唯一编号
.setSubject(subject) // 主题
.setIssuer("user") // 签发者
.setIssuedAt(now) // 签发时间
.claim("roles", roles) //自定义
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
String jwt = builder.compact();
System.out.println("签发的jwt: " + jwt);
return jwt;
}
/**
* @Auther: Gale Li
* @Date: 2020/2/19 19:50
* @Description:
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode("MTGG2#J2*qlk64&HYU");
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析JWT字符串
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) {
try {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
} catch (ExpiredJwtException e) {
log.error("过期异常:{}", e.toString());
return null;
} catch (Exception e) {
log.error("解析异常:「」:{}", e.toString());
return null;
}
}
}
如何主动使token失效方案?
将 token 存入 DB(如 Redis)中,失效则删除;但增加了一个每次校验时候都要先从 DB 中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则(这不就和 session 一样了么?)。
维护一个 token 黑名单,失效则加入黑名单中。个人还是推荐这种方式,可以设置token到redis中,其过期时间可以就是当初用JWT生成token时的失效时间,因为先生成后设置黑名单,所以黑名单中的token一定在JWT的token失效时间之后
网上还有说白名单,同理;还有更改secret的,每个用户都有一个自己的secret,想了一下一样要借助外部记录自己的secret,在更改的时候才会起作用
比较尴尬的token存储的数据不能修改,如果找到一种方式修改,那么不依赖外物可修改本身的一个version,解密的时候验证此version就可以达到验证失败的效果。如果大神有办法可以不吝赐教哦????
整体来说比较好用,jwt应该还有其他好的处理,待后续深入研究后补充
参考: https://www.jianshu.com/p/99a458c62aa4
上一篇: 解决输入框被输入法遮挡的问题
下一篇: 分享python流程控制函数总结