OpenQQAuth
QQ 驱动的 Minecraft 登录 / 账户认证系统(MQE 开源版 + 增量)。
OpenQQAuth 运行在 Velocity 代理端:玩家未验证身份时被隔离在登录界面(图形化认证界面:dialog / 虚拟箱子菜单 / 虚拟铁砧,全协议版本适配),验证通过后才被放行到后端 Paper 主服。它以 QQ 为每个账户的强制归属凭证,支持三种登录方式:
- 离线密码登录 —— 铁砧界面输入密码
- 正版快捷登录 —— 加密握手 + hasJoined 验证,绑定 QQ 后免密
- 基岩版登录 —— 微软账号身份(Floodgate),绑定 QQ 即放行、可继承 Java 账户数据
并额外提供**登录凭证(Token)**能力:安装 Fabric / NeoForge 双端合一 mod 后,玩家可在多人游戏界面点击"用凭证连接",输入由 QQ 群机器人私发的凭证即可免密进服(30 天有效,可吊销)。
领域术语表见
CONTEXT.md,架构决策见docs/adr/,规格见.scratch/qqauth/spec.md,外部 API 事实查证见.scratch/qqauth/api-reference.md。
目录
- 功能特性
- 架构总览
- 环境要求
- 快速开始
- 配置文件说明
- QQ 群命令参考
- 游戏内命令参考
- 登录与绑定流程
- 登录凭证(mod 端)
- 消息互通
- 权限模型
- 从源码构建
- 开发指南
- 常见问题 FAQ
- License
功能特性
- 门禁(Gate):未验证身份连接被限制在登录界面内,验证通过后才放行到主服
- 图形化认证界面:1.21.6+ 客户端使用 dialog 选择界面,旧版本回退虚拟箱子菜单;密码输入统一使用虚拟铁砧(开窗 / 改名 / 左键确认 / 右键清空 / 灰色混凝土重置)
- 全协议版本适配:铁砧与物品序列化按协议族参数化(旧版族 1.7.2
1.12.2 / 扁平化 1.131.14.4 / 经典 NBT 1.151.20.4 / 组件时代 1.20.51.21.1 / 菜单重构 1.21.2~1.21.5 / dialog 时代 1.21.6+),改名传输分界(≤1.12.2 插件通道 / 1.13+ 改名包) - 三种登录方式:离线密码、正版快捷(加密握手 + hasJoined 双保险校验)、基岩版免密
- QQ 绑定:验证码机制(游戏内显示验证码 → 群里发送
绑定 <验证码>,发送者 QQ 与目标 QQ 必须一致,防抢答) - 正版绑定:聊天栏可点击提示触发,正版会话 + QQ 验证码双因素;
premiumUuid固定命中(改名不影响登录);/opa bindpremium跨名绑定;顶名离线拒绝 + 冒名建号防线 - 基岩版支持:Floodgate 识别、Cumulus 表单绑定、
/opa bindxbox继承绑定(基岩绑到 Java 账户共享位置/背包)、Java/基岩互斥在线、/opa unbindxbox解绑恢复独立身份 - 快捷登录:同 IP 30 分钟内且在线满 10 分钟断线重连免密(窗口内一次);触发时向绑定 QQ 发送通知("非本人操作请回复冻结",可配置开关)
- 登录凭证(Token):QQ 群
获取凭证机器人私发 → mod 端"用凭证连接"免密进服;30 天有效、可吊销;防重放(一次性 nonce 挑战-响应) - 远程自助管理:QQ 群内
kick/freeze/unfreeze/ban/unban/list/status/check/exec/reload/reconnect - 自愿移交:名字冲突时原主解绑 → 管理员
transfer confirm确认 → 目标接管(ADR-0007,无强制夺权) - 领域事件:
PlayerAuthenticatedEvent/AccountBoundEvent/AccountFrozenEvent/AccountBannedEvent发布,供其他插件经 Bukkit ServicesManager 订阅 - 只读 API:
ReadOnlyAuthApi供 Paper 端插件(经济 / 领地 / 白名单联动)依赖 - 群聊互通:可选启用消息同步(群 ↔ 游戏内聊天双向转发)
- i18n:中英双语消息(
zh_CN/en_US),全量翻译键覆盖 - 安全:密码哈希(jBCrypt)、连错锁定(5 次锁 1 分钟)、每 IP 并发上限、绑定频率限制、会话超时踢出、防重放、全管理操作日志留痕
架构总览
┌──────────────┐ ┌──────────────────────────────┐ ┌──────────────┐
│ Minecraft │────▶│ Velocity (OpenQQAuth 门禁) │────▶│ Paper 主服 │
│ 客户端 │ │ dialog/箱子/铁砧 认证界面 │ │ (只读 API/事件)│
└──────────────┘ │ 登录判定管线 LoginPipeline │ └──────────────┘
│ 加密握手/正版验证/基岩 Floodgate│
└──────────────┬───────────────┘
│ OneBot (WebSocket)
┌──────────────▼───────────────┐
│ QQ 群机器人(绑定/重置/管理/凭证) │
└──────────────────────────────┘
| 模块 | 说明 |
|---|---|
core | 全部领域逻辑:账户/验证码/状态机/登录判定管线/凭证/事件总线/命令框架,平台无关 |
bootstrap/velocity | Velocity 代理插件:门禁、虚拟窗口 UI、协议族编解码、OneBot 接线、mod 登录查询 |
bootstrap/paper | Paper 插件:只读 API 暴露(Bukkit ServicesManager)、领域事件订阅示例 |
bootstrap/bukkit / bungeecord | 模板兼容骨架(当前仅加载占位) |
bootstrap/fabric + bootstrap/neoforge | 双端合一 jar:客户端"用凭证连接" UI 与凭证传输、服务端占位 |
build-logic | Gradle 约定插件(Java/Kotlin 编译、checkstyle、spotless 统一配置) |
环境要求
- Java 21(构建与运行)
- Gradle 9.6.1(系统安装;本项目不使用 Gradle wrapper)
- Velocity 代理(生产部署的登录门禁载体)
- OneBot 11 实现(如 NapCat / go-cqhttp),WebSocket 方式连接
- Paper 主服(后端)
- 可选:Floodgate / Geyser(基岩版支持)、Fabric / NeoForge 客户端 mod(凭证登录)
快速开始
1. 构建
gradle --no-daemon :bootstrap:velocity:build # Velocity 插件
gradle --no-daemon :bootstrap:paper:build # Paper 插件
gradle --no-daemon :bootstrap:fabric:build :bootstrap:neoforge:build # mod 双端 jar
产物位于各模块 build/libs/ 下。
2. 安装
- 将 velocity 插件 jar 放入
velocity/plugins/,paper 插件 jar 放入paper/plugins/ - 启动一次 Velocity 生成默认
config.yml,按需修改(见配置文件说明) - 配置 QQ 机器人(OneBot 11 WebSocket),填入
qq-bot.ws-url与qq-bot.ws-token,将管理 QQ 加入qq-bot.admin-qqs - 重启 Velocity 使配置生效;群内发送
reload或管理员执行/opa reload可热重载配置
3. 最小可用配置示例
config-version: 1
qq-bot:
ws-url: "ws://127.0.0.1:6700" # OneBot WebSocket 服务地址
ws-token: "" # 访问令牌(如启用鉴权)
admin-qqs: # 管理员 QQ 列表
- "10001"
login:
max-failed-attempts: 5 # 密码连错锁定阈值
lockout-minutes: 1 # 锁定时长(分钟)
storage:
driver: sqlite
sqlite-file: openqqauth.db
配置文件说明
所有配置集中在 Velocity 插件目录的 config.yml(QqAuthConfiguration,Configurate 加载)。
qq-bot(OneBot 机器人)
| 键 | 默认值 | 说明 |
|---|---|---|
qq-bot.ws-url | ws://127.0.0.1:6700 | OneBot 11 WebSocket 服务地址 |
qq-bot.ws-token | "" | 访问令牌 |
qq-bot.admin-qqs | [] | 管理员 QQ 列表(拥有 ADMIN 权限) |
accounts(账户)
| 键 | 默认值 | 说明 |
|---|---|---|
accounts.per-qq-limit | 3 | 单个 QQ 可绑定的最大账户数 |
login(登录判定)
| 键 | 默认值 | 说明 |
|---|---|---|
login.fast-login-window | 30 | 快捷登录窗口(分钟):断线重连免密的时间窗 |
login.fast-login-min-online-minutes | 10 | 触发快捷登录要求此前在线满时长(分钟) |
login.fast-login-notify-qq | true | 快捷登录触发时向绑定 QQ 发送通知 |
login.max-failed-attempts | 5 | 密码连错锁定阈值 |
login.lockout-minutes | 1 | 锁定时长(分钟) |
login.max-sessions-per-ip | 3 | 每 IP 并发连接上限(超额拒绝) |
login.bind-frequency-per-minute | 3 | 绑定频率限制(每分钟) |
login.session-timeout-seconds | 300 | 会话超时踢出(秒) |
login.bind-timeout-seconds | 180 | 绑定流程超时(秒) |
login.inherit-bind-window-minutes | 5 | 基岩继承绑定窗口(分钟) |
login.max-name-length | 30 | 改名输入长度上限 |
verify-code(验证码)
| 键 | 默认值 | 说明 |
|---|---|---|
verify-code.length | 4 | 验证码长度 |
verify-code.expire-seconds | 180 | 验证码有效期(秒) |
token(登录凭证)
| 键 | 默认值 | 说明 |
|---|---|---|
token.enabled | true | 是否启用凭证登录路径 |
token.ttl-days | 30 | 凭证有效期(天) |
sync-groups(群聊互通)
| 键 | 默认值 | 说明 |
|---|---|---|
sync-groups.enabled | false | 启用消息同步 |
sync-groups.group-ids | [] | 参与同步的群 ID 列表 |
sync-groups.game-to-group | true | 游戏内 → 群 转发 |
sync-groups.group-to-game | true | 群 → 游戏内 转发 |
sync-groups.merge-window-seconds | 5 | 合并窗口(秒) |
sync-groups.max-forwards-per-minute | 10 | 每分钟最大转发条数 |
storage(存储)
| 键 | 默认值 | 说明 |
|---|---|---|
storage.driver | sqlite | 存储驱动(当前支持 SQLite) |
storage.sqlite-file | openqqauth.db | SQLite 数据库文件路径 |
premium(正版检测)
| 键 | 默认值 | 说明 |
|---|---|---|
premium.enabled | true | 启用正版检测 |
premium.name-query-cache-hours | 24 | 名字→UUID 查询缓存时长(小时) |
premium.http-timeout-seconds | 5 | Mojang API 超时(秒) |
premium.rate-limit-per-window | 600 | 限速窗口内最大查询数 |
premium.rate-limit-window-minutes | 10 | 限速窗口(分钟) |
premium.bind-wait-minutes | 5 | 正版绑定等待标记时长(分钟) |
QQ 群命令参考
命令由 QQ 群机器人接收。权限分级见权限模型。
玩家自助命令(BOUND_MEMBER)
| 命令 | 说明 |
|---|---|
kick | 踢出发送者名下全部在线账户(自助踢出异常登录) |
freeze | 冻结发送者名下账户(临时冻结,本人可解) |
unfreeze | 解冻发送者名下账户 |
status | 查看发送者名下账户信息与验证状态 |
list | 查看在线列表 |
获取凭证 | 为发送者名下账户签发新登录凭证并私发(重复获取覆盖旧凭证) |
吊销凭证 [<账户名>] | 立即作废名下账户的凭证 |
验证码命令(群内确认敏感操作)
| 命令 | 说明 |
|---|---|
绑定 <验证码> | 确认 QQ 绑定(游戏内界面显示的验证码) |
重置 <验证码> | 确认密码重置(铁砧灰色混凝土发起) |
解绑正版 <验证码> | 解绑正版身份关联 |
解绑基岩 <验证码> | 解绑基岩账户(恢复独立身份) |
管理员命令(ADMIN)
| 命令 | 说明 |
|---|---|
bind 1 <QQ> <玩家名> | 强制绑定账户(随后自动私发登录凭证) |
bind 2 <QQ> | 重置账户密码(新密码打印控制台) |
ban @<目标> [名字] / unban | 封禁 / 解封账户(持久,仅管理员可解) |
freeze @<目标> [名字] / unfreeze | 冻结 / 解冻任意账户(临时可逆) |
check | 查看任意玩家完整信息与服务器概况 |
exec <服务器> <指令> | 远程执行后端服务器命令(前后自动审计留痕) |
transfer confirm <账户> <目标QQ> <正版名> | 确认自愿移交(原主解绑后) |
transfer list | 查看待确认移交队列 |
reload | 热重载配置 |
reconnect | 重连 QQ 机器人 |
游戏内命令参考
游戏内使用 /opa 前缀(Velocity 命令注册):
| 命令 | 说明 |
|---|---|
/opa bindpremium <名字> | 跨名正版绑定(正版身份接管原离线账户) |
/opa bindxbox <名字> | 发起基岩继承绑定(基岩账户绑到指定 Java 账户,共享位置/背包) |
/opa unbindxbox | 解绑基岩继承(恢复独立身份,数据保留) |
/opa reload | 热重载配置(管理员) |
登录与绑定流程
新玩家(离线)
- 进入服务器 → 被门禁拦截,直接进入 QQ 绑定流程
- 填写 QQ 号 → 游戏内界面显示验证码
- 在 QQ 群发送
绑定 <验证码>(发送者 QQ 必须与填写的 QQ 一致,防抢答) - 绑定成功 → 铁砧界面创建密码(两次输入确认)→ 进入服务器
老玩家(离线)
- 登录界面选择"离线"→ 铁砧输入密码 → 验证通过放行
- 忘记密码:铁砧点击灰色混凝土发起重置 → 设置新密码 → 群里发
重置 <验证码>确认
正版玩家
- 离线服上名字对应正版账号时,聊天栏出现"检测到 [id] 有正版账号,是否绑定"提示
- 点击提示 → 被踢出并提示"使用正版客户端进入服务器"
- 正版客户端重连 → 加密握手 + hasJoined 验证 → 群里发送
绑定正版 <验证码>完成 QQ 第二因素 - 绑定后正版客户端登录免密(按
premiumUuid命中账户并做身份替换,改名不影响) - 名字不同但同属一人:
/opa bindpremium手动跨名绑定
基岩版玩家(Floodgate)
- Floodgate 识别基岩连接 → 跳过 Java 登录界面,直接用 Cumulus 表单填写 QQ 号
- 群内发送
绑定 <验证码>确认 → 绑定即放行(无密码) /opa bindxbox:将基岩账户继承绑定到 Java 账户(共享位置/背包数据),Java/基岩互斥在线/opa unbindxbox或群内解绑基岩:恢复独立身份(QQ 账户数未达上限时数据保留)
快捷登录
- 断线重连:同 IP 30 分钟内、之前在线满 10 分钟 → 免密自动放行(窗口内仅一次)
- 触发时向绑定 QQ 发送通知:"账户 <名字> 刚通过快捷登录进入服务器,非本人操作请回复冻结"(
fast-login-notify-qq可关)
登录凭证(mod 端)
玩家使用
- 在 QQ 群发送
获取凭证→ 机器人私发 30 天有效的登录凭证 - 启动安装了 OpenQQAuth mod(Fabric 或 NeoForge)的客户端
- 多人游戏界面右下角点击 "用凭证连接" → 输入凭证 → 连接
- 服务器校验通过即免密进服;凭证泄露可随时群内
吊销凭证作废
工作原理
- 登录阶段经 mod 通道(
openqqauth:token)发送凭证,Velocity 端以 128 位随机 nonce 做挑战-响应防重放 - 校验(
TokenLoginChecker):凭证 + 账户绑定关系 + 未过期 + 未吊销 → 命中即免密放行 - 凭证校验失败不影响密码登录路径(保持密码输入界面)
- 非 mod 客户端(vanilla)不受影响,照常走密码登录
- 凭证明文本地保存可选(
save-token开关,关闭即清除)
消息互通
启用 sync-groups.enabled 后:
- 群消息 → 游戏内聊天(
group-to-game) - 游戏内聊天 → 群(
game-to-group) - 游戏内可用
/opa at@ 群成员、/opa re回复群消息
权限模型
| 权限级 | 含义 | 获得方式 |
|---|---|---|
ANY | 任意群成员 | 默认 |
BOUND_MEMBER | 已绑定账户的玩家 | 完成 QQ 绑定 |
ADMIN | 管理员 | 配置 qq-bot.admin-qqs |
管理员指令(bind / ban / exec / check / transfer / reload / reconnect 等)执行前后自动写入 CommandAuditLog 审计留痕。
从源码构建
# 全模块编译 + 测试 + checkstyle + spotless
gradle --no-daemon build
# 仅编译某模块
gradle --no-daemon :core:compileJava
gradle --no-daemon :bootstrap:velocity:compileJava
gradle --no-daemon :bootstrap:paper:compileJava
gradle --no-daemon :bootstrap:fabric:compileJava
gradle --no-daemon :bootstrap:neoforge:compileJava
# 跑某模块测试
gradle --no-daemon :core:test
gradle --no-daemon :bootstrap:velocity:test
# 代码风格校验
gradle --no-daemon :core:checkstyleMain :core:spotlessCheck
系统需安装 Gradle 9.6.1(不使用 wrapper);构建内存建议
-Xmx2G(见gradle.properties)。
开发指南
- 票据与规格:
.scratch/<feature>/(spec 与编号票据),见docs/agents/issue-tracker.md - triage 标签:
needs-triage/needs-info/ready-for-agent/ready-for-human/wontfix,见docs/agents/triage-labels.md - 领域术语:
CONTEXT.md(账户 / QQ / 绑定 / 门禁 / 登录界面 / 正版绑定 / 继承绑定等) - 架构决策:
docs/adr/0001~0008(虚拟铁砧门禁、离线正版检测、OneBot 11、登录界面分层、登录凭证、固定账户键、名字冲突、快捷登录信任模型) - 协作约定:见
CLAUDE.md(worktree 隔离、影响面验证、按票据提交、并发上限等)
代码结构速览(core)
core/src/main/java/dev/nmouzh/openqqauth/
├── account/ # 账户模型、SQLite 仓储
├── anvil/ # 铁砧窗口状态机(UI 无关核心)
├── api/ # 只读查询 API(供 Paper 插件)
├── bedrock/ # 基岩支持(表单发送、绑定流程)
├── configuration/# 配置模型(Configurate)
├── event/ # 领域事件总线(SimpleDomainEventBus)
├── gate/ # 登录判定管线 LoginPipeline、会话、锁定、快捷登录
├── i18n/ # TranslationKey 枚举 + 中英双语资源
├── mojang/ # Mojang API 客户端(名字→UUID、hasJoined)
├── onebot/ # OneBot 11 客户端(WebSocket 消息收发)
├── password/ # 密码哈希(jBCrypt)
├── qq/ # QQ 域:绑定/重置/解绑状态机、群命令、审计日志、自愿移交
├── token/ # 登录凭证:签发/吊销/校验、传输协议、mod 通道
├── util/ # 工具类
└── verify/ # 验证码服务
常见问题 FAQ
Q:提示"机器人离线"无法绑定?
A:新玩家绑定前置检查机器人在线状态(BotOnlineProvider),机器人离线时绑定类操作降级为提示并踢出。确认 OneBot 已连接(reconnect 可重连)。
Q:1.12.2 等旧版本客户端无法登录?
A:铁砧协议与物品序列化已按协议族参数化支持旧版族(1.7.21.12.2,理论下限;1.12.2 承诺可用)。1.13 族内更早子版本与 1.7.21.12.1 的包 ID 漂移按"理论下限不承诺"处理;真实 Velocity 连接接线(内部 MinecraftConnection 桥接)为独立后续票据。
Q:凭证校验失败会怎样? A:不影响密码登录路径——校验失败仅记录日志,玩家回到密码输入界面,可正常用密码登录。
Q:如何让其他插件读取账户信息?
A:Paper 端经 Bukkit ServicesManager 获取 ReadOnlyAuthApi(账户/会话查询),并可订阅领域事件(PlayerAuthenticatedEvent 等)。集成示例见 bootstrap/paper 的 PaperAuthEvents。
Q:数据存储在哪里?
A:默认 SQLite 文件 openqqauth.db(storage.sqlite-file 可改路径)。
Q:名字冲突如何处理?
A:遵循 ADR-0007"过去先到先得、未来正版优先":冲突双方可走自愿移交(原主解绑 → 管理员 transfer confirm 确认 → 目标接管),系统不提供强制夺权路径。
License
本项目采用 CC0 1.0 Universal 许可,你可按需自由使用(包括修改许可用于自己的项目)。
选择礼物
使用社区积分,为喜欢的作品送上一份支持
打赏记录
感谢每一位送出礼物的社区成员

