当前位置: 首页 > news >正文

Hyperf方案 API网关统一鉴权

<?php/** * 案例054:API网关统一鉴权 * 说明:网关层统一验证JWT Token,不通过就拦截,通过才放行到业务服务 * 需要安装的包:hyperf/jwt-auth (^2.1) 或 lcobucci/jwt (^5.0) */declare(strict_types=1);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');}$tokenString=substr($authHeader,7);// 去掉 "Bearer " 前缀try{$payload=JwtService::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(['code'=>401,'message'=>$message,])->withStatus(401);}}/** * JWT生成和验证服务 */classJwtService{privatestatic?Configuration$config=null;privatestaticfunctiongetConfig():Configuration{if(self::$config===null){$secret=env('JWT_SECRET','your-secret-key-change-this-in-production');self::$config=Configuration::forSymmetricSigner(newSha256(),// HMAC-SHA256签名算法InMemory::plainText($secret)// 密钥);}returnself::$config;}/** * 生成JWT Token(登录成功后调用) */publicstaticfunctiongenerate(array$userData):string{$config=self::getConfig();$now=new\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{$config=self::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{$config=self::getConfig();$now=new\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['code'=>401,'message'=>'账号或密码错误'];}$accessToken=JwtService::generate((array)$user);$refreshToken=JwtService::generateRefreshToken($user->id);return['code'=>0,'data'=>['access_token'=>$accessToken,'refresh_token'=>$refreshToken,'expires_in'=>7200,// 2小时],];}}
http://www.jsqmd.com/news/620294/

相关文章:

  • 时序数据库选型指南:InfluxDB与TDengine的性能对比与实战安装
  • 高性能无人机飞控系统源码:基于Cesium+Vue3+Vite的三维可视化平台
  • 技术视角:Behdad字体 - 波斯语开源字体的现代化设计与工程实践
  • geoserver修改密码
  • 豆包Trace Ide使用技巧汇总
  • ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
  • Hyperf方案 API限流熔断
  • 告别黑壳子DAP!用AT32F403A和CherryUSB自制高速DAPLink V2仿真器(附完整源码)
  • E2E_P01Check 函数笔记
  • 终极宝可梦随机化器ZX:重新定义七代游戏的个性化冒险体验
  • 一条命令搞定OpenClaw部署?PPClaw的便利背后,你得先看清这些代价
  • python学习笔记2
  • OpenClaw 横向评测|对比 AutoGPT、CoPaw、NanoClaw 等主流 AI Agent,谁更适合你?
  • Keyviz:终极免费工具!实时可视化你的键盘鼠标操作,让教学录制更轻松
  • Hyperf方案 API签名验证
  • 第15届省赛蓝桥杯大赛C/C++大学B组
  • C++跨平台性能监控实战:构建CPU、GPU、磁盘与网络一体化探针
  • nav标签适用场景是什么_导航栏语义化写法【方法】
  • 部署成本降60%,响应提速10倍:镜像视界AI视频孪生的工程化价值
  • mysql事务隔离级别切换注意事项_如何保证系统平滑过渡
  • 【传统图像分割算法】- 图像分割之自适应阈值(Adaptive Thresholding)完全解析
  • 嵌入式Linux实战:手把手教你为EC20 4G模块编译GobiNet驱动(附完整Makefile配置)
  • HY-Motion 1.0开源大模型部署教程:支持企业级3D数字人动作生成生产环境
  • 2279 上市公司跨国供应链【存续力】(Sustainability) 指标(2004.07-2026.02)
  • C++ vs .NET 数组原地反转实测:小数组 C++ 碾压,大数组 .NET 反杀?好
  • 蛋白靶点CD49e(整合素α5):细胞黏附机制与抗体药物研发技术解析
  • 《算法题讲解指南:动态规划算法--回文串问题》--35.回文子串,36. 最长回文子串,37.分割回文串 IV,38.分割回文串 II,39.最长回文子序列,40.让字符串成为回文串的最少插入次数
  • 一个拉胯的分库分表方案有多绝望?整个部门都在救火!
  • 茉莉花插件:5步掌握Zotero中文文献管理终极技巧
  • 【GUI-Agent】阶跃星辰 GUI-MCP 解读---()---GUI-MCP 整体架构炔