|
|
<?php
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
use Firebase\JWT\JWT;
|
|
|
use Firebase\JWT\Key;
|
|
|
use Illuminate\Support\Carbon;
|
|
|
use InvalidArgumentException;
|
|
|
use Firebase\JWT\ExpiredException;
|
|
|
use Firebase\JWT\SignatureInvalidException;
|
|
|
|
|
|
class JwtService
|
|
|
{
|
|
|
/**
|
|
|
* 生成访问令牌
|
|
|
* @param array $payload 自定义载荷(如用户ID、角色)
|
|
|
* @return string
|
|
|
*/
|
|
|
public function generateToken(array $payload): string
|
|
|
{
|
|
|
$secret = config('app.jwt.secret');
|
|
|
$algorithm = config('app.jwt.algorithm');
|
|
|
$expiresAt = Carbon::now()->addSeconds(config('app.jwt.expire'))->timestamp;
|
|
|
|
|
|
// 标准JWT载荷 + 自定义载荷
|
|
|
$jwtPayload = array_merge([
|
|
|
'iss' => config('app.url'), // 签发者
|
|
|
'iat' => Carbon::now()->timestamp, // 签发时间
|
|
|
'exp' => $expiresAt, // 过期时间
|
|
|
'nbf' => Carbon::now()->timestamp, // 生效时间
|
|
|
], $payload);
|
|
|
|
|
|
return JWT::encode($jwtPayload, $secret, $algorithm);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成刷新令牌
|
|
|
* @param int $userId 用户ID
|
|
|
* @return string
|
|
|
*/
|
|
|
public function generateRefreshToken(int $userId): string
|
|
|
{
|
|
|
$secret = config('app.jwt.secret');
|
|
|
$algorithm = config('app.jwt.algorithm');
|
|
|
$expiresAt = Carbon::now()->addSeconds(config('app.jwt.refresh_expire'))->timestamp;
|
|
|
|
|
|
$refreshPayload = [
|
|
|
'iss' => config('app.url'),
|
|
|
'iat' => Carbon::now()->timestamp,
|
|
|
'exp' => $expiresAt,
|
|
|
'sub' => 'refresh_token', // 标识为刷新令牌
|
|
|
'user_id' => $userId,
|
|
|
];
|
|
|
|
|
|
return JWT::encode($refreshPayload, $secret, $algorithm);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 验证并解析JWT令牌
|
|
|
* @param string $token
|
|
|
* @return array 解析后的载荷
|
|
|
* @throws ExpiredException|SignatureInvalidException|InvalidArgumentException
|
|
|
*/
|
|
|
public function verifyToken(string $token): array
|
|
|
{
|
|
|
$secret = config('app.jwt.secret');
|
|
|
$algorithm = config('app.jwt.algorithm');
|
|
|
|
|
|
try {
|
|
|
// 验证并解析令牌
|
|
|
$decoded = JWT::decode($token, new Key($secret, $algorithm));
|
|
|
return (array) $decoded;
|
|
|
} catch (ExpiredException $e) {
|
|
|
throw new ExpiredException('Token已过期', 401);
|
|
|
} catch (SignatureInvalidException $e) {
|
|
|
throw new SignatureInvalidException('Token签名无效', 400);
|
|
|
} catch (InvalidArgumentException $e) {
|
|
|
throw new InvalidArgumentException('Token格式错误', 400);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 解析令牌(不验证签名/过期,仅用于读取载荷)
|
|
|
* @param string $token
|
|
|
* @return array
|
|
|
*/
|
|
|
public function decodeTokenWithoutVerify(string $token): array
|
|
|
{
|
|
|
$parts = explode('.', $token);
|
|
|
if (count($parts) !== 3) {
|
|
|
throw new InvalidArgumentException('Token格式错误');
|
|
|
}
|
|
|
$payload = json_decode(base64_decode($parts[1]), true);
|
|
|
return $payload ?: [];
|
|
|
}
|
|
|
}
|