一、Token
是什么
JSON Web Token
(缩写 JWT
)是目前最流行的跨域认证解决方案。JWT官方网站:https://jwt.io/
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样:
{
"userName": "Tom",
"role": "admin",
"time": "2020-5-22 13:49:03"
}
以后,用户与服务端通信的时候,都要发回这个 JSON
对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
二、JWT
出现的背景
基于session的认证应用存在的最大问题是,没有分布式架构,无法支持横向扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户。
三、JWT
解决了什么问题
Token
解决了以下问题:
Token
完全由应用管理,所以它可以避开同源策略★Token
可以避免 CSRF 攻击Token
可以是无状态的,可以在多个服务间共享
2.1. 跨域认证问题
互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是持久化session数据,该解决方案的优点在于架构清晰,而缺点是架构修改非常耗时,需要重写整个服务的验证逻辑层,工作量相对较大。且由于依赖于持久层的数据库或者问题系统,会有单点风险,如果持久层失败,整个认证体系都会失效。
另一种解决方案更为灵活,通过客户端保存数据,而服务器完全不保存会话数据,将所有请求都发送回服务器。 JWT正是这种解决方案的代表。用户与服务端通信的时候,都要发回这个 JSON
对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
四、Token
长什么样?
JWT
的三个部分依次如下。
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行,就是下面的样子:
- Header . Payload . Signature
所以拼在一起就是这样:
五、Token
的有效期
对于这个问题,我们不妨先看两个例子。一个例子是登录密码,一般要求定期改变密码,以防止泄漏,所以密码是有有效期的;另一个例子是安全证书。SSL 安全证书都有有效期,目的是为了解决吊销的问题,对于这个问题的详细情况,来看看知乎的回答。所以无论是从安全的角度考虑,还是从吊销的角度考虑,Token 都需要设有效期。
那么有效期多长合适呢?
只能说,根据系统的安全需要,尽可能的短,但也不能短得离谱——想像一下手机的自动熄屏时间,如果设置为 10 秒钟无操作自动熄屏,再次点亮需要输入密码,会不会疯?如果你觉得不会,那就亲自试一试,设置成可以设置的最短时间,坚持一周就好(不排除有人适应这个时间,毕竟手机厂商也是有用户体验研究的)。
然后新问题产生了,如果用户在正常操作的过程中,Token
过期失效了,要求用户重新登录……用户体验岂不是很糟糕?
为了解决在操作过程不能让用户感到 Token
失效这个问题,可以使用 Refresh Token
,它可以避免频繁的读写操作。
这种方式中,服务端不需要刷新 Token
的过期时间,一旦 Token
过期,就反馈给前端,前端使用 Refresh Token
申请一个全新 Token
继续使用。这种方案中,服务端只需要在客户端请求更新 Token
的时候对 Refresh Token
的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token
也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。
六、 时序图表示
6.1. 登录
6.2. 业务请求
6.3. Token 过期,刷新 Token
七、JWT 的几个特点
-
JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
-
JWT 不加密的情况下,不能将秘密数据写入 JWT。
-
JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
-
JWT 的最大缺点是,由于服务器不保存
session
状态,因此无法在使用过程中废止某个Token
,或者更改Token
的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。(所以联系之前采取的Refresh Token
的方式也可以在修改当前用户权限后及时更新用户Token
) -
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
-
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
八、JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie
里面,也可以储存在 localStorage
。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie
里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP
请求的头信息Authorization
字段里面。
- Authorization: Bearer
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
目前所在的项目中是采取第一种形式, 存放在 localStorage
中, 每次发送请求时携带上:
九、服务器校验 JWT
的方式
经查找资料,有以下几种校验方式:
- 服务器端生成 JWT 后存放在数据库中,当前端发送请求时,提取出请求头中的 token 值,与数据库中的存档作比较
- 服务器端生成 JWT 后存放在 redis 中,在有效期结束之前,当前端发送请求时,提取出请求头中的 token 值,与 redis 中的存档作比较,有效期结束后清除。
- 服务器端生成 JWT 后不进行保存,当前端发送请求时,按照此前生成的方式重新生成一遍,再与提取出请求头中的 token 值进行比较
后来去后台源码中进行查找,发现现在在做的项目中,当服务器端接收到请求,会从签名中提取 role
值,比较是否为管理员,以此判断用户权限。
参考资料: