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

Hyperf方案 多因素认证(MFA)

最佳选择:spomky-labs/otphp(RFC 标准,活跃维护)做 TOTP,lbuchs/WebAuthn 做 Passkey。 ┌─────────────────────────────┬───────────────────┬────────────┐ │ 方式 │ 库 │ 场景 │ ├─────────────────────────────┼───────────────────┼────────────┤ ────────────────────── │ TOTP(Google Authenticator)│ spomky-labs/otphp │ 主流 MFA │ ├─────────────────────────────┼───────────────────┼────────────┤ │ WebAuthn/Passkey │ lbuchs/WebAuthn │ 无密码登录 │ ├─────────────────────────────┼───────────────────┼────────────┤ │ 备用码 │ 原生实现 │ 兜底方案 │ └─────────────────────────────┴───────────────────┴────────────┘ --- 安装composerrequire spomky-labs/otphp bacon/bacon-qr-code ---1. TOTP 绑定与验证<?php namespace App\Service;use OTPHP\TOTP;use BaconQrCode\Renderer\ImageRenderer;use BaconQrCode\Renderer\Image\SvgImageBackEnd;use BaconQrCode\Renderer\RendererStyle\RendererStyle;use BaconQrCode\Writer;class MfaService{// 生成密钥 + 二维码 publicfunctionsetup(string$userEmail): array{$totp=TOTP::generate();$totp->setLabel($userEmail);$totp->setIssuer(env('APP_NAME','MyApp'));$writer=new Writer(new ImageRenderer(new RendererStyle(200), new SvgImageBackEnd()));$qrCode=base64_encode($writer->writeString($totp->getProvisioningUri()));return['secret'=>$totp->getSecret(),'qr_svg'=>$qrCode,];}// 验证 OTP publicfunctionverify(string$secret, string$code): bool{$totp=TOTP::createFromSecret($secret);return$totp->verify($code, null,1);// 允许前后1个时间窗口}// 生成备用码 publicfunctiongenerateBackupCodes(): array{returnarray_map(fn()=>strtoupper(bin2hex(random_bytes(4))).'-'.strtoupper(bin2hex(random_bytes(4))), range(1,8));}}---2. MFA 控制器<?php namespace App\Controller;use App\Service\MfaService;use Hyperf\HttpServer\Annotation\Controller;use Hyperf\HttpServer\Annotation\PostMapping;use Hyperf\HttpServer\Annotation\GetMapping;#[Controller(prefix: '/api/mfa')]class MfaController{publicfunction__construct(private MfaService$mfa){}// 初始化绑定#[GetMapping(path: 'setup')]publicfunctionsetup(): array{$user=auth()->user();$data=$this->mfa->setup($user->email);// 临时存 session,确认后再持久化 session(['mfa_secret_pending'=>$data['secret']]);return['qr_svg'=>$data['qr_svg']];}// 确认绑定#[PostMapping(path: 'confirm')]publicfunctionconfirm(): array{$secret=session('mfa_secret_pending');$code=$this->request->input('code');if(!$secret||!$this->mfa->verify($secret,$code)){return$this->response->json(['error'=>'Invalid code'],422);}$backupCodes=$this->mfa->generateBackupCodes();auth()->user()->update(['mfa_secret'=>encrypt($secret),'mfa_enabled'=>true,'mfa_backup_codes'=>encrypt(json_encode(array_map('password_hash',$backupCodes, array_fill(0,8, PASSWORD_BCRYPT)))),]);return['backup_codes'=>$backupCodes];// 仅展示一次}// 登录时验证#[PostMapping(path: 'verify')]publicfunctionverify(): array{$user=auth()->user();$code=$this->request->input('code');$secret=decrypt($user->mfa_secret);if($this->mfa->verify($secret,$code)){session(['mfa_passed'=>true]);return['verified'=>true];}// 尝试备用码if($this->verifyBackupCode($user,$code)){session(['mfa_passed'=>true]);return['verified'=>true,'backup_used'=>true];}return$this->response->json(['error'=>'Invalid code'],401);}privatefunctionverifyBackupCode($user, string$input): bool{$codes=json_decode(decrypt($user->mfa_backup_codes),true);foreach($codesas$i=>$hash){if(password_verify(strtoupper($input),$hash)){$codes[$i]='USED';// 一次性消费$user->update(['mfa_backup_codes'=>encrypt(json_encode($codes))]);returntrue;}}returnfalse;}}---3. MFA 中间件(强制二步验证)<?php namespace App\Middleware;use Psr\Http\Message\ResponseInterface;use Psr\Http\Message\ServerRequestInterface;use Psr\Http\Server\MiddlewareInterface;use Psr\Http\Server\RequestHandlerInterface;class MfaMiddleware implements MiddlewareInterface{publicfunctionprocess(ServerRequestInterface$request, RequestHandlerInterface$handler): ResponseInterface{$user=auth()->user();if($user?->mfa_enabled&&!session('mfa_passed')){return$this->response->json(['error'=>'MFA required','redirect'=>'/mfa/verify'],403);}return$handler->handle($request);}}路由注册: Router::addGroup('/api/admin',function(){Router::get('/dashboard',[DashboardController::class,'index']);},['middleware'=>[AuthMiddleware::class, MfaMiddleware::class]]);---4. Migration Schema::table('users',function(Blueprint$table){$table->boolean('mfa_enabled')->default(false);$table->text('mfa_secret')->nullable();$table->text('mfa_backup_codes')->nullable();});--- 核心要点: - 密钥用 encrypt()存储,不明文入库 - 备用码用 password_hash 存哈希,使用后标记 USED - verify()第三参数1=允许30秒时钟偏差 - MFA 状态存 session,敏感路由加 MfaMiddleware
http://www.jsqmd.com/news/658028/

相关文章:

  • 如何快速配置插件系统:面向新手的5步完整指南
  • Docker一键部署Puter:打造私有云桌面与远程开发环境全攻略
  • 批量生成流程卡功能,助力企业简化工序流转与信息录入工作
  • 015、LangChain + RAG实战:把知识库问答系统真正串成一条可维护的工程链路
  • 2026 年阻垢剂领域优质企业推荐榜:上海环巨科技领衔,聚焦阻垢剂、缓蚀阻垢剂、反渗透阻垢剂、水处理阻垢剂专业服务商 - 海棠依旧大
  • 2026年维普论文AI率超标被打回?这份降AI攻略帮你一次过 - 我要发一区
  • 折叠波导慢波结构 CST 仿真全流程:从建模到注波互作用
  • 一人公司(OPC)典型案例与商业模式研究报告
  • 收藏!AI赋能程序员单干时代:一人公司如何从0到1?
  • REST 到底是什么?一篇讲透 + FastAPI 实战
  • 基于二阶RC模型的锂电池SOC估计自适应无迹卡尔曼滤波算法研究——包含噪声系数自适应的Matl...
  • 基于ITR9909与BC517达林顿管的光电感应开关改造实战
  • 广东企业注意!下一个高新技术企业就是你,但申报路上这些坑别踩 - 沐霖信息科技
  • 暗黑3终极自动化指南:D3KeyHelper图形化宏工具完整配置教程
  • 2026年维普AI检测不通过怎么办?从60%降到5%的完整攻略 - 我要发一区
  • VMamba在图像分类任务中的性能优化与实践
  • Pycharm运行程序时,会报错:Process finished with exit code -1073740791 (0xC0000409),无法查看详细的报错信息
  • AD22更新网表时总是显示 net with name XXX In already exists
  • IRremoteESP8266库实战:三种方法解析与发送空调红外码
  • 密码进行加盐哈希 using CSharp,Python,Go,Java
  • 桌面端社区体验革命:Coolapk-UWP如何重新定义Windows平台社交应用
  • HideMockLocation终极指南:5步快速隐藏Android模拟位置设置
  • STM32实战:ADS8688多通道数据采集系统驱动设计与优化
  • 瀚高数据库安全版4.5.10及其以上版本使用pg_cron定时任务
  • Panel故障排除终极指南:10个快速解决数据可视化问题的完整方案
  • QMCDecode技术解析:QQ音乐加密音频格式解密实现原理
  • 别再手动写JCo3.0连接代码了!用Spring Boot整合SAP RFC接口的完整配置流程
  • F28379D DAC实战:从内部基准电压选择到外部引脚测量,这些细节坑你踩过吗?
  • 02华夏之光永存:黄大年茶思屋榜文解法「第7期2题」大规模光网络多约束寻路算法·双路径解法
  • 解密GodMode9权限系统:从绿色到红色的安全操作指南