XIUNOX OAuth 登录安全机制 [Copy Link]

三级用户组

适用人群:站长 / 开发者 最后更新:2026-08-06

概述

Xiuno X 的 OAuth 登录安全机制旨在解决第三方登录场景下的账号接管风险。当用户通过 QQ、微信、GitHub、Google 等第三方平台登录时,系统会自动检测账号的密码和邮箱完整性,强制要求用户补全缺失的安全信息,形成"授权登录 → 强制补全 → 完整账号"的安全闭环。

核心目标:防止纯 OAuth 无密码账号被会话劫持后,攻击者直接改绑邮箱、再通过"忘记密码"流程接管账号。

安全威胁模型

风险场景

场景

攻击路径

危害

会话劫持

窃取 OAuth 登录后的 Session Cookie

攻击者进入用户账号

邮箱改绑

劫持会话后修改账号邮箱

攻击者控制通信渠道

密码重置

通过新绑定的邮箱走"忘记密码"流程

攻击者设置新密码,永久接管账号

防护策略

Plain Text

风险链路:Session 劫持 → 改绑邮箱 → 忘记密码 → 账号接管
    ↓
防护措施:强制补全密码 → 改绑邮箱需密码验证(已具备)
                         ↑
                  前提:账号必须有密码

核心思路:强制每个 OAuth 用户必须设置本地密码,这样即使会话被劫持,攻击者在改绑邮箱时也需要验证密码,而无法直接走通接管链路。

三层防护体系

第一层:登录后即时拦截

OAuth 回调成功后,系统立即检查账号的密码和邮箱完整性:

Plain Text

用户发起 OAuth 登录
    ↓
第三方授权回调 → handleCallback()
    ↓
登录/绑定/合并成功
    ↓
needsProfileCompletion($uid) 检查
    ↓
┌─────────────────────────────────────┐
│ 缺密码或缺邮箱?                      │
│   是 → 设置 session 标记             │
│         跳转到 oauth-perfect 补全页   │
│   否 → 正常跳转个人中心               │
└─────────────────────────────────────┘

关键代码位置:

  • OAuthService::needsProfileCompletion() - 判断逻辑

  • route/oauth.php callback 分支 - 拦截与跳转

第二层:全局路由强制拦截

即使补全流程被跳过,全局 Hook 也会强制拦截未完成的用户:

PHP

// plugin/xnx_oauth/hook/index_inc_route_before.php
// 在每次请求分发前执行
if (class_exists('OAuthService', false)) {
    $_oauth_perfect_uid = intval($uid);
    if ($_oauth_perfect_uid > 0 && OAuthService::isOAuthUser($_oauth_perfect_uid)) {
        $_oauth_perfect_state = OAuthService::needsProfileCompletion($_oauth_perfect_uid);
        if (!$_oauth_perfect_state['complete']) {
            // 仅放行:补全页、OAuth 子路由、登录/注册/退出、验证码/附件/API
            if (!$_oauth_allow) {
                http_location(url('oauth-perfect'));
            }
        }
    }
}

放行规则(防止用户被锁死):

  • oauth 全部子路由(补全页自身)

  • user 路由下的 login/logout/create(登录、退出、注册)

  • captcha/lang/attach/api(资源和 API 路由)

第三层:补全页安全验证

补全页包含完整的安全验证机制:

密码设置

  • 复杂度校验:通过 SecurityConfigService::checkPasswordPolicy() 校验密码强度

  • 两次输入确认:防止输入错误

  • 防重提交:xnx_form_guard() 防止表单重复提交

邮箱绑定

  • 邮箱格式校验:filter_var($email, FILTER_VALIDATE_EMAIL)

  • 域名白名单:管理员配置允许的邮箱域名

  • 唯一性检查:邮箱未被其他用户使用

  • 验证码验证:通过邮件发送 6 位随机验证码

  • 频率限制:xn_email_rate_check() + xn_email_rate_record() 防止轰炸

核心 API

OAuthService 静态方法

isOAuthUser($uid)

判断用户是否为 OAuth 绑定用户(任一第三方有绑定记录即算)。

PHP

use OAuthService;

// 检查 uid=123 是否为 OAuth 用户
$isOAuth = OAuthService::isOAuthUser(123);
// 返回:bool

使用场景:

  • 全局拦截器判断是否需要强制补全

  • 个人中心显示"绑定账号"入口

  • 管理员审计 OAuth 用户比例

needsProfileCompletion($uid)

