JWT详解
1、有状态和无状态登录
有状态:
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如Tomcat中的Session。例如登录:用户登录后,我们把用户的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session,然后下次请求,用户携带cookie值来(这一步有浏览器自动完成),我们就能识别到对应session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,不支持集群化部署
无状态:
微服务集群中的每个服务,对外提供的都使用RESTful风格的接口。而RESTful风格的一个最重要的规范就是:服务的无状态性,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
那么这种无状态性有哪些好处呢?
客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
服务端的集群和状态对客户端透明
服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
减小服务端存储压力
1.1 如何实现无状态登录
无状态登录的流程:
首先客户端发送账户名/密码到服务端进行认证
认证通过后,服务端将用户信息加密并且编码成一个token,返回给客户端
以后客户端每次发送请求,都需要携带认证的token
服务端对客户端发送来的token进行解密,判断是否有效,并且获取用户登录信息
2、JWT
2.1 简介
JSON Web Token简称JWT
2.2 认证流程
2.3 JWT结构
JWT
包含三部分数据:
Header
:标头通常由两部分组成:令牌的类型(即JwT)和所使用的签名算法,例如 HMAC SHA256或RSA。它会使用Base64编码(可解码)组成JWT结构的第一部分。这里头部的内容一般不做修改注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
typ
声明类型,这里是JWTalg
加密算法,自定义
{ "alg":"HS256", "typ":"JWT" }
Payload
:令牌的第二部分是有效负载
,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分不要放一些敏感的信息,例如用户的密码
{"authorities":"ROLE_user,","sub":"zs","exp":1585668423}
Signature
:签名,是整个数据的认证信息。用于验证整个数据完整和可靠性。前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。 Signature需要使用编码后的 header和 payload以及我们提供的一个密钥,然后使用 header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过例如:签名:
HMACSHA256(base64urlencode(header)+"."+base64urlencode(payload), secret);
- 签名的目的:签名目的最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的內容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的.如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
示例格式:Header.Payload.Signature
eyJhbGciOiJIUzUxMiJ9.
{"alg":"HS512"}
eyJhdXRob3JpdGllcyI6IlJPTEVfdXNlciwiLCJzdWIiOiJ6cyIsImV4cCI6MTU4NTY2ODQyM30.
{"authorities":"ROLE_user,","sub":"zs","exp":1585668423}
5IgsesOWhkVPpyLbIHWbgeNNQFZvIRik2FlZHWCIoD7jH1b2BOgYHqWKb7LehZ_lyK53ZggXIdLVQJ_cqnVPtQ
- 最后一段无法解密
2.4 JWT token动态刷新
https://www.zhihu.com/question/309815576/answer/579255324
实现目标:
- 延长
token
过期时间 - 活跃用户在
token
过期时,在用户无感知的情况下动态刷新token
,做到一直在线状态 - 不活跃用户在
token
过期时,直接定向到登录页
动态刷新token:
前端检测到token
过期后,携带refreshJwt
访问后台刷新token
的接口,服务端在拦截器中依然对refreshJwt
进行解析鉴权
- 假如
refreshJwt
也过期了,提示登录过期,强制跳转登录页 - 假如
refreshJwt
还在有效期,则签发新的token
返回,前端使用最新的token
进行接口请求 - 如果
refreshJwt
过期则直接提示需要重新登录
总结:
- 如果是活跃用户,那么允许他在
refreshJwt
过期时间与token
过期时间的差值这段时间内,不停的动态刷新token
,使其做到无感知的状态下一直保持登录状态 - 如果用户不活跃,在
refreshJwt
过期时间到了,依然没有使用系统,那么将判定为不活跃用户,此时应当重定向到登录页了
token验证策略:
可以用拦截器做到针对一些方法调用用户认证,也可以考虑自定义注解来标识一些方法,过滤器也可以达到相同效果。我使用jwt时会使用redis存储设置有效期,生成的token同样在redis中存储,登录时验证是否在redis中存在,如果存在则说明token有效,一般来说在登出时将redis中对应的token删除,这样保证不会别人拿到token就可以随意登录,哪怕token在jwt生成的有效期内。至于你说的不同业务的过期时间不一样,这个要用token来做吗?至于你说的“一段时间”的情况,应该根据具体业务,可能会重新生成token并返回来保证会话,也可能直接退出重新登录
3、项目整合示例
详情请参考本目录下的 jwt_test 项目