json web token
前言
最近我一个朋友换到了新的公司,接手了一个不算太旧的项目,大概三四年吧。
然后他跟我说,他们的token
是自生成的,是一串毫无意义的uuid
。
然后业务用户的信息就需要通过这串毫无意义的uuid
去数据库查询对应的user_id
,再通过user_id
查到该用户的信息
真是一串冗长的废话操作不说,还浪费了性能,真是把博主给看笑了。
同时博主也很疑惑,都 2024 年了,就算是三四年的项目,也才 2020 年。竟然还有人不知道 jwt
的存在吗
什么是 jwt
jwt
全称是json web token
,它是一个开放标准
(RFC 7519)
,它定义了一种紧凑且自包含的方式,使用JSON对象在各方之间安全地传输信息。此信息可以被验证和信任,因为它是数字签名的。jwt
可以使用密钥(通过HMAC
算法)进行签名,或者使用公钥/私钥对,采用RSA
或ECDSA
算法进行签名。
尽管jwt
可以被加密以在各方之间提供保密性,我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则将这些声明隐藏起来,不让其他方看到。当使用公钥/私钥对对令牌进行签名时,签名还证实了只有持有私钥的一方才是签名者。
jwt
的使用场景
授权
jwt
最常见的使用场景是用户身份验证。一旦用户登录成功,服务器会生成一个包含用户信息的jwt
,并将其返回给客户端。在后续的请求中,客户端只需携带这个jwt
,服务器就可以通过验证jwt
的有效性来确认用户的身份,并授权其访问相应的资源。这种方式避免了频繁查询数据库,提高了应用的性能。SSO
sso
即是单点登录,jwt
在单点登录场景中也非常有用。单点登录允许用户在一个应用系统中登录后,无需再次登录即可访问其他相关应用。通过使用jwt
,可以将用户的登录状态信息编码在token
中,并在多个应用之间共享。这样,用户只需在一次登录后,就可以无缝地切换到其他应用,提高了用户体验。无状态认证 对于
RESTful API
来说,jwt
是实现无状态认证的一种理想方式。无状态认证意味着服务器不保存任何会话信息,每次请求都需要携带足够的认证信息。通过使用jwt
,客户端可以在每个请求中附带token
,服务器通过验证token
的有效性来确认客户端的身份,从而实现了无状态认证。
结构
jwt由三部分组成,并用.
相连接,三部分分别为
Header
-- (头部)Payload
-- (载荷)Signature
-- (签名)
所以基本上常见的jwt
都是 xxxxxxx.yyyyyyy.zzzzzzz
Header
标头通常由两部分组成:令牌的类型和使用的签名算法
{
"alg": "HS256",
"typ": "JWT"
}
这个json
是使用Base64 Url
编码的。
Payload
令牌的第二部分是有效载荷,包含声明。声明是关于实体(通常是用户信息)和附加数据的声明。有三种类型:注册,公共和私有声明。
Registered claims
这是一组预定义的声明,不是强制性的,推荐使用。提供一组有用的、可互操作的声明。比如:iss
/exp
/sub
/aud
等。Public claims
这些可以由使用jwt
的人随意定义。但是为了避免冲突,它们应该在IANA JSON Web
令牌注册表中定义。Private claims
这些自定义声明是为了在各方之间共享信息而创建的,既不是注册的也不是公开的声明。
{
"sub": "admin",
"name": "ctexthuang",
"admin": true
}
jwt
的有效载荷或头部元素中,除非它被加密。Signature
签名部分就是通过代码来实现了,但是首先要获取正确的算法也就是header
中声明的
public static function encode(
array $payload,
$key,
string $alg,
string $keyId = null,
array $head = null
): string {
$header = ['typ' => 'JWT'];
if (isset($head) && \is_array($head)) {
$header = \array_merge($header, $head);
}
$header['alg'] = $alg;
if ($keyId !== null) {
$header['kid'] = $keyId;
}
$segments = [];
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
$signing_input = \implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
return \implode('.', $segments);
}
签名用于验证消息在发送过程中没有被沿着更改。并且,在使用私钥签名的令牌的情况下,它还可以验证jwt
的发送者是否是它所说的那个人。
最终
输出的是由点分隔Base64-URL
字符串,可以在HTML
和HTTP
环境中轻松传递。同时,与基于XML
的标准(如SAML
)相比更加紧凑。
下面显示了一个jwt
,它具有头部和有效负载编码,并且使用加密签名。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MzA4NjkzNjIsIm5iZiI6MTczMDg2OTM2MiwiZXhwIjoxNzMwODcyOTYyLCJkYXRhIjp7ImlkIjoxLCJyb2xlIjoxfX0.yG7VzTj-6N86h8f0VFIO2MESCmJ3LA9DWcV51XRA0Kc
可以尝试用
在线jwt编译器
来解码、验证和生成jwt
实现原理
在身份验证中,当用户成功登录后,将返回JSON Web Token。由于令牌是凭证,因此必须非常小心,防止出现安全问题。
由于缺乏浏览器安全性,您还不应将敏感会话数据存储在浏览器中。
每当用户想要访问受保护的路由或资源时,用户发送JWT,通常在使用Bearer模式的Authorization头中设置。标题的内容应该如下所示:
Authorization: Bearer <jwt>
在某些情况下,可以是一种无状态的授权机制。服务器的protected routes
将检查Authorization
头中是否存在有效的jwt
。如果存在,则允许用户访问受保护的资源。
请注意,如果您通过HTTP
协议的标头发送jwt
令牌,则应尝试防止它们太大。有些服务器不接受超过8K的头文件。如果您试图在jwt
令牌中嵌入太多信息,例如包含所有用户的权限,则可能需要替代解决方案,例如:
Auth 0 Fine-Grained Authorization
如果令牌在Authorization
报头中发送,跨域资源共享将不会成为问题,因为它不使用Cookie
。
下图显示了如何获取jwt
并将其用于访问api
或资源:
请注意,使用签名令牌时,令牌中包含的所有信息都会暴露给用户或其他方。这意味着您不应该将机密信息放入令牌中。
为什么
json
没有XML
那么冗长。所以,在编码时,它的大小也更小。这使得jwt
比SAML
更紧凑。
安全方面,SWT
只能通过使用HMAC
算法进行对称签名。但是,jwt
和SAML
令牌可以使用X.509证书
形式的公钥/私钥对进行签名。与json
签名的简单性相比,使用XML
数字签名而不引入模糊的安全漏洞是非常困难的。
json
解析器在大多数编程语言中很常见,因为它们直接映射到对象。相反,XML
没有自然的文档到对象的映射。