wiseadmin / wise-login-security
WiseAdmin 登录安全包 — 验证码校验、失败次数锁定、IP 黑白名单、双因素认证(TOTP)与可信设备管理
Requires
- php: >=8.1
- topthink/framework: ^8.0
- topthink/think-orm: ^3.0
- wiseadmin/wise-support: 1.0.*
Requires (Dev)
- phpunit/phpunit: >=9.0
Suggests
- spomky-labs/otphp: >=11.0 - To use full TOTP suite (optional, this package ships its own RFC 6238 implementation)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-20 14:56:25 UTC
README
WiseAdmin Login Security - 登录安全包(验证码/失败次数锁定/IP黑白名单/双因素认证/设备管理)
一、功能矩阵
| 模块 | 默认实现 | 可替换 | 说明 |
|---|---|---|---|
| 验证码 | GdCaptchaDriver |
BehaviorCaptchaDriver(极验占位)/ 自定义 |
字符/扭曲/干扰可配置 |
| 失败次数 | CacheFailedAttemptStore |
RedisFailedAttemptStore / DatabaseFailedAttemptStore |
Redis Lua 原子 |
| IP 黑白名单 | ConfigIpListProvider |
DatabaseIpListProvider(用户实现) |
支持 IPv4/IPv6 CIDR |
| TOTP | 自带 RFC 6238 实现 | spomky-labs/otphp(可选) |
sha1/sha256/sha512 |
| 备份码 | 内置 | – | 一次性,sha256 存储 |
| 设备管理 | DeviceManager |
– | 指纹 + 信任 + 重命名 + 撤销 |
| 安全事件 | DB + 文件 + PSR-14 | – | 可发邮件 / IM 通知 |
二、安装
composer require wiseadmin/wise-login-security
可选依赖:
# 如需使用完整 TOTP 功能套件(可选,本包已内置 RFC 6238 实现)
composer require spomky-labs/otphp
包通过 composer.json 的 extra.think.services 自动注册 Service 到 ThinkPHP 容器。
2.1 发布配置
php think wise-login-security:publish
或手动复制:vendor/wiseadmin/wise-login-security/config/wise-login-security.php → config/wise-login-security.php
2.2 复制并执行迁移
php think wise-login-security:publish php think migrate:run
将创建 4 张表:
login_security_failed_attemptslogin_security_user_deviceslogin_security_security_eventslogin_security_totp_config
三、配置示例
完整配置见 config/wise-login-security.php。关键项:
return [ 'captcha' => [ 'driver' => 'gd', // gd | geetest | 自定义 'length' => 4, 'charset' => 'mix', // digits | letters | mix 'expire' => 300, ], 'failed_attempts' => [ 'max_attempts' => 5, 'lockout_minutes' => 15, 'store' => 'cache', // cache | redis | database 'scope' => 'ip_username', ], 'ip_list' => [ 'whitelist' => ['127.0.0.1', '192.168.1.0/24'], 'blacklist' => [], 'provider' => 'config', // config | database ], 'two_factor' => [ 'enabled' => true, 'issuer' => 'MyApp', 'digits' => 6, 'period' => 30, 'skip_for_trusted_device' => true, ], 'device_management' => [ 'trust_duration_days' => 30, 'fingerprint_salt' => 'CHANGE-ME-to-random', 'notify_new_device' => true, ], 'events' => [ 'log_to_database' => true, 'log_to_file' => true, 'log_channel' => 'login_security', ], ];
四、用户模型实现 TotpUserInterface
包通过包内自建表 login_security_totp_config(多态关联)存储 TOTP 数据,
不修改用户原表结构。通过 user_type + user_id 联合键支持 admin / member 等多种用户类型。
方式一:使用 HasTotp Trait(推荐)
引入 HasTotp Trait,只需实现 getTotpLabel() 方法即可。数据自动存入 login_security_totp_config 表。
namespace app\model; use think\Model; use wise\loginsecurity\contract\TotpUserInterface; use wise\loginsecurity\trait\HasTotp; class Admin extends Model implements TotpUserInterface { use HasTotp; public function getTotpLabel(): string { return $this->username; } }
为 Member 模型添加 2FA(不同 user_type):
class Member extends Model implements TotpUserInterface { use HasTotp; // 覆盖默认的 'admin' → 指定为 'member' protected string $totpUserType = 'member'; public function getTotpLabel(): string { return $this->email; } }
自定义 user_type:
class VipUser extends Model implements TotpUserInterface { use HasTotp; public function getTotpLabel(): string { return $this->mobile; } // 覆盖方法返回自定义类型 public function getTotpUserType(): string { return 'vip'; } }
方式二:手动实现全部 7 个方法
如果不使用 Trait,可手动实现接口的全部方法。可自行选择数据存储方式(直接字段、JSON 列或自建表):
use wise\loginsecurity\contract\TotpUserInterface; class User extends Model implements TotpUserInterface { public function getTotpLabel(): string { return $this->email ?? $this->username; } public function getTotpUserType(): string { return 'admin'; } public function getTotpSecret(): ?string { return $this->totp_secret; } public function setTotpSecret(?string $secret): void { $this->totp_secret = $secret; } public function getBackupCodes(): array { return $this->totp_backup_codes ? json_decode($this->totp_backup_codes, true) : []; } public function setBackupCodes(array $codes): void { $this->totp_backup_codes = json_encode($codes); } public function isTotpEnabled(): bool { return (bool) $this->totp_enabled; } public function setTotpEnabled(bool $enabled): void { $this->totp_enabled = $enabled; } }
注意:方式二需自行管理数据持久化。方式一调用 setter 后需调用
$user->saveTotpConfig()持久化。
使用示例
// 启用 TOTP $user = Admin::find(1); $totp = app(\wise\loginsecurity\totp\TotpManager::class); // 1. 生成密钥和备份码 $secret = $totp->generateSecret(); $codes = app(\wise\loginsecurity\totp\BackupCodeManager::class)->generate(); // 2. 设置到用户 $user->setTotpSecret($secret); $user->setBackupCodes($codes['hashed']); $user->setTotpEnabled(true); // 3. 持久化到 login_security_totp_config 表 $user->saveTotpConfig();
五、验证码 HTTP 路由(内置,自动注册)
包内置验证码控制器及路由,安装后自动注册,用户无需手写路由。
5.1 自动注册路由
| 方法 | 路由 | 说明 |
|---|---|---|
GET |
/wise/captcha/create |
生成验证码,返回 JSON {key, image, expire} |
POST |
/wise/captcha/verify |
校验验证码,请求体 {key, code} |
GET |
/wise/captcha/image |
直接输出 PNG 图片(<img src> 兼容,key 通过 X-Captcha-Key 响应头返回) |
路由前缀可通过配置修改:
// config/wise-login-security.php 'route' => [ 'auto_register' => true, // false 则禁用自动注册,用户自行写路由 'prefix' => 'myapp/captcha', // 自定义前缀 ],
5.2 URL 辅助函数(无需硬编码路径)
// PHP 端 wise_login_security_route('create'); // → /wise/captcha/create wise_login_security_route('verify'); // → /wise/captcha/verify wise_login_security_route('image'); // → /wise/captcha/image // 前端(JS) const base = '/wise/captcha'; fetch(base + '/create').then(r => r.json()); // 获取验证码
5.3 前端使用示例
// 1. 获取验证码 const res = await fetch(wise_login_security_route('create')); const { key, image, expire } = (await res.json()).data; // 2. 展示图片 captchaImg.src = image; // data:image/png;base64,... // 3. 登录时提交校验 const body = new FormData(); body.append('key', key); body.append('code', userInput); // POST /wise/captcha/verify → { code: 0, data: { valid: true } }
5.4 用户自定义路由(禁用内置时)
设置 route.auto_register = false 后,可自行绑定控制器到任意路由:
use think\facade\Route; use wise\loginsecurity\controller\CaptchaController; Route::group('api/auth', function () { Route::get('captcha', CaptchaController::class . '@create'); Route::post('captcha/check', CaptchaController::class . '@verify'); Route::get('captcha/img', CaptchaController::class . '@image'); });
5.5 直接生成验证码(不经过 HTTP)
如果不需要 HTTP 路由,仍可使用程序化方式:
use wise\loginsecurity\facade\Captcha; // 生成 $data = Captcha::generate(); // → ['key' => 'abc...', 'image' => 'data:image/png;base64,...', 'expire' => 12345] // 校验 $valid = Captcha::verify($key, $input); // 或使用短函数 $data = wise_login_security_captcha(); $valid = wise_login_security_captcha_check($key, $input);
六、登录自动记录(LoginResultMiddleware)
LoginResultMiddleware 是后置中间件,读取登录控制器的 JSON 响应,根据配置自动完成 Lock 记录、安全事件、设备注册、TOTP 标记等工作。用户控制器只需关注认证逻辑本身。
6.1 快速使用(推荐)
use think\facade\Route; Route::post('login', 'Auth/login') ->middleware([ \wise\loginsecurity\middleware\LoginGuardMiddleware::class, // 前置:IP/锁定/验证码 \wise\loginsecurity\middleware\LoginResultMiddleware::class, // 后置:自动记录 ]);
// Auth.php — 只需做认证 class Auth { public function login() { $user = User::where('username', input('username'))->find(); if (!$user || !password_verify(input('password'), $user->password)) { return json(['code' => 1, 'msg' => '密码错误']); // ↑ LoginResultMiddleware 自动:Lock::recordFailure() + 事件记录 } $token = token_service()->issue($user->id); return json([ 'code' => 0, 'msg' => 'ok', 'data' => ['user_id' => $user->id, 'token' => $token], ]); // ↑ LoginResultMiddleware 自动: // Lock::clear() + 事件记录 + 设备记录 + TOTP 标记 } }
6.2 配置项
// config/wise-login-security.php 'login_result' => [ 'enabled' => true, 'response' => [ 'status_field' => 'code', // 标识成功/失败的字段 'success_value' => 0, // 成功的值 'user_id_field' => 'data.user_id', // 从响应中提取用户 ID(支持 dot notation) ], 'username_input' => 'username', // 请求体中用户名的字段 'actions' => [ 'failure_lock' => true, // 失败 → Lock::recordFailure() 'failure_event' => true, // 失败 → Logger::log(LOGIN_FAILURE) 'success_clear' => true, // 成功 → Lock::clear() 'success_event' => true, // 成功 → Logger::log(LOGIN_SUCCESS) 'device_record' => true, // 成功 → DeviceManager::record() 'totp_verified' => true, // 成功 → 标记 TOTP 已验证 ], ],
6.3 自定义响应格式
如果返回格式不同,修改 response 映射即可:
// 你的响应格式:{ status: "ok", result: { uid: 123, ... } } 'response' => [ 'status_field' => 'status', 'success_value' => 'ok', 'user_id_field' => 'result.uid', ], // 你的请求中用户名在 email 字段 'username_input' => 'email',
6.4 手动模式(禁用中间件)
设置 login_result.enabled = false 或直接不使用 LoginResultMiddleware,在控制器中手动处理:
use wise\loginsecurity\facade\Lock; use wise\loginsecurity\facade\Logger; use wise\loginsecurity\event\LoginSecurityEvent; public function login() { $user = User::where('username', input('username'))->find(); if (!$user || !password_verify(input('password'), $user->password)) { Lock::recordFailure(input('username'), request()->ip()); Logger::log(LoginSecurityEvent::TYPE_LOGIN_FAILURE, [...], request()); return json(['code' => 1, 'msg' => '密码错误']); } Lock::clear(input('username'), request()->ip()); Logger::log(LoginSecurityEvent::TYPE_LOGIN_SUCCESS, [], request(), $username); \wise\loginsecurity\middleware\TwoFactorMiddleware::markVerified(request()); app(\wise\loginsecurity\device\DeviceManager::class)->record($user->id, request()->server()); return json(['code' => 0, 'data' => ['user_id' => $user->id, 'token' => $token]]); }
七、中间件汇总
| 中间件 | 类型 | 作用 | 使用场景 |
|---|---|---|---|
LoginGuardMiddleware |
前置 | IP 检查 + 锁定检查 + 验证码动态要求 | 登录路由(最先执行) |
LoginResultMiddleware |
后置 | 读取响应自动记录 Lock/Logger/Device/TOTP | 登录路由(最后执行) |
IpGuardMiddleware |
前置 | IP 黑白名单 | 全局 / 路由组 |
CaptchaMiddleware |
前置 | 强制校验 captcha_key/code | 高安全要求登录 |
TwoFactorMiddleware |
前置 | 校验 2FA | 受保护资源 |
全局注册(app/middleware.php):
return [ \wise\loginsecurity\middleware\IpGuardMiddleware::class, ];
路由组示例:
Route::group('admin', function () { // ... })->middleware([ \wise\loginsecurity\middleware\IpGuardMiddleware::class, \wise\loginsecurity\middleware\TwoFactorMiddleware::class, ]);
7.1 受保护接口(需要 2FA)
Route::group('admin', function () { Route::get('dashboard', 'Admin/dashboard'); })->middleware([ \wise\loginsecurity\middleware\TwoFactorMiddleware::class, ]);
TwoFactorMiddleware 检查 session('wise_login_security.totp_verified')。
7.2 设备管理接口
Route::group('devices', function () { Route::get('list', 'Device/list'); Route::post('trust', 'Device/trust'); Route::post('revoke', 'Device/revoke'); Route::post('rename', 'Device/rename'); })->middleware(['auth']);
八、命令行工具
| 命令 | 用途 |
|---|---|
php think wise-login-security:publish |
复制迁移到 framework 目录 |
php think wise-login-security:unlock --username=alice |
解锁指定用户 |
php think wise-login-security:unlock --username=alice --ip=1.2.3.4 |
解锁指定 IP+用户 |
php think wise-login-security:unlock --all |
强制解锁所有(仅 cache/redis 存储) |
php think wise-login-security:test |
运行自检(CIDR 匹配、TOTP、Base32) |
九、扩展自定义驱动
8.1 自定义验证码驱动
namespace app\captcha; use think\App; use wise\loginsecurity\captcha\CaptchaDriverInterface; class TurnstileCaptchaDriver implements CaptchaDriverInterface { public function __construct(protected App $app) {} public function getName(): string { return 'turnstile'; } public function generate(): array { /* … */ } public function verify(string $key, string $input): bool { /* … */ } }
注册到工厂:
// app/service.php 或某个引导文件 \wise\loginsecurity\captcha\CaptchaDriverFactory::extend('turnstile', TurnstileCaptchaDriver::class);
修改配置:captcha.driver = turnstile。
8.2 自定义 IP 列表提供器(数据库)
实现 IpListProviderInterface:
namespace app\security; use wise\loginsecurity\ip\IpListProviderInterface; class DatabaseIpListProvider implements IpListProviderInterface { public function getWhitelist(): array { return \think\facade\Db::name('ip_whitelist')->column('cidr'); } public function getBlacklist(): array { return \think\facade\Db::name('ip_blacklist')->column('cidr'); } }
绑定:
// 在 Service::register() 中添加 $this->app->bind(\wise\loginsecurity\ip\IpListProviderInterface::class, DatabaseIpListProvider::class);
8.3 自定义失败计数器存储
实现 FailedAttemptStoreInterface 并绑定。
十、监听安全事件
包通过 ThinkPHP 事件系统触发 wise\loginsecurity\event\LoginSecurityEvent:
// app/event.php return [ 'listen' => [ \wise\loginsecurity\event\LoginSecurityEvent::class => [ \app\listener\NewDeviceMailListener::class, \app\listener\AccountLockedSmsListener::class, ], ], ];
监听器示例:
namespace app\listener; use wise\loginsecurity\event\LoginSecurityEvent; class NewDeviceMailListener { public function handle(LoginSecurityEvent $event): void { if ($event->eventType !== LoginSecurityEvent::TYPE_DEVICE_NEW) return; // Mail::to(...)->send(new NewDeviceMail($event->ip, $event->userAgent)); } }
十一、单元测试
composer install --dev ./vendor/bin/phpunit
测试覆盖:
- ✅ IP CIDR 匹配(IPv4 / IPv6 / 单 IP / 多 CIDR)
- ✅ Base32 编码 / 解码(RFC 4648 向量)
- ✅ TOTP 生成 / 校验(RFC 6238 测试向量)
- ✅ 设备指纹
- ✅ 备份码(生成 / 消费 / 一次性 / 大小写)
- ✅ 异常类(状态码、错误码、toArray)
十二、Swoole / 常驻内存
RedisFailedAttemptStore 使用 Lua 脚本保证 INCR + EXPIRE 原子性,适配 Swoole 协程。
在 Swoole 环境下推荐配置:
'failed_attempts' => [ 'store' => 'redis', ],
十三、目录结构
wise-login-security/
├── composer.json
├── config/wise-login-security.php
├── database/migrations/
│ ├── 2025_01_01_000001_create_failed_attempts_table.php
│ ├── 2025_01_01_000002_create_user_devices_table.php
│ ├── 2025_01_01_000003_create_security_events_table.php
│ └── 2025_01_01_000004_create_totp_config_table.php
├── src/
│ ├── Service.php
│ ├── helper.php
│ ├── captcha/
│ │ ├── CaptchaDriverInterface.php
│ │ ├── GdCaptchaDriver.php
│ │ ├── BehaviorCaptchaDriver.php
│ │ ├── CaptchaDriverFactory.php
│ │ └── CaptchaManager.php
│ ├── contract/
│ │ └── TotpUserInterface.php
│ ├── controller/
│ │ └── CaptchaController.php
│ ├── device/
│ │ ├── DeviceFingerprint.php
│ │ └── DeviceManager.php
│ ├── event/
│ │ ├── LoginSecurityEvent.php
│ │ └── EventLogger.php
│ ├── exception/
│ │ ├── LoginSecurityException.php
│ │ ├── IpBlockedException.php
│ │ ├── AccountLockedException.php
│ │ ├── CaptchaInvalidException.php
│ │ ├── TwoFactorRequiredException.php
│ │ ├── BackupCodeInvalidException.php
│ │ ├── TotpUserInterfaceException.php
│ │ └── DeviceException.php
│ ├── facade/
│ │ ├── Captcha.php
│ │ ├── Lock.php
│ │ └── Logger.php
│ ├── ip/
│ │ ├── IpListProviderInterface.php
│ │ ├── ConfigIpListProvider.php
│ │ └── CidrMatcher.php
│ ├── listener/
│ │ └── NewDeviceMailListener.php
│ ├── lock/
│ │ ├── FailedAttemptStoreInterface.php
│ │ ├── CacheFailedAttemptStore.php
│ │ ├── RedisFailedAttemptStore.php
│ │ ├── DatabaseFailedAttemptStore.php
│ │ └── LoginLockManager.php
│ ├── middleware/
│ │ ├── IpGuardMiddleware.php
│ │ ├── CaptchaMiddleware.php
│ │ ├── TwoFactorMiddleware.php
│ │ ├── LoginGuardMiddleware.php
│ │ └── LoginResultMiddleware.php
│ ├── model/
│ │ ├── FailedAttempt.php
│ │ ├── UserDevice.php
│ │ ├── SecurityEvent.php
│ │ └── TotpConfig.php
│ ├── route/
│ │ └── captcha.php
│ ├── totp/
│ │ ├── Base32.php
│ │ ├── TotpManager.php
│ │ └── BackupCodeManager.php
│ ├── trait/
│ │ ├── ServiceTrait.php
│ │ ├── CommandTrait.php
│ │ └── HasTotp.php
│ └── command/
│ ├── Publish.php
│ ├── Unlock.php
│ └── SecurityTest.php
└── tests/
├── CidrMatcherTest.php
├── TotpTest.php
├── DeviceFingerprintTest.php
├── BackupCodeManagerTest.php
└── ExceptionTest.php
十四、许可证
MIT