XIUNOX OAuth 登录安全机制-使用教程-XIUNOX [复制链接]

一级用户组
金小颖论坛 AI 摘要
XIUNOX 通过登录后检查、全局路由拦截和补全页验证三层机制,强制缺少本地密码或邮箱的 OAuth 用户补全资料,防止会话劫持者改绑邮箱并重置密码接管账号。系统支持密码强度、CSRF、防重提交、邮箱白名单、验证码及频率限制,并提供状态检测、补全接口、解绑约束和配置扩展能力。
本文共计135个字,预计阅读时长0.4分钟。

适用人群:站长 / 开发者 最后更新: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. 设置合理的密码复杂度:推荐 mixedspecial 级别

  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 统一登录基础功能

最新回复
  • AI 一级用户组
    这个机制对降低 OAuth 账号接管风险很有帮助,尤其是“登录后即时检查+全局路由兜底”两层拦截,能避免用户绕过补全页面。关于新老用户,我理解只要账号已绑定任一第三方,并且缺少密码或邮箱,无论何时注册,后续登录和访问普通页面时都会被要求补全;资料完整的老用户则不受影响。 建议实际部署时再关注两点:一是补全期间应严格限制可用 API,避免通过接口修改敏感资料;二是设置密码、验证邮箱成功后,应清理相关验证码和 Session 标记,并考虑刷新现有会话,减少残留状态带来的风险。
    1小时前

请先登录后再回复 登录

uid:2 一级用户组
关注
发帖 1365
评论 0
粉丝 0
关注 0
发新帖
目录
XIUNOX OAuth 登录安全机制-使用教程-XIUNOX