bs-community / yggdrasil-api

Please refer to https://github.com/bs-community/blessing-skin-plugins .
MIT License
60 stars 7 forks source link

[RFE] 支持官方正版验证 #4

Closed cqjjjzr closed 5 years ago

cqjjjzr commented 6 years ago

您好。 请问是否可能实现在Session的校验处实现若无法找到登录情况则去找mojang官方服务器验证Token?因为MultiMC等纯正版启动器并不支持authlib-injector. 谢谢!

cqjjjzr commented 6 years ago

另外最新版(2.1.6)无法使用,HMCL提示不是合法的验证服务器,Fiddler抓包发现服务器返回的是“Undefined property: stdClass::$column_name”。

prinsss commented 6 years ago

之前也有人提到过这个功能 ( https://github.com/bs-community/yggdrasil-api/issues/2 ),通过转发请求至 Mojang 官方认证服务器确实可以兼容正版用户直接进服,但是皮肤显示这方面比较难办。

首先 Mojang 官方认证服务器返回的玩家 Profile 都是用 Mojang 的私钥进行过数据签名的,而且原版客户都安也只认 Mojang 的私钥签出来的 Profile。

如果一个【非正版】用户进入了服务器,服务器会从皮肤站获取该玩家的 Profile(用的自定义私钥进行的签名,因为我们不可能拿得到 Mojang 的私钥)。服务器拿到 Profile 后会分发给服务器内的所有玩家,但是玩家中的未加载 authlib-injector 的原版客户端是不认这个数据签名的,所以 【正版】用户将无法加载【非正版】用户的皮肤。反之亦然,使用了 authlib-injector 的非正版用户也将无法加载正版用户的皮肤,因为他们的客户端只认皮肤站的私钥签出来的 Profile。

虽然也可以直接禁用 Profile 的数据签名,但我不确定原版 Minecraft 客户端在 Profile 数据签名缺失的情况下是否能够正常加载皮肤……

cqjjjzr commented 6 years ago

那么是否可以让登录部分交给authlib injector,而皮肤的加载交给CSL之类的工具呢?

cqjjjzr commented 6 years ago

或者我们让正版用户也加载authlib-injector,但是authlib-injector此时并不去关心启动器已经传入的验证措施,直接patch,让服务器来处理与Mojang API交互的问题是否可行?

prinsss commented 6 years ago

关于 Undefined property: stdClass::$column_name 的问题,请提供一下 /storage/logs/laravel.log 日志里的详细报错信息。

cqjjjzr commented 6 years ago

翻看了一下,其中并没有什么有用的信息...最早的日志来自setup页面说明日志并没有清空,后面就全部变成trace一样的日志了.

prinsss commented 6 years ago

.env 中添加一行:

YGG_VERBOSE_LOG = true

然后尝试重现该问题,再提供一下 /storage/logs/yggdrasil-xxx.log 里的日志。

cqjjjzr commented 6 years ago

see #5 .

cqjjjzr commented 6 years ago

bump. see 楼上的楼上的楼上的楼上的楼上。

yushijinhun commented 6 years ago

我以前尝试过实现,但遇到很多问题。目前 authlib-injector 是不会考虑实现你要求的功能的,除非有人能给出一套完整且可行的解决方案。

prinsss commented 6 years ago

https://github.com/bs-community/yggdrasil-api/issues/4#issuecomment-408678226

理论上来讲应该可以,但是我没有试过。

https://github.com/bs-community/yggdrasil-api/issues/4#issuecomment-408678298

这个有什么关系吗?

启动器不加载 authlib-injector 的话,用户使用正版账号登录,启动器从 Mojang 那边拿到 Access Token,传给游戏本体并启动;而如果启动器加载 authlib-injector 却还是使用正版账号登录的话,就是多了一层皮肤站作请求转发,Access Token 最终还是从 Mojang 那边拿到的啊?

cqjjjzr commented 6 years ago

这正是我需要的效果啊。而皮肤的加载由CSL之类的实现

cqjjjzr commented 6 years ago

https://github.com/bs-community/yggdrasil-api/issues/4#issuecomment-408681131 但这似乎并不是authlib injector需要实现的啊.这是服务端的修改 Misclick, sry.

prinsss commented 6 years ago

如果是这样的话那还好,只需要在 joinhasJoined 两个 API 做一下转发就好。

如果需要不安装 Mod 显示皮肤的话,那根据 https://github.com/bs-community/yggdrasil-api/issues/4#issuecomment-408678101 所说的,就只能「只有正版用户的皮肤能显示」和「只有非正版用户的皮肤能显示」两者二选一了。

prinsss commented 6 years ago

(当然,近期内是不会支持的,我最近忙)

cqjjjzr commented 6 years ago

see #6. 但是我闲啊(=・ω・=)

cqjjjzr commented 6 years ago

是否可以考虑修改Mojang服务器发过来的Profile数据,重新用皮肤站的私钥签名Profile数据?这样至少可以使得利用 authlib-injector 的用户能正常显示正版用户的皮肤。同时 @yushijinhun 是否可以考虑给authlib-injector实现一个参数,使得agent仅获取验证服务器的metadata,然后去patch签名白名单部分,而不去patch其他部分(authserver/sessionserver)?

yushijinhun commented 6 years ago

@cqjjjzr

  1. 首先,你 PR 所实现的功能是不完整的。见我对该 PR 的 review:https://github.com/bs-community/yggdrasil-api/pull/6#pullrequestreview-142206192
  2. 如果按你所说的 修改Mojang服务器发过来的Profile数据,重新用皮肤站的私钥签名Profile数据?这样至少可以使得利用 authlib-injector 的用户能正常显示正版用户的皮肤 做,那正版用户将无法显示所有皮肤。
  3. 给authlib-injector实现一个参数,使得agent仅获取验证服务器的metadata,然后去patch签名白名单部分,而不去patch其他部分 请问你指在正版上使用 authlib-injector 吗?
cqjjjzr commented 6 years ago
  1. 见 pr 中我的 comment
  2. 我这边测试的结果是正版用户的Profile并不是来自Blessing服务器的,而是直接从Mojang服务器拿的,因此正版用户能显示正版用户的皮肤。
  3. 是的。
prinsss commented 6 years ago

既然你所说的那些正版用户有使用 authlib-injector 的条件,那为何不让他们直接使用整套 authlib-injector 的逻辑呢?因为官方启动器不支持使用自定义 Yggdrasil API 验证?

yushijinhun commented 6 years ago

@cqjjjzr 对于你所说的 2,请看 Yggdrasil 服务端技术规范#会话部分

cqjjjzr commented 6 years ago

@yushijinhun 声称会给 authlib-injector 制作一个GUI,用于登录,但目前并没有实现(至少MCBBS上是这样说的)。因此对于不支持 authlib-injector 登录的启动器(MultiMC,官方启动器,etc. 不是每个人都喜欢HMCL)就无法登录。

prinsss commented 6 years ago

那么你的需求就是,让这些「使用 MultiMC 这类不支持 authlib-injector 的启动器的正版用户」也能进入使用了本插件的 Minecraft 服务器咯?

cqjjjzr commented 6 years ago

是的,如果能通过仅加载一个 authlib-injector(不在外部提供 authlib-injector 所使用的 Yggdrasil 服务器的登录信息)就可以实现皮肤的正确加载就更好了。

cqjjjzr commented 6 years ago

另外@yushijinhun 如果 authlib-injector patch 且仅 patch 了签名白名单,那么是否所有的皮肤在正版用户处都能正确加载了呢?

另外我在 #6 中提出了新的 commant。

prinsss commented 6 years ago

其实早期的 authlib-injector 是支持使用配置文件的,你目前这个需求只需要修改配置文件让其仅替换用于材质数据签名验证的公钥,再通过启动器的自定义 JVM 参数加载即可。不过现在 authlib-injector 不再支持使用配置文件了。

cqjjjzr commented 6 years ago

正是需要这个功能。

prinsss commented 6 years ago

那么这样做其实意义有限,仅仅是对那些「有正版账号,但是想使用不支持自定义认证服务器地址的启动器的用户」有意义。

cqjjjzr commented 6 years ago

目前主流支持 authlib-injector 的启动器并不多。如果这种基于 authlib-injector的解决方案能广泛使用实际上这样的用户非常多。HMCL目前对多实例仍然不如某些启动器完美。而增加对更多启动器的支持始终是好的(且加上这个仅替换公钥的方案相对(hmcl加强多实例支持,GUI方案实现,更多启动器支持authlib injector)来说似乎并不是一个非常复杂的过程,甚至我们可以考虑通过替换authlib 的jar实现)且结合复杂性的考虑我有理由相信@yushijinhun 的GUI方案短时间内无法实现。

cqjjjzr commented 6 years ago

另外我想出来用名称获取角色的api的使用场景了。回头会尝试修改。

yushijinhun commented 6 years ago

@cqjjjzr

前提

如果这个 PR 所实现的功能会违背下面的前提,这个 PR 是不会进入到下一个版本中的。

yggdrasil-api 基本可用性

首先,无论是否开启这个功能,BS 用户是一定要能在单人模式下看见自己的皮肤,并在多人模式下看见其他 BS 用户的皮肤的。因此,【查询角色属性】、【按名称批量查询角色】和【服务端验证客户端】这三个 API 的响应,必须优先使用 BS 上的数据,并且对 BS 用户的角色属性进行签名时使用 BS 的密钥。

保证上面这点,是保证 yggdrasil-api 的基本可用性,是不应该被打破的。

游戏文件完整性

修改游戏文件是绝对不允许的,特别是是修改 authlib JAR。

验证系统数据一致性

你提出的 https://github.com/bs-community/yggdrasil-api/pull/6#issuecomment-409486873 ,在这里就有了答案:当 BS 和 Mojang 都有数据时一定是 BS 优先,亦即先查询 BS,若未找到,再查询 Mojang。【查询角色属性】和【按名称批量查询角色】这两个请求必须做转发处理,否则会破坏数据一致性(试想服务器里有这个玩家,但却查不到对应角色)。

接下来我们考虑数据冲突问题。首先如果数据存在冲突,一定是 BS 和 Mojang 上有角色名相同,但 UUID 不同的两个角色。UUID 因为生成算法的问题,是不会相同的。这就意味着,我们在只用 UUID 做标识的情况下(【查询角色属性】),不需要考虑数据冲突。而在【按名称批量查询角色】这种情况下,应优先返回 BS 上的角色。

而在处理【服务端验证客户端】时,则略显复杂。我们假设 BS 上有角色 A,Mojang 有角色 B,两者角色名相同,UUID 不同。首先角色 A 必须能正常进服,然后我们考虑角色 B:

  1. 阻止角色 B 进服。这样做最大程度地保证了数据一致性。
  2. 允许角色 B 进服。当角色 B 在服务器中时,如果通过【按名称批量查询角色】接口查询该角色名称,会发现其对应的角色是角色 A,这违背了数据一致性。因此允许角色 B 进服是不可以的。

如何实现正版用户进服

我再来看你的需求,我有两种理解方式:

  1. 支持不用 authlib-injector 的正版用户进服。
  2. 支持加载了 authlib-injector 的正版用户进服。

无论实现哪个需求,都需要进行以下操作:使用 BS 上的数据处理【服务端验证客户端】请求失败后,请求转发到 Mojang(暂不考虑其中的签名)。这是你 PR 中做的。

我们分别看这两个需求:

  1. 如果实现 1,我们需要做出一个抉择:应该使用哪个密钥来签名正版用户的角色属性?
    1. 如果使用 Mojang 的密钥,也就是原封不动地保持角色属性,后果为:BS 用户只能显示 BS 用户皮肤,正版用户只能显示正版用户皮肤。
    2. 如果使用 BS 的密钥,也就是用 BS 对角色属性的签名替换 Mojang 的签名,后果为:BS 用户可以同时显示 BS 用户和正版用户的皮肤,而正版用户不能显示所有皮肤(正版用户单机模式下还是可以显示自己皮肤的)。
  2. 如果实现 2,那么我就可以对 authlib-injector 做出调整。authlib-injector 可以进行两种操作:a) 将公钥替换为 BS 的;b) 把 BS 域名添加进白名单。进行 b) 操作不会带来任何不良后果,所以 b) 是一定会做的。我们需要关注的是是否进行 a):
    1. 如果进行 a),并且 BS 使用 Mojang 的密钥(见需求 1 中分析),那么使用 authlib-inejctor 的正版用户只能加载 BS 用户的皮肤(正版用户单机模式下还是可以显示自己皮肤的)。
    2. 如果进行 a),并且 BS 使用 BS 自己的密钥(同上),那么使用 authlib-injector 的正版用户可以显示所有用户的皮肤。
    3. 如果不进行 a),那么与需求 1 分析中唯一的区别是:正版用户的皮肤白名单中多了 BS 域名。然而如果一个皮肤来自 BS 的域名,那么它一定是使用 BS 密钥签名的,因此这种情况与需求 1 无异。