检查用户是否需要补全账号信息(密码/邮箱)。

PHP

// 获取补全状态
$state = OAuthService::needsProfileCompletion(123);
// 返回:array
// array(
//     'need_password' => bool,  // 是否需要设置密码
//     'need_email'    => bool,  // 是否需要绑定邮箱
//     'complete'      => bool,  // 是否已完成补全
// )

状态说明:

need_password

need_email

complete

说明

true

true

false

新 OAuth 用户,需设置密码和邮箱

true

false

false

有邮箱但无密码(罕见)

false

true

false

有密码但无邮箱(需绑定邮箱)

false

false

true

已完成补全,正常用户

路由说明

路由

方法

说明

oauth-perfect

GET

渲染补全页

oauth-perfect

POST

保存密码和邮箱

oauth-perfect-sendcode

POST

发送邮箱验证码

Session 标记

系统使用以下 Session 变量管理补全流程:

Session 键

类型

说明

oauth_perfect_required

int

需要补全的用户 UID

email_change_code

string

当前邮箱验证码

email_change_target

string

验证码对应的邮箱地址

补全页交互流程

Plain Text

┌─────────────────────────────────────────────────────────────┐
│                    完善账号信息                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [头像]                                                     │
│  [用户名]                                                   │
│  为保障账号安全,请设置登录密码并绑定邮箱                    │
│                                                             │
│  ────────────────────────────────────────────────            │
│                                                             │
│  新密码:    [_________________________]                     │
│  确认密码:  [_________________________]                     │
│                                                             │
│  新邮箱:    [_________________________]                     │
│  验证码:    [___________] [发送验证码] 60s倒计时              │
│                                                             │
│  [保存并继续]                                                │
│                                                             │
│  ────────────────────────────────────────────────            │
│                                                             │
│  [退出登录]                                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

表单防重提交

JavaScript

// 防止快速双击提交
function xnx_form_guard(formId, btnId) {
    // 1. capture 阶段同步拦截(最快)
    form.addEventListener('submit', function(evt) {
        if(busy) { evt.preventDefault(); return; }
        busy = true;
        btn.disabled = true;
    }, true);
    
    // 2. 请求期间禁用按钮 + 显示 loading
    form.addEventListener('htmx:before:request', () => { ... });
    form.addEventListener('htmx:after:request', restore);
    form.addEventListener('htmx:response:error', restore);
    
    // 3. 30 秒超时兜底
    setTimeout(restore, 30000);
}

验证码发送流程

Plain Text

用户点击"发送验证码"
    ↓
前端校验邮箱格式
    ↓
POST oauth-perfect-sendcode
    ↓
┌─────────────────────────────────┐
│ 1. CsrfService::check()         │
│ 2. 检查 need_email 状态          │
│ 3. 邮箱格式验证                   │
│ 4. 域名白名单检查                 │
│ 5. 邮箱唯一性检查                 │
│ 6. 生成 6 位随机验证码            │
│ 7. 存入 Session                   │
│ 8. 检查频率限制                   │
│ 9. 发送邮件                       │
│ 10. 返回成功 + 倒计时秒数         │
└─────────────────────────────────┘
    ↓
前端显示 60 秒倒计时
    ↓
倒计时结束后可重新发送

配置与扩展

密码策略配置

密码复杂度通过 SecurityConfigService 统一管理:

PHP

// 后台路径:系统设置 → 安全 → 账号安全

// 读取当前配置
$minLength = SecurityConfigService::get('security_password_min_length', 6);
$complexity = SecurityConfigService::get('security_password_complexity', 'none');

// 复杂度档位:
// - none: 仅最小长度
// - number: 必须包含数字
// - mixed: 必须包含大小写字母
// - special: 必须包含大小写字母、数字、特殊字符

邮箱域名白名单

PHP

// 允许的邮箱域名列表
$allowedDomains = SecurityConfigService::get('security_allowed_email_domains', '');
// 格式:gmail.com,qq.com,163.com
// 空值表示不限制

邮件发送配置

OAuth 补全使用与系统相同的邮件发送机制:

  • SMTP 配置:后台"邮件设置"

  • 邮件模板:email_change_code(验证码邮件)

  • 频率限制:每邮箱每小时最大发送次数

常见问题

Q1:新老 OAuth 用户都会被强制补全吗?

是的。只要是 OAuth 绑定用户(isOAuthUser($uid) 返回 true),且账号缺密码或邮箱,下次登录都会被拦截。这是预期行为,确保所有 OAuth 用户都有完整的账号信息。

