mirror of
https://gitee.com/ctexthuang/hyperf_rbac_framework_server_ctexthuang.git
synced 2025-12-25 11:22:10 +08:00
Compare commits
2 Commits
a80c237bbb
...
ea6abe3043
| Author | SHA1 | Date | |
|---|---|---|---|
| ea6abe3043 | |||
| ff3e0105ec |
@@ -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;
|
||||
}
|
||||
|
||||
32
app/Controller/Admin/AdminUserController.php
Normal file
32
app/Controller/Admin/AdminUserController.php
Normal 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();
|
||||
}
|
||||
}
|
||||
65
app/Exception/Handler/JwtExceptionHandler.php
Normal file
65
app/Exception/Handler/JwtExceptionHandler.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use App\Constants\ResultCode;
|
||||
use App\Exception\ErrException;
|
||||
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(JWTException $e): Throwable
|
||||
{
|
||||
// 根据不同的异常类型设置不同的code和message
|
||||
switch ($e->getMessage()) {
|
||||
case 'The token is expired':
|
||||
$code = ResultCode::JWT_EXPIRED;
|
||||
$message = 'token已过期';
|
||||
break;
|
||||
default:
|
||||
$code = ResultCode::JWT_ERROR;
|
||||
$message = 'token错误';
|
||||
}
|
||||
|
||||
if (method_exists($e, 'setCustomCode')) {
|
||||
$e->setCustomCode($code);
|
||||
}
|
||||
if (method_exists($e, 'setCustomMessage')) {
|
||||
$e->setCustomMessage($message);
|
||||
}
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return $throwable instanceof JWTException;
|
||||
}
|
||||
}
|
||||
10
app/Interface/CheckTokenInterface.php
Normal file
10
app/Interface/CheckTokenInterface.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interface;
|
||||
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
|
||||
interface CheckTokenInterface
|
||||
{
|
||||
public function checkJwt(UnencryptedToken $token): void;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
14
app/Lib/Jwt/RequestScopedTokenTrait.php
Normal file
14
app/Lib/Jwt/RequestScopedTokenTrait.php
Normal 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');
|
||||
}
|
||||
}
|
||||
80
app/Middleware/Token/AbstractTokenMiddleware.php
Normal file
80
app/Middleware/Token/AbstractTokenMiddleware.php
Normal 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 '';
|
||||
}
|
||||
}
|
||||
29
app/Middleware/Token/AdminTokenMiddleware.php
Normal file
29
app/Middleware/Token/AdminTokenMiddleware.php
Normal 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错误');
|
||||
}
|
||||
}
|
||||
34
app/Service/Admin/AdminUser/UserService.php
Normal file
34
app/Service/Admin/AdminUser/UserService.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
]);
|
||||
}
|
||||
}
|
||||
54
app/Service/BaseTokenService.php
Normal file
54
app/Service/BaseTokenService.php
Normal 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已过期');
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,5 @@ declare(strict_types=1);
|
||||
*/
|
||||
return [
|
||||
Hyperf\Database\Schema\Blueprint::class => App\Common\Macros\BlueprintMacros::class,
|
||||
App\Interface\CheckTokenInterface::class => App\Service\BaseTokenService::class,
|
||||
];
|
||||
|
||||
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
return [
|
||||
'handler' => [
|
||||
'http' => [
|
||||
@@ -16,6 +17,7 @@ return [
|
||||
App\Exception\Handler\ErrExceptionHandler::class,
|
||||
App\Exception\Handler\ValidationExceptionHandler::class,
|
||||
App\Exception\Handler\AppExceptionHandler::class,
|
||||
App\Exception\Handler\JwtExceptionHandler::class,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -31,34 +31,20 @@ return [
|
||||
],
|
||||
'claims' => [
|
||||
// 默认的jwt claims
|
||||
RegisteredClaims::ISSUER => (string) env('APP_NAME'),
|
||||
RegisteredClaims::ISSUER => (string) env('APP_NAME') . '_default',
|
||||
RegisteredClaims::AUDIENCE => 'default', // 明确标识受众
|
||||
],
|
||||
],
|
||||
'admin' => [
|
||||
// jwt 配置 https://lcobucci-jwt.readthedocs.io/en/latest/
|
||||
'driver' => Jwt::class,
|
||||
// jwt 签名key
|
||||
'key' => InMemory::base64Encoded(env('JWT_SECRET')),
|
||||
// jwt 签名算法 可选 https://lcobucci-jwt.readthedocs.io/en/latest/supported-algorithms/
|
||||
'alg' => new Sha256(),
|
||||
// token过期时间,单位为秒
|
||||
'ttl' => (int) env('ADMIN_JWT_TTL', 3600),
|
||||
'ttl' => (int) env('ADMIN_JWT_TTL', 1),
|
||||
// 刷新token过期时间,单位为秒
|
||||
'refresh_ttl' => (int) env('ADMIN_JWT_REFRESH_TTL', 7200),
|
||||
// 黑名单模式
|
||||
'blacklist' => [
|
||||
// 是否开启黑名单
|
||||
'enable' => true,
|
||||
// 黑名单缓存前缀
|
||||
'prefix' => 'jwt_blacklist',
|
||||
// 黑名单缓存驱动
|
||||
'connection' => 'default',
|
||||
// 黑名单缓存时间 该时间一定要设置比token过期时间要大一点,最好设置跟过期时间一样
|
||||
'ttl' => (int) env('ADMIN_JWT_BLACKLIST_TTL', 7201),
|
||||
],
|
||||
'refresh_ttl' => (int) env('ADMIN_JWT_REFRESH_TTL', 10),
|
||||
'claims' => [
|
||||
// 默认的jwt claims
|
||||
RegisteredClaims::ISSUER => 'ADMIN'. env('APP_NAME'),
|
||||
RegisteredClaims::ISSUER => (string) env('APP_NAME') .'_admin',
|
||||
],
|
||||
],
|
||||
// 在你想要使用不同的场景时,可以在这里添加配置.可以填一个。其他会使用默认配置
|
||||
|
||||
@@ -4,7 +4,30 @@ Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
username=admin&password=admin
|
||||
|
||||
#> {%
|
||||
# client.global.set("admin_token", response.body.data.token);
|
||||
#%}
|
||||
> {%
|
||||
client.global.set("admin_token", response.body.data.access_token);
|
||||
client.global.set("refresh_token", response.body.data.refresh_token);
|
||||
%}
|
||||
|
||||
|
||||
### 登录
|
||||
GET {{host}}/admin/adminUser/getInfo
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
|
||||
### 登录
|
||||
POST {{host}}/admin/passport/login
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
username=admin&password=123456
|
||||
|
||||
> {%
|
||||
client.global.set("admin_token", response.body.data.access_token);
|
||||
%}
|
||||
|
||||
|
||||
### 登录
|
||||
GET {{host}}/admin/passport/getInfo
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Reference in New Issue
Block a user