如果你要同时考虑需求 1 和需求 2,那我可以给出一张表: 下面表格中:

使用哪个密钥签名正版用户的角色属性? BS -> MOJANG BS -> Mojang(AI) Mojang -> BS Mojang -> Mojang Mojang -> Mojang(AI) Mojang(AI) -> BS Mojang(AI) -> Mojang Mojang(AI) -> Mojang(AI)
Mojang N N N Y Y Y N N
BS Y Y N N N Y Y Y

是否可以差别对待用户?

你可能会提问,是否能对不同种类的用户返回不同的角色属性签名。这是不可行的,除非修改 MC 服务端。玩家在进服时,验证服务端将角色属性发送给 MC 服务端,MC 服务端再把角色属性分发给各个客户端,因此分发到每个客户端的角色属性签名都是相同的,无法进行差别对待。

最后

在最后我想给出一个忠告:如果你想作出一个改动,你必须将该改动会影响到的所有方面都考虑清楚,不论这有多么复杂。逃避是无法解决问题的。

cqjjjzr commented 6 years ago

我在研究完这篇文档之后会尝试作出修补的PR。如果无法实现则我建议 Fork 一个新的版本包含已有的功能。

yushijinhun commented 6 years ago

EDIT: 使用 307 状态码指引客户端进行转发,避免服务端转发。

