type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Dec 18, 2023 01:45 AM
Parent item
领域
JWT
是 JSON
结构的 token
。token是访问资源的凭据。例如当你调用Google API,需要带上有效 token 来表明你请求的合法性。token 是 Google 给你的,这代表 Google 给你的授权使得你有能力访问 API 背后的资源。用于调用 API 的 token 我们称为 access token,用于更新access token的token叫做refresh token。一旦 access token 过期,你就可以通过 refresh token 再次请求 access token。如果 refesh token 也过期了,就需要用户重新登陆授权。
基于session认证所显露的问题
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
为什么需要JWT
- 无状态认证,类似邮件激活中的一次性验证
- 有状态认证,类似session
- 同时支持浏览器类客户端和非浏览器类客户端
- 支持跨域访问: Cookie是不允许垮域访问的,Token机制传输的用户认证信息通过HTTP头传输。
- 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息。
- 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可。
- 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可。
- 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
- CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
- 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多。
- 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理。
- 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)。
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
- 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
- 它不需要在服务端保存会话信息, 所以它易于应用的扩展。
JWT 需要注意的点[8]
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
Refresh Token
为什么我们需要 refresh token?
这样的处理是为了职责的分离:refresh token 负责身份认证,access token 负责请求资源。虽然 refresh token 和 access token 都由 IdP 发出,但是 access token 还要和 SP 进行数据交换,如果公用的话这样就会有身份泄露的可能。并且 IdP 和 SP 可能是完全不同的服务提供的。而在第一小节中我们之所以没有这样的顾虑是因为 IdP 和 SP 都是 Google
- Refresh 通常比 Access Token "存活"的时间长, 例如一个月或两个月.
- 我们将 Refresh Token 存放在数据库, 每次客户端需要更新 Access Token 我们就验证它. 它是一个随机生成的字符串(例如: Lxd6bj7w33GEX1GOSgzCNZWNSMskaUmPwgG6uM)
Refresh Token 更新
- 客户端通过登录信息/密码对进行验证.
- 如果成功, 服务器创建新的 Access Token 和 Refresh Token,
- Refresh Token 和 他的生命周期一起存放到数据库.
- 服务器以这个形式响应给客户端:
- 客户端保存这些信息.
- 每次请求前, 客户端检测 Access Token 是否过期. 如果未过期, 请求携带 token 一起发送.
- 为了刷新token, 客户端发送 Refresh Token 到指定的 api 路径(例如:
/v1/account/refresh-token
).
- 服务器通过查询数据库, 对比发送的 refresh token 是否正确, 并且检测是否过期.
- 如果 refresh token 过期, 或者数据库不存在此 token, 则取消认证并向客户端返回 401 错误.
- 如果 refresh token 存在并且未过期, 则创建新的 access token 并更新 refresh token, 以 步骤3 的格式返回给客户端.
- 客户端现在可以通过新的 access token 继续进行请求操作.
JWT处理过程
- 用户向认证服务器提交用户名和密码,认证服务器也可以和应用服务器部署在一起,但往往是独立的居多;
- 认证服务器校验用户名和密码组合,然后创建一个JWT token,token的Payload里面包含用户的身份信息,以及过期时间戳;
- 认证服务器使用密钥对Header和Payload进行签名,然后发送给客户端;
- 客户端获取到经过签名的JWT token,然后在之后的每个HTTP请求中附带着发送给应用服务器。经过签名的JWT就像一个临时的用户凭证,代替了用户名和密码组合,之后都是JWT token和应用服务器打交道了;
- 应用服务器检查JWT签名,确认Payload确实是由密钥拥有者签过名的;
- Payload身份信息代表了某个用户;
- 只有认证服务器拥有私钥,并且认证服务器只把token发给提供了正确密码的用户;
- 因此应用服务器可以认为这个token是由认证服务器颁发的也是安全的,因为该用户具有了正确的密码;
- 应用服务器继续完成HTTP请求,并认为这些请求确实属于这个用户;
JWT token组成
一个JWT token实际上就是一个字符串,它由三部分组成,通过
.
分割:头部header
签名算法有如下,来自
golang-jwt/jwt/v4
:如果是非对称签名算法, 那么secretkey是私钥。
然后将头部序列化并进行base64编码,得到一个字符串:
载荷payload
签名sign
签名是为了防止header和payload在传输过程中被劫持篡改。因为签名时加入了secret,可以保证签名无法被伪造。
签名算法如下:
- 从 接口服务端 拿到 密钥,假设为
secret
。
- 对
header
进行base64
编码,假设结果为headerStr
。
- 将
payload
进行base64
编码,假设结果为payloadStr
。
- 将
headerStr
和payloadStr
用.
字符 拼装起来成为字符data
。
- 以
data
和secret
作为参数,使用 哈希算法 计算出 签名。
得到我们加密后的内容:
在服务器端完成secret保存和jwt的签发,secret就是你服务端的私钥,在任何场景都不应该流露出去。
服务端验证签名
采用对应签名算法的验签方法进行验签
客户端验证签名
如果是采用非对称签名, 那么在客户端也可以验签,证明该token是经过认证的服务端签发的。
JWT潜在问题及其解决方案
- secret泄漏风险
- secret的保存应该和服务器证书的安全等级一致
- 保证每个用户一个secret,不要使用全局唯一secret
- 每个用户的secret和密码绑定, 在用户修改密码之后更新secret,并签发新的token
- token泄漏。在客户端或传输过程中被窃取token, 通过HTTPS解决。
- 重放攻击。http请求被拦截重放,这个安全性对于cookie和jwt是一样的。对于严格要求拒绝重放(当前会话被重放)的场景,可以每次生成token的时候,jwt的jti使用全局唯一ID,例如使用UUID,然后在redis中存储该ID,过期时间设置为token的过期时间,每次验证token有效性时,从redis里面取值判断,若存在,则说明是重放,拒绝该请求。该token在有效时间内只能使用一次。
- 续签问题/用户注销/修改密码。结合redis缓存维护token状态。当用户修改密码和注销时直接将redis中该用户的Token设置失效。