101 lines
3.5 KiB
PHP
101 lines
3.5 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | Author: ScottPan Team
|
||
// +----------------------------------------------------------------------
|
||
|
||
namespace app\services\auth;
|
||
|
||
use crmeb\exceptions\AuthException;
|
||
use crmeb\services\CacheService;
|
||
use crmeb\utils\ApiErrorCode;
|
||
use crmeb\utils\JwtAuth;
|
||
use Firebase\JWT\ExpiredException;
|
||
|
||
/**
|
||
* 本项目自有 access token 服务,不依赖 CRMEB 商业授权基础类。
|
||
*/
|
||
class AccessTokenService
|
||
{
|
||
/**
|
||
* 创建 access token,并写入对应类型的 token bucket。
|
||
*/
|
||
public function createToken(int $id, string $type, string $authHash, array $extra = []): array
|
||
{
|
||
/** @var JwtAuth $jwtAuth */
|
||
$jwtAuth = app()->make(JwtAuth::class);
|
||
|
||
return $jwtAuth->createToken($id, $type, $extra + ['auth' => $authHash]);
|
||
}
|
||
|
||
/**
|
||
* 解析并校验 access token。
|
||
*
|
||
* @param callable $resolver 根据 token 内的 id 读取账号模型或数组
|
||
* @param callable $authHashResolver 根据账号返回当前有效的 auth hash
|
||
* @param callable|null $accountValidator 根据账号判断当前 token 是否仍可用
|
||
* @return mixed
|
||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||
*/
|
||
public function parseToken(string $token, string $type, callable $resolver, callable $authHashResolver, callable $accountValidator = null)
|
||
{
|
||
if (!$token || $token === 'undefined') {
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN);
|
||
}
|
||
|
||
/** @var JwtAuth $jwtAuth */
|
||
$jwtAuth = app()->make(JwtAuth::class);
|
||
|
||
[$id, $tokenType, $auth] = $jwtAuth->parseToken($token);
|
||
if (!$id || $tokenType !== $type) {
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
|
||
}
|
||
|
||
$md5Token = md5($token);
|
||
$cacheToken = CacheService::redisHandler($type)->get($md5Token, null);
|
||
if (!$cacheToken) {
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN);
|
||
}
|
||
|
||
if (isset($cacheToken['invalidNum']) && $cacheToken['invalidNum'] >= 3) {
|
||
$this->clearToken($md5Token, $type);
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
|
||
}
|
||
|
||
try {
|
||
$jwtAuth->verifyToken();
|
||
CacheService::setTokenBucket($md5Token, $cacheToken, $cacheToken['exp'] ?? null, $type);
|
||
} catch (ExpiredException $e) {
|
||
$cacheToken['invalidNum'] = ($cacheToken['invalidNum'] ?? 0) + 1;
|
||
CacheService::setTokenBucket($md5Token, $cacheToken, $cacheToken['exp'] ?? null, $type);
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN);
|
||
} catch (\Throwable $e) {
|
||
$this->clearToken($md5Token, $type);
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
|
||
}
|
||
|
||
$account = $resolver($id);
|
||
if (!$account) {
|
||
$this->clearToken($md5Token, $type);
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN);
|
||
}
|
||
|
||
if ($accountValidator && !$accountValidator($account)) {
|
||
$this->clearToken($md5Token, $type);
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
|
||
}
|
||
|
||
if ($auth !== $authHashResolver($account)) {
|
||
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
|
||
}
|
||
|
||
return $account;
|
||
}
|
||
|
||
protected function clearToken(string $md5Token, string $type): void
|
||
{
|
||
if (!request()->isCli()) {
|
||
CacheService::redisHandler($type)->delete($md5Token);
|
||
}
|
||
}
|
||
}
|