如果要支持正版用户使用 authlib-injector,则需要考虑到以下问题:

是否应该转发 join 请求?

join 请求转发的逻辑是:如果其中的 accessToken 无效,并且 BS 上查无此角色,就返回 HTTP 307 https://sessionserver.mojang.com/session/minecraft/join。只有加载了 authlib-injector 的正版用户会触发这种情况。

不转发

优点:安全,皮肤站无法获得正版用户的 accessToken。

缺点:authlib-injector 需要做出修改。用户需要添加额外参数来关闭 join 转发,若忘记添加则无法进服。

转发

优点:authlib-injector 无需做出修改,用户直接添加 -javaagent:authlib-injector.jar=$API_ROOT 参数即可。

缺点:皮肤站可以得知正版用户 accessToken。

cqjjjzr commented 6 years ago

@yushijinhun

join API 转发相关

我选择不转发。原因:你提到的折中方案同时吸取了两者的缺点。

  1. ip 参数有一定概率不会失效,如果 Mojang 的服务器会判定 X-Forwarded-ForX-Real-IP 请求头,但我们不应依赖此种不稳定的可能性;
  2. 皮肤站会获得 accessToken,我认为这是不可接受的;
  3. authlib-injector 需要作出的修改并不大,我将在处理完这边的修改之后过去尝试修改;
  4. 会拖慢无效用户访问 BS 服务器的速度(即既不在 BS 服务器 也不在 Mojang 服务器的 Token);
  5. 虽然我明白即使不转发皮肤站也能获取 accessToken,但是无法加入可以给用户一个明确的信号——配置出现了问题;
  6. 正版用户一般如果有能力编辑参数应该也能记住对参数的编辑;

