Difference between revisions of "Zh:Authentication"

From wiki.vg
Jump to navigation Jump to search
m (Kaniol moved page ZH:认证 to ZH:Authentication)
(One intermediate revision by the same user not shown)
Line 1: Line 1:
Minecraft 1.6引入了一种全新的叫作'''Yggdrasil'''的认证方案,它彻底地取代了[[ZH:旧版认证|先前的认证系统]]。Mojang的其他游戏,Scrolls,同样也使用了该认证方法。Mojang曾经说过[https://twitter.com/KrisJelbring/status/453573406341206016 每个人都应使用此认证系统来进行自定义登录],但是[https://twitter.com/KrisJelbring/status/461390585086361600 永远不会从用户收集凭据]
+
Minecraft 1.6 引入了一个名为 '''Yggdrasil'''验证方案 来完全替代 [[Legacy Authentication|先前的验证系统]]. Mojang的其他游戏, Scrolls, 也使用此验证方法. Mojang 说 [https://twitter.com/KrisJelbring/status/453573406341206016 每个人都应该使用这个验证系统进行自定义登录], 但是 [https://twitter.com/KrisJelbring/status/461390585086361600 不会向用户索要凭据].
  
 
== 请求格式 ==
 
== 请求格式 ==
所有对Yggdrasil的请求都会发送到以下服务器:
+
 
 +
所有的 Yggdrasil 请求都发送到下列服务器
  
 
  https://authserver.mojang.com
 
  https://authserver.mojang.com
  
此外,它们应满足以下规则:
+
此外,他们应该遵循以下规则
  
* <code>POST</code>请求
+
* 使用 <code>POST</code> 请求
* <code>Content-Type</code>头设置为<code>application/json</code>
+
* 包含 <code>Content-Type</code> 头,并设置为 <code>application/json</code>
* 以包含[[wikipedia:zh:JSON|JSON]]编码的字典作为负载
+
* 包含一个使用 [[wikipedia:JSON|JSON]] 格式编码的有效载荷
  
如果请求成功,服务器将响应:
 
  
* 状态码<code>200</code>
+
如果请求成功,服务器将做出如下响应
* 根据以下规范使用[[wikipedia:zh:JSON|JSON]]编码的字典
 
  
但如果请求失败,服务器会响应:
+
* 状态码 <code>200</code>
 +
* 一个符合下文规范的 [[wikipedia:JSON|JSON]]数据
 +
如果请求失败,服务器将做出如下响应
  
* 适当的非200[[wikipedia:zh:HTTP状态码|HTTP状态码]]
+
* 恰当的非200状态码 [[wikipedia:List of HTTP status codes|HTTP status code]]
* 拥有以下格式的[[wikipedia:zh:JSON|JSON]]编码的字典:
+
* 一个包含如下信息的[[wikipedia:JSON|JSON]]数据
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "error": "错误的简短描述",
+
     "error": "简短的描述(机器可读)",
     "errorMessage": "用于向用户显示的更长的描述",
+
     "errorMessage": "一个向用户展示的错误消息(人类可读)",
     "cause": "错误原因" // 可选的
+
     "cause": "引起错误的原因" // 可选
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 32: Line 33:
 
== 错误 ==
 
== 错误 ==
  
这些是可能遇到的一些错误:
+
以下是一些可能遇到的错误:
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 38: Line 39:
 
  ! 错误
 
  ! 错误
 
  ! 原因
 
  ! 原因
  ! 错误信息
+
  ! 错误消息
 
  ! 注释
 
  ! 注释
 
  |-
 
  |-
 
  | <code>Method Not Allowed</code>
 
  | <code>Method Not Allowed</code>
 
  |
 
  |
  | The method specified in the request is not allowed for the resource identified by the request URI<br/>请求URI中标识的资源不允许使用请求中指定的方法
+
  | 请求所使用的方法不允许访问当前URI,这里通常是由于非POST方法引起的。
  | 收到了除POST请求以外的信息。
+
  |
 
  |-
 
  |-
 
  | <code>Not Found</code>
 
  | <code>Not Found</code>
 
  |
 
  |
  | The server has not found anything matching the request URI<br/>服务器未找到与请求URI匹配的任何内容
+
  | 服务器没有找到URI所对应的资源
  | 调用了不存在的端点。
+
  |
 
  |-
 
  |-
 
  | <code>ForbiddenOperationException</code>
 
  | <code>ForbiddenOperationException</code>
 
  | <code>UserMigratedException</code>
 
  | <code>UserMigratedException</code>
  | Invalid credentials. Account migrated, use e-mail as username.<br/>无效凭据。账号已迁移,使用邮箱作为用户名。
+
  | 无效的凭证。账户已迁移。请使用电子邮件作为用户名。
 
  |  
 
  |  
 
  |-
 
  |-
 
  | <code>ForbiddenOperationException</code>
 
  | <code>ForbiddenOperationException</code>
 
  |  
 
  |  
  | Invalid credentials. Invalid username or password.<br/>无效凭据。无效的用户名或密码。
+
  | 无效的凭证。用户名或密码无效。
 
  |  
 
  |  
 
  |-
 
  |-
 
  | <code>ForbiddenOperationException</code>
 
  | <code>ForbiddenOperationException</code>
 
  |  
 
  |  
  | Invalid credentials.<br/>无效凭据。
+
  | 无效的凭证。
  | 该用户名最近的尝试登录过多(见<code>/authenticate</code>)。注意用户名和密码可能仍然有效!
+
  | 使用同一用户名进行了太多的登录尝试(参见 <code>/authenticate</code>)。注释:用户名密码也许是有效的。
 
  |-
 
  |-
 
  | <code>ForbiddenOperationException</code>
 
  | <code>ForbiddenOperationException</code>
 
  |  
 
  |  
  | Invalid token.<br/>无效凭据。
+
  | Invalid token.
  | <code>accessToken</code>失效了。
+
  | <code>accessToken</code> was invalid.
 
  |-
 
  |-
 
  | <code>IllegalArgumentException</code>
 
  | <code>IllegalArgumentException</code>
 
  |  
 
  |  
  | Access token already has a profile assigned.<br/>访问令牌已经被分配了档案。
+
  | Access token already has a profile assigned.
  | 还没有实现选择档案。
+
  | Selecting profiles isn't implemented yet.
 
  |-
 
  |-
 
  | <code>IllegalArgumentException</code>
 
  | <code>IllegalArgumentException</code>
 
  |  
 
  |  
  | credentials is null<br/>凭据为空
+
  | credentials is null
  | 用户名/密码未提交。
+
  | Username/password was not submitted.
 
  |-
 
  |-
 
  | <code>IllegalArgumentException</code>
 
  | <code>IllegalArgumentException</code>
 
  |  
 
  |  
  | Invalid salt version<br/>无效的盐版本
+
  | Invalid salt version
  | ???
+
  | ???
 
  |-
 
  |-
 
  | <code>Unsupported Media Type</code>
 
  | <code>Unsupported Media Type</code>
 
  |  
 
  |  
  | 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<br/>服务器拒绝为请求提供服务,因为请求实体的格式不受请求方法所请求的资源支持
+
  | 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
+
  | Data was not submitted as application/json
 
  |}
 
  |}
  
