| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- <?php
- namespace app\common\library;
- class JwtToken
- {
- /**
- * Generate an HS256 JWT.
- *
- * @param array $payload
- * @param string $secret
- * @return string
- */
- public static function encode(array $payload, $secret)
- {
- if (empty($secret)) {
- throw new \InvalidArgumentException('JWT密钥不能为空');
- }
- $header = [
- 'typ' => '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, '-_', '+/'));
- }
- }
|