如何在 WordPress 中生成和验证 JWT(JSON Web Token)

好吧,终于在 WPJAM Basic 中实现了 JWT 登录和认证功能,今天就整理一下,首先按照惯例做一些 JWT 的介绍。

什么是 JWT

JWT(JSON Web Token)是一种轻量级的、自包含的、通常用于身份验证和授权的令牌,它将用户信息(如用户ID、角色和权限等)编码到一个 JSON 对象中,然后对其进行数字签名,从而生成一个经过签名的令牌。

由于 JWT 在客户端和服务器之间传输时是加密的,因此服务器可以轻松验证该令牌的有效性和真实性。JWT 常用于无状态的 RESTful API,作为访问受保护资源的凭据,可以在每次请求时附加在请求头中,下图是使用 JWT 的一个常见的交互流程:

JWT 的结构

JWT(JSON Web Token)由三部分组成,分别是:Header(头部)、Payload(负载)和 Signature(签名)。

这三部分使用点(.)分隔,合并为一个字符,如下所示:

header.payload.signature

Header(头部)

Header 是一个 JSON 对象,通常包含两个属性:alg 和 typalg 属性表示签名算法(如 HS256、RS256 等),typ 属性表示令牌类型,通常为 "JWT"。Header 会被 Base64Url 编码,得到一个字符串。例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload(负载):

Payload 是一个 JSON 对象,包含一些“声明”(Claim),用于传递用户信息和其他业务数据,声明可以是预定义的(如 iss、exp、sub 等),也可以是自定义的,Payload 会被 Base64Url 编码,得到一个字符串。例如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

下面这 7 个字段都是由官方所定义的,也就是预定义(Registered claims)的,但并不都是必需的,

  • iss (issuer):签发人
  • sub (subject):主题
  • aud (audience):受众
  • exp (expiration time):过期时间
  • nbf (Not Before):生效时间,在此之前是无效的
  • iat (Issued At):签发时间
  • jti (JWT ID):唯一身份标识,主要用来作为一次性 token,从而回避重放攻击

另外声明名称只有三个字符,因为 JWT 意味着是紧凑的。

Signature(签名):

签名用于保证 JWT 的完整性和安全性,它是将 Header、Payload 和一个密钥(Secret)通过签名算法进行加密得到的,签名可以防止 JWT 被篡改和伪造,例如,使用 HMAC-SHA256 算法生成签名的方法如下:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

HeaderPayloadSignature 用点(.)连接起来,就得到了一个完整的 JWT,例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

在 WordPress 中实现 JWT

通过上面 JWT 的介绍和 JWT 结构可知,生成 JWT,就是通过签名算法和一个用于生成签名的密钥(Secret)对 Payload 生成签名:我们为了方便就只支持 SHA256 签名算法:

function wpjam_generate_jwt($payload, $secret='', $header=[]){
	//无法生成没有设置过期时间的 JWT
	if(empty($payload['exp'])){
		return false;
	}

	$header	= wp_parse_args($header, [
		'alg'	=> 'HS256',
		'typ'	=> 'JWT'
	]);

	if($header['alg'] == 'HS256'){
		$header		= base64_urlencode(wpjam_json_encode($header));
		$payload	= base64_urlencode(wpjam_json_encode($payload));
		$jwt		= $header.'.'.$payload;
		$secret		= $secret ?: wp_salt();

		return $jwt.'.'.base64_urlencode(hash_hmac('sha256', $jwt, $secret, true));
	}
}

上面的代码首先对 HeaderPayload 进行 JSON 编码和 URL 安全的 Base64 编码,生成签名的密钥(Secret)如果为空,则使用 WordPress 默认的盐值函数来生成,最后把 HeaderPayload 和生成的签名通过点(.)连接起来得到了一个完整的 JWT 。

那么怎么验证 JWT 呢,验证的过程就是生成的反过程:首先通过点(.)将 JWT 分割成 HeaderPayloadSignature 三段,然后对 HeaderPayload 进行 URL 安全的 Base64 解码和JSON 解码,通过签名的密钥最后验证签名:

function wpjam_verify_jwt($token, $secret=''){
	$tokens	= explode('.', $token);

	if(count($tokens) != 3){
		return false;
	}

	list($header, $payload, $sign) = $tokens;

	$jwt		= $header.'.'.$payload;
	$secret		= $secret ?: wp_salt();
	$header		= wpjam_json_decode(base64_urldecode($header));
	$payload	= wpjam_json_decode(base64_urldecode($payload));

	if(empty($header['alg']) || $header['alg'] != 'HS256'){
		return false;
	}

	if(!hash_equals(base64_urlencode(hash_hmac('sha256', $jwt, $secret, true)), $sign)){
		return false;
	}

	//签发时间大于当前服务器时间验证失败
	if(isset($payload['iat']) && $payload['iat'] > time()){
		return false;
	}

	//该nbf时间之前不接收处理该Token
	if(isset($payload['nbf']) && $payload['nbf'] > time()){
		return false;
	}

	//没有设置过期时间,或过期时间小于当前服务器时间验证失败
	if(empty($payload['exp']) || $payload['exp'] < time()){
		return false;
	}

	return $payload;
}

我们这边生成和验证函数有点特殊处理,就是都要实现过期时间设置,意思就是不能设置没有过期时间的 JWT。

wpjam_generate_jwtwpjam_verify_jwt 这两个函数在 WPJAM Basic 中已经内置了,通过他们就可以在 WordPress 实现 JWT 的生成和验证了。

特别提醒一下过程中用到的 URL 安全的 Base64 编码和解码函数 base64_urlencodebase64_urldecode 也是在 WPJAM Basic 中定义了。


©我爱水煮鱼,本站推荐使用的主机:阿里云,国外主机建议使用BlueHost

本站长期承接 WordPress 优化建站业务,请联系微信:「chenduopapa」。