什么是JWT? Token? 如何基于Token进行身份验证?

JWT (JSON Web Token)

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。

JWT 由 3 部分构成:

一个 JWT token 是一个字符串,它由三部分组成,头部、载荷与签名,中间用 . 分隔,例如:xxxxx.yyyyy.zzzzz

  • Header :令牌的类型(即 JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA.)。
{
  "alg": "HS256",
  "typ": "JWT"
}
然后用 Base64Url 编码得到头部,即 xxxxx。
  • Payload(负载):用来存放实际需要传递的数据
载荷中放置了 token 的一些基本信息,以帮助接受它的服务器来理解这个 token。同时还可以包含一些自定义的信息,用户信息交换。
{
  "sub": "1",
  "iss": "http://localhost:8000/auth/login",
  "iat": 1451888119,
  "exp": 1454516119,
  "nbf": 1451888119,
  "jti": "37c107e4609ddbcc9c096ea5ee76c667",
  "aud": "dev"
}
iss (issuer):签发人
sub (subject):主题
aud (audience):受众
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):编号
对payload进行Base64编码就得到JWT的第二部分
  • Signature(签名):服务器通过Payload、Header和一个密钥(secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)
加密后再进行 base64url 编码最后得到的字符串就是 token 的第三部分 zzzzz。

组合便可以得到 token:xxxxx.yyyyy.zzzzz。

签名的作用:保证 JWT 没有被篡改过,原理如下:

HMAC 算法是不可逆算法,类似 MD5 和 hash ,但多一个密钥,密钥(即上面的 secret)由服务端持有,客户端把 token 发给服务端后,服务端可以把其中的头部和载荷再加上事先共享的 secret 再进行一次 HMAC 加密,得到的结果和 token 的第三段进行对比,如果一样则表明数据没有被篡改。

基于token的鉴权机制

基于token的鉴权机制:

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

用户使用用户名密码来请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据

客户端将 Token 保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization字段中:Authorization: Bearer Token。

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。

Token 认证的优势

1.无状态

token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。但是,也正是由于 token 的无状态,也导致了它最大的缺点:当后端在token 有效期内废弃一个 token 或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户 Logout 的话,token 也还有效。除非,我们在后端增加额外的处理逻辑。

2.有效避免了CSRF 攻击

那为什么 token 不会存在这种问题呢?

我是这样理解的:一般情况下我们使用 JWT 的话,在我们登录成功获得 token 之后,一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。

但是这样会存在 XSS 攻击中被盗的风险,为了避免 XSS 攻击,你可以选择将 token 存储在标记为httpOnly 的cookie 中。但是,这样又导致了你必须自己提供CSRF保护。

如果放在cookie的话就要防两种,设置httpOnly 防XSS,同时要验证来源站点利用headr 的referer

3.适合移动端应用

使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),移动端无cookie ,所以不适合移动端。但是,使用 token 进行身份认证就不会存在这种问题,因为只要 token 可以被客户端存储就能够使用,而且 token 还可以跨语言使用。

4.(跨资源共享CORS)

WT 最大的优势是服务器不再需要存储 Session,使得服务器认证鉴权业务可以方便扩展。但这也是 JWT 最大的缺点:由于服务器不需要存储 Session 状态,因此使用过程中无法废弃某个 Token 或者更改 Token 的权限。也就是说一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。

Token 认证常见问题以及解决办法

1.注销登录等场景下 token 还有效

与之类似的具体相关场景有:
退出登录;
修改密码;
服务端修改了某个用户具有的权限或者角色;
用户的帐户被删除/暂停。
用户由管理员注销;
这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 token 认证的方式就不好解决了。我们也说过了,token 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。那么,我们如何解决这个问题呢?查阅了很多资料,总结了下面几种方案:

  • 将 token 存入内存数据库:将 token 存入 DB 中,redis 内存数据库在这里是是不错的选择。如果需要让某个 token 失效就直接从 redis 中删除这个 token 即可。但是,这样会导致每次使用 token 发送请求都要先从 DB 中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则。

  • 黑名单机制:和上面的方式类似,使用内存数据库比如 redis 维护一个黑名单,如果想让某个 token 失效的话就直接将这个 token 加入到 黑名单 即可。然后,每次使用 token 进行请求的话都会先判断这个 token 是否存在于黑名单中。

  • 修改密钥 (Secret) : 我们为每个用户都创建一个专属密钥,如果我们想让某个 token 失效,我们直接修改对应用户的密钥即可。但是,这样相比于前两种引入内存数据库带来了危害更大,比如:1⃣️如果服务是分布式的,则每次发出新的 token 时都必须在多台机器同步密钥。为此,你需要将必须将机密存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别了。2⃣️如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。

  • 保持令牌的有效期限短并经常轮换 :很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。
    对于修改密码后 token 还有效问题的解决还是比较容易的,说一种我觉得比较好的方式:使用用户的密码的哈希值对 token 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。

2.token 的续签问题

token 有效期一般都建议设置的不太长,那么 token 过期后如何认证,如何实现动态刷新 token,避免用户经常需要重新登录?

我们先来看看在 Session 认证中一般的做法:假如 session 的有效期30分钟,如果 30 分钟内用户有访问,就把 session 有效期被延长30分钟。

  • 类似于 Session 认证中的做法:这种方案满足于大部分场景。假设服务端给的 token 有效期设置为30分钟,服务端每次进行校验时,如果发现 token 的有效期马上快过期了,服务端就重新生成 token 给客户端。客户端每次请求都检查新旧token,如果不一致,则更新本地的token。这种做法的问题是仅仅在快过期的时候请求才会更新 token ,对客户端不是很友好。
    每次请求都返回新 token :这种方案的的思路很简单,但是,很明显,开销会比较大。

  • token 有效期设置到半夜 :这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。

  • 用户登录返回两个 token:第一个是 acessToken ,它的过期时间 token 本身的过期时间比如半个小时,另外一个是 refreshToken 它的过期时间更长一点比如为1天。

客户端登录后,将 accessToken和refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录即可。该方案的不足是:1⃣️需要客户端来配合;2⃣️用户注销的时候需要同时保证两个 token 都无效;3⃣️重新请求获取 token 的过程中会有短暂 token 不可用的情况(可以通过在客户端设置定时器,当accessToken 快过期的时候,提前去通过 refreshToken 获取新的accessToken)。

1)登录在这里插入图片描述
2)业务请求
在这里插入图片描述
3)Token过期,刷新 Token
在这里插入图片描述
上面的时序图中并未提到 Refresh Token 过期怎么办。不过很显然,Refresh Token 既然已经过期,就该要求用户重新登录了。

JWT 与 Oauth2.0

Oauth2.0 是一个方便的第三方授权规范,而 JWT 是一个 token 结构规范。只是 JWT 常用来登陆鉴权,而 Oauth2.0 在授权时也涉及到了登陆,所以就比较容易搞混。

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

Oauth2.0参考:OAuth 2.0 的一个简单解释

参考:
JWT 身份认证优缺点分析以及常见问题解决方案
JWT 超详细分析
一问带你区分清楚Authentication,Authorization以及Cookie、Session、Token

猜你喜欢

转载自blog.csdn.net/HZ___ZH/article/details/114632081