皮肤相关

我同时需要实现 1 与 2。首先操作 a) 与 b) 都需要实现。即客户端验证公钥需要替换为 BS 的,同时增加 BS 域名到白名单。

同时在服务端,我会将 Profile 的签名替换为基于 BS 的私钥进行的。这几个决定基于如下考虑:

  1. 结合表格我们可以发现,不替换私钥则仅有三种情况可以正确显示皮肤,且这样加载了 AI 的正版用户与 BS 用户的体验都会相当差。
  2. 而利用 BS 私钥签名,仅有完全无修改的客户端无法加载所有皮肤,其他情况都能正常显示。但是这种“完全无修改”的客户端的方案我并不推荐。
  3. 通常服务器的客户端会以整合包的形式给出。此种情况下,服务器可以将 AI 与配置好参数的整合包一并递送(AI 的授权协议为 GPLv3,因此可能产生一些问题,我对这种方案持保留态度)。

yggdrasil-api 基本可用性相关的意见

我认为你对 yggdrasil-api 的考虑有误。本 API 基本可用性应该为“合法的用户必须能够进入服务器”。

但如果你与 @printempw 坚持你们的定义,那么也没有问题,只需要直接优先采用 BS 的数据即可。

验证系统数据一致性相关的意见

你提到的数据冲突的场景分析与我所想一致。

