摘要
本文记述了基于 Yggdrasil API 规范,通过 authlib-injector 进行客户端认证,以 Flask 编写服务端程序的 Minecraft 游戏身份认证和皮肤自定义系统的实现过程和实现方式。
定义和说明
- 本文所称“服务器”,是指广义上的服务器,包括与 Minecraft 游戏相关的服务端程序和服务器基础设施。“认证服务端”,是指用于 Minecraft 用户身份认证的服务端程序,不包括游戏服务端本体。“游戏服务端”,是指用于提供 Minecraft 多人游戏服务的服务端程序。
- 本文所称“客户端”,是指最终分发到最终用户手中的程序,包括游戏本体、启动器和其他相关内容和程序。“启动器”,是指客户端中用于启动 Minecraft 核心 Jar 文件的程序。
- 本文采用的软件和软件版本为:Python 3.6; MySQL 8.0.17; 相关库版本见
requirements.txt
。 - 服务器的代码可以此处找到:GitHub: baobao1270/joseph-minecraft-auth-server, Gitee: josephcz/joseph-minecraft-auth-server。
概述
最近本人开设了一个 Minecraft 服务器,由于服务器模组越加越多,难于管理;服务器玩家需要自定义皮肤;服务器在初期采用了 Minecraft Server(官方服务器)而无法使用登录验证插件但需要进行登录验证,故决定自行实现一个 Minecraft 认证服务器。
在此之前,已有许多方案实现 Minecraft 服务器的登录认证或服务器。对于正版玩家,只需开启在线模式(Online Mode),即可集成 Mojang 账号进行认证。但是由于国内盗版玩家居多,故此种方案在国内采用较少。对于非正版玩家,最传统的莫过于 AuthMe 插件,该插件支持服务器权限系统、支持多种加密方式和数据库保存密码、支持反机器人等,但由于采用了插件的形式,无法在 Minecraft 官方游戏服务端中安装。同时,采用 CostumSkin 或 SkinMe 等皮肤站提供的皮肤模组实现自定义皮肤。但是,最近 SkinMe 服务转型导致非付费用户服务不稳定、对旧版本兼容不友好,同时在技术上使用非可控的服务具有很大的不确定性,故研究一套自主的、开源的、可独立部署的 Minecraft 认证服务,是及其有必要的。
实现本系统的目标是:建立一套具有注册登录、三端验证、账户管理、材质文件下发功能的系统,包括:
- 基本的用户账户系统,包括:用户注册、用户登录、Web 端认证、游戏客户端认证、游戏服务端认证功能
- 简单的材质数据系统,包括:用户角色、皮肤材质和披风材质的增、删、查、改;用户和用户角色、用户角色和材质的一对多绑定
- 对以上系统进行操作的语义化 HTTP API
- 按照 Yggdrasil API 标准,用于游戏客户端和游戏服务端认证的 HTTP API
- 一套简单的用于账户注册和管理的 Web 界面
- 数据库驱动程序,包括具有抗时序攻击的用户密码加密功能的用户信息数据驱动程序、会话和令牌临时储存驱动程序和材质数据储存驱动程序
技术架构选择:客户端
客户端采用第三方 Hello Minecraft Luncher (HMCL) 进行启动,其内部已经内置 authlib-injector 验证方式。通过修改 version.json
文件和 hmcl.json
配置文件,达到引导游戏玩家在首次启动时自动引导其采用该方式进行登录的目的。客户端的分发,一开始采用的是使用启动器自带的整合包工具进行整合包分发,后续采用自解压压缩包的方式进行分发,并采用 Windows Cmd 脚本进行更新操作。这种方式的缺点是,需要玩家手动进行 Java 的安装、手动解压文件并启动更新脚本,并且对不同系统的兼容性不高。
在 2.1.1 版本中,客户端改为了使用 NSIS 进行打包,并集成了 Java 二进制文件,方便新人更加快捷地进入游戏。随后的版本均需要卸载原来的版本后再安装新版。
技术架构选择:服务端
本服务端实现采用 Flask 作为 Web 框架,采用 Python 作为编程语言。这种架构开发简单,方便独立部署,无需编译,且由于 Linux 自带 Python,迁移成本也较低。
为了防止版本问题和包的冲突,还使用了 Virtualenv 作为运行环境。
在服务端的自带账户管理界面种,使用了较为简单的 jQuery 作为 JavaScript 框架啊,并采用 MDUI 作为前端库。前端与后端的通信,基本通过 Ajax 与 WebAPI 通信。
在服务端的运行方面,采用了 Tornado 的独立 WSGI 容器,并将其配置为 Systemd 服务,以便于开机自动运行和服务的统一管理。
最后,为了实现在 80 端口上与其他站点共存,服务端的 WSGI 容器被配置为只允许本地通信,并通过 Apache2 统一进行反向代理提供最外层的 HTTP 服务。
客户端的部署与配置
由于 HMCL 已经继承了 authlib-injector,故无需另行配置。打开 HMCL,将登录类型改为“外置登录(authlib-injector)”,并输入认证服务端的地址,即可完成配置。
但是由于 HMCL 在处理用户语言时的 bug,对于采用正版登陆或外置登陆的 1.7.10 版本用户,在游戏配置文件(.minecraft/<game_name>/<game_name>.json
)中,需要将 minecraftArguments
参数中的 --userProperties {"perferedLanguage": "zh_CN"}
改为 --userProperties {}
。猜测是该版本不支持这个字段,导致游戏本体启动时报错。
客户端采用 NSIS 进行打包。同时写入了相关的注册表,方便今后的维护。在打包时,加入了 Java (JRE) 文件,方便未安装 Java 的用户直接启动客户端。
在打包客户端时,还需要注意三个问题:一是游戏快捷方式的创建,NSIS 创建快捷方式的命令格式为:CreateShortCut <path> <target> <args> <icon>
。NSIS 默认在“开始”菜单创建快捷方式,同时我们需要另外在桌面上创建快捷方式,因此需要使用 $DESKTOP 变量。同时,为了指定我们随客户端分发的 JRE 为默认 JRE,在“目标程序”应当填写 javaw.exe 的位置,而非启动器的位置。在参数上,应当填写 "-jar $\"$INSTDIR\hmcl.jar$\""
。需要指出的是,在填写时要注意路径需要用引号括起来,并且在 NSIS 脚本中引号的转义符为 $\"
而非 \"
。此外,由于采用的是 Java 版的启动器,因此需要指定一个图标来保持美观。最终的命令为:CreateShortCut "$DESKTOP\U406 Minecraft.lnk" "$INSTDIR\jre\bin\javaw.exe" "-jar $\"$INSTDIR\hmcl.jar$\"" "$INSTDIR\favicon.ico"
。
第二个问题是,在写入注册表时,由于 32 位注册表和 64 位注册表不互通,而安装程序为 32 位、客户端为 64 位,导致客户端无法读取注册表。此时,需要使用 SetRegView 命令。为了保险起见,最终选择了同时写入两个注册表。具体命令如下:
1 | SetRegView 64 |
相应的,在卸载程序中,也要删除两次注册表:
1 | SetRegView 64 |
第三则是如果卸载时文件夹非空,则删除文件夹命令无效。因此需要加上 /r
选项:RMDir /r "$INSTDIR"
。
认证服务端实现
服务端借鉴了 MVC 的模式,请求首先到达“蓝图”,进行 JSON 解析,其次在蓝图中操作作为模型曾的 ORM类。
在数据库结构的设计上,将 User(用户)一对多关联到 Profile(游戏角色),再将 Profile 一对一关联到 Skin(皮肤)和 Cape(披风),形成了简单的 ORM 结构。
再密码设计上,使用 Bcrypt 加密算法。
在服务端,还设计了 ObjectiveRespone 结构(简称 R 数据结构),用于将数据库中取出的数据转换为 Yggdrasil 协议标准的响应数据。
服务端设计了四个蓝图:SessionServer 蓝图用于客户端的用户登陆验证和令牌的管理;AuthServer 蓝图用于进入游戏的身份验证;WebAPI 是采用简单的用户名—密码认证进行简单的数据管理的 API;Dashboard 蓝图是一个简单的控制面板。WebAPI 和 Dashboard 可以再配置文件中开关。
Dashboard 本质上是前后端分离的,除了一些基础数据外,全部使用 Ajax 请求 WebAPI 来加载和修改数据。
认证服务端部署
服务端部署在我的另一篇博文:采用 Tornado WSGI 容器部署 Flask 为 Systemd 服务并使用 Apache2 进行反向代理的方法 中已经阐述,在此不再赘述。
游戏服务端部署
参见:在 Minecraft 服务端使用 authlib injector