一、前言
大家好呀,我是summo,之前有自学过Shrio框架,网上一搜就有SpringBoot整合Shrio+ JWT的文章,我是在学习Shrio框架的时候顺带学的JWT。后来我还看见有很多博主专门写文章介绍JWT,说这个东西的优点很多,安全性好、去中心化、方便啥的,我就把JWT也应用在我们自己的系统中了。但最近发现这玩意越来越让我觉得别扭,总感觉哪里不太对劲,重新审查我的登录认证逻辑之后才发现:我不应该用JWT的!
这里我用一句解释不该用的原因,省得浪费大家的时间:我的系统有Redis,而且还用Redis存了JWT,随着系统升级,JWT越来越像普通Token!
明白原理的同学可能心中暗笑,直接跳过看下一篇了,不明白原理的同学,可以看看这个四不像是怎么被我搭出来的。
二、JWT是什么?
看我文章的有很多大神,也有一些小白,所以为了不让小白们看的云里雾里,我还是有必要介绍一些基本原理。JWT是 JSON Web Token 的缩写,可以对JSON对象进行编码(加密),并通过这个编码传递信息。
1. JWT的结构
(1)头部(Header)
头部通常由两部分组成,即令牌的类型(typ)和所使用的算法(alg)。例如,一个头部可能是 {"alg": "HS256", "typ": "JWT"},表示使用 HMAC SHA-256 算法对令牌进行签名。
(2)载荷(Payload)
载荷包含了 JWT 的声明信息,用于描述令牌的相关内容。载荷可以包含标准声明(例如:发行者、主题、过期时间等),也可以包含自定义声明。例如,一个载荷可能是 {"sub": "1234567890", "name": "John Doe", "exp": 1516239022}。
(3)签名(Signature)
签名用于验证令牌的完整性和真实性。签名通常由头部、载荷和密钥一起计算而得。验证者可以使用相同的密钥重新计算签名,并将结果与令牌中的签名进行比较,以确认令牌的真实性。
2. SpringBoot使用JWT
(1)maven引入
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
(2)代码示例
import java.util.Date;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@Slf4j
public class JWTUtil {
/**
* 过期时间
*/
private static final long EXPIRE_TIME = 60 * 1000;
/**
* 校验 token是否正确
*
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
verifier.verify(token);
log.info("token is valid");
return true;
} catch (Exception e) {
log.info("token is invalid{}", e.getMessage());
return false;
}
}
/**
* 从 token中获取用户名
*
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
log.error("error:{}", e.getMessage());
return null;
}
}
/**
* 生成 token
*
* @param username 用户名
* @param secret 用户的密码
* @return token
*/
public static String sign(String username, String secret) {
try {
username = StringUtils.lowerCase(username);
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
log.error("error:{}", e);
return null;
}
}
public static void main(String[] args) {
//对数据进行加密
String token = sign("zhangshan", "123456");
System.out.println(token);
//对数据进行解密
System.out.println(getUsername(token));
}
}
运行一下
JWT还是很简单的,一学就会,很多博主在介绍它的时候都会说它安全、方便、去中心化等等,然后强烈推荐大家使用。但这里我要就要给大家泼冷水了,学是肯定要学的,用就需要看情况了,不能别人说它好,你就无脑用,然后用成一个四不像。至于我为什么说我用来是四不像,接着看!
三、JWT vs Token+Redis
在设计no session系统时,有两种可选方案:JWT与Token+Redis。
1. 原理简介
-
JWT: 生成并发给客户端之后,后台是不用存储,客户端访问时会验证其签名、过期时间等再取出里面的信息(如username),再使用该信息直接查询用户信息完成登录验证。jwt自带签名、过期等校验,后台不用存储,缺陷是一旦下发,服务后台无法拒绝携带该jwt的请求(如踢除用户);
-
Token+Redis: 是自己生成个32位的key,value为用户信息,访问时判断redis里是否有该token,如果有,则加载该用户信息完成登录。服务需要存储下发的每个token及对应的value,维持其过期时间,好处是随时可以删除某个token,阻断该token继续使用。
2. 两种方案的优缺点
(1)去中心化的JWT
优点:
- 去中心化,便于分布式系统使用
- 基本信息可以直接放在token中。 username,nickname,role
- 功能权限较少的话,可以直接放在token中。用bit位表示用户所具有的功能权限
缺点:
- 服务端不能主动让token失效
(2)中心化的Redis+Token
优点:
- 服务端可以主动让token失效
缺点:
- 依赖内存或redis存储。
- 分布式系统的话,需要redis调用增加了系统复杂性。
光看优缺点的话,JWT优点还比Redis+Token多,在小白时期的我一看:好家伙,JWT这么多优点,用它准没错,看来简历上又可以加上一笔了!
四、我的方案
想法是美好的,但现实是残酷的,为什么我会觉得越来越别扭,先上一张流程图,让大家看看我在业务中是怎么做的,如下:
上面的方案是JWT或者Redis+Token,而我的方案是JWT+Redis
标签:username,jwt,JWT,Redis,token,应该,String From: https://www.cnblogs.com/wlovet/p/18303453