95 lines
3.2 KiB
PHP
95 lines
3.2 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
|
|||
|
|
* @return mixed
|
|||
|
|
* @throws \Psr\SimpleCache\InvalidArgumentException
|
|||
|
|
*/
|
|||
|
|
public function parseToken(string $token, string $type, callable $resolver, callable $authHashResolver)
|
|||
|
|
{
|
|||
|
|
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 ($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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|