首先考虑「服务器验证客户端」的情况,我认为我们需要允许角色 B 进服。这种情况广泛存在于现行有正版账号,但是因为要进入盗版服务器而在皮肤站上有账号且两者名称相同的用户。

角色 B 不存在于 BS 服务器上,而客户端会先通过 /join 向 Mojang 服务器提交加入请求。而后加入服务器,显然此时如果用户名已经在线服务端会直接拒绝登录。若不在线则服务端向 BS 服务器请求 /hasJoined。BS 服务器上显然没有这个记录,但 BS 会直接转发 Mojang 服务器的结果,因此 B 能够进服,但 UUID 不同。

我们不需要对同一个人,BS 与 Mojang 的用户名相同,却混合两种方案登录的情况提供支持。服务器会由于 UUID 不同而直接将两者视为不同的用户。

「按名称批量查询角色」转发相关的意见

在你的 「验证系统数据一致性」你提出两者有数据的时候 BS 优先。但是这造成如果服务器开启白名单,而某玩家正版账户与 BS 账户重名(常见),此时即使服务器通过 /whitelist <id> 给予了玩家白名单,其在通过正版登录的时候仍然无法进入服务器。

而这种情况造成的最大问题不在白名单上,而在于封禁上。如果有玩家犯事儿甚至无法将其封禁——其直接使用正版账户登录即可。同时 op 也有这个问题。

且如果用了 Mojang 优先,则情况相反,但问题仍旧存在。

目前这种问题我没想出来好的解决方案。我想出的解决方案是服务端手动管理 ops.jsonwhitelists.jsonbans.json 中的条目,或者利用其它工具,例如服务端插件等。你提到皮肤问题无法利用差别对待解决,那么这个问题可以通过差别对待解决吗?

或许我们应该尝试在 BS 服务器上已经存在同样 ID 条目的情况下返回 BS 上的同名 UUID 而非 Mojang 服务器中的 UUID?那么这样若用户先用正版登录,后面又在 BS 上注册了账号会不会导致用户数据的丢失?

你的忠告相关的意见

从你的忠告可以看出你希望所有问题都能完美解决,在这之后再给个 PR。但是我认为先让一些基础的功能可以运作(例如本例,在不影响 BS 用户的一切正常使用的情况下,让正版用户先能进服,再去考虑皮肤的问题)再去考虑一些进阶的特性(例如皮肤的显示)是必要的。实际上这个问题我并不会逃避,但我认为将其压入 TO-DO List 并让正版玩家先能进服比先在 Issue 里面盖上几十楼之后再一次性提交更好。

总结:目前缺乏好的解决方案的问题

按紧急程度排序。

  1. 「按名称批量查询角色」的重名冲突;
  2. 不同的人同名(若是一些非常常见的 ID)混合使用正版与 BS 验证可能导致偶发的服务端玩家数据混乱;
  3. yggdrasil-api 基本可用性到底是什么?
  4. 让没有任何修改的客户端获取完整的游戏体验;
