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

# 加密登录

> 为 PlayFab 客户端登录 API 调用启用自定义加密，以保护身份验证载荷并加强应用程序对抗篡改的安全性。

PlayFab 允许你通过使用自定义加密保护某些客户端 API 调用来加强应用程序安全性。本教程向你展示如何为你的客户端启用加密。

我们将使用的方法允许你保护*任何*登录 API 调用。由于该过程*始终*相似，我们只演示如何保护*一个*特定方法，`LoginWithCustomID`。

<Info>
  登录加密旨在在 title 创建后为*所有*玩家使用，或者完全不使用。这*不是*可以在以后启用的功能。你必须从*一开始*就使用它，或者完全不使用。特别是，*已加密*的玩家永远无法*未加密*登录，而*未加密*的玩家也永远无法成为*已加密*的玩家。
</Info>

在本指南中，我们将：

1. 创建一个 player-shared secret。
2. 引入一条 API 策略规则以对某个方法启用保护。
3. 更改客户端以使用 player-shared 密钥检索公共 title 密钥并加密载荷。

<Note>
  PlayFab 提供以下免责声明：“我们所有的 API 调用已经使用现代标准安全加密，标准的 API 调用加密就是大多数客户所需的一切。此功能代表围绕使玩家更难使用未经授权的客户端构建的*额外*一层安全。它*不是*万无一失的——它只是提高了黑客的难度门槛。对于大多数开发者来说，轻微的安全提升不值得额外的努力。”
</Note>

## 创建 player-shared secret

PlayFab Admin API 公开了一个方法来管理你的 player-shared secret。

<Note>
  创建具有特定名称的新共享密钥将覆盖具有相同名称的现有密钥（如果有）。此外，你可以在不同名称下注册*多个*共享密钥。
</Note>

运行以下代码将 player-shared secret 添加到你的 title。

```csharp theme={null}
PlayFabSettings.staticSettings.DeveloperSecretKey = "__DEVELOPER_KEY__";
PlayFabSettings.staticSettings.TitleId = "__TITLE_ID__";
var response = await PlayFabAdminAPI.CreatePlayerSharedSecretAsync(new CreatePlayerSharedSecretRequest()
{
    FriendlyName = "__KEY_NAME__"
});

if (response.Error != null)
{
    Console.WriteLine(response.Error.GenerateErrorReport());
}
else
{
    Console.WriteLine(response.Result.SecretKey);
}
```

要运行此代码，你需要一个开发者密钥。有关密钥的更多信息，请参阅[密钥管理](/services/playfab/live-service-management/gamemanager/secret-key-management)。

此应用程序应打印新创建的 player-shared secret。*请务必保存它*。如果丢失，你将不得不通过再次运行应用程序生成新密钥。

密钥如下所示：

`QC953WQ3TU6ZJTZMAT1FNJQIKR92FPUQTISW4Q6WD8SY841MQQ`

## 更新策略

我们创建了一个新的共享密钥。现在我们需要告诉 PlayFab 要保护哪些 API 调用。

运行下面所示的代码以保护 `LoginWithCustomId` API 调用，并*取消保护*其余的 API 调用。

```csharp theme={null}
// Set development key and title id
PlayFabSettings.DeveloperSecretKey = "__DEVELOPER_KEY__";
PlayFabSettings.TitleId = "__TITLE_ID__";

public static async Task SetApiPermission(bool restrictCustomId)
{
    // The first statement denies every call to LoginWithCustomID that is not properly encrypted
    var filterCustom = new PermissionStatement
    {
        // Statement effects any action
        Action = "*",
        // Filter the case where there is no signature and payload is not encrypted
        ApiConditions = new ApiCondition()
        {
            HasSignatureOrEncryption = Conditionals.False
        },
        Comment = "Deny every request to LoginWithCustomID that is not properly encrypted",
        // Specify the resource name
        Resource = "pfrn:api--/Client/LoginWithCustomID", // Resource name
        // Deny any of such requests
        Effect = EffectType.Deny,
        // For any user
        Principal = "*"
    };
    // The second statement allows every other API call
    var filterNothing = new PermissionStatement()
    {
        // Statement effects any action
        Action = "*",
        Comment = "Allow the rest API calls",
        // For any resource name
        Resource = "pfrn:api--*",
        // Allow any request
        Effect = EffectType.Allow,
        // For any user
        Principal = "*"
    };

    // Update the policy
    var request = new UpdatePolicyRequest()
    {
        // ApiPolicy controls access to API methods
        PolicyName = "ApiPolicy",
        // In this example we overwrite the policy. Consider appending to the existing policy instead.
        OverwritePolicy = true,
        // Introduce policy statements
        Statements = new List<PermissionStatement> { filterNothing }
    };
    if (restrictCustomId)
        request.Statements.Add(filterCustom);
    var result = await PlayFabAdminAPI.UpdatePolicyAsync(request);

    // Handle possible errors
    if (result.Error != null)
        Console.WriteLine(result.Error.GenerateErrorReport());
    else
        Console.WriteLine("Policy updated");
}
```

