Skip to content

Commit 7c0ae6f

Browse files
Steven Qiutnqzh123
authored andcommitted
[yggdrasil-connect] new plugin
1 parent 0801e9b commit 7c0ae6f

68 files changed

Lines changed: 3837 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

plugins/yggdrasil-connect/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2016-present LittleSkin
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Yggdrasil Connect for Blessing Skin
2+
3+
本插件实现了 [Yggdrasil 服务端技术规范](https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil%20%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83),可与 [authlib-injector](https://github.com/yushijinhun/authlib-injector) 及支持的启动器配合实现 Minecraft 外置登录身份验证,并可与 [Janus](https://github.com/bs-community/janus) 项目配合实现基于 [Yggdrasil Connect 协议](https://github.com/yushijinhun/authlib-injector/issues/268) 的 OAuth 身份验证。要了解更多关于 Yggdrasil Connect 和 Janus 的信息,请阅读下面的 [关于 Yggdrasil Connect](#关于-yggdrasil-connect) 部分。
4+
5+
本插件由原版 Yggdrasil API 插件重构而来,使用 Laravel Passport 的个人访问令牌(Personal Access Token)作为访问令牌,通过在 JWT Payload 中添加角色 UUID 并重新签名实现访问令牌与角色的绑定。首次启用本插件时,请务必按照下方的 [插件使用方法](#插件使用方法) 部分中的说明执行操作,否则本插件可能无法正常工作。
6+
7+
本插件不需要也不能与原版 Yggdrasil API 插件同时启用,但插件数据可与原版 Yggdrasil Connect 插件通用。如需从原版 Yggdrasil API 插件迁移至本插件,请务必按照下方的 [插件使用方法](#插件使用方法) 部分的第三步处理 `uuid` 表,否则本插件无法正常工作。
8+
9+
本插件在一定程度上修复了原版 Yggdrasil API 插件的多个 Bug。要了解具体细节,请阅读下面的 [关于 Bug 修复](#关于-bug-修复) 部分。
10+
11+
## 插件使用方法
12+
13+
1. 通过 Blessing Skin 插件市场安装并启用本插件。
14+
2. 进入终端,在 Blessing Skin Server 根目录下执行 `php artisan yggc:create-personal-access-client` 命令,创建个人访问客户端(Personal Access Client)。
15+
- 创建完成后,请在 .env 中新建一条配置 `PERSONAL_ACCESS_CLIENT_ID`,并将其值设为命令返回的个人访问客户端的 Client ID。
16+
3. 如果你是从原版 Yggdrasil API 插件迁移而来,请在终端中执行 `php artisan yggc:fix-uuid-table` 命令,以清除原版 Yggdrasil API 插件的 UUID 表中可能存在的异常数据,并修改数据表结构。
17+
- **该指令会直接删除 `uuid` 表中的部分记录,因此在执行该指令前,请务必备份原先的 `uuid` 表!!!**
18+
- 要了解该指令对你的 `uuid` 表都做了什么,请阅读下面的 [关于 Bug 修复](#关于-bug-修复) 部分。
19+
- 如果你没有安装过原版 Yggdrasil API 而直接安装了本插件,则无需执行这条命令。
20+
4. 如需启用 Yggdrasil Connect,请在部署好 Janus 后,在本插件的配置页面填写你的 Janus 实例的 OpenID 提供者标识符。
21+
- 要了解 Yggdrasil Connect 和 Janus 是什么,请阅读下面的 [关于 Yggdrasil Connect](#关于-yggdrasil-connect) 部分。
22+
- 要了解如何部署 Janus,请查看 [Janus 项目的代码仓库](https://github.com/bs-community/janus)
23+
24+
## 关于 Yggdrasil Connect
25+
26+
Yggdrasil Connect 是基于 OAuth 2.0 和 OpenID Connect 协议的 Minecraft 外置登录身份验证协议,其核心目标是取代 Yggdrasil API 中的 Auth Server 部分,从而改善 authlib-injector 外置登录方案的安全性和用户体验。
27+
28+
在 Yggdrasil API 的 Auth Server 部分中,用户需要将自己的用户名和密码直接暴露给第三方应用,才能通过第三方应用访问 Yggdrasil API,这增加了用户账户关键信息泄露的风险;且该部分的 API 在设计上几乎没有考虑到二步验证,无法良好地保障用户账户的安全。并且,由于各个第三方应用在该部分的实现的差异,用户在不同应用之间的登录体验存在较大割裂,时常出现应用实现不完整导致用户无法正常登录的问题。
29+
30+
Yggdrasil Connect 的出现正是为了解决这些问题。通过 OAuth 2.0 和 OpenID Connect 协议,Yggdrasil Connect 可以实现用户在不同应用间的登录体验的统一,同时方便各验证服务器自行设计用户身份认证方式,以保障用户账户的安全。
31+
32+
对于 Blessing Skin Server 来说,Yggdrasil Connect 还解决了「通过社交网站 OAuth 注册的用户默认没有密码,无法在启动器中登录」的问题:通过 Yggdrasil Connect,用户可以通过社交网站账户登录皮肤站并授予启动器权限,而无需输入密码。
33+
34+
要了解更多关于 Yggdrasil Connect 协议的信息,请阅读 [Yggdrasil Connect 协议规范](https://github.com/yushijinhun/authlib-injector/issues/268)
35+
36+
### 为 Blessing Skin Server 启用 Yggdrasil Connect
37+
38+
要为 Blessing Skin Server 启用 Yggdrasil Connect,则必须部署 Janus。
39+
40+
Janus 是一个独立于 Blessing Skin Server 运行、但与 Blessing Skin Server 使用同一个数据库的 Yggdrasil Connect 服务端。由于 Laravel 框架缺乏合适的 OpenID Connect 服务端扩展包,故采取这种外挂 OpenID Connect 服务端的方式实现 Yggdrasil Connect。
41+
42+
要了解如何部署 Janus,请查看 [Janus 项目的代码仓库](https://github.com/bs-community/janus)
43+
44+
## 关于 Bug 修复
45+
46+
本插件在一定程度上修复了原版 Yggdrasil API 插件的多个影响用户体验乃至导致用户无法正常登录 Minecraft 游戏服务器的 Bug:
47+
48+
- **使用角色名登录时,并不会自动选择角色**[#123](https://github.com/bs-community/blessing-skin-plugins/issues/123)):
49+
- 本插件在用户使用角色名登录时,会自动将 Access Token 绑定至角色名对应的角色,同时在 API 响应中添加 `selectedProfile` 字段。
50+
- 同时,对于签发 Access Token 时能确定 Access Token 绑定到的角色的场景(使用角色名登录、令牌刷新),API 响应中的 `availableProfiles` 字段中将仅包含 Access Token 绑定到的角色的信息,以避免客户端出现异常行为。
51+
- **uuid 表中数据不一致**[#151](https://github.com/bs-community/blessing-skin-plugins/issues/151)):
52+
- 本插件重新设计了 `uuid` 表,将 UUID 记录与角色模型的关联字段从角色名(`name`)改为了 PID(`pid`)。同时,本插件为 `uuid` 表中 `pid``name``uuid` 字段添加了 UNIQUE 约束,确保不会出现两条拥有相同的 PID、角色名或 UUID 的记录。因此,在从原版 Yggdrasil API 插件迁移至本插件时,必须在终端中执行 `php artisan yggc:fix-uuid-table` 命令,以清除 `uuid` 表中异常的数据,并为 `uuid` 表添加 `pid` 字段及相关约束。
53+
- 但这项改动也会导致「正版验证」(`mojang-verification`)插件的「更新 UUID」功能无法正常工作,具体表现为该功能可能会在 `uuid` 表中插入一条 `pid``null` 的无效记录,该记录可能导致后续插入相同角色名的正确的 UUID 记录时失败并报错。考虑到该功能即使是在配合原版 Yggdrasil API 插件使用的情况下也可能会导致更大程度的数据错乱,建议直接将该功能禁用。
54+
- 为保证与原版 Yggdrasil API 插件的兼容性,UUID 表中的角色名字段(`name`)仍被保留,并监听了 `player.renamed` 事件,使得 UUID 表中记录的角色名可以在角色更名时被一同更新。
55+
- **角色改名(新旧名字仅大小写不同)会导致丢失 uuid**[#152](https://github.com/bs-community/blessing-skin-plugins/issues/152)):
56+
- 当 UUID 算法为 v3 时,这是预期行为,无需修复。
57+
- 当 UUID 算法为 v4 时,本插件使用 PID 而非角色名作为 UUID 记录与角色模型的关联字段,PID 全局唯一且不可更改,不会出现该问题。
58+
- **删除角色时不会删除 uuid 表中的对应的记录**[#202](https://github.com/bs-community/blessing-skin-plugins/issues/202)):
59+
- 本插件在 `uuid` 表中将 `pid` 字段定义为了外键,关联至 `players` 表中的 `pid` 字段,并添加了级联删除规则,确保用户删除角色时 `uuid` 表中的 UUID 记录会被一起删除。
60+
- 即使出现意外情况,导致 `uuid` 表中的记录未在用户删除角色时删除,考虑到本插件使用角色 PID 作为关联字段,而 PID 是全局唯一的,这些孤立的记录并不会与其他角色发生错误关联,从而避免了该 Bug 带来的数据错乱的问题。
61+
- **通过刷新获得的 accessToken 在用户邮箱存在大写字母的情况下无法通过校验**[#212](https://github.com/bs-community/blessing-skin-plugins/issues/212)):
62+
- 本插件使用 Laravel Passport 的个人访问密钥(Personal Access Token)作为 Access Token,其使用用户的 UID 作为 Access Token 所有者的身份标识符,不会出现类似的问题。
63+
- 但这项改动也要求站点管理员在终端中执行 `php artisan yggc:create-personal-access-client` 命令创建个人访问客户端(Personal Access Client),并在 .env 中配置 `PERSONAL_ACCESS_CLIENT_ID` 的值为个人访问客户端的 Client ID 后,传统 Auth Server 才可正常运行。
64+
- **角色改名后 Access Token 仍然有效**[#231](https://github.com/bs-community/blessing-skin-plugins/issues/231)):
65+
- 本插件监听了 `player.renamed` 事件,在角色更名后,本插件会在缓存中记录角色更名的时间。
66+
- 在验证 Access Token 时,本插件会检查 Access Token 的签发时间,如早于缓存中记载的角色更名时间,则视为 Access Token 暂时失效,返回错误。
67+
- 为确保 UserInfo Endpoint 与进服请求中的令牌验证结果一致(即,避免 UserInfo Endpoint 验证令牌有效,但进服请求验证令牌无效的情况出现),本插件对于 UserInfo Endpoint 也会执行该检查。这可能导致通过 Yggdrasil Connect 签发的 Access Token 被刷新的频率增加(因为 UserInfo Endpoint 中已包含有最新的角色信息,启动器本可直接请求 UserInfo Endpoint 获取最新角色信息,而无需通过刷新令牌的方式更新角色信息,但由于 Access Token 被认为是过期的,UserInfo Endpoint 会返回 `invalid_token` 错误),但只要启动器正确按照正常的令牌失效刷新流程处理这种情况,就不会产生影响用户体验的问题。
68+
- **查询角色属性时返回的角色名大小写和实际角色名不符**[#232](https://github.com/bs-community/blessing-skin-plugins/issues/232)
69+
- 本插件在返回角色信息时会返回角色在数据库中记录的角色名,而非请求中的角色名。
70+
71+
## 版权
72+
73+
Copyright (c) 2025-present LittleSkin. All rights reserved. Open source under the MIT license.
74+
75+
_Disclaimer:你站产品经理自己写代码的原则就是代码和人有一个能跑就行,自然有些代码很粗糙很难看很低效。如果你看着哪里的代码不爽,欢迎直接重构并 PR。_
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { notify, t } = globalThis.blessing
2+
3+
document
4+
.querySelector('[name=generate-key]')!
5+
.addEventListener('click', async () => {
6+
type Ok = { code: 0; key: string }
7+
type Err = { code: 1; message: string }
8+
9+
const response: Ok | Err = await globalThis.blessing.fetch.post(
10+
'/admin/plugins/config/yggdrasil-connect/generate',
11+
)
12+
13+
if (response.code === 0) {
14+
notify.toast.success(t('yggdrasil-connect.key-generated'))
15+
16+
document.querySelector<HTMLTextAreaElement>('td.value textarea')!.value =
17+
response.key
18+
const form = document.querySelector('input[value=keypair]')!
19+
.parentElement as HTMLFormElement
20+
form.submit()
21+
} else {
22+
notify.toast.error(response.message)
23+
}
24+
})
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
document.body.addEventListener('dragstart', (event) => {
2+
if (!event.target) {
3+
return
4+
}
5+
6+
const target = event.target as HTMLElement
7+
8+
if (target.id === 'ygg-dnd-button') {
9+
const uri =
10+
'authlib-injector:yggdrasil-server:' +
11+
encodeURIComponent(target.dataset.clipboardText!)
12+
13+
if (event.dataTransfer) {
14+
event.dataTransfer.setData('text/plain', uri)
15+
event.dataTransfer.dropEffect = 'copy'
16+
}
17+
}
18+
})
19+
20+
document
21+
.querySelector<HTMLButtonElement>('#ygg-dnd-button')
22+
?.addEventListener('click', async (event) => {
23+
const target = event.target as HTMLButtonElement
24+
await navigator.clipboard.writeText(target.dataset.clipboardText!)
25+
26+
const originalContent = target.textContent
27+
target.disabled = true
28+
target.innerHTML = `<i class="fas fa-check mr-1"></i>${globalThis.blessing.t(
29+
'yggdrasil-connect.copied',
30+
)}`
31+
32+
setTimeout(() => {
33+
target.textContent = originalContent
34+
target.disabled = false
35+
}, 1000)
36+
})

0 commit comments

Comments
 (0)