Minecraft 微软正版验证流程及 Python 实现

Minecraft 微软正版验证流程及 Python 实现

前言

Minecraft Mojang 账户迁移至 Microsoft 微软账户似乎已经近乎尾声,新的微软账户需要新的正版验证方式。这里我们将讨论整套的验证流程,并研究其使用 Python 实现的可能性。

Python 实现的原理是使用 Python 编写 Minecraft 启动器,由启动器接管正版验证。
本文仅探讨使用 Python 实现正版验证的流程,不涉及启动器的核心原理。

本教程使用到的 Python 库
requests 与 PySide2(或 PyQt5)

开始

在浏览器窗口登录

首先,第一步是需要玩家登陆他本人的微软账户。为了方便玩家在这个页面进行更多操作,我们需要创建一个浏览器窗口,在其中加载微软账户的登录页面。PyQt 和 PySide 提供可用的浏览器窗口,您需要做的是在这个窗口中加载下面这个 URL:

https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf   

请不要修改这其中的任何内容,这是登录 Minecraft 专用的页面。您可以在自己的浏览器中尝试访问这个 URL,进行登录之后,您应当被重定向到另一个空白页面,其 URL 应为:

https://login.live.com/oauth20_desktop.srf?code=<code>&lc=<不重要>   

我们需要做的,就是提取这个重定向 URL 中的 <code> ,这就是第一步。

如下的 Python 代码可以实现这个步骤:

def auth_microsoft_1():
    """
    验证玩家的 Microsoft 账户 Minecraft 游戏凭证,并且配置玩家的正版登录。这是第一步。
 
    :return: 此函数不返回值。
    """
    global web_view
 
    auth_url_1 = "https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL &redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"
 
    # 使用 web 窗口打开微软账户登录页面
    web_view = FormAuth()
    web_view.browser = QWebEngineView()
    web_view.browser.load(QUrl(auth_url_1))
    web_view.setCentralWidget(web_view.browser)
    web_view.browser.urlChanged.connect(auth_microsoft_2)
    web_view.show()
    logger.Logger.info(f"微软账户登录窗口已经打开。")
 
    return None

注意,虽然此代码被写在我的某个启动器项目中,但只有这样的代码并不能真正创建一个可见的窗口。如果您想要直接使用此代码,请使用搜索引擎学习如何添加 QApplication(sys.argv)。另外,FormAuth() 是一个自定义的 Widget 窗口类,auth_microsoft_2 是连接到第二步的绑定函数。

关闭窗口,开始验证

重定向并获取到 code 之后,接下来您需要使用这个 code 获取微软授权令牌 access_token。此后的步骤不再需要浏览器窗口,其一是因为不再需要用户确认任何信息,另外是防止一些不必要的用户隐私泄露。

下一步,向 https://login.live.com/oauth20_token.srf 发起 POST 请求,负载格式如下:

{
  "client_id": "00000000402b5328",
  "code": <code>,
  "grant_type": "authorization_code",
  "redirect_uri": "https://login.live.com/oauth20_desktop.srf",
  "scope": "service::user.auth.xboxlive.com::MBI_SSL"
}

在 code 对应的值中装载上一步获取的 code,其它的值不做改变;在请求头中设置 Content-Type: application/x-www-form-urlencoded,然后发送请求。

响应看起来应当如下:

 {
   "token_type":"bearer",
   "expires_in":86400,
   "scope":"service::user.auth.xboxlive.com::MBI_SSL",
   "access_token":<access_token>,
   "refresh_token":<refresh_token>,
   "user_id":"889ed4a3d844f672",
   "foci":"1"
 }

从响应中提取 access_token 和 refresh_token 两个令牌,前一个令牌应该比后一个令牌长一点(就一点,假的,长了好多)

access_token 是进行下一步验证所需要的微软验证令牌,我们现在已经通过了微软的身份验证。下一步,是向 Xbox live 进行身份验证,这也是微软的服务之一。验证 URL 为 https://user.auth.xboxlive.com/user/authenticate,您需要向其发送 POST 请求,负载如下:

{
  "Properties": {
    "AuthMethod": "RPS",
    "SiteName": "user.auth.xboxlive.com",
    "RpsTicket": access_token // 第二步中获取的访问令牌
    },
  "RelyingParty": "http://auth.xboxlive.com",
  "TokenType": "JWT"
 }

不要忘记设置 Content-Type: application/json;帆帆测试时没有设置 Accept: application/json 也能进行验证,如果您只接收到奇奇怪怪的返回值和返回代码,不妨加上试试。

如果没有异常,您应当收到如下响应:

{
  "IssueInstant":"2020-12-07T19:52:08.4463796Z",
  "NotAfter":"2020-12-21T19:52:08.4463796Z",
  "Token":token, // 保存它,这是你的 Xbox live 令牌
  "DisplayClaims":{
     "xui":[
        {
           "uhs":"uhs" // 保存它,这是一个用户信息哈希值
        }
     ]
  }
}

保存 token 和 uhs(是 user hash 的意思),然后开始下一步验证。