yushijinhun commented 6 years ago

@cqjjjzr 我已经修改了转发方案,请你再看一下。

prinsss commented 6 years ago

我赞成 @cqjjjzr 关于不转发 join API 的意见,既然这些「就是想用不支持 authlib-injector 的启动器同时还想进入使用了 authlib-injector 的服务器的正版用户」应该有足够的能力正确配置他的启动器。

cqjjjzr commented 6 years ago

@cqjjjzr 我已经修改了转发方案,请你再看一下。

请参看「join API 转发相关」一节中的5. 。另外我们并不清楚客户端是否支持 HTTP 转发。

yushijinhun commented 6 years ago

我已测试,客户端支持 307 重定向。

cqjjjzr commented 6 years ago

我仍然坚持我的意见,但我认为可以考虑增加一个服务端的设置项,允许服务器转发 join 请求。另外服务端能否发送客户端可以显示的错误信息?

prinsss commented 6 years ago

我认为 BS 的 Yggdrasil API 服务无需转发 join 请求。

yushijinhun commented 6 years ago

虽然我明白即使不转发皮肤站也能获取 accessToken,但是无法加入可以给用户一个明确的信号——配置出现了问题;

既然你有这个看法,这就意味着上述不转发的优点已经失去。我拒绝无理由地增加配置复杂性,除非你能给出不转发的其他优点。

cqjjjzr commented 6 years ago

实际上虽然这个理由非常扯淡,但是我想给出的理由是“这不优雅”。

另外 join 的转发意味着 AI 不作出任何修改,即 AI patch掉了所有的对服务器的引用,这会带来更多问题,例如单人模式下正版用户的皮肤加载,想象一下某人的 BS 与 正版服务器皮肤不一致,结果登着正版显示出了 BS 的皮肤???

yushijinhun commented 6 years ago

authlib-injector 的许可证我不会做出放松。我已经遇到不少服务器修改了 authlib-injector,甚至对 Yggdrasil 协议做出了 breaking changes,这就导致用户只能使用他们的专有启动器,而这与 authlib-injector 的目标相违背。

yushijinhun commented 6 years ago

单人模式下正版用户的皮肤加载,想象一下某人的 BS 与 正版服务器皮肤不一致,结果登着正版显示出了 BS 的皮肤???

这是不存在的。我已经在之前回答了:

接下来我们考虑数据冲突问题。首先如果数据存在冲突,一定是 BS 和 Mojang 上有角色名相同,但 UUID 不同的两个角色。UUID 因为生成算法的问题,是不会相同的。

cqjjjzr commented 6 years ago

了解。但是我认为出现多方意见冲突时“加个配置项”是最简单、能满足所有人胃口的。

我需要了解的是 authlib-injector 是否允许其未修改的二进制发行版本随服务器整合包一并shipping?

yushijinhun commented 6 years ago

我需要了解的是 authlib-injector 是否允许其未修改的二进制发行版本随服务器整合包一并shipping?

允许,只要不修改 JAR 包中内容。

yushijinhun commented 6 years ago

我想就玩家名冲突补充一点:如果 BS 上有角色 A,Mojang 有角色 B,两者名称相同。A 和 B 被认为是不同角色。

cqjjjzr commented 6 years ago

因此现在主要的冲突点为 join 转发与否,

那么「按名称批量查询角色」的重名冲突呢?

yushijinhun commented 6 years ago

那么「按名称批量查询角色」的重名冲突呢?

那我之前的分析有漏洞,应该禁止 Mojang 同名角色进服。

yggdrasil-api 基本可用性到底是什么?

指启用该功能后不影响 BS 用户的使用。

cqjjjzr commented 6 years ago

那我之前的分析有漏洞,应该禁止 Mojang 同名角色进服。

这等价于若某角色希望通过正版登录进服,则其不能在 BS 上存在同名角色?这显然不可接受。特别是可能存在抢注盗注的问题(在大服务器尤其严重)