JWT
JWT
官方网站:jwt
参考博客:Understanding JWT - DEV Community
签名的生成过程
签名的生成过程:
-
首先对头部和有效载荷进行编码:通过 Base64Url 编码把它们转换成 URL 安全的格式。
例如,假设我们有以下的头部和有效载荷:- Header(头部):
{ "alg": "HS256", "typ": "JWT" }
- Payload(有效载荷):
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
- Header(头部):
-
然后将头部和有效载荷拼接在一起:它们被拼接成一个字符串,使用
.
(点号)分隔。
例如:base64UrlEncode(header) + "." + base64UrlEncode(payload)
-
用密钥和签名算法计算哈希:然后,将拼接后的字符串与一个密钥(对于 HMAC 算法来说)一起进行哈希计算。如果是使用 HMAC SHA256 算法,通常的计算方式是:
HMACSHA256(encoded_header + "." + encoded_payload, secret_key)
其中
secret_key
是服务器和客户端预先共享的密钥。 -
最终得到签名:该哈希值作为签名部分,确保 JWT 在传输过程中没有被篡改。
其实常规的签名就是一个 Hash 函数,发送之前进行 hash,得到一串字符,这一串字符就是签名,我们会把签名跟传输的内容一起发给接收端,接收端收到传输的内容之后,我们用同样的 hash 函数或者说同样的签名算法也算一次,如果得到的一串字符跟发送端发过来的那一串不一样的话,就说明传输的内容被篡改了。
但是道高一尺魔高一丈,作为篡改数据的人,我在拦截发送方的数据之后,可以在把传输内容换了的时候,用同样的签名算法再算一个新的签名发给服务端啊,对,这就叫中间人攻击,为了避免中间人把签名也改了,发送方和接收方会先约定好一个密钥,签名完之后,用密钥把这个前面的字符串再加个密,如果接收端发现我用密钥无法解密这个签名,或者我把传输的内容签个名,然后用约定好的密钥进行加密,跟发送端发过来那个加过密的签名不一样,接收端就知道传输内容或者签名都被改了。
这种签名之后再用密钥加密的做法,跟 SSL 证书是一样的,请参考 TSL证书
签名的作用
- 数据完整性验证:签名确保了 JWT 的数据(头部、有效载荷)在传输过程中没有被修改。接收方在验证时,会根据相同的算法和密钥生成签名,若生成的签名与传输过来的签名一致,就可以确定数据没有被篡改。
- 防止伪造:只有拥有密钥的一方才能生成正确的签名,从而确保消息是由合法的发送方发出的。
总结来说,签名的确是基于内容(头部和有效载荷)通过哈希和密钥计算得到的,主要目的是确保数据的安全性和完整性。
检查 JWT 是否有效
- 提取 JWT 中的有效载荷: JWT 由三个部分组成:头部(Header)、有效载荷(Payload)、签名(Signature)。你需要首先解码出 JWT 中的有效载荷部分,它包含了
exp
字段。
JWT 的有效载荷部分是经过 Base64Url 编码的 JSON 字符串。你可以通过 Base64Url 解码获取其中的内容。
例如:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1675169032 // 过期时间(UNIX 时间戳) }
- 检查
exp
字段:exp
是一个标准的声明字段,表示 JWT 的过期时间(UNIX 时间戳)。- 当
exp
的时间值小于当前时间戳时,表示 JWT 已过期。
- 对比当前时间: 获取当前时间戳(可以使用语言提供的时间库获取,例如 JavaScript 中的
Date.now()
或 Python 中的time.time()
),然后与 JWT 中的exp
字段进行对比:- 如果当前时间戳大于
exp
字段的值,则表示 JWT 已经过期。 - 如果当前时间戳小于
exp
字段的值,则表示 JWT 仍然有效。
- 如果当前时间戳大于
其他注意事项:
exp
字段是可选的:JWT 不一定会包含exp
字段。如果没有该字段,JWT 本身没有明确的过期时间,那么需要根据业务需求进行管理。nbf
字段:除了exp
字段,还有一个nbf
(Not Before)字段,用于指定 JWT 在某个时间点之前不应该被使用。如果 JWT 中有nbf
字段,你还需要检查当前时间是否晚于nbf
。- 签名校验:虽然校验过期时间是 JWT 校验的一部分,但一般情况下,你还需要验证签名是否有效。签名有效性校验通常涉及对 JWT 中的头部、有效载荷以及密钥进行验证。
简介
官网:introduction
什么是 JWT ?
JSON Web 令牌 (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用来在各方之间安全地传输信息,这些信息都是 JSON 对象。该信息可以被校验和信任,因为它是数字签名的。可以使用密钥 (使用 HMAC 算法) 或使用 RSA 或 ECDSA 的公钥/私钥对对 jwt 进行签名。
虽然 jwt 也可以加密以在各方之间提供保密性,但我们将重点关注已签名的令牌(也就是主要以签名的方式处理 JWT)。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方是对其进行签名的一方。
关于加密和签名的区别,请看《非对称加密》的
签名和加密的不同
小节
JWT 主要用于什么场景?
-
身份认证:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(SSO:Single Sign On)是目前 JWT 广泛使用的一个特性,因为它的开销很小,并且能够轻松地跨不同的域使用。
-
信息交换:JSON Web Token 是多方之间安全传输信息的好方法。因为可以对 jwt 进行签名 (例如,使用公钥/私钥对),所以可以确保发送者就是 JWT 中所说的那个人。此外,由于签名是使用报头和有效负载计算的,因此您还可以验证内容是否未被篡改。
请注意,使用已签名的令牌的时候,令牌中包含的所有信息都将公开给用户或其他接收方的,即使他们无法更改它。这意味着您不应该在令牌中放入机密信息。
优点和缺点:
优点:
-
由于令牌包含所有必要的信息,因此您不必一直查询数据库
-
服务器不需要维护外部存储来处理用户状态
-
易于跨多个服务进行验证。您只需一台服务器进行授权,然后在需要该信息的多个服务之间传递令牌,也就是自带 SSO
缺点:
-
如果有人得到 JWT,他们可能能够冒充这个人
-
不能将大量信息存储到 JWT 令牌中,因为这会造成数据过载。建议只保存基本信息,其他数据根据实际情况通过查询数据库获取。同样对于 web 应用程序,您将通过 cookie 来保存使用 JWT,因此只能存储特定字节的数据。
SpringBoot 整合 JWT
参考教程:
java - 深入总结SpringBoot整合JWT,这应该是全网讲的最通俗易懂的了 - 架构人生 - SegmentFault 思否
Springboot整合jwt实现token登录验证功能 - 掘金
过程也很简单,引入相关依赖,添加登录拦截器即可。
Spring Security 整合 JWT
整合SpringSecurity和JWT实现登录认证和授权 - 掘金
https://mp.weixin.qq.com/s/KGy02byw74P-xZRK5wzXDw
用的最多。
token 是放在 cookie 中好,还是放到 header 中好
关于 cookie 的相关解析,请看《会话跟踪技术.txt》
JWT 的 token 可以放在 cookie 中或者放在 header 中,具体取决于您的应用程序需求和设计选择。
放在 cookie 中的优点:
-
自动处理:浏览器会自动将 cookie 包含在每个请求中,无需手动处理。
-
跨域支持:cookie 可以自动处理跨域请求,无需额外配置。 -
跟 nginx 配合自动实现单点登录
因为 token 是能登录所有服务器实例的,只要 token 能在客户端跟 nginx 之间来回传递,再由 nginx 带着这个 token 去访问各个服务的时候,就可以登录所有的服务器实例
Nginx 反向代理 Web 服务的时候其实不适合使用 session 进行登录状态同步,session 是保留在 Web 容器内部的,无法在多个 web 容器之间共享和同步,除非使用单点登录平台,因此,干脆就不使用 session,采用跟服务器无关的 JWT,就非常方便。
放在 header 中的优点:
-
明确控制:通过将 JWT 放在请求头中,您可以更明确地控制哪些请求需要使用 JWT 进行身份验证。
-
安全性:将 JWT 放在请求头中可以提供更好的安全性,因为 cookie 容易受到跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)的影响。
-
不存在跨域问题
根据实际情况,您可以选择将 JWT 放在 cookie 中或者放在请求头中。通常,对于 API 身份验证,将 JWT 放在请求头中更为常见和推荐。而对于传统的 Web 应用程序,将 JWT 放在 cookie 中可能更为方便。无论您选择哪种方式,请确保在处理 JWT 时采取适当的安全措施,例如对 JWT 进行签名和验证,并设置适当的过期时间
注意,如果通过 HTTP 头发送 JWT 令牌,则应该尽量防止它们变得太大。有些服务器不接受超过 8KB 的消息头。如果您试图在 JWT 令牌中嵌入太多信息,比如包含所有用户的权限,那么您可能需要一个替代解决方案,比如 Auth0 Fine-Grained Authorization.
而且放到请求头中也不会存在跨域问题
JWT 与密码的使用场景的区别
JWT 相比密码,区别在于,JWT 是临时生成的,在有效期内总是有效,超过有效期需要重新生成;而密码是用户设置的,在用户主动修改之前总是有效的,有效期由其他策略控制,但通常都很长,用户修改密码后旧密码立即失效。所以,如果使用 JWT 做 session 保持,那么在用户修改密码之后,修改密码之前登录并签发的 JWT 不会立即过期,所有持有 JWT 的人依旧可以继续访问。因为 JWT 本身做不到主动失效,相当于 JWT 是用户的一个不受用户控制的“密码”,
如果你的系统支持 JWT 续期,那么一旦 JWT 泄漏,用户将无法找回账号,因为第三方可以拿着截获的 JWT 进行无限续期,用户改密码都不行的。解决办法就是,在用户退出,或是修改密码的时候,使此用户的所有 JWT 立即失效。前面说过,JWT 一旦生成,在有效期之前总是有效的,是不受控制的……所以为了使 JWT 立即过期,服务端必须记录所有生成的有效的 JWT,在使用 secret 校验的时候检查 JWT 是否在有效 JWT 列表中,如果不在,那么即便是 JWT 有效也拒绝认证。这样,只要在用户退出登录,或是修改密码的时候,删除服务端此用户的所有 JWT 记录即可。
但是,到了这个模式,如果服务端要支持用户退出,则必须要记录 JWT 的有效性,而记录 JWT 有效性,不如直接记录一个安全随机数……也就是说,JWT 本身并不是用来做 session 保持的,session 保持还是用服务端生成的一段安全随机数来进行比较好。而 JWT 的使用场景,通常是在服务切换的时候,用作身份令牌的。比如从 A 服务跳转到 B 服务,要带过去一些信息,这时候就可以用 JWT 来实现了,JWT 的有效期通常设置为不超过 5 秒。
JWT 安全性问题
在进行身份验证的时候,客户端(一般是浏览器)是不需要对 JWT 进行解密的,登录成功之后只需要在每次请求的时候带上 JWT 即可,此外,JWT 也是不能泄露的,因为如果别人拿到了这个 JWT,那么别人就可以冒充你,因为服务端只认 JWT,只要 JWT 是有效的,就会通过服务端的验证。
因此,将 JWT 保存在 cookie 中不一定是一个好主意。
-
不要将 JWT 存储在浏览器的本地存储中(Local storage)。任何恶意的 javascript 代码都可以很容易地访问它。
-
如果可能的话,创建寿命较短的 jwt。但是要小心,如果您使 JWT 无效,您的用户将被视为已注销。你不希望你的用户一直在登录。
-
确保您使用 SSL,因为它将加密您的网络流量,这将防止您的 JWT 被任何可以访问您的网络的人窃取。
如果泄露了 JWT 怎么办?
泄露的 JWT 与泄露的密码相同。您应该遵循与密码泄露相同的步骤。这可能包括
-
封了用户的帐户,除了改密码这一条什么都不能让看
-
撤销该用户的所有权限
-
要求用户立即更改密码,修改密码必须输入之前的密码
如何检测 JWT 泄露
-
监视客户端的位置。如果有人盗用了客户端的 JWT,他将尝试从其他位置使用令牌,而不是原始客户端经常使用的位置。这可以很好地指示您的 JWT 是否被盗。但冒充位置也是一项简单的事情。
-
分析用户模式。如果攻击者窃取了客户端的 JWT,他将尽可能快地利用它。因此,您必须检查 jwt 的行为。例如,如果通常每分钟发送 10 个请求的用户开始发送 50 个请求,那么这是丢失 JWT 的迹象。
如何避免 JWT 令牌 token 泄露恶意使用
-
ip 绑定方式
生成token的时候,加密的payload加入当前用户ip。 拦截器解密后,获取payload的ip和当前访问ip判断是否同个,如果不是则提示重新登录 优点:服务端无需存储相关内容,性能高,假如用户广州登录,泄露了token给杭州的黑客,依旧用不了 缺点:如果用户用使用过程中ip变动频繁,则操作会经常提示重新登录,体验不友好 当然也可以让用户开启安全模式和非安全模式,让用户自己知道这个情况,一些区块链、比特币交易所里面就会让用户自己选择控制这个token令牌安全是否和ip、终端、地理网络信息进行绑定
-
客户端属性绑定
-
地理网络位置信息绑定
保护 JWT 本身就是一项复杂的任务,如果您关心用户的数据,则需要付出很大的努力。这就是为什么总是建议您将处理身份验证的任务委托给外部服务,如 Auth0 或 Okta 或 FusionAuth。对于象征性的收费,这些服务将处理您的所有身份验证问题。