如何在SpringBoot中集成JWT(JSON Web Token)鉴权

42次阅读

共计 3896 个字符,预计需要花费 10 分钟才能阅读完成。

这篇博客主要是简单介绍了一下什么是 JWT,以及如何在 Spring Boot 项目中使用 JWT(JSON Web Token)。
1. 关于 JWT
1.1 什么是 JWT
老生常谈的开头,我们要用这样一种工具,首先得知道以下几个问题。

这个工具是什么,这个工具解决了什么问题
是否适用于当前我们所处得业务场景
用了之后是否会带来任何其他问题
怎么用才是最佳实践

那什么是 JWT 呢?以下是我对 jwt 官网上对 JWT 介绍的翻译。
JSON Web Token(JWT)是一种定义了一种紧凑并且独立的,用于在各方之间使用 JSON 对象安全的传输信息的一个开放标准(RFC 7519)。
现在我们知道,JWT 其实是一种开放标准,用于在多点之间安全地传输用 JSON 表示的数据。在传输的过程中,JWT 以字符串的形式出现在我们的视野中。该字符串中的信息可以通过数字签名进行验证和信任。
1.2 应用场景
JWT 在实际的开发中,有哪些应用场景呢?
1.2.1 授权
这应该算是 JWT 最常见的使用场景。在前端界面中,一旦用户登录成功,会接收到后端返回的 JWT。后续的请求都会包含后端返回的 JWT,作为对后端路由、服务以及资源的访问的凭证。
1.2.2 信息交换
利用 JWT 在多方之间相互传递信息具有一定的安全性,例如 JWT 可以用 HMAC、RSA 非对称加密算法以及 ECDSA 数字签名算法对 JWT 进行签名,可以确保消息的发送者是真的发送者,而且使用 header 和 payload 进行的签名计算,我们还可以验证发送的消息是否被篡改了。
2.JWT 的结构
通俗来讲 JWT 由 header.payload.signature 三部分组成的字符串,网上有太多帖子介绍这一块了,所以在这里就简单介绍一下就好了。
2.1 header
header 由使用的签名算法和令牌的类型的组成,例如令牌的类型就是 JWT 这种开放标准,而使用的签名算法就是 HS256,也就是 HmacSHA256 算法。其他的加密算法还有 HmacSHA512、SHA512withECDSA 等等。
然后将这个包含两个属性的 JSON 对象转化为字符串然后使用 Base64 编码,最终形成了 JWT 的 header。
2.2 payload
payload 说直白一些就类似你的 requestBody 中的数据。只不过是分了三种类型,预先申明好的、自定义的以及私有的。像 iss 发件人,exp 过期时间都是预先注册好的申明。
预先申明在载荷中的数据不是强制性的使用,但是官方建议使用。然后这串类似于 requestBody 的 JSON 经过 Base64 编码形成了 JWT 的第二部分。
2.3 signature
如果要生成 signature,就需要使用 jwt 自定义配置项中的 secret,也就是 Hmac 算法加密所需要的密钥。将之前经过 Base64 编码的 header 和 payload 用. 相连,再使用自定义的密钥,对该消息进行签名,最终生成了签名。
生成的签名用于验证消息在传输的过程中没有被更改。在使用非对称加密算法进行签名的时候,还可以用于验证 JWT 的发件人是否与 payload 中申明的发件人是同一个人。
3.JWT 在 Spring 项目中的应用场景
3.1 生成 JWT
代码如下。
public String createJwt(String userId, String projectId) throws IllegalArgumentException, UnsupportedEncodingException {
Algorithm al = Algorithm.HMAC256(secret);
Instant instant = LocalDateTime.now().plusHours(outHours).atZone(ZoneId.systemDefault()).toInstant();
Date expire = Date.from(instant);
String token = JWT.create()
.withIssuer(issuer)
.withSubject(“userInfo”)
.withClaim(“user_id”, userId)
.withClaim(“project_id”, projectId)
.withExpiresAt(expire)
.sign(al);
return token;
}
传入的两个 Claim 是项目里自定义的 payload,al 是选择的算法,而 secret 就是对信息签名的密钥,subject 则是该 token 的主题,withExpiresAt 标识了该 token 的过期时间。
3.2 返回 JWT
在用户登录系统成功之后,将 token 作为返回参数,返回给前端。
3.3 验证 token
在 token 返回给前端之后,后端要做的就是验证这个 token 是否是合法的,是否可以访问服务器的资源。主要可以通过以下几种方式去验证。
3.3.1 解析 token
使用 JWTVerifier 解析 token,这是验证 token 是否合法的第一步,例如前端传过来的 token 是一串没有任何意义的字符串,在这一步就可以抛出错误。示例代码如下。
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException e) {
e.printStackTrace();
}
JWTVerifier 可以使用用制定 secret 签名的算法,指定的 claim 来验证 token 的合法性。
3.3.2 判断 token 时效性
判断了 token 是有效的之后,再对 token 的时效性进行验证。
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
if (jwt.getExpiresAt().before(new Date())) {
System.out.println(“token 已过期 ”);
return null;
}
} catch (JWTVerificationException e) {
e.printStackTrace();
return null;
}
如果该 token 过期了,则不允许访问服务器资源。具体的流程如下。

