88 lines
3.5 KiB
PHP
88 lines
3.5 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\api\middleware;
|
||
|
||
use app\api\cache\UserCache;
|
||
use app\api\util\ReturnCode;
|
||
use app\dice\model\player\DicePlayer;
|
||
use plugin\saiadmin\exception\ApiException;
|
||
use Tinywan\Jwt\JwtToken;
|
||
use Tinywan\Jwt\Exception\JwtTokenException;
|
||
use Tinywan\Jwt\Exception\JwtTokenExpiredException;
|
||
use Webman\Http\Request;
|
||
use Webman\Http\Response;
|
||
use Webman\MiddlewareInterface;
|
||
|
||
/**
|
||
* 校验 token 请求头(JWT)
|
||
* 解码 JWT 取 username,与 Redis 中当前有效 token 比对;不一致则旧 token 已失效,请重新登录
|
||
* 通过后注入 request->player_id、request->player
|
||
*/
|
||
class TokenMiddleware implements MiddlewareInterface
|
||
{
|
||
public function process(Request $request, callable $handler): Response
|
||
{
|
||
$token = $request->header('token');
|
||
if ($token === null || $token === '') {
|
||
$auth = $request->header('authorization');
|
||
if ($auth && stripos($auth, 'Bearer ') === 0) {
|
||
$token = trim(substr($auth, 7));
|
||
}
|
||
}
|
||
$token = $token !== null ? trim((string) $token) : '';
|
||
if ($token === '') {
|
||
throw new ApiException('Please provide token', ReturnCode::UNAUTHORIZED);
|
||
}
|
||
|
||
try {
|
||
$decoded = JwtToken::verify(1, $token);
|
||
} catch (JwtTokenExpiredException $e) {
|
||
throw new ApiException('Token expired, please login again', ReturnCode::TOKEN_INVALID);
|
||
} catch (JwtTokenException $e) {
|
||
throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
|
||
} catch (\Throwable $e) {
|
||
throw new ApiException('Token format invalid', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
|
||
$extend = $decoded['extend'] ?? [];
|
||
if ((string) ($extend['plat'] ?? '') !== 'api_login') {
|
||
throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
$username = trim((string) ($extend['username'] ?? ''));
|
||
if ($username === '') {
|
||
throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
|
||
$currentToken = UserCache::getSessionTokenByUsername($username);
|
||
if ($currentToken === null || $currentToken === '') {
|
||
$player = DicePlayer::where('username', $username)->find();
|
||
if (!$player) {
|
||
throw new ApiException('Please register', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
throw new ApiException('Please login again', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
if ($currentToken !== $token) {
|
||
throw new ApiException('Please login again (account logged in elsewhere)', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
|
||
// 优先从 Redis 缓存取玩家,避免每次请求都查库
|
||
$player = null;
|
||
$cached = UserCache::getPlayerByUsername($username);
|
||
if ($cached !== null && isset($cached['id'])) {
|
||
$player = (new DicePlayer())->data($cached, true);
|
||
}
|
||
if ($player === null) {
|
||
$player = DicePlayer::where('username', $username)->find();
|
||
if (!$player) {
|
||
UserCache::deleteSessionByUsername($username);
|
||
throw new ApiException('Please login again', ReturnCode::TOKEN_INVALID);
|
||
}
|
||
UserCache::setPlayerByUsername($username, $player->hidden(['password'])->toArray());
|
||
}
|
||
$request->player_id = (int) $player->id;
|
||
$request->player = $player;
|
||
return $handler($request);
|
||
}
|
||
}
|