-
Notifications
You must be signed in to change notification settings - Fork 72
启动器技术规范
本文旨在为启动器实现 authlib-injector 规范提供技术指导。由于该功能需要调用 Yggdrasil API,因此建议您在阅读本文前,先阅读 Yggdrasil 服务端技术规范。
在启动器中,本登录方式可以被称为【外置登录(authlib-injector)】或【authlib-injector 登录】。我们推荐您使用含义更为明确的前者。
验证服务器(即 Yggdrasil 服务器)是整个验证系统的核心,所有验证相关的请求都将被发往它。
为了确定一个验证服务器,启动器应当存储该验证服务器的 API 地址(即 API Root,如 https://example.com/api/yggdrasil/
)。
启动器可以仅支持一个验证服务器,也可以支持多个验证服务器。支持多验证服务器就意味着,启动器中可以同时存在多个账户,并且这些账户可以属于不同的验证服务器。
设置验证服务器的操作一般由玩家完成,但也存在着服主进行设置、配置文件同启动器和游戏一起分发的情况。下面介绍几种设置验证服务器的途径:
启动器可以将 API 地址直接存储于配置文件中,让用户通过修改配置文件的方式设置验证服务器。这种配置方式实现简单,如果你的启动器只用作服务器专用启动器,则可以使用这种配置方式。
在这种配置方式下,用户通过在启动器中输入 URL 来完成验证服务器的设置。这里的 URL 可能是完整的 API 地址(如 https://example.com/api/yggdrasil/
),也可能是缩略的地址(如 example.com
)。
当 URL 未标明协议(HTTPS 或 HTTP)时,我们约定将其自动补全为 HTTPS 协议。亦即,example.com/api/yggdrasil/
应当被理解为 https://example.com/api/yggdrasil/
。
出于安全考虑,启动器即使无法通过 HTTPS 协议连接,也不得降级到明文的 HTTP 协议。
同时,authlib-injector 规定了一种服务发现机制,称为 API 地址指示(ALI)。它用于将用户输入的缩略的、不完整的地址,转换为完整的 API 地址。
为了将用户输入的地址解析为真正的 API 地址,启动器需要进行如下操作:
- 如果 URL 缺少协议,则将其补全为 HTTPS 协议。
- 向 URL 发送 GET 请求(跟随 HTTP 重定向)。
- 如果响应包含 ALI 头(HTTP 头部
X-Authlib-Injector-API-Location
),那么 ALI 指向的 URL 就是 API 地址。-
X-Authlib-Injector-API-Location
可以是绝对 URL,亦可以是相对 URL。 - 如果 ALI 指向其自身,那就意味着当前 URL 就是 API 地址。
-
- 如果响应不包含 ALI 头部,则默认认为当前 URL 是 API 地址。
伪代码:
function resolve_api_url(url)
response = http_get(url) // follow redirects
if response.headers["x-authlib-injector-api-location"] exists
new_url = to_absolute_url(response.headers["x-authlib-injector-api-location"])
if new_url != url
return new_url
// if you are going to fetch the metadata next, 'response' can be reused
return url
此方式允许用户通过鼠标拖拽 (DnD)来设置验证服务器。
DnD 源可以是浏览器或者其他应用程序,而 DnD 目标则是启动器。DnD 源需要展示一段文字、图片或其他内容,示意用户将此内容拖入启动器,以添加验证服务端。在此过程中,验证服务器信息从 DnD 源传输到启动器。在完成 DnD 动作后,启动器向用户确认是否要添加此验证服务器。
拖动数据的 MIME 类型为 text/plain
,内容为一段 URI,其格式如下:
authlib-injector:yggdrasil-server:{验证服务端的 API 地址}
其中的 API 地址是 URI 的一个组成,应当被编码。
拖动效果为复制(copy
)。
在需要拖动的 DOM 节点上添加 draggable="true"
,并处理 dragstart
事件:
<span id="dndLabel" draggable="true" ondragstart="dndLabel_dragstart(event);">example.yggdrasil.yushi.moe</span>
function dndLabel_dragstart(event) {
let yggdrasilApiRoot = "https://example.yggdrasil.yushi.moe/";
let uri = "authlib-injector:yggdrasil-server:" + encodeURIComponent(yggdrasilApiRoot);
event.dataTransfer.setData("text/plain", uri);
event.dataTransfer.dropEffect = "copy";
}
通过向 API 地址发送 GET 请求,启动器可以获取到验证服务器的元数据(响应格式),例如服务器名称。启动器可以利用这些元数据提升用户体验。
验证服务器在 meta
中的 serverName
里指定了验证服务器的名称。当启动器需要向用户显示一个验证服务器时,便可以使用该名称。
需要注意的是,验证服务器名称可能会出现冲突,因此启动器应当提供查看验证服务器 API 地址的方法。例如,当鼠标悬浮在验证服务器名称上时,启动器在 Tooltip 中显示其 API 地址。
当用户尝试设置一个使用明文 HTTP 协议的验证服务器时,启动器应向用户显示醒目警告,告知用户这可能将其信息安全置于危险之中,用户的密码将会被明文传输。
一个账户对应了游戏中的一个玩家,用户可以在启动时选择一个账户进行游戏。
账户、用户和角色的关系:启动器中账户的概念,与验证服务器中用户的概念并不相同。与启动器中的账户相对应的,是验证服务器中的角色(Profile)。验证服务器中的用户则是一个或多个角色的所有者,其在启动器中并无对应实体。
启动器通过以下三个不可变的属性来确定一个账户:
- 账户所属验证服务器
- 账户的标识(如邮箱)
- 通常账户标识即为邮箱。但若验证服务器返回的元数据中
feature.non_email_login
字段为 true,则表明验证服务器支持使用邮箱以外的凭证登录,即账户标识可以不是邮箱。此时,启动器不应当期望用户输入的账户一定是邮箱,同时应注意措辞(如使用「账户」一词代替「邮箱」一词),以免造成迷惑。(详见 Yggdrasil 服务端技术规范 § 使用角色名称登录)
- 通常账户标识即为邮箱。但若验证服务器返回的元数据中
- 账户所对应角色的 UUID
仅当两个账户的以上三个属性都相同时,这两个账户才是相同的,其中一个属性相同并不能代表两账户相同。同一验证服务器上可以存在多个角色;多个角色可以属于同一个用户;不同验证服务器上也可以出现具有相同 UUID 的角色。因此,启动器应同时使用这三个属性来标识账户。
除了以上三个属性外,账户还具有以下属性:
- 令牌(accessToken 及 clientToken)
- 账户所对应角色的名称
- 用户的 ID
- 用户的属性
安全警告:
- 记住登录状态记录的是令牌,不是用户的密码。密码在任何时候都不应该被明文存储。
以上属性都是可变的。每次登录或刷新操作后,启动器都需要更新存储的账户属性。
在下面所有的登录和刷新操作中,请求中 requestUser
参数都为 true
,这样启动器便能够即时更新用户 ID 和用户属性。
如果用户要添加一个账户,则启动器需要询问用户使用的验证服务器、用户的账户和密码。这里的验证服务器,可以是预先设置好的,可以是用户从验证服务器列表中选择的,也可以是用户即时设置的(见上文)。
此后,启动器进行以下操作:
- 调用相应验证服务器的登录接口,其中包含用户输入的账户和密码。
- 如果响应中
selectedProfile
不为空,则登录成功,使用响应中的信息更新账户属性。流程结束。 - 如果响应中
availableProfiles
为空,则用户没有任何角色,触发异常。 - 提示用户从
availableProfiles
中选择一个角色。 - 调用刷新接口,其中的令牌为登录操作所返回的令牌,
selectedProfile
为上一步中用户选择的角色。 - 登录成功,使用刷新响应中的信息更新账户属性。
启动器在使用凭证前(例如启动游戏前),需要确认其有效性。如果凭证失效,则需要用户重新登录。确认凭证有效性的步骤如下:
- 调用验证令牌接口,其中包含账户的 accessToken 和 clientToken。
- 如果请求成功,则当前凭证有效,流程结束。否则继续执行。
- 调用刷新接口,其中包含账户的 accessToken 和 clientToken。
- 如果请求成功,则使用刷新响应中的信息更新账户属性,流程结束。否则继续执行。
- 启动器要求用户重新输入密码。
- 调用登录接口,其中包含用户的账户和上一步输入的密码。
- 如果登录响应中
selectedProfile
不为空,则:- 如果
selectedProfile
中的uuid
与账户对应角色的 UUID 相同,则使用登录响应中的信息更新用户属性。流程结束。 - 触发异常(原账户的角色已不可用)。
- 如果
- 从
availableProfiles
中找出 UUID 与账户对应角色相同的角色。如果没有,则触发异常(原账户的角色已不可用)。 - 调用刷新接口,其中令牌为登录操作返回的令牌,
selectedProfile
为上一步中所找到的角色。 - 登录成功,使用刷新响应中的信息更新账户属性。
启动器在显示账户时,除了账户对应角色的名称之外,还应该显示账户所属的验证服务器(见上文),防止用户混淆不同验证服务器上的同名角色。
如果启动器要显示角色皮肤,则可以调用查询角色属性接口获取角色属性,角色属性中包含了角色的皮肤信息。
启动器在启动游戏前需要进行以下工作:
其中第 1、2、3 步可以并行执行,以提升启动速度。
启动器可以自带 authlib-injector.jar,也可以在启动游戏前下载一个(并缓存)。本项目提供了一个 API 用于下载 authlib-injector。
如果你的用户主要在中国大陆,我们推荐你从 BMCLAPI 镜像下载。
在启动前,启动器需要向 API 地址发送 GET 请求,获取 API 元数据。该元数据会在启动时被传入游戏,这样 authlib-injector 就不必直接请求验证服务器,进而能提升启动速度,并防止因网络故障而导致的游戏启动时崩溃。
启动器需要添加以下 JVM 参数(应加在主类参数前):
- javaagent 参数:
-javaagent:{authlib-injector.jar 的路径}={验证服务器 API 地址}
- 配置预获取:
-Dauthlibinjector.yggdrasil.prefetched={Base64 编码的 API 元数据}
下面以 example.yggdrasil.yushi.moe 为例:
- authlib-injector.jar 位于
/home/user/.launcher/authlib-injector.jar
。 - 验证服务器的 API 地址为
https://example.yggdrasil.yushi.moe/
。 - 向
https://example.yggdrasil.yushi.moe/
发送 GET 请求,获取到 API 元数据:对上面的响应进行 Base64 编码,得到:{"skinDomains":["yushi.moe"],"signaturePublickey":...(省略)
eyJza2luRG9tYWluc...(省略)
- 故添加的 JVM 参数为:
-javaagent:/home/user/.launcher/authlib-injector.jar=https://example.yggdrasil.yushi.moe/ -Dauthlibinjector.yggdrasil.prefetched=eyJza2luRG9tYWluc...(省略)
游戏版本 JSON 文件(versions/<version>/<version>.json
)中规定了启动器启动游戏时使用的参数,其中部分与验证相关的参数模板应按下表进行替换:
参数模板 | 替换为 |
---|---|
${auth_access_token} | 账户的 accessToken |
${auth_session} | 账户的 accessToken |
${auth_player_name} | 角色的名称 |
${auth_uuid} | 角色的 UUID(无符号) |
${user_type} | mojang |
${user_properties} | 用户的属性(JSON 格式)(若启动器不支持,可替换为 {} ) |
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.