Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_EleVue/zh/core/login-log.md
T
2026-10-08 17:50:29 +08:00

9.9 KiB
Raw Blame History

5.15 登录日志

:::tip 概述 登录日志记录用户的登录/登出行为,成功与失败均记录,便于安全审计和异常行为分析。与操作日志不同,登录日志由 AuthController 手动记录(writeLoginLog),而非中间件自动记录;登录日志的 param 只记录用户名,不涉及密码。 :::

记录流程

用户提交登录
    │
    ▼
AuthController::login(LoginRequest)
    │
    ├─ LoginRequest 参数格式校验
    │
    ├─ CaptchaService::verify(key, code)
    │   └─ 失败 → 写登录日志(status=1 失败, error="验证码错误")→ 返回失败
    │
    ├─ JwtService::login()
    │   ├─ 查询用户 → 不存在 → BizException → 写登录日志(失败, "用户名或密码错误")
    │   ├─ 校验状态 → 禁用 → BizException → 写登录日志(失败, "账号已被禁用")
    │   ├─ 校验密码 → 错误 → BizException → 写登录日志(失败, "用户名或密码错误")
    │   └─ 签发 Token → 成功
    │
    ├─ 写登录日志(status=0 成功,含耗时/归属地/浏览器)
    │
    └─ 返回 { access_token, refresh_token, token_type, expires_in, user }  ← raw snake_case

:::tip 双写保险 无论走成功分支还是异常分支,AuthController 都会调用 writeLoginLog——登录尝试必留痕(含失败原因),这是登录安全审计的基础。 :::

登录/登出记录

┌─────────────────────────────────────────────────────────────┐
│ 登录成功                                                      │
│ AuthController::login() 成功 → writeLoginLog(成功)            │
├─────────────────────────────────────────────────────────────┤
│ 登录失败(验证码/密码/状态任一失败)                            │
│ AuthController::login() 异常 → writeLoginLog(失败 + error)     │
├─────────────────────────────────────────────────────────────┤
│ 登出                                                          │
│ AuthController::logout() → revokeToken() 吊销令牌              │
│                  → LoginLog::create(log_type=2)               │
└─────────────────────────────────────────────────────────────┘

记录字段

字段 类型 说明 示例
username VARCHAR(50) 用户名(即使登录失败也记录) admin
log_type TINYINT 类型:1=登录 2=登出 1
source TINYINT 来源端:0=后台 1=前台 0
method VARCHAR(10) HTTP 方法 POST
request_method VARCHAR(50) 入口标识 login
url VARCHAR(255) 请求 URL /api/login
param TEXT 请求参数——只记录用户名(JSON) {"username":"admin"}
result TEXT 结果(JSON:code + msg) {"code":0,"msg":"登录成功"}
status TINYINT 状态:0=成功 1=失败 0
error VARCHAR(200) 错误信息(仅失败时写入) 用户名或密码错误
ip VARCHAR(50) 客户端 IP(经 ProxyMiddleware 还原) 192.168.1.100
location VARCHAR(100) IP 归属地(ip2region) 江苏省南京市
os VARCHAR(100) 操作系统 Windows 11
browser VARCHAR(100) 浏览器 Chrome 128
consume_time INT 登录耗时(毫秒) 260
create_time DATETIME 登录时间 2026-10-02 10:30:00

:::tip 耗时字段的用途 登录耗时异常增大可能是 bcrypt 暴力破解尝试、数据库慢查询或 Redis 阻塞的信号——安全巡检时可按 consume_time 排序排查。 :::

核心代码

登录记录(AuthController)

// app/Http/Controllers/Api/AuthController.php(节选)

public function login(LoginRequest $request): JsonResponse
{
    // ... 参数与验证码校验 ...
    $startTime = microtime(true);

    try {
        $result = $this->jwtService->login($username, $password);
        $this->writeLoginLog($request, $username, 'login', true, '登录成功', $startTime);

        return $this->success($result, '登录成功');
    } catch (\Exception $e) {
        $this->writeLoginLog($request, $username, 'login', false, $e->getMessage(), $startTime);

        return $this->fail($e->getMessage());
    }
}

private function writeLoginLog(Request $request, string $username, string $entry,
    bool $success, string $message, float $startTime): void
{
    $duration = round((microtime(true) - $startTime) * 1000);
    $requestInfo = RequestInfoService::create($request);

    $data = [
        'username' => $username,
        'log_type' => 1,                          // 1=登录
        'source' => 0,                            // 0=后台
        'method' => $request->method(),
        'request_method' => $entry,               // login 等入口
        'url' => $request->fullUrl(),
        'param' => json_encode(['username' => $username], JSON_UNESCAPED_UNICODE),  // 只记录用户名
        'result' => json_encode(['code' => $success ? 0 : 1, 'msg' => $message], JSON_UNESCAPED_UNICODE),
        'ip' => $requestInfo->getIp(),
        'location' => $requestInfo->getIpLocation(),
        'os' => $requestInfo->getOs(),
        'browser' => $requestInfo->getBrowser(),
        'consume_time' => $duration,
        'status' => $success ? 0 : 1,              // 0=成功 1=失败
        'create_time' => now(),
    ];

    if (! $success) {
        $data['error'] = $message;
    }

    LoginLog::create($data);
}

登出记录(AuthController::logout)

public function logout(Request $request): JsonResponse
{
    $token = $request->bearerToken();
    if ($token) {
        $this->jwtService->revokeToken($token);   // 吊销 refresh_token(即时失效)

        $requestInfo = RequestInfoService::create($request);
        $userInfo = $request->attributes->get('userInfo');
        // 登出日志:log_type=2
        LoginLog::create([
            'username' => $userInfo->username ?? '',
            'log_type' => 2,
            'source' => 0,
            // ... 其余字段同登录记录 ...
        ]);
    }

    return $this->success(null, '登出成功');
}

API 接口

方法 路径 权限码 说明
GET /api/login/log/page sys:loginLog:page 分页列表
GET /api/login/log/detail/{id} sys:loginLog:detail 详情
DELETE /api/login/log/delete/{id} sys:loginLog:delete 删除
DELETE /api/login/log/batchDelete sys:loginLog:batchDelete 批量删除

安全分析

暴力破解检测

-- 短时间内同一 IP 大量失败登录(5分钟内失败超过10次)
SELECT ip, COUNT(*) as fail_count, MIN(create_time) as first_time, MAX(create_time) as last_time
FROM laravel_login_log
WHERE status = 1
  AND create_time > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
GROUP BY ip
HAVING fail_count >= 10
ORDER BY fail_count DESC;

异常账号检测

-- 同一账号短时间内大量失败(可能被暴力破解)
SELECT username, COUNT(*) as fail_count
FROM laravel_login_log
WHERE status = 1
  AND create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)
GROUP BY username
HAVING fail_count >= 5
ORDER BY fail_count DESC;

高频恶意 IP

-- 统计失败登录最多的 IP(最近7天)
SELECT ip, location, COUNT(*) as fail_count
FROM laravel_login_log
WHERE status = 1
  AND create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY ip, location
ORDER BY fail_count DESC
LIMIT 20;

成功率分析

-- 最近30天登录成功率
SELECT
    DATE(create_time) as date,
    COUNT(*) as total,
    SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) as success,
    ROUND(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) as success_rate
FROM laravel_login_log
WHERE log_type = 1
  AND create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(create_time)
ORDER BY date;

:::tip status 语义提醒 本项目 status:0=成功,1=失败(与操作日志一致)。上面的检测 SQL 已按此约定编写。 :::

与操作日志的区别

维度 登录日志 操作日志
表 laravel_login_log laravel_operation_log
记录方 AuthController 手动记录 LogOperation 中间件(terminate 落库)
记录内容 仅登录/登出 写操作 + 导出/导入/下载
触发方式 代码中 writeLoginLog() #[OperationLog] 注解
用途 登录安全审计 业务操作审计
失败也记录 ✓(登录失败必须记录) ✓(响应失败状态也记录)

日志保留策略

策略 说明 建议保留时间
按时间清理 定期删除 N 天前的日志 180 天(6个月)
安全合规 某些行业要求保留更长时间 按合规要求
-- 示例:清理 180 天前的登录日志
DELETE FROM laravel_login_log WHERE create_time < DATE_SUB(NOW(), INTERVAL 180 DAY);

最佳实践

实践 说明
成功和失败都记录 安全审计需要完整的登录行为记录
统一错误提示 "用户名或密码错误"不区分用户不存在和密码错误
定期分析 定期检查异常登录模式(暴力破解、异地登录)
IP 黑名单 将高频恶意 IP 加入黑名单(Nginx/防火墙层)
配合验证码 验证码防刷 + 登录日志分析形成双重防护(配合 IP 限流,见 5.3 验证码机制)