3.3.3 刷新过期时间
上面创建 token 的有效时间是可以配置的,假设是 2 个小时,并且用户登录进来连续工作了 1 小时 59 分钟,在进行一个很重要的操作的时候,点击确定,这个时候 token 过期了。如果程序没有保护策略,那么用户接近两个小时的工作就成为了无用功。
遇到这样的问题,我们之前的流程设计必然面对一次重构。可能大家会有疑问,不就是在用户登录之后,每次操作对去刷新一次 token 的过期时间吗?
那么问题来了,我们知道 token 是由 header.payload.signature 三段内容组成的,而过期时间则是属于 payload,如果改变了过期的时间,那么最终生成的 payload 的 hash 则势必与上一次生成的不同。
换句话说,这是一个全新的 token。前端要怎么接收这个全新的 token 呢?可想到的解决方案无非就是每次请求,根据 response header 中的返回不断的刷新的 token。但是这样的方式侵入了前端开发的业务层。使其每一个接口都需要去刷新 token。
大家可能会说,无非就是加一个拦截器嘛,对业务侵入不大啊。即使这部分逻辑是写在拦截器里的,但是前端因为 token 鉴权的逻辑而多出了这部分代码。而这部分代码从职能分工上来说,其实是后端的逻辑。
说的直白一些,刷新 token,对 token 的时效性进行管理,应该是由后端来做。前端不需要也不应该去关心这一部分的逻辑。
3.3.4 redis 大法好
综上所述,刷新 token 的过期时间势必要放到后端,并且不能通过判断 JWT 中 payload 中的 expire 来判断 token 是否有效。
所以,在用户登录成功之后并将 token 返回给前端的同时,需要以某一个唯一表示为 key,当前的 token 为 value,写入 Redis 缓存中。并且在每次用户请求成功后,刷新 token 的过期时间,流程如下所示。

经过这样的重构之后,流程就变成了这样。

在流程中多了一个刷新 token 的流程。只要用户登录了系统,每一次的操作都会刷新 token 的过期时间,就不会出现之前说的在进行某个操作时突然失效所造成数据丢失的情况。
在用户登录之后的两个小时内,如果用户没有进行任何操作,那么 2 小时后再次请求接口就会直接被服务器拒绝访问。
4. 总结
总的来说,JWT 中是不建议放特别敏感信息的。如果没有用非对称加密算法的话,把 token 复制之后直接可以去 jwt 官网在线解析。如果请求被拦截到了,里面的所有信息等于是透明的。
但是 JWT 可以用来当作一段时间内运行访问服务器资源的凭证。例如 JWT 的 payload 中带有 userId 这个字段,那么就可以对该 token 标识的用户的合法性进行验证。例如,该用户当前状态是否被锁定?该 userId 所标识的用户是否存在于我们的系统?等等。
并且通过实现 token 的公用,可以实现用户的多端同时登录。像之前的登录之后创建 token,就限定了用户只能同时在一台设备上登录。
欢迎大家浏览之前的文章:个人博客,如果有说的不对的地方,还请不吝赐教。
5. 参考
JWT 官网

正文完
 0