== 认证 ==
+
== Authenticate ==
  
使用密码认证用户。
+
Authenticates a user using their password.
  
=== 端点 ===
+
=== Endpoint ===
  
 
  /authenticate
 
  /authenticate
  
=== 负载 ===
+
=== Payload ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "agent": {                              // 默认为Minecraft
+
     "agent": {                              // defaults to Minecraft
         "name": "Minecraft",                // 对于Mojang的其他游戏Scrolls,则应该使用"Scrolls"
+
         "name": "Minecraft",                // For Mojang's other game Scrolls, "Scrolls" should be used
         "version": 1                        // 以后的原版客户端
+
         "version": 1                        // This number might be increased
                                             // 可能会增加该数字
+
                                             // by the vanilla client in the future
 
     },
 
     },
     "username": "mojang帐号名",             // 可以是电子邮箱地址或
+
     "username": "mojang account name",     // Can be an email address or player name for
                                             // 玩家名称(对于为迁移的账号)
+
                                             // unmigrated accounts
     "password": "mojang帐号密码",
+
     "password": "mojang account password",
     "clientToken": "客户端标识符",           // 可选的
+
     "clientToken": "client identifier",     // optional
     "requestUser": true                    // 可选的,默认为false,若为true则将user对象加入到响应中
+
     "requestUser": true                    // optional; default: false; true adds the user object to the response
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
<code>clientToken</code>应该是一个随机生成的标识符而且必须每次请求都是相同的。原版启动器会在第一次运行时生成一个随机的(v4)UUID并保存,在后续每次请求中复用它。如果省略,那么服务器会生成一个基于Java的[http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html#toString() <code>UUID.toString()</code>]的随机令牌,它应该由客户端保存下来。然而这也会使用户之前在所有客户端上获取的<code>accessToken</code>失效。
+
The <code>clientToken</code> should be a randomly generated identifier and must be identical for each request. The vanilla launcher generates a random (version 4) UUID on first run and saves it, reusing it for every subsequent request. In case it is omitted the server will generate a random token based on Java's [http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html#toString() <code>UUID.toString()</code>] which should then be stored by the client. This will however also invalidate all previously acquired <code>accessToken</code>s for this user across all clients.
  
=== 响应 ===
+
=== Response ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "accessToken": "随机访问令牌",               // 十六进制
+
     "accessToken": "random access token",     // hexadecimal or JSON-Web-Token (unconfirmed) [The normal accessToken can be found in the payload of the JWT (second by '.' separated part as Base64 encoded JSON object), in key "yggt"]
     "clientToken": "客户端标识符",               // 与接收到的相同
+
     "clientToken": "client identifier",       // identical to the one received
     "availableProfiles": [                    // 仅在接收到agent字段时出现
+
     "availableProfiles": [                    // only present if the agent field was received
 
         {
 
         {
             "id": "档案标识符",                 // 十六进制
+
            "agent": "minecraft",              // Presumably same value as before
             "name": "玩家名称",
+
             "id": "profile identifier",       // hexadecimal
             "legacy": true或false              // 事实上它仅为true时出现。默认为false。
+
             "name": "player name",
 +
            "userId": "hex string",
 +
            "createdAt": 1325376000000,        // Milliseconds since Jan 1 1970
 +
            "legacyProfile": true or false,    // Present even when false
 +
            "suspended": true or false,        // probably false
 +
            "paid": true or false,            // probably true
 +
            "migrated": true or false,        // Seems to be false even for migrated accounts...?  (https://bugs.mojang.com/browse/WEB-1461)
 +
             "legacy": true or false            // Only appears in the response if true. Default to false.  Redundant to the newer legacyProfile...
 
         }
 
         }
 
     ],
 
     ],
     "selectedProfile": {                      // 仅在接收到agent字段时出现
+
     "selectedProfile": {                      // only present if the agent field was received
         "id": "不含分隔符的uuid",
+
         "id": "uuid without dashes",
         "name": "玩家名称",
+
         "name": "player name",
         "legacy": true或false
+
        "userId": "hex string",
 +
        "createdAt": 1325376000000,
 +
        "legacyProfile": true or false,
 +
        "suspended": true or false,
 +
        "paid": true or false,
 +
        "migrated": true or false,
 +
         "legacy": true or false
 
     },
 
     },
     "user": {                                  // 仅在请求负载中的requestUser为true出现
+
     "user": {                                  // only present if requestUser was true in the request payload
         "id": "用户标识符",                     // 十六进制
+
         "id": "user identifier",               // hexadecimal
 +
        "email": "user@email.example",        // Hashed(?) value for unmigrated accounts
 +
        "username": "user@email.example",      // Regular name for unmigrated accounts, email for migrated ones
 +
        "registerIp": "198.51.100.*",          // IP address with the last digit censored
 +
        "migratedFrom": "minecraft.net",
 +
        "migratedAt": 1420070400000,
 +
        "registeredAt": 1325376000000,        // May be a few minutes earlier than createdAt for profile
 +
        "passwordChangedAt": 1569888000000,
 +
        "dateOfBirth": -2208988800000,
 +
        "suspended": false,
 +
        "blocked": false,
 +
        "secured": true,
 +
        "migrated": false,                    // Seems to be false even when migratedAt and migratedFrom are present...
 +
        "emailVerified": true,
 +
        "legacyUser": false,
 +
        "verifiedByParent": false,
 
         "properties": [
 
         "properties": [
 
             {
 
             {
                 "name": "preferredLanguage",  // 也许不会对所有账号显示
+
                 "name": "preferredLanguage",  // might not be present for all accounts
                 "value": "en"                  // Java locale格式 (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--
+
                 "value": "en"                  // Java locale format (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
 
             },
 
             },
 
             {
 
             {
                 "name": "twitch_access_token", // 仅在关联twitch账号时出现(见https://account.mojang.com/me/settings)
+
                 "name": "twitch_access_token", // only present if a twitch account is associated (see https://account.mojang.com/me/settings)
                 "value": "twitch oauth token"  // OAuth 2.0令牌,字母+数字,如https://api.twitch.tv/kraken?oauth_token=[...]
+
                 "value": "twitch oauth token"  // OAuth 2.0 Token; alphanumerical; e.g. https://api.twitch.tv/kraken?oauth_token=[...]
                                               // Twitch API的文档:https://github.com/justintv/Twitch-API
+
                                               // the Twitch API is documented here: https://github.com/justintv/Twitch-API
 
             }
 
             }
 
         ]
 
         ]
Line 154: Line 183:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''注意:'''如果用户希望能在他们的电脑上保存登录状态,那么强烈建议应该存储<code>accessToken</code>而不是密码本身。
+
'''Note:''' If a user wishes to stay logged in on their computer you are strongly advised to store the received <code>accessToken</code> instead of the password itself.
  
当前每个账号只拥有一个档案,一个账号拥有多个档案还在未来计划中。如果用户尝试登入一个没有附加Minecraft许可的Mojang账号,那么认证将会成功,但是响应将不包含<code>selectedProfile</code>字段,而且<code>availableProfiles</code>数组也是空的。
+
Currently each account will only have one single profile, multiple profiles per account are however planned in the future. If a user attempts to log into a valid Mojang account with no attached Minecraft license, the authentication will be successful, but the response will not contain a <code>selectedProfile</code> field, and the <code>availableProfiles</code> array will be empty.
  
有一些实例曾观察到Mojang对于旧版账号失败的刷新请求返回了一个平坦的<code>null</code>。还不清楚什么实际错误绑定了这个空响应,而且它极为罕见,但作为实现应该注意对该响应的空输出。
+
Some instances in the wild have been observed of Mojang returning a flat <code>null</code> for failed refresh attempts against legacy accounts. It's not clear what the actual error tied to the null response is and it is extremely rare, but implementations should be wary of null output from the response.
  
这个端点是严格速率限制的:短时间内同一账号的多次<code>/authenticate</code>请求(例如在几秒内3次请求),即使密码正确也会导致一个<code>Invalid credentials.</code>响应。该错误会在几秒后被清除。
+
This endpoint is severely rate-limited: multiple <code>/authenticate</code> requests for the same account in a short amount of time (think 3 requests in a few seconds), even with the correct password, will eventually lead to an <code>Invalid credentials.</code> response. This error clears up a few seconds later.
  
== 刷新 ==
+
== Refresh ==
  
刷新一个有效的<code>accessToken</code>。它可以用于在游戏会话间保持登录状态,这优于在文件中保存用户的密码(见[[ZH:lastlogin|lastlogin]])。
+
Refreshes a valid <code>accessToken</code>. It can be used to keep a user logged in between gaming sessions and is preferred over storing the user's password in a file (see [[lastlogin]]).
  
=== 端点 ===
+
=== Endpoint ===
  
 
  /refresh
 
  /refresh
  
=== 负载 ===
+
=== Payload ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "accessToken": "有效的accessToken",
+
     "accessToken": "valid accessToken",
     "clientToken": "客户端标识符",         // 这需要与第一处用来获取
+
     "clientToken": "client identifier", // This needs to be identical to the one used
                                         // accessToken的那个相同
+
                                         // to obtain the accessToken in the first place
     "selectedProfile": {                // 可选的,发送它将导致错误
+
     "selectedProfile": {                // optional; sending it will result in an error
         "id": "档案标识符",               // 十六进制
+
         "id": "profile identifier",     // hexadecimal
         "name": "玩家名称"
+
         "name": "player name"
 
     },
 
     },
     "requestUser": true                  // 可选的,默认为false,若为true则将user对象加入到响应中
+
     "requestUser": true                  // optional; default: false; true adds the user object to the response
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
注意:提供的<code>accessToken</code>将失效。
+
Note: The provided <code>accessToken</code> gets invalidated.
  
=== 响应 ===
+
=== Response ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "accessToken": "随机访问令牌",               // 十六进制
+
     "accessToken": "random access token",     // hexadecimal
     "clientToken": "客户端标识符",               // 与接收到的相同
+
     "clientToken": "client identifier",       // identical to the one received
 
     "selectedProfile": {
 
     "selectedProfile": {
         "id": "档案标识符",                     // 十六进制
+
         "id": "profile identifier",           // hexadecimal
         "name": "玩家名称"
+
         "name": "player name"
 
     },
 
     },
     "user": {                                  // 仅在请求负载中的requestUser为true出现
+
     "user": {                                  // only present if requestUser was true in the request payload
         "id": "用户标识符",                     // 十六进制
+
         "id": "user identifier",               // hexadecimal
 
         "properties": [
 
         "properties": [
 
             {
 
             {
                 "name": "preferredLanguage",  // 也许不会对所有账号显示
+
                 "name": "preferredLanguage",  // might not be present for all accounts
                 "value": "en"                  // Java locale格式(https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--
+
                 "value": "en"                  // Java locale format (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
 
             },
 
             },
 
             {
 
             {
                 "name": "twitch_access_token", // 仅在关联twitch账号时出现(见https://account.mojang.com/me/settings)
+
                 "name": "twitch_access_token", // only present if a twitch account is associated (see https://account.mojang.com/me/settings)
                 "value": "twitch oauth token"  // OAuth 2.0令牌,字母+数字,如https://api.twitch.tv/kraken?oauth_token=[...]
+
                 "value": "twitch oauth token"  // OAuth 2.0 Token; alphanumerical; e.g. https://api.twitch.tv/kraken?oauth_token=[...]
                                               // Twitch API的文档:https://github.com/justintv/Twitch-API
+
                                               // the Twitch API is documented here: https://github.com/justintv/Twitch-API
 
             }
 
             }
 
         ]
 
         ]
Line 213: Line 242:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== 验证 ==
+
== Validate ==
  
检查<code>accessToken</code>是否可用于Minecraft服务器的认证。Minecraft启动器(自1.6.13版本起)会在启动器调用此端点来验证保存的令牌是否仍然可用,并会在返回错误时调用<code>/refresh</code>
+
Checks if an <code>accessToken</code> is usable for authentication with a Minecraft server. The Minecraft Launcher (as of version 1.6.13) calls this endpoint on startup to verify that its saved token is still usable, and calls <code>/refresh</code> if this returns an error.
  
请注意<code>accessToken</code>可能会不可用与Minecraft服务器的认证,而对于<code>/refresh</code>来说足够可用。这主要会发生在一个人使用了另一个客户端(如在别的PC上使用相同的帐号游玩了Minecraft)。看起来只有给定帐号最新获得的<code>accessToken</code>才能可靠地用于认证(第二新的令牌看起来也仍然有效,但请不要依赖它)。
+
Note that an <code>accessToken</code> may be unusable for authentication with a Minecraft server, but still be good enough for <code>/refresh</code>. This mainly happens when one has used another client (e.g. played Minecraft on another PC with the same account). It seems only the most recently obtained <code>accessToken</code> for a given account can reliably be used for authentication (the next-to-last token also seems to remain valid, but don't rely on it).
  
<code>/validate</code>可以在有或没有<code>clientToken</code>时调用。如果提供了<code>clientToken</code>,它应当与获取<code>accessToken</code>的那个相匹配。Minecraft启动器会向<code>/validate</code>发送<code>clientToken</code>
+
<code>/validate</code> may be called with or without a <code>clientToken</code>. If a <code>clientToken</code> is provided, it should match the one used to obtain the <code>accessToken</code>. The Minecraft Launcher does send a <code>clientToken</code> to <code>/validate</code>.
  
=== 端点 ===
+
=== Endpoint ===
  
 
  /validate
 
  /validate
  
=== 负载 ===
+
=== Payload ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "accessToken": "有效的accessToken",
+
     "accessToken": "valid accessToken",
     "clientToken": "关联的clientToken"   // 可选的,见上
+
     "clientToken": "associated clientToken" // optional, see above
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== 响应 ===
+
=== Response ===
  
若成功返回空响应(<code>204 No Content</code>),否则返回错误JSON和状态码<code>403 Forbidden</code>
+
Returns an empty payload (<code>204 No Content</code>) if successful, an error JSON with status <code>403 Forbidden</code> otherwise.
  
== 登出 ==
+
== Signout ==
  
使用帐号的用户名和密码使<code>accessToken</code>失效。
+
Invalidates <code>accessToken</code>s using an account's username and password.
  
=== 端点 ===
+
=== Endpoint ===
  
 
  /signout
 
  /signout
  
=== 负载 ===
+
=== Payload ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "username": "mojang帐号名称",
+
     "username": "mojang account name",
     "password": "mojang帐号密码"
+
     "password": "mojang account password"
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== 响应 ===
+
=== Response ===
  
若成功返回一个空负载。
+
Returns an empty payload if successful.
  
== 使失效 ==
+
== Invalidate ==
  
使用client/access令牌对使<code>accessToken</code>失效。
+
Invalidates <code>accessToken</code>s using a client/access token pair.
  
=== 端点 ===
+
=== Endpoint ===
  
 
  /invalidate
 
  /invalidate
  
=== 负载 ===
+
=== Payload ===
  
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
     "accessToken": "有效的accessToken",
+
     "accessToken": "valid accessToken",
     "clientToken": "客户端标识符"         // 这需要与第一处用来获取
+
     "clientToken": "client identifier"   // This needs to be identical to the one used
                                         // accessToken的那个相同
+
                                         // to obtain the accessToken in the first place
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== 响应 ===
+
=== Response ===
  
若成功返回一个空负载。
+
Returns an empty payload if successful.
  
== 加入服务器  ==
+
== Joining a Server ==
  
[[ZH:协议加密#认证|协议加密#认证]]
+
See [[Protocol Encryption#Authentication]]
  
[[Category:ZH:协议细节]]
+
[[Category:Protocol Details]]
[[Category:ZH:Minecraft Modern]]
+
[[Category:Minecraft Modern]]

Revision as of 02:20, 6 November 2019

Minecraft 1.6 引入了一个名为 Yggdrasil验证方案 来完全替代 先前的验证系统. Mojang的其他游戏, Scrolls, 也使用此验证方法. Mojang 说 每个人都应该使用这个验证系统进行自定义登录, 但是 不会向用户索要凭据.

请求格式

所有的 Yggdrasil 请求都发送到下列服务器

https://authserver.mojang.com

此外,他们应该遵循以下规则

  • 使用 POST 请求
  • 包含 Content-Type 头,并设置为 application/json
  • 包含一个使用 JSON 格式编码的有效载荷


如果请求成功,服务器将做出如下响应

  • 状态码 200
  • 一个符合下文规范的 JSON数据

如果请求失败,服务器将做出如下响应

{
    "error": "简短的描述(机器可读)",
    "errorMessage": "一个向用户展示的错误消息(人类可读)",
    "cause": "引起错误的原因" // 可选
}

错误

以下是一些可能遇到的错误:

错误 原因 错误消息 注释
Method Not Allowed 请求所使用的方法不允许访问当前URI,这里通常是由于非POST方法引起的。
Not Found 服务器没有找到URI所对应的资源
ForbiddenOperationException UserMigratedException 无效的凭证。账户已迁移。请使用电子邮件作为用户名。
ForbiddenOperationException 无效的凭证。用户名或密码无效。
ForbiddenOperationException 无效的凭证。 使用同一用户名进行了太多的登录尝试(参见 /authenticate)。注释:用户名密码也许是有效的。
ForbiddenOperationException Invalid token. accessToken was invalid.
IllegalArgumentException Access token already has a profile assigned. Selecting profiles isn't implemented yet.
IllegalArgumentException credentials is null Username/password was not submitted.
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 Data was not submitted as application/json

Authenticate

Authenticates a user using their password.

Endpoint

/authenticate

Payload

{
    "agent": {                              // defaults to Minecraft
        "name": "Minecraft",                // For Mojang's other game Scrolls, "Scrolls" should be used
        "version": 1                        // This number might be increased
                                            // by the vanilla client in the future
    },
    "username": "mojang account name",      // Can be an email address or player name for
                                            // unmigrated accounts
    "password": "mojang account password",
    "clientToken": "client identifier",     // optional
    "requestUser": true                     // optional; default: false; true adds the user object to the response
}

The clientToken should be a randomly generated identifier and must be identical for each request. The vanilla launcher generates a random (version 4) UUID on first run and saves it, reusing it for every subsequent request. In case it is omitted the server will generate a random token based on Java's UUID.toString() which should then be stored by the client. This will however also invalidate all previously acquired accessTokens for this user across all clients.

Response

{
    "accessToken": "random access token",      // hexadecimal or JSON-Web-Token (unconfirmed) [The normal accessToken can be found in the payload of the JWT (second by '.' separated part as Base64 encoded JSON object), in key "yggt"]
    "clientToken": "client identifier",        // identical to the one received
    "availableProfiles": [                     // only present if the agent field was received
        {
            "agent": "minecraft",              // Presumably same value as before
            "id": "profile identifier",        // hexadecimal
            "name": "player name",
            "userId": "hex string",
            "createdAt": 1325376000000,        // Milliseconds since Jan 1 1970
            "legacyProfile": true or false,    // Present even when false
            "suspended": true or false,        // probably false
            "paid": true or false,             // probably true
            "migrated": true or false,         // Seems to be false even for migrated accounts...?  (https://bugs.mojang.com/browse/WEB-1461)
            "legacy": true or false            // Only appears in the response if true. Default to false.  Redundant to the newer legacyProfile...
        }
    ],
    "selectedProfile": {                       // only present if the agent field was received
        "id": "uuid without dashes",
        "name": "player name",
        "userId": "hex string",
        "createdAt": 1325376000000,
        "legacyProfile": true or false,
        "suspended": true or false,
        "paid": true or false,
        "migrated": true or false,
        "legacy": true or false
    },
    "user": {                                  // only present if requestUser was true in the request payload
        "id": "user identifier",               // hexadecimal
        "email": "user@email.example",         // Hashed(?) value for unmigrated accounts
        "username": "user@email.example",      // Regular name for unmigrated accounts, email for migrated ones
        "registerIp": "198.51.100.*",          // IP address with the last digit censored
        "migratedFrom": "minecraft.net",
        "migratedAt": 1420070400000,
        "registeredAt": 1325376000000,         // May be a few minutes earlier than createdAt for profile
        "passwordChangedAt": 1569888000000,
        "dateOfBirth": -2208988800000,
        "suspended": false,
        "blocked": false,
        "secured": true,
        "migrated": false,                     // Seems to be false even when migratedAt and migratedFrom are present...
        "emailVerified": true,
        "legacyUser": false,
        "verifiedByParent": false,
        "properties": [
            {
                "name": "preferredLanguage",   // might not be present for all accounts
                "value": "en"                  // Java locale format (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
            },
            {
                "name": "twitch_access_token", // only present if a twitch account is associated (see https://account.mojang.com/me/settings)
                "value": "twitch oauth token"  // OAuth 2.0 Token; alphanumerical; e.g. https://api.twitch.tv/kraken?oauth_token=[...]
                                               // the Twitch API is documented here: https://github.com/justintv/Twitch-API
            }
        ]
    }
}

Note: If a user wishes to stay logged in on their computer you are strongly advised to store the received accessToken instead of the password itself.

Currently each account will only have one single profile, multiple profiles per account are however planned in the future. If a user attempts to log into a valid Mojang account with no attached Minecraft license, the authentication will be successful, but the response will not contain a selectedProfile field, and the availableProfiles array will be empty.

Some instances in the wild have been observed of Mojang returning a flat null for failed refresh attempts against legacy accounts. It's not clear what the actual error tied to the null response is and it is extremely rare, but implementations should be wary of null output from the response.

This endpoint is severely rate-limited: multiple /authenticate requests for the same account in a short amount of time (think 3 requests in a few seconds), even with the correct password, will eventually lead to an Invalid credentials. response. This error clears up a few seconds later.

Refresh

Refreshes a valid accessToken. It can be used to keep a user logged in between gaming sessions and is preferred over storing the user's password in a file (see lastlogin).

Endpoint

/refresh

Payload

{
    "accessToken": "valid accessToken",
    "clientToken": "client identifier",  // This needs to be identical to the one used
                                         // to obtain the accessToken in the first place
    "selectedProfile": {                 // optional; sending it will result in an error
        "id": "profile identifier",      // hexadecimal
        "name": "player name"
    },
    "requestUser": true                  // optional; default: false; true adds the user object to the response
}

Note: The provided accessToken gets invalidated.

Response

{
    "accessToken": "random access token",      // hexadecimal
    "clientToken": "client identifier",        // identical to the one received
    "selectedProfile": {
        "id": "profile identifier",            // hexadecimal
        "name": "player name"
    },
    "user": {                                  // only present if requestUser was true in the request payload
        "id": "user identifier",               // hexadecimal
        "properties": [
            {
                "name": "preferredLanguage",   // might not be present for all accounts
                "value": "en"                  // Java locale format (https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toString--)
            },
            {
                "name": "twitch_access_token", // only present if a twitch account is associated (see https://account.mojang.com/me/settings)
                "value": "twitch oauth token"  // OAuth 2.0 Token; alphanumerical; e.g. https://api.twitch.tv/kraken?oauth_token=[...]
                                               // the Twitch API is documented here: https://github.com/justintv/Twitch-API
            }
        ]
    }
}

Validate

Checks if an accessToken is usable for authentication with a Minecraft server. The Minecraft Launcher (as of version 1.6.13) calls this endpoint on startup to verify that its saved token is still usable, and calls /refresh if this returns an error.

Note that an accessToken may be unusable for authentication with a Minecraft server, but still be good enough for /refresh. This mainly happens when one has used another client (e.g. played Minecraft on another PC with the same account). It seems only the most recently obtained accessToken for a given account can reliably be used for authentication (the next-to-last token also seems to remain valid, but don't rely on it).

/validate may be called with or without a clientToken. If a clientToken is provided, it should match the one used to obtain the accessToken. The Minecraft Launcher does send a clientToken to /validate.

Endpoint

/validate

Payload

{
    "accessToken": "valid accessToken",
    "clientToken": "associated clientToken" // optional, see above
}

Response

Returns an empty payload (204 No Content) if successful, an error JSON with status 403 Forbidden otherwise.

Signout

Invalidates accessTokens using an account's username and password.

Endpoint

/signout

Payload

{
    "username": "mojang account name",
    "password": "mojang account password"
}

Response

Returns an empty payload if successful.

Invalidate

Invalidates accessTokens using a client/access token pair.

Endpoint

/invalidate

Payload

{
    "accessToken": "valid accessToken",
    "clientToken": "client identifier"   // This needs to be identical to the one used
                                         // to obtain the accessToken in the first place
}

Response

Returns an empty payload if successful.

Joining a Server

See Protocol Encryption#Authentication