'JWT', 'alg' => 'HS256', ]; $segments = [ self::base64UrlEncode(json_encode($header, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)), self::base64UrlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)), ]; $signature = hash_hmac('sha256', implode('.', $segments), $secret, true); $segments[] = self::base64UrlEncode($signature); return implode('.', $segments); } /** * Decode a JWT and return the payload without verifying the signature. * * 注意:此方法只做 base64url 解码,不校验签名,返回的数据可能被篡改, * 仅用于查看内容;做安全判断请使用 verify()。 * * @param string $jwt * @return array|null 解析成功返回 payload 数组,token 格式非法时返回 null */ public static function decode($jwt) { if (!is_string($jwt) || $jwt === '') { return null; } $segments = explode('.', $jwt); if (count($segments) !== 3) { return null; } $payload = json_decode(self::base64UrlDecode($segments[1]), true); if ($payload === null && json_last_error() !== JSON_ERROR_NONE) { return null; } return $payload; } /** * Verify a JWT's HS256 signature and expiration time. * * @param string $jwt * @param string $secret * @return array|false 验证通过返回 payload 数组,验签失败/已过期/格式非法时返回 false * @throws \InvalidArgumentException 密钥为空时抛出,与 encode() 行为一致 */ public static function verify($jwt, $secret) { if (empty($secret)) { throw new \InvalidArgumentException('JWT密钥不能为空'); } if (!is_string($jwt) || $jwt === '') { return false; } $segments = explode('.', $jwt); if (count($segments) !== 3) { return false; } list($headerSegment, $payloadSegment, $signatureSegment) = $segments; // 1. 校验签名(hash_equals 防止时序攻击) $expectedSignature = hash_hmac('sha256', $headerSegment . '.' . $payloadSegment, $secret, true); if (!hash_equals(self::base64UrlEncode($expectedSignature), $signatureSegment)) { return false; } // 2. 校验算法声明,防止算法混淆攻击 $header = json_decode(self::base64UrlDecode($headerSegment), true); if (!is_array($header) || !isset($header['alg']) || $header['alg'] !== 'HS256') { return false; } // 3. 解析 payload 并检查过期时间 $payload = json_decode(self::base64UrlDecode($payloadSegment), true); if (!is_array($payload)) { return false; } if (isset($payload['exp']) && time() > (int) $payload['exp']) { return false; } return $payload; } private static function base64UrlEncode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } private static function base64UrlDecode($data) { return base64_decode(strtr($data, '-_', '+/')); } }