Search by

wiseadmin / wise-login-security

wiseadmin

WiseAdmin 登录安全包 — 验证码校验、失败次数锁定、IP 黑白名单、双因素认证(TOTP)与可信设备管理

Package info

github.com/hiwiseadmin/wise-login-security

pkg:composer/wiseadmin/wise-login-security

Statistics

Installs: 2

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-10-20 14:23 UTC

This package is auto-updated.

Last update: 2026-10-20 14:56:25 UTC


README

WiseAdmin Login Security - 登录安全包(验证码/失败次数锁定/IP黑白名单/双因素认证/设备管理)

PHP Version ThinkPHP License Packagist

一、功能矩阵

模块 默认实现 可替换 说明
验证码 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_attempts
  • login_security_user_devices
  • login_security_security_events
  • login_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