Zh:Authentication
Minecraft 1.6引入了一种全新的叫作Yggdrasil的认证方案,它彻底地取代了先前的认证系统。Mojang的其他游戏,Scrolls,同样也使用了该认证方法。Mojang曾经说过每个人都应使用此认证系统来进行自定义登录,但是永远不会从用户收集凭据。
Contents
请求格式
所有对Yggdrasil的请求都会发送到以下服务器:
https://authserver.mojang.com
此外,它们应满足以下规则:
- 为
POST
请求 Content-Type
头设置为application/json
- 以包含JSON编码的字典作为负载
如果请求成功,服务器将响应:
- 状态码
200
- 根据以下规范使用JSON编码的字典
但如果请求失败,服务器会响应:
{
"error": "错误的简短描述",
"errorMessage": "用于向用户显示的更长的描述",
"cause": "错误原因" // 可选的
}
错误
这些是可能遇到的一些错误:
错误 | 原因 | 错误信息 | 注释 |
---|---|---|---|
Method Not Allowed
|
The method specified in the request is not allowed for the resource identified by the request URI 请求URI中标识的资源不允许使用请求中指定的方法 |
收到了除POST请求以外的信息。 | |
Not Found
|
The server has not found anything matching the request URI 服务器未找到与请求URI匹配的任何内容 |
调用了不存在的端点。 | |
ForbiddenOperationException
|
UserMigratedException
|
Invalid credentials. Account migrated, use e-mail as username. 无效凭据。账号已迁移,使用邮箱作为用户名。 |
|
ForbiddenOperationException
|
Invalid credentials. Invalid username or password. 无效凭据。无效的用户名或密码。 |
||
ForbiddenOperationException
|
Invalid credentials. 无效凭据。 |
该用户名最近的尝试登录过多(见/authenticate )。注意用户名和密码可能仍然有效!
| |
ForbiddenOperationException
|
Invalid token. 无效凭据。 |
accessToken 失效了。
| |
IllegalArgumentException
|
Access token already has a profile assigned. 访问令牌已经被分配了档案。 |
还没有实现选择档案。 | |
IllegalArgumentException
|
credentials is null 凭据为空 |
用户名/密码未提交。 | |
IllegalArgumentException
|
Invalid salt version 无效的盐版本 |
??? | |
Unsupported Media Type
|
The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method 服务器拒绝为请求提供服务,因为请求实体的格式不受请求方法所请求的资源支持 |
数据未提交为application / json |
认证
使用密码认证用户。
端点
/authenticate
负载
{
"agent": { // 默认为Minecraft
"name": "Minecraft", // 对于Mojang的其他游戏Scrolls,则应该使用"Scrolls"
"version": 1 // 以后的原版客户端
// 可能会增加该数字
},
"username": "mojang帐号名", // 可以是电子邮箱地址或
// 玩家名称(对于为迁移的账号)
"password": "mojang帐号密码",
"clientToken": "客户端标识符", // 可选的
"requestUser": true // 可选的,默认为false,若为true则将user对象加入到响应中
}
clientToken
应该是一个随机生成的标识符而且必须每次请求都是相同的。原版启动器会在第一次运行时生成一个随机的(v4)UUID并保存,在后续每次请求中复用它。如果省略,那么服务器会生成一个基于Java的UUID.toString()
的随机令牌,它应该由客户端保存下来。然而这也会使用户之前在所有客户端上获取的accessToken
失效。
响应
{
"accessToken": "随机访问令牌", // 十六进制或JSON-Web-Token(未确认)[普通accessToken可以在JWT的响应中找到(使用‘.’分隔的第二部分,以Base64编码的JSON对象),在“yggt”键中]
"clientToken": "客户端标识符", // 与接收到的相同
"availableProfiles": [ // 仅在接收到agent字段时出现
{
"agent": "minecraft", // 可能与之前值相同
"id": "档案标识符", // 十六进制
"name": "玩家名称",
"userId": "十六进制字符串",
"createdAt": 1325376000000, // 自1970年1月1日起的毫秒数
"legacyProfile": true或false, // 即使为false也出现
"suspended": true或false, // 可能为false
"paid": true或false, // 可能为true
"migrated": true或false, // 似乎即使是已迁移账号也为false…?(https://bugs.mojang.com/browse/WEB-1461)
"legacy": true或false // 它仅为true时出现。默认为false。与较新的legacyProfile重复…
}
],
"selectedProfile": { // 仅在接收到agent字段时出现
"id": "不含分隔符的uuid",
"name": "玩家名称",
"userId": "十六进制字符串",
"createdAt": 1325376000000,
"legacyProfile": true或false,
"suspended": true或false,
"paid": true或false,
"migrated": true或false,
"legacy": true或false
},
"user": { // 仅在请求负载中的requestUser为true出现
"id": "用户标识符", // 十六进制
"email": "user@email.example", // 未迁移账号的哈希(?)值
"username": "user@email.example", // 未迁移账号的正常名称或已迁移账号的电子邮箱
"registerIp": "198.51.100.*", // 最后一位打码的IP地址
"migratedFrom": "minecraft.net",
"migratedAt": 1420070400000,
"registeredAt": 1325376000000, // 也许比profile的createdAt要早几分钟
"passwordChangedAt": 1569888000000,
"dateOfBirth": -2208988800000,
"suspended": false,
"blocked": false,
"secured": true,
"migrated": false, // 似乎即使migratedAt和migratedFrom出现时也为false…
"emailVerified": true,
"legacyUser": false,
"verifiedByParent": false,
"properties": [
{
"name": "preferredLanguage", // 也许不会对所有账号显示
"value": "en" // Java locale格式 (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
},
{
"name": "twitch_access_token", // 仅在关联twitch账号时出现(见https://account.mojang.com/me/settings)
"value": "twitch oauth token" // OAuth 2.0令牌,字母+数字,如https://api.twitch.tv/kraken?oauth_token=[...]
// Twitch API的文档:https://github.com/justintv/Twitch-API
}
]
}
}
注意:如果用户希望能在他们的电脑上保存登录状态,那么强烈建议应该存储accessToken
而不是密码本身。
当前每个账号只拥有一个档案,一个账号拥有多个档案还在未来计划中。如果用户尝试登入一个没有附加Minecraft许可的Mojang账号,那么认证将会成功,但是响应将不包含selectedProfile
字段,而且availableProfiles
数组也是空的。
有一些实例曾观察到Mojang对于旧版账号失败的刷新请求返回了一个平坦的null
。还不清楚什么实际错误绑定了这个空响应,而且它极为罕见,但作为实现应该注意对该响应的空输出。
这个端点是严格速率限制的:短时间内同一账号的多次/authenticate
请求(例如在几秒内3次请求),即使密码正确也会导致一个Invalid credentials.
响应。该错误会在几秒后被清除。
刷新
刷新一个有效的accessToken
。它可以用于在游戏会话间保持登录状态,这优于在文件中保存用户的密码(见lastlogin)。
端点
/refresh
负载
{
"accessToken": "有效的accessToken",
"clientToken": "客户端标识符", // 这需要与第一处用来获取
// accessToken的那个相同
"selectedProfile": { // 可选的,发送它将导致错误
"id": "档案标识符", // 十六进制
"name": "玩家名称"
},
"requestUser": true // 可选的,默认为false,若为true则将user对象加入到响应中
}
注意:提供的accessToken
将失效。
响应
{
"accessToken": "随机访问令牌", // 十六进制
"clientToken": "客户端标识符", // 与接收到的相同
"selectedProfile": {
"id": "档案标识符", // 十六进制
"name": "玩家名称"
},
"user": { // 仅在请求负载中的requestUser为true出现
"id": "用户标识符", // 十六进制
"properties": [
{
"name": "preferredLanguage", // 也许不会对所有账号显示
"value": "en" // Java locale格式(https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
},
{
"name": "twitch_access_token", // 仅在关联twitch账号时出现(见https://account.mojang.com/me/settings)
"value": "twitch oauth token" // OAuth 2.0令牌,字母+数字,如https://api.twitch.tv/kraken?oauth_token=[...]
// Twitch API的文档:https://github.com/justintv/Twitch-API
}
]
}
}
验证
检查accessToken
是否可用于Minecraft服务器的认证。Minecraft启动器(自1.6.13版本起)会在启动器调用此端点来验证保存的令牌是否仍然可用,并会在返回错误时调用/refresh
。
请注意accessToken
可能会不可用与Minecraft服务器的认证,而对于/refresh
来说足够可用。这主要会发生在一个人使用了另一个客户端(如在别的PC上使用相同的帐号游玩了Minecraft)。看起来只有给定帐号最新获得的accessToken
才能可靠地用于认证(第二新的令牌看起来也仍然有效,但请不要依赖它)。
/validate
可以在有或没有clientToken
时调用。如果提供了clientToken
,它应当与获取accessToken
的那个相匹配。Minecraft启动器会向/validate
发送clientToken
。
端点
/validate
负载
{
"accessToken": "有效的accessToken",
"clientToken": "关联的clientToken" // 可选的,见上
}
响应
若成功返回空响应(204 No Content
),否则返回错误JSON和状态码403 Forbidden
。
登出
使用帐号的用户名和密码使accessToken
失效。
端点
/signout
负载
{
"username": "mojang帐号名称",
"password": "mojang帐号密码"
}
响应
若成功返回一个空负载。
使失效
使用client/access令牌对使accessToken
失效。
端点
/invalidate
负载
{
"accessToken": "有效的accessToken",
"clientToken": "客户端标识符" // 这需要与第一处用来获取
// accessToken的那个相同
}
响应
若成功返回一个空负载。