Hyperf方案 API网关统一鉴权

张开发
2026/4/10 22:25:10 15 分钟阅读

分享文章

Hyperf方案 API网关统一鉴权
?php/** * 案例054API网关统一鉴权 * 说明网关层统一验证JWT Token不通过就拦截通过才放行到业务服务 * 需要安装的包hyperf/jwt-auth (^2.1) 或 lcobucci/jwt (^5.0) */declare(strict_types1);namespaceApp\Middleware;useLcobucci\JWT\Configuration;useLcobucci\JWT\Signer\Hmac\Sha256;useLcobucci\JWT\Signer\Key\InMemory;useLcobucci\JWT\Token\Plain;useLcobucci\JWT\Validation\Constraint\IssuedBy;useLcobucci\JWT\Validation\Constraint\PermittedFor;useLcobucci\JWT\Validation\Constraint\SignedWith;useLcobucci\JWT\Validation\Constraint\StrictValidAt;usePsr\Http\Message\ResponseInterface;usePsr\Http\Message\ServerRequestInterface;usePsr\Http\Server\MiddlewareInterface;usePsr\Http\Server\RequestHandlerInterface;/** * JWT鉴权中间件 * 注册到 config/autoload/middlewares.php 的全局中间件里 * 或者只在需要鉴权的路由组上挂 */classJwtAuthMiddlewareimplementsMiddlewareInterface{// 不需要登录就能访问的接口白名单privatearray$whitelist[/api/v1/auth/login,/api/v1/auth/register,/api/v1/health,// 健康检查接口/api/v1/articles,// 文章列表可以不登录看];publicfunctionprocess(ServerRequestInterface$request,RequestHandlerInterface$handler):ResponseInterface{$path$request-getUri()-getPath();// 白名单接口直接放行不验证Tokenif($this-isWhitelisted($path)){return$handler-handle($request);}// 从请求头取Token格式Authorization: Bearer eyJxxx$authHeader$request-getHeaderLine(Authorization);if(!str_starts_with($authHeader,Bearer )){return$this-unauthorized(缺少Authorization Header);}$tokenStringsubstr($authHeader,7);// 去掉 Bearer 前缀try{$payloadJwtService::verify($tokenString);// 验证并解析Token}catch(\Exception$e){return$this-unauthorized(Token无效.$e-getMessage());}// 把用户信息存到协程上下文后续的Controller可以直接取\Hyperf\Context\Context::set(user_id,$payload[user_id]);\Hyperf\Context\Context::set(user_roles,$payload[roles]??[]);\Hyperf\Context\Context::set(jwt_payload,$payload);return$handler-handle($request);// 验证通过放行}privatefunctionisWhitelisted(string$path):bool{foreach($this-whitelistas$pattern){// 支持前缀匹配比如 /api/v1/articles 能匹配 /api/v1/articles/1if($path$pattern||str_starts_with($path,$pattern./)){returntrue;}}returnfalse;}privatefunctionunauthorized(string$message):ResponseInterface{$response\Hyperf\Utils\ApplicationContext::getContainer()-get(\Hyperf\HttpServer\Contract\ResponseInterface::class);return$response-json([code401,message$message,])-withStatus(401);}}/** * JWT生成和验证服务 */classJwtService{privatestatic?Configuration$confignull;privatestaticfunctiongetConfig():Configuration{if(self::$confignull){$secretenv(JWT_SECRET,your-secret-key-change-this-in-production);self::$configConfiguration::forSymmetricSigner(newSha256(),// HMAC-SHA256签名算法InMemory::plainText($secret)// 密钥);}returnself::$config;}/** * 生成JWT Token登录成功后调用 */publicstaticfunctiongenerate(array$userData):string{$configself::getConfig();$nownew\DateTimeImmutable();$token$config-builder()-issuedBy(your-app)// 签发方-permittedFor(your-app-client)// 受众-identifiedBy(bin2hex(random_bytes(8)))// Token唯一ID-issuedAt($now)// 签发时间-expiresAt($now-modify(2 hours))// 过期时间2小时// 自定义Claim存业务数据-withClaim(user_id,$userData[id])-withClaim(username,$userData[username])-withClaim(roles,$userData[roles]??[])-getToken($config-signer(),$config-signingKey());// 签名return$token-toString();}/** * 验证Token并返回Payload */publicstaticfunctionverify(string$tokenString):array{$configself::getConfig();try{/** var Plain $token */$token$config-parser()-parse($tokenString);// 解析Token字符串}catch(\Exception$e){thrownew\RuntimeException(Token格式不正确);}// 验证签名、过期时间、签发方等$config-validator()-assert($token,...[newSignedWith($config-signer(),$config-verificationKey()),newStrictValidAt(new\Lcobucci\Clock\SystemClock(new\DateTimeZone(UTC))),newIssuedBy(your-app),]);// 提取业务数据return[user_id$token-claims()-get(user_id),username$token-claims()-get(username),roles$token-claims()-get(roles,[]),];}/** * 生成Refresh Token长效Token用来换新的Access Token */publicstaticfunctiongenerateRefreshToken(int$userId):string{$configself::getConfig();$nownew\DateTimeImmutable();$token$config-builder()-issuedAt($now)-expiresAt($now-modify(30 days))// Refresh Token30天有效-withClaim(user_id,$userId)-withClaim(type,refresh)// 标记为refresh token-getToken($config-signer(),$config-signingKey());return$token-toString();}}/** * 登录Controller */classAuthController{publicfunctionlogin(array$params):array{// 验证账号密码省略数据库查询$user\Hyperf\DbConnection\Db::table(users)-where(username,$params[username])-first();if(!$user||!password_verify($params[password],$user-password)){return[code401,message账号或密码错误];}$accessTokenJwtService::generate((array)$user);$refreshTokenJwtService::generateRefreshToken($user-id);return[code0,data[access_token$accessToken,refresh_token$refreshToken,expires_in7200,// 2小时],];}}

更多文章