## 设置客户端

现在当策略更新后，你不再能够直接调用 `LoginWithCustomID` API。请考虑下面显示的代码。

```csharp theme={null}
var result = await PlayFabClientAPI.LoginWithCustomIDAsync(new LoginWithCustomIDRequest()
{
    CreateAccount = true,
    CustomId = "Some_Custom_Id"
});

if (result.Error != null)
{
    Console.WriteLine(result.Error.GenerateErrorReport());
}
else
{
    Console.WriteLine(result.Result.PlayFabId);
}
```

通常，这会毫无问题地登录用户。但是，*现在*这个 API 调用被*保护*了——代码将产生 `Not Authorized` 错误（不要与 `Not Authenticated` 混淆）。

我们需要修改客户端以正确加密调用载荷。这分两步完成：

1. 使用 player-shared secret 获取 title 公钥。
2. 使用 title 公钥加密载荷。

下面显示的代码说明了这一点。

```csharp theme={null}
public static async Task DoEncryptedLogin()
{
    Console.WriteLine("Begin DoEncryptedLogin");

    // Use Player Shared Secret to get Title Public Key
    var titleKeyResult = await PlayFabClientAPI.GetTitlePublicKeyAsync(new GetTitlePublicKeyRequest
    {
        TitleId = TITLE_ID,
        TitleSharedSecret = CLIENT_SECRET_KEY
    });

    Console.WriteLine("Encrypt request");
    // Convert public key to bytes
    var cspBlob = Convert.FromBase64String(titleKeyResult.Result.RSAPublicKey);

    // Serialize certain part of the model into string (this will be encrypted).
    var encryptionModel = JsonWrapper.SerializeObject(new LoginWithCustomIDRequest { CustomId = "SOME_PLAYER_ID_ENCRYPTED" });
    string encryptedPayload;

    // RSA encryption
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportCspBlob(cspBlob);
        var bytesToEncrypt = Encoding.UTF8.GetBytes(encryptionModel);
        var encryptedBytes = rsa.Encrypt(bytesToEncrypt, false);
        encryptedPayload = Convert.ToBase64String(encryptedBytes);
    }

    // Use encrypted payload to construct a model
    var model = new LoginWithCustomIDRequest
    {
        EncryptedRequest = encryptedPayload,
        PlayerSecret = CLIENT_SECRET_KEY,
        CreateAccount = true
    };

    Console.WriteLine("Call LoginWithCustomIDAsync");
    // Finally execute the call
    var result = await PlayFabClientAPI.LoginWithCustomIDAsync(model);
    Console.WriteLine("LoginWithCustomIDAsync done");
    bool successful = result.Error == null && result.Result != null;
    if (!successful)
        Console.WriteLine(result.Error.GenerateErrorReport());
    else
        Console.WriteLine("Login Successful" + result.Result.PlayFabId);
}
```

一旦你运行代码，你应该能够登录。请记住，一旦创建了 player-shared secret，就必须将其硬编码到你的客户端代码中，因为*无法*通过任何 API 调用获取或请求它。


## Related topics

- [玩家加密服务](/zh-CN/services/playfab/identity/player-identity/encryption/player-encryption-services.md)
- [IXtfUserClient::AddUser Method](/zh-CN/reference/tools/xtf/xtfuser/classes/IXtfUserClient/methods/adduser-ixtfuserclient-xtfuser-xbox-windows-m.md)
- [IXtfUserClient::SigninUserId](/zh-CN/reference/tools/xtf/xtfuser/classes/IXtfUserClient/methods/signinuserid-ixtfuserclient-xtfuser-xbox-windows-m.md)
- [IXtfUserClient::SigninUser](/zh-CN/reference/tools/xtf/xtfuser/classes/IXtfUserClient/methods/signinuser-ixtfuserclient-xtfuser-xbox-windows-m.md)
- [登录基础和最佳实践](/zh-CN/services/playfab/identity/player-identity/login/login-basics-best-practices.md)