下一步的验证方是 XSTS,XSTS 会返回我们可用的 XSTS 令牌(没错,还是一个中间令牌)。验证 URL 是 https://xsts.auth.xboxlive.com/xsts/authorize,依然是 POST 请求,负载如下:

{
  "Properties": {
      "SandboxId": "RETAIL",
      "UserTokens": [
          token // 你上一步中获取的 token 就填在这儿
      ]
  },
  "RelyingParty": "rp://api.minecraftservices.com/",
  "TokenType": "JWT"
}

同样设置 Content-Type: application/json,然后发送,响应应该如下:

{
  "IssueInstant":"2020-12-07T19:52:09.2345095Z",
  "NotAfter":"2020-12-08T11:52:09.2345095Z",
  "Token":token, // 保存它,这是你的 XSTS 令牌
  "DisplayClaims":{
    "xui":[
       {
          "uhs":"uhs" // 与上个请求相同
       }
    ]
  }
}

现在,我们终于可以回归到 Minecraft 本身上了。通过 XSTS 令牌和 uhs,我们可以获取到用于启动游戏的 Minecraft 访问令牌。接下来让我们转向 https://api.minecraftservices.com/authentication/login_with_xbox,向其发送 POST 请求,附上一个简洁干净的负载:

{
  "identityToken": "XBL3.0 x=<uhs>;<xsts_token>"  // 在这里添加之前获取的 uhs 和 XSTS 令牌
}

您应当收到如下响应:

{
  "username" : "some uuid", // 这并不是该用户的 UUID
  "roles" : [ ],
  "access_token" : minecraft_access_token, // JWT,可以使用的 Minecraft 访问令牌
!
  "token_type" : "Bearer",
  "expires_in" : 86400
}

保存这个 minecraft_access_token,然后 —— 不要着急,直到现在为止我们都还没有获取这个微软账户拥有的 Minecraft 用户名 —— 我们甚至没有检查其是否真的拥有 Minecraft 呢!

接下来的步骤继续需要我们向 Minecraft 进行验证,向 https://api.minecraftservices.com/entitlements/mcstore 发送一个 GET 请求,把 minecraft_access_token 塞到请求头里:

Authorization: Bearer token // 只有 token 是你获取的 Minecraft 访问令牌,Bearer 不要动它,不然会报 401!   

如果对应的账户拥有游戏资格,则会返回一堆东西;如果不然,则返回空负载。

检测到返回了有效的内容之后,我们就可以继续获取这个账户的 Minecraft 用户名和 UUID 用来启动游戏。向 https://api.minecraftservices.com/minecraft/profile 发送一个 GET 请求,使用与上一步同样的请求头,接收到如下负载:

{
 "id" : UUID, // 账号的真实 UUID
 "name" : PlayerName, // 该账号的 Minecraft 用户名
 "skins" : [ {
   "id" : "6a6e65e5-76dd-4c3c-a625-162924514568",
   "state" : "ACTIVE",
   "url" : url,  // 指向一个纹理,应该是皮肤的图片文件
   "variant" : "CLASSIC",
   "alias" : "STEVE"
 } ],
 "capes" : [ ]
}

从中提取 name 和 id 两个值,其他的值可选。现在,我们就已经获得了启动 Minecraft 所需的全部信息。

以下是整个步骤的 Python 实现,使用了 requests 网络库。

