226 lines
5.7 KiB
PHP
226 lines
5.7 KiB
PHP
<?php
|
||||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
namespace core\jwt;
|
|||
|
|
|
|||
|
|
use DateTimeImmutable;
|
|||
|
|
use JsonSerializable;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Token 数据对象
|
|||
|
|
*/
|
|||
|
|
readonly class Token implements JsonSerializable
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* @param string $accessToken 访问令牌
|
|||
|
|
* @param string $refreshToken 刷新令牌
|
|||
|
|
* @param int $expiresIn 有效期(秒)
|
|||
|
|
* @param string $tokenType 令牌类型
|
|||
|
|
* @param string $jti 令牌唯一标识
|
|||
|
|
* @param string $id 用户ID(雪花ID)
|
|||
|
|
* @param string $clientType 客户端类型
|
|||
|
|
* @param DateTimeImmutable $issuedAt 签发时间
|
|||
|
|
* @param DateTimeImmutable $expiresAt 过期时间
|
|||
|
|
* @param array $payload 载荷数据
|
|||
|
|
*/
|
|||
|
|
public function __construct(
|
|||
|
|
public string $accessToken,
|
|||
|
|
public string $refreshToken,
|
|||
|
|
public int $expiresIn,
|
|||
|
|
public string $tokenType = 'Bearer',
|
|||
|
|
public string $jti = '',
|
|||
|
|
public string $id = '',
|
|||
|
|
public string $clientType = '',
|
|||
|
|
public DateTimeImmutable $issuedAt = new DateTimeImmutable(),
|
|||
|
|
public DateTimeImmutable $expiresAt = new DateTimeImmutable(),
|
|||
|
|
public array $payload = [],
|
|||
|
|
) {}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建 Token 实例
|
|||
|
|
*
|
|||
|
|
* @param string $accessToken 访问令牌
|
|||
|
|
* @param string $refreshToken 刷新令牌
|
|||
|
|
* @param int $expiresIn 有效期(秒)
|
|||
|
|
* @param array $payload 完整载荷
|
|||
|
|
* @return static
|
|||
|
|
*/
|
|||
|
|
public static function create(
|
|||
|
|
string $accessToken,
|
|||
|
|
string $refreshToken,
|
|||
|
|
int $expiresIn,
|
|||
|
|
array $payload = []
|
|||
|
|
): static {
|
|||
|
|
$now = new DateTimeImmutable();
|
|||
|
|
$expiresAt = (new DateTimeImmutable())->setTimestamp($now->getTimestamp() + $expiresIn);
|
|||
|
|
|
|||
|
|
return new static(
|
|||
|
|
accessToken: $accessToken,
|
|||
|
|
refreshToken: $refreshToken,
|
|||
|
|
expiresIn: $expiresIn,
|
|||
|
|
jti: $payload['jti'] ?? '',
|
|||
|
|
id: (string)($payload['id'] ?? ''),
|
|||
|
|
clientType: $payload['client'] ?? '',
|
|||
|
|
issuedAt: $now,
|
|||
|
|
expiresAt: $expiresAt,
|
|||
|
|
payload: $payload,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从请求中解析 Token
|
|||
|
|
*
|
|||
|
|
* 支持多种格式:
|
|||
|
|
* 1. Header: Authorization: Bearer <token>
|
|||
|
|
* 2. Header: Authorization: <token>
|
|||
|
|
* 3. 参数: access_token=<token>
|
|||
|
|
* 4. 参数: token=<token>
|
|||
|
|
*
|
|||
|
|
* @return string|null
|
|||
|
|
*/
|
|||
|
|
public static function fromRequest(): ?string
|
|||
|
|
{
|
|||
|
|
// 1. Header Authorization Bearer
|
|||
|
|
$auth = request()->header('Authorization', '');
|
|||
|
|
if (!empty($auth)) {
|
|||
|
|
if (str_starts_with($auth, 'Bearer ')) {
|
|||
|
|
return substr($auth, 7);
|
|||
|
|
}
|
|||
|
|
// 直接是 token
|
|||
|
|
return $auth;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 参数 access_token
|
|||
|
|
$token = request()->input('access_token');
|
|||
|
|
if (!empty($token)) {
|
|||
|
|
return $token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 参数 token
|
|||
|
|
$token = request()->input('token');
|
|||
|
|
if (!empty($token)) {
|
|||
|
|
return $token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 Token 字符串
|
|||
|
|
*
|
|||
|
|
* @param string $tokenString JWT token 字符串
|
|||
|
|
* @param int $expiresIn 有效期(秒)
|
|||
|
|
* @param array $payload 载荷数据
|
|||
|
|
* @return static
|
|||
|
|
*/
|
|||
|
|
public static function parse(string $tokenString, int $expiresIn, array $payload = []): static
|
|||
|
|
{
|
|||
|
|
return static::create($tokenString, '', $expiresIn, $payload);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取访问令牌
|
|||
|
|
*/
|
|||
|
|
public function getAccessToken(): string
|
|||
|
|
{
|
|||
|
|
return $this->accessToken;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取刷新令牌
|
|||
|
|
*/
|
|||
|
|
public function getRefreshToken(): string
|
|||
|
|
{
|
|||
|
|
return $this->refreshToken;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取用户ID(雪花ID)
|
|||
|
|
*/
|
|||
|
|
public function getId(): string
|
|||
|
|
{
|
|||
|
|
return $this->id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取客户端类型
|
|||
|
|
*/
|
|||
|
|
public function getClientType(): string
|
|||
|
|
{
|
|||
|
|
return $this->clientType;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取令牌唯一标识
|
|||
|
|
*/
|
|||
|
|
public function getJti(): string
|
|||
|
|
{
|
|||
|
|
return $this->jti;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取过期时间戳
|
|||
|
|
*/
|
|||
|
|
public function getExpiresAt(): int
|
|||
|
|
{
|
|||
|
|
return $this->expiresAt->getTimestamp();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查是否已过期
|
|||
|
|
*/
|
|||
|
|
public function isExpired(): bool
|
|||
|
|
{
|
|||
|
|
return time() >= $this->expiresAt->getTimestamp();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取剩余有效期(秒)
|
|||
|
|
*/
|
|||
|
|
public function getRemainingTime(): int
|
|||
|
|
{
|
|||
|
|
$remaining = $this->expiresAt->getTimestamp() - time();
|
|||
|
|
return max(0, $remaining);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 转换为数组
|
|||
|
|
*/
|
|||
|
|
public function toArray(): array
|
|||
|
|
{
|
|||
|
|
return [
|
|||
|
|
'token_type' => $this->tokenType,
|
|||
|
|
'access_token' => $this->accessToken,
|
|||
|
|
'refresh_token' => $this->refreshToken,
|
|||
|
|
'expires_in' => $this->expiresIn,
|
|||
|
|
'expires_at' => $this->expiresAt->format('Y-m-d H:i:s'),
|
|||
|
|
'jti' => $this->jti,
|
|||
|
|
'id' => $this->id,
|
|||
|
|
'client_type' => $this->clientType,
|
|||
|
|
'issued_at' => $this->issuedAt->format('Y-m-d H:i:s'),
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 转换为前端格式
|
|||
|
|
*/
|
|||
|
|
public function toFrontend(): array
|
|||
|
|
{
|
|||
|
|
return [
|
|||
|
|
'token' => $this->accessToken,
|
|||
|
|
'refresh_token' => $this->refreshToken,
|
|||
|
|
'expires_in' => $this->expiresIn,
|
|||
|
|
'expires_at' => $this->expiresAt->getTimestamp(),
|
|||
|
|
'token_type' => $this->tokenType,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* JSON 序列化
|
|||
|
|
*/
|
|||
|
|
public function jsonSerialize(): array
|
|||
|
|
{
|
|||
|
|
return $this->toArray();
|
|||
|
|
}
|
|||
|
|
}
|