> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用许可证令牌在你的服务上校验授权

> 使用许可证令牌在后端服务上校验用户对 XBOX 游戏或附加内容的授权，包括令牌结构与验证流程。

Microsoft Game Development Kit (GDK) 运行时中的许可证令牌为游戏服务提供了一种方法，无需信任客户端即可验证客户端是否拥有正在运行的应用的有效许可证。
这些令牌用于验证在 PC 上运行的应用的许可证，XBOX 应用不需要使用它们。

许可证令牌采用 JSON Web Token (JWT) 格式，可以使用现有的 JWT 处理程序进行解包。
进行验证测试时，你可以将合成的测试许可证令牌输入 [JWT 解码工具](https://jwt.io)。
请勿将生产令牌或凭据提交到公共解码工具。

许可证令牌包含下表所示的各个部分。

| 部分 | 说明 |
| - | - |
| 标头 (Header) | 提供 x5t，用于确定验证令牌时应使用的正确证书。 |
| 有效负载 (Payload) | 在 `LicenseTokenClaim` 值中包含一个 base64 编码的字符串，提供来自客户端的许可证的附加信息。还提供 `LicenseToken` 的过期日期。 |
| 签名 (Signature) | 标准 JWT 签名，用于验证令牌的完整性和来源。请使用从 Microsoft 下载的签名证书的公共证书进行验证。 |

## 从客户端获取许可证令牌

若要获取许可证令牌，客户端必须使用产品的 Store ID 和自定义开发者字符串调用 [XStoreQueryLicenseTokenAsync](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenasync.md) API。
此 API 根据当前登录 Microsoft Store 应用的用户账户的权益，检索该 productID 的许可证令牌。
如果你在开发沙盒中工作，你的[测试账户](https://docs.xbox.com/zh-CN/services/xbox-services/develop/test-accounts/live-setup-testaccounts.md)必须同时登录 XBOX Windows 应用和 Microsoft Store 应用。检索到令牌后，应将其发送到你自己的服务进行验证。
不应在客户端上验证许可证令牌，因为这样会使从客户端传到你的服务的结果失去可信度。

有关如何获取许可证令牌的示例，请参阅[游戏内商店示例](https://aka.ms/gdkdl)。

## 在你的服务上验证许可证令牌

有关如何处理许可证令牌的完整代码示例，请参阅[游戏服务示例](https://aka.ms/gdkdl)。

你需要从用于签名该令牌的证书中获取 RSA 公钥。
若要确定下载证书的完整 URL，你需要通过以下方式之一确定 `certificateId`。

* 从 base64 编码的有效负载中提取 `certificateId` 值。
* 将标头中的 x5t 值通过 base64 解码为字节数组，并将其转换为字符串。
  游戏服务示例中提供了示例代码。
* 获得 `certificateId` 后，取解码字符串中的该值，并将其追加到 `https://licensing.mp.microsoft.com/v8.0/licenseToken/fullCertificate/{certificateId}`，以获得证书的直接下载链接。

结果是一个包含原始证书数据的 XML 文档，随后可用于创建证书对象，例如 X509Certificate2（有关源代码，请参阅游戏服务示例）。
你应缓存此证书，这样就无需在每次验证许可证令牌时都下载它。

有了证书的 RSA 公钥，你就可以使用标准 JWT 签名验证，确保许可证令牌来自可靠的来源，且令牌内容未被修改。

## 使用许可证令牌中的信息

许可证令牌的有效负载包含一个 `LicenseTokenClaim` 值，它是额外 JSON 数据的 base64 编码字符串。
解码后，字符串的第一部分可以忽略，因此请查找第一个 "\{" 来确定 JSON 数据的开头。

在以下示例中，`SIGNING_CERTIFICATE_ID` 表示从实际令牌中提取的证书 ID，`RESERVED_PAYLOAD` 表示保留的有效负载字符串。请将 `SERVER_GENERATED_NONCE` 替换为你的服务器为此次许可证检查生成的唯一字符串。其余标识符和用户 ID 仅作说明用途；请验证已签名令牌中的值，而不要将此示例用作凭据。

```json theme={null}
{
    "certificateId": "SIGNING_CERTIFICATE_ID",
    "customDeveloperString": "SERVER_GENERATED_NONCE",
    "licensableProducts": [
        {
            "endDate": "9999-12-31T23:59:59.9999999+00:00",
            "isShared": false,
            "id": "fc80277459b04bc7a158b49c0c5574e1",
            "productId": "9NN4ZHKML55R",
            "skuId": "0010",
            "userId": "m8jGdShdpG8vu9nIQiAn3lBIQJ+TD0r2jAJvfmGYmGI="
        }
    ],
    "payload": "RESERVED_PAYLOAD",
    "tokenVersion": 1
}
```

如果客户端拥有该产品的有效许可证，则该产品的信息会包含在 `licensableProducts` 列表中，每个产品包含以下参数。

| 参数 | 类型 | 说明 | 必需 |
| - | - | - | - |
| `endDate` | `datetime` | 该项目许可证结束时的 UTC 日期时间。在大多数情况下设置为最大日期 9999-12-31 | 是 |
| `isShared` | `bool` | 返回 false，并不实际指示当前账户是直接所有者，还是基于[游戏的产品共享模型](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-product-sharing.md)共享的许可证 | 是 |
| `id` | `string` | 该许可证所代表的权益的唯一标识符，可映射到特定账户。与针对拥有该许可证的用户调用 Collections 服务时返回的 `id` 值相匹配。 | 是 |
| `productId` | `string` | | 是 |
| `skuId` | `string` | | 是 |
| `userId` | `string` | 与该许可证关联的用户的 ID。 | 是 |

`payload` 字符串保留供将来使用

### userId 和 ID 值及其用途

`id` 值：

* 与从商店检索到的特定许可证实例绑定
* 可用于识别和跟踪正在使用的许可证，包括跨多次登录
* 可用于实现针对特定许可证的封禁
* 同一 ID 值在多个会话或多个用户之间重复出现，可能表明存在许可证共享或重复使用

`userId` 值：

* 表示其账户拥有该许可证的用户
* 其格式不易映射到其他 XBOX 相关 ID（XUID、pXUID），但如果针对同一 ProductId 调用 [Collections](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-query-entitlements.md) 时返回的 `id` 值与许可证令牌中的值匹配，则表示该许可证令牌和 userId 与你代表其调用 Collections 的账户绑定

## 使用 customDeveloperString 参数防止重放攻击

由于客户端可能会拦截发送到你的服务的有效令牌，然后重放该有效令牌，因此正确使用 `XStoreQueryLicenseToken` API 的 `customDeveloperString` 参数非常重要。
此字符串应在你的服务器上生成，并且对于你要执行的每次许可证检查都应是唯一的。
生成字符串后，应将其下发给客户端，再由客户端将该字符串传递给 `XStoreQueryLicenseToken` API。
随后该值会嵌入到许可证令牌的有效负载中，并受到令牌上验证签名的保护，防止被修改。

客户端将令牌上传到你的服务后，你可以检查 `customDeveloperString` 值是否与服务器生成并发送给客户端的值完全匹配。
如果该值不匹配，则可能表明有人在重放旧令牌以试图欺骗服务，因为令牌的其他所有方面仍然有效。
我们建议在每次执行许可证检查时，将 `customDeveloperString` 设置为随机字符串或 GUID。

## 参考 API 文档

* [XStore（API 内容）](https://docs.xbox.com/zh-CN/reference/system/xstore/xstore_members.md)
  * 函数
    * [XStoreQueryLicenseTokenAsync](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenasync.md)
    * [XStoreQueryLicenseTokenResult](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenresult.md)

## 另请参阅

[商务](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-commerce-overview.md)

[XStore](https://docs.xbox.com/zh-CN/reference/system/xstore/xstore_members.md)

[XStoreQueryLicenseTokenAsync](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenasync.md)

[XStoreQueryLicenseTokenResult](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenresult.md)


## Related topics

- [GDK 商务系统概述](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-overview.md)
- [XStoreQueryLicenseTokenAsync](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenasync.md)
- [XStoreQueryLicenseTokenResult](https://docs.xbox.com/zh-CN/reference/system/xstore/functions/xstorequerylicensetokenresult.md)
- [PC 上的 XStore — DRM、许可证令牌和沙盒](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-pc.md)
- [启用授权测试](https://docs.xbox.com/zh-CN/publishing/xstore-commerce/xstore-licensing-setup.md)