def auth_microsoft_2():
    """
    验证微软账户第二步。
 
    :return: 此函数不返回值。
    """
    global web_view
 
    url: str = web_view.browser.url().toString()
 
    if not "https://login.live.com/oauth20_desktop.srf?code=" in url:
        return None
 
    web_view.close()
    logger.Logger.debug(f"微软账户登录 - 完成,重定向地址为:{url}")
 
    temp_code = url.replace("https://login.live.com/oauth20_desktop.srf?code=", "").split("&")[0]
 
    auth_url_2 = "https://login.live.com/oauth20_token.srf"
 
    temp_data_1 = {"client_id": "00000000402b5328",
                   "code": temp_code,
                   "grant_type": "authorization_code",
                   "redirect_uri": "https://login.live.com/oauth20_desktop.srf",
                   "scope": "service::user.auth.xboxlive.com::MBI_SSL"}
 
    rq_1 = rq.post(auth_url_2,
                   data=temp_data_1,
                   headers={'Content-Type': 'application/x-www-form-urlencoded'})
 
    back_json_1 = rq_1.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,微软 Oauth 回传 JSON 为:{back_json_1} | {rq_1.url} | {rq_1.status_code}")
 
    access_token = json.loads(back_json_1)["access_token"]
 
    auth_url_3 = "https://user.auth.xboxlive.com/user/authenticate"
 
    temp_data_2 = {
        "Properties": {
            "AuthMethod": "RPS",
            "SiteName": "user.auth.xboxlive.com",
            "RpsTicket": access_token
        },
        "RelyingParty": "http://auth.xboxlive.com",
        "TokenType": "JWT"
    }
 
    temp_data_2 = json.dumps(temp_data_2)  # 必须进行此步操作否则 Xbox live 一定会 400,我也不知道为什么好离谱!
 
    rq_2 = rq.post(auth_url_3,
                   data=temp_data_2,
                   headers={"Content-Type": "application/json"},
                   )
 
    back_json_2 = rq_2.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,Xbox live 回传 JSON 为:{back_json_2} | {rq_2.url} | {rq_2.status_code}")
 
    back_json_2_ = json.loads(back_json_2)
 
    token_1 = back_json_2_["Token"]
    uhs_1 = back_json_2_["DisplayClaims"]["xui"][0]["uhs"]
 
    # 验证 XSTS 令牌
 
    auth_url_4 = "https://xsts.auth.xboxlive.com/xsts/authorize"
 
    temp_data_3 = {
        "Properties": {
            "SandboxId": "RETAIL",
            "UserTokens": [
                token_1
            ]
        },
        "RelyingParty": "rp://api.minecraftservices.com/",
        "TokenType": "JWT"
    }
 
    temp_data_3 = json.dumps(temp_data_3)
 
    rq_3 = rq.post(auth_url_4,
                   data=temp_data_3,
                   headers={"Content-Type": "application/json"},
                   )
 
    back_json_3 = rq_3.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,XSTS 验证回传 JSON 为:{back_json_3} | {rq_3.url} | {rq_3.status_code}")
 
    back_json_3_ = json.loads(back_json_3)
 
    token_2 = back_json_3_["Token"]
    uhs_2 = back_json_3_["DisplayClaims"]["xui"][0]["uhs"]
 
    if uhs_1 != uhs_2:
        logger.Logger.warning("微软账户登录 - 错误,Xbox live 与 XSTS 回传的用户 uhs 不一致,登录取消。")
        raise
 
    auth_url_5 = "https://api.minecraftservices.com/authentication/login_with_xbox"
 
    temp_data_4 = {
        "identityToken": f"XBL3.0 x={uhs_1};{token_2}"
    }
 
    temp_data_4 = json.dumps(temp_data_4)
 
    rq_4 = rq.post(auth_url_5,
                   data=temp_data_4,
                   )
 
    back_json_4 = rq_4.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,Minecraft API Xbox 验证回传 JSON 为:{back_json_4} | {rq_4.url} | {rq_4.status_code}")
 
    minecraft_access_token = json.loads(back_json_4)["access_token"]
 
    auth_url_6 = "https://api.minecraftservices.com/entitlements/mcstore"
 
    rq_5 = rq.get(auth_url_6,
                  headers={"Authorization": f"Bearer {minecraft_access_token}"}
                  )
 
    back_json_5 = rq_5.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,Minecraft API 玩家游戏凭证回传 JSON 为:{back_json_5} | {rq_5.url} | {rq_5.status_code}")
 
    if not back_json_5:
        logger.Logger.warning("微软账户登录 - 错误,该账户似乎并不拥有 Minecraft,登录取消。")
        raise
 
    auth_url_7 = "https://api.minecraftservices.com/minecraft/profile"
 
    rq_6 = rq.get(auth_url_7,
                  headers={"Authorization": f"Bearer {minecraft_access_token}"}
                  )
 
    back_json_6 = rq_6.content
 
    logger.Logger.debug(f"微软账户登录 - 完成,Minecraft API 玩家信息回传 JSON 为:{back_json_6} | {rq_6.url} | {rq_6.status_code}")
 
    back_json_6_ = json.loads(back_json_6)
 
    player_uuid = back_json_6_["id"]
    player_name = back_json_6_["name"]
 
    player = MinecraftPlayerTools(player_name).auth_microsoft(minecraft_access_token, player_uuid)
 
    logger.Logger.info(f"微软账户登录 - 完成,玩家{player.player_name}({player.player_uuid})通过微软账户验证,MinecraftPlayerTools 对象已经建立。")
 
    return None

附注

我是个菜鸟,别骂我(抱头就跑 啊 头没了)

本人 Python 目前只有入门水平,这边给的代码也跑不起来(因为你没有我这边的一整个环境,我各种引用外置库搞得乱七八糟的(捂脸))

过阵子我会把这个项目传到 GitHub 或者 Gitee 上,然后大家可以去研究,现在先将就着看一看吧。

欸嘿~(再次抱头就跑)


验证流程中有一处获取了 refresh_token,这个令牌可以用于刷新对应的 access_token 的有效期,从而确保最终的 minecraft_access_token 的有效性,在外部链接第一条中已有详细描述,这里不再说明。

外部链接

Zh:Microsoft Authentication Scheme – wiki.vg

【编程开发】初探新版 Minecraft 正版验证方法 – 哔哩哔哩 (bilibili.com)

Minecraft 新版验证方法的解决方案 – 知乎 (zhihu.com)

© 版权声明
THE END
喜欢就支持一下吧
点赞10赞赏 分享
评论 共1条

请登录后发表评论

    请登录后查看评论内容