赞
踩
Json web token (JWT), 跨域身份验证解决方案,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
jwt本质上是一种令牌格式。它和终端设备无关,同样和服务器无关,甚至与如何传输无关,它只是规范了令牌的格式而已
通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。
JWT主要用于用户登陆鉴权, 在之前可能会使用session和token认证, 下面简述三者session和JWT的区别
用户向服务器发送一个请求时, 服务器并不知道该请求是谁发的, 所以在用户发送登录请求时, 服务器会将用户提交的用户名和密码等信息保存在session会话中(一段内存空间)。同时服务器保存的用户信息会生成一个sessionid(相当于用户信息是一个value值, 而sessionid是value值的key)返回给客户端, 客户端将sessionid保存到cookie中,当用户再次访问服务器时,会携带sessionid,服务器会拿着sessionid从服务器获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持。
弊端:
token是一串随机的字符串也叫令牌, 其原理和session类似, 当用户登录时, 提交的用户名和密码等信息请求给服务端, 服务端会根据用户名或者其他信息生成一个token而不是sessionid, 这和sessionid唯一区别就是, token不再存储用户信息, 客户端下一次请求会携带token, 此时服务器根据此次token进行认证。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
token认证时也会到数据库中查询, 会造成数据库压力过大。
流程上是这样的:
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。那么我们现在回到JWT的主题上。
JWT将登录时所有信息都存在自己身上, 并且以json格式存储, JWT不依赖Redis或者数据库, JWT安全性不太好, 所以不能存储敏感信息
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
jwt由三部分组成:header、payload、signature。主体信息在payload
header
jwt的头部承载两部分信息:
完整的头部就像下面这样的JSON:
- {
- 'typ': 'JWT',
- 'alg': 'HS256'
- }
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
标准中注册的声明 (建议但不强制使用) :
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
- {
- "sub": "1234567890",
- "name": "John Doe",
- "admin": true
- }
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
Signature 部分是对前两部分的签名,防止数据篡改。
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256)产生签名。算出签名以后,把 (base64加密后)Header、(base64加密后)Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
通过前面讲解的 jwt 生成规则,jwt 前两部分是对 header 以及 payload 的 base64 编码。 当服务器收到客户端的 token 后,解析前两部分得到 header 以及 payload,并使用 header 中的算法与 服务端本地私有 secret 进行签名,判断与 jwt 中携带的签名是否一致,即在服务端在执行一次 signature = 加密算法(header + "." + payload, 密钥), 然后对比 signature 是否一致。
关于传输:
- fetch('api/user/1', {
- headers: {
- 'Authorization': 'Bearer ' + token //authorization: bearer jwt令牌
- }
- })
这种格式是OAuth2附带token的一种规范格式
授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
总之:JWT要解决的问题,就是为多种终端设备,提供统一的、安全的令牌格式。
登陆验证
登陆验证:不需要控制登录设备数量以及注销登陆情况,无状态的 jwt 是一个不错的选择。具体实现流程,可以看上文中的校验原理,校验原理使用的登陆验证例子。
当需求中出现控制登陆设备数量,或者可以注销掉用户时,可以考虑使用原有的 session 模式,因为针对这种登陆需求,需要进行的状态存储对 jwt 添加额外的状态支持,增加了认证的复杂度,此时选用 session 是一个不错的选择。 针对上面的特殊需求,可能也有小伙伴仍喜欢使用 jwt ,补充一下特殊案例
注销登陆
用户注销时候要考虑 token 的过期时间。
用户登陆设备控制
适合做那些事来讲的,其实也就是针对JWT的优势来说的,还有一些辩证性的理解。接下来说说 JWT 的缺点。
① 请求时token过期自动刷新token?
Token 认证的作用就是为了安全,用户登陆时,服务器会随机生成一个有时效性的token,用户的每一次请求都需要携带上token,证明其请求的合法性,服务器会验证token,只有通过验证才会返回请求结果。
当token失效时,现在的网站一般会做两种处理方法:
1)跳转到登陆页面,让用户重新登陆获取新的token
2)当检测到请求失效时,网站自动去请求新的token(在app保持登陆状态上面用得比较多)
判断服务端返回的code是不是失效,如果是,则把每一个token失效的请求存到一个Promise函数集合里面,当刷新token的函数执行完毕后,才会批量执行这些Promise函数,返回请求结果。还要设置一个刷新token的开关isRefreshing,防止重复请求。
② 为什么要token?
session机制是把一把钥匙保存在客户端,请求的时候送到服务端,用来打开服务端的保险箱(当然,服务端的保险箱一般情况下都没有加密);
JWT机制就是把一个保险箱放在客户端,请求的时候把整个保险箱送到服务端,服务端使用自己独有的钥匙打开保险箱获得或者更新里面的东西;
③ 用户登出,如何设置token无效?
JWT是无状态的,用户登出设置token无效就已经违背了JWT的设计原则,但是在实际应用场景中,这种功能是需要的,那该如何实现呢?提供几种思路:
为了保持数据的一致性,每一次认证都需要从redis中取出对应的token,每一次都以redis中的token为准。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。