Q2:普通注册用户会被强制补全吗?

不会。补全机制仅对 OAuth 用户生效。普通注册用户即使缺邮箱也不会被拦截,因为他们有本地密码作为第二身份验证。

Q3:补全页能跳过吗?

不能。三层防护确保无法绕过:

  1. OAuth 回调成功后直接跳转到补全页

  2. 全局路由拦截所有非放行路由

  3. 完成前 session 标记持续有效

Q4:补全期间用户能退出登录吗?

可以。补全页底部提供"退出登录"链接,用户可在任何时候退出。

Q5:已有密码的 OAuth 用户需要重新设置密码吗?

不需要。needsProfileCompletion() 只检查是否"缺"密码,已有密码的用户跳过密码设置步骤。

Q6:补全完成后还能解绑所有 OAuth 登录方式吗?

不能。系统会检查是否有其他登录方式:

  • 有密码 → 可解绑所有 OAuth

  • 有其他 OAuth 绑定 → 可解绑当前 provider

  • 无密码且仅有一个 OAuth → 不允许解绑

Q7:邮箱验证码有效期多久?

验证码存储在 Session 中,有效期与 Session 相同(默认 1 小时)。验证成功后立即清除。

Q8:能自定义补全页吗?

可以。补全页模板位于:

Plain Text

plugin/xnx_oauth/view/htm/oauth_perfect.htm

可通过模板继承或插件 Hook 修改显示内容,但核心验证逻辑不应修改。

Q9:如何手动触发补全检查?

PHP

// 在任意位置检查用户状态
$state = OAuthService::needsProfileCompletion($uid);
if (!$state['complete']) {
    // 跳转到补全页
    header('Location: ' . url('oauth-perfect'));
    exit;
}

Q10:API 登录的 OAuth 用户如何补全?

API 场景下 OAuth 补全通过专用接口处理:

  1. 获取当前补全状态:GET /api/oauth-perfect

  2. 发送邮箱验证码:POST /api/oauth-perfect-sendcode

  3. 提交补全信息:POST /api/oauth-perfect

相关文件

核心代码

文件

说明

plugin/xnx_oauth/model/OAuthService.php

OAuth 服务类(含补全判断方法)

plugin/xnx_oauth/route/oauth.php

OAuth 路由处理(含补全分支)

plugin/xnx_oauth/hook/index_inc_route_before.php

全局强制拦截 Hook

plugin/xnx_oauth/view/htm/oauth_perfect.htm

补全页模板

配置与语言

文件

说明

plugin/xnx_oauth/conf.json

插件配置(版本号 1.1.1)

plugin/xnx_oauth/hook/lang_zh_cn_bbs.php

简体中文语言包

plugin/xnx_oauth/hook/lang_zh_tw_bbs.php

繁体中文语言包

plugin/xnx_oauth/hook/lang_en_us_bbs.php

英文语言包

依赖服务

文件

说明

lib/security/SecurityConfigService.php

安全配置服务(密码策略)

lib/CsrfService.php

CSRF 令牌验证

xiunophp/xn_send_mail.func.php

邮件发送函数

conf/email_templates.conf.php

邮件模板配置

安全最佳实践

站长配置建议

  1. 设置合理的密码复杂度:推荐 mixed 或 special 级别

  2. 配置邮箱域名白名单:限制为企业域名或常用邮箱服务商

  3. 启用邮件频率限制:防止验证码被滥用

  4. 定期审计 OAuth 用户:检查是否有异常绑定

插件开发者注意事项

  1. 不要绕过全局拦截:自定义 OAuth 插件应复用 OAuthService::needsProfileCompletion()

  2. 保持 Session 标记同步:自定义补全流程时需维护 oauth_perfect_required

  3. 使用标准验证函数:密码校验走 SecurityConfigService::checkPasswordPolicy()

  4. 记录补全日志:补全成功/失败记录到安全日志

版本历史

版本

日期

变更内容

1.1.1

2026-08-06

新增 OAuth 登录后强制补全机制(三层防护)

1.1.0

2026-08-05

OAuth 2.0 统一登录基础功能


New Post

Please log in to reply Login

uid:4 三级用户组
Follow
Posts 64
Comments 1
Followers 0
Following 0
Publish
Table of Contents
XIUNOX OAuth 登录安全机制
This Ad Slot Available
This Ad Slot Available
This Ad Slot Available
Ad Available
Ad Available
Ad Available
Ad Available
Ad Available
Ad Available