feat : jwt

This commit is contained in:
2025-09-12 18:12:30 +08:00
parent a80c237bbb
commit ff3e0105ec
14 changed files with 362 additions and 38 deletions

View File

@@ -19,4 +19,10 @@ class ResultCode extends AbstractConstants
#[Message("failed")]
final public const int ERROR = 1;
#[Message("token已过期")]
final public const int JWT_EXPIRED = 10001;
#[Message("token错误")]
final public const int JWT_ERROR = 10002;
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Annotation\ResponseFormat;
use App\Middleware\Token\AdminTokenMiddleware;
use App\Service\Admin\AdminUser\UserService;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Validation\Annotation\Scene;
#[Controller(prefix: "admin/adminUser")]
#[ResponseFormat('admin')]
#[Middleware(AdminTokenMiddleware::class)]
class AdminUserController
{
#[RequestMapping(path: "getInfo", methods: "GET")]
public function getInfo()
{
return (new UserService)->handle();
}
#[RequestMapping(path: "refresh", methods: "POST")]
public function refresh()
{
return (new UserService)->refresh();
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Exception\Handler;
use App\Constants\ResultCode;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Lcobucci\JWT\Exception as JWTException;
class JwtExceptionHandler extends BaseErrExceptionHandler
{
/**
* @param Throwable $throwable
* @param ResponseInterface $response
* @return ResponseInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
{
if ($throwable instanceof JWTException) {
$throwable = $this->modifyException($throwable);
// 传递给基类处理
return $this->handlerResponse($throwable, $response);
}
return $response;
}
protected function modifyException(Throwable $e): Throwable
{
// 根据不同的异常类型设置不同的code和message
switch ($e->getMessage()) {
case 'The token is expired':
$e->code = ResultCode::JWT_EXPIRED;
$e->message = 'token已过期';
break;
default:
$e->code = ResultCode::JWT_ERROR;
$e->message = 'token错误';
}
return $e;
}
/**
* @param Throwable $throwable
* @return bool
*/
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof JWTException;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Interface;
use Lcobucci\JWT\UnencryptedToken;
interface CheckTokenInterface
{
public function checkJwt(UnencryptedToken $token): void;
}

View File

@@ -11,6 +11,7 @@ use Lcobucci\JWT\Builder;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token\RegisteredClaims;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
@@ -45,7 +46,8 @@ abstract class AbstractJwt implements JwtInterface
$this->getSigner(),
$this->getSigningKey(),
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
$builder = $builder->identifiedBy($sub);
$builder = $builder->identifiedBy($sub)
->issuedBy($this->getConfig('claims.'.RegisteredClaims::ISSUER,''));
if ($callable !== null) {
$builder = $callable($builder);
}
@@ -65,8 +67,10 @@ abstract class AbstractJwt implements JwtInterface
$this->getSigner(),
$this->getSigningKey(),
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
$builder = $builder->identifiedBy($sub);
$builder = $builder->expiresAt($this->getRefreshExpireAt($immutable));
$builder = $builder->identifiedBy($sub)
->issuedBy($this->getConfig('claims.'.RegisteredClaims::ISSUER,''))
->expiresAt($this->getRefreshExpireAt($immutable));
if ($callable !== null) {
$builder = $callable($builder);
}
@@ -81,6 +85,7 @@ abstract class AbstractJwt implements JwtInterface
*/
public function parserAccessToken(string $accessToken): UnencryptedToken
{
echo 1;
return $this->getJwtFacade()
->parse(
$accessToken,

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Lib\Jwt;
use Hyperf\Context\RequestContext;
use Lcobucci\JWT\UnencryptedToken;
trait RequestScopedTokenTrait
{
public function getToken(): ?UnencryptedToken
{
return RequestContext::get()->getAttribute('token');
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Middleware\Token;
use App\Interface\CheckTokenInterface;
use App\Interface\JwtInterface;
use App\Lib\Jwt\JwtFactory;
use Hyperf\Collection\Arr;
use Hyperf\Stringable\Str;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\UnencryptedToken;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Swow\Psr7\Message\ServerRequestPlusInterface;
use function Hyperf\Support\value;
abstract class AbstractTokenMiddleware
{
public function __construct(
protected ContainerInterface $container,
protected readonly JwtFactory $jwtFactory,
protected readonly CheckTokenInterface $checkToken,
) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$token = $this->parserToken($request);
$this->checkToken->checkJwt($token);
$this->checkIssuer($token);
return $handler->handle(
value(
static function (ServerRequestPlusInterface $request, UnencryptedToken $token) {
return $request->setAttribute('token', $token);
},
$request,
$token
)
);
}
/**
* @param UnencryptedToken $token
* @return void
*/
abstract public function checkIssuer(UnencryptedToken $token): void;
abstract public function getJwt(): JwtInterface;
/**
* @param ServerRequestInterface $request
* @return Token
*/
protected function parserToken(ServerRequestInterface $request): Token
{
return $this->getJwt()->parserAccessToken($this->getToken($request));
}
/**
* @param ServerRequestInterface $request
* @return string
*/
protected function getToken(ServerRequestInterface $request): string
{
if ($request->hasHeader('Authorization')) {
return Str::replace('Bearer ', '', $request->getHeaderLine('Authorization'));
}
if ($request->hasHeader('token')) {
return $request->getHeaderLine('token');
}
if (Arr::has($request->getQueryParams(), 'token')) {
return $request->getQueryParams()['token'];
}
return '';
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Middleware\Token;
use App\Exception\ErrException;
use App\Interface\JwtInterface;
use Lcobucci\JWT\Token\RegisteredClaims;
use Lcobucci\JWT\UnencryptedToken;
use function Hyperf\Support\env;
final class AdminTokenMiddleware extends AbstractTokenMiddleware
{
public function getJwt(): JwtInterface
{
return $this->jwtFactory->get('admin');
}
/**
* @param UnencryptedToken $token
* @return void
*/
public function checkIssuer(UnencryptedToken $token): void
{
$audience = $token->claims()->get(RegisteredClaims::ISSUER);
if ($audience === env('APP_NAME') .'_admin') throw new ErrException('token错误');
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Admin\AdminUser;
use App\Lib\Jwt\RequestScopedTokenTrait;
use App\Service\Admin\BaseAdminService;
use Lcobucci\JWT\Token\RegisteredClaims;
class UserService extends BaseAdminService
{
use RequestScopedTokenTrait;
public function handle(): array
{
var_dump($this->getToken()->claims()->all());
var_dump($this->getToken()->claims()->get(RegisteredClaims::ID));
var_dump($this->getToken()->claims()->get(RegisteredClaims::AUDIENCE));
return $this->adminReturn->success();
}
public function refresh(): array
{
return $this->adminReturn->success();
}
}

View File

@@ -16,6 +16,7 @@ use App\Interface\JwtInterface;
use App\Lib\Jwt\JwtFactory;
use App\Repository\AdminUserRepository;
use App\Service\Admin\BaseAdminService;
use App\Service\BaseTokenService;
use Hyperf\Di\Annotation\Inject;
class LoginService extends BaseAdminService
@@ -32,10 +33,10 @@ class LoginService extends BaseAdminService
protected AdminUserRepository $userRepository;
/**
* @var JwtFactory
* @var BaseTokenService
*/
#[Inject]
protected JwtFactory $jwtFactory;
protected BaseTokenService $tokenService;
/**
* @return array
@@ -53,20 +54,12 @@ class LoginService extends BaseAdminService
if ($adminInfo->status == AdminUserStatusCode::DISABLE) throw new ErrException('用户已禁用');
$jwtHandle = $this->getJwt();
$jwtHandle = $this->tokenService->getJwt();
return [
return $this->adminReturn->success('success',[
'access_token' => $jwtHandle->builderAccessToken((string) $adminInfo->id)->toString(),
'refresh_token' => $jwtHandle->builderRefreshToken((string) $adminInfo->id)->toString(),
'expire_at' => (int) $jwtHandle->getConfig('ttl', 0),
];
}
/**
* @return JwtInterface
*/
private function getJwt(): JwtInterface
{
return $this->jwtFactory->get($this->jwt);
]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service;
use App\Exception\ErrException;
use App\Interface\CheckTokenInterface;
use App\Lib\Jwt\JwtFactory;
use Hyperf\Di\Annotation\Inject;
use Lcobucci\JWT\UnencryptedToken;
use App\Interface\JwtInterface;
final class BaseTokenService implements CheckTokenInterface
{
private string $jwt = 'admin';
/**
* @var JwtFactory
*/
#[Inject]
protected JwtFactory $jwtFactory;
/**
* @return JwtInterface
*/
public function getJwt(): JwtInterface
{
return $this->jwtFactory->get($this->jwt);
}
/**
* @return JwtInterface
*/
public function getApiJwt(): JwtInterface
{
return $this->jwtFactory->get('api');
}
/**
* @param UnencryptedToken $token
* @return void
*/
public function checkJwt(UnencryptedToken $token): void
{
$this->getJwt()->hasBlackList($token) && throw new ErrException('token已过期');
}
}