# 身份验证

**URL:** https://heroiclabs.com/docs/zh/nakama/tutorials/unity/pirate-panic/authentication/
**Summary:** 学习如何为Pirate Panic教程游戏添加身份验证功能。

---


# 身份验证

我们将首先讨论多人游戏的一个更重要的功能：让玩家能够证明自己的用户身份，并相互交流。

Nakama 可帮助我们实现这一点，其方法是让您可以轻松创建存储和处理数据的**集中化服务器**，并让玩家（**客户端**）可以连接到服务器。

## 建立连接

在此我们连接 Unity 客户端和 Nakama 服务器。

第一步是在 Unity 中游戏开始时注册一个新的 Client 对象。该对象将是在客户端和服务器之间进行任何交互的界面。

下面是对客户端进行初始化的主菜单脚本的片段：

**Scene01MainMenuController.cs**

```csharp
var client = new Client("http", "localhost", 7350, "defaultkey", UnityWebRequestAdapter.Instance);
client.Timeout = 5;
```

在此我们传送以下连接值，创建连到您在本地启动的服务器（位于 `http://localhost:7350`）的连接：

- `Scheme`：连接方案，此处为 `http`。
- `Host`：服务器主机，对于本例为 `localhost`。
- `Port`：服务器端口，默认设置为 `7350`。
- `ServerKey`：默认使用 `defaultkey`。可通过[服务器配置](../../../../getting-started/configuration/#socket)更改。

## 设备身份验证

建立服务器连接后，我们对玩家进行身份验证，以便我们可以将他们连接到一个标识。

启动 Pirate Panic 时，您可能会注意到您已自动经过身份验证并被分配了一个用户名。这种类型的无缝身份验证是设备身份验证，它创建与运行游戏的设备（例如手机或计算机）相关联的帐户。

在 Unity 中，完成设备身份验证的方法是获取设备 ID 并将其传递到 `AuthenticateDeviceAsync`：

```csharp
string deviceId = SystemInfo.deviceUniqueIdentifier;
session = await client.AuthenticateDeviceAsync(deviceId);
```

{{< note "important" >}}
如面向 WebGL 构建，您可能需要使用 `System.Guid.NewGuid().ToString()` 而非 `deviceUniqueIdentifier`。
{{< / note >}}

身份验证完成后，玩家加入***会话***，这表示玩家登录的一个时间段。参见[会话](../../../../concepts/session/)，了解详情。

会话对象可让我们主要通过 `Socket` 和 `Account` 对象，发出请求和访问用户信息：

```csharp
var socket = client.NewSocket(useMainThread: true);
await socket.ConnectAsync(session);
account = await client.GetAccountAsync(session);
```

在 Pirate Panic 中，`client`、`socket`、`account` 和 `session` 属性保存在 `GameConnection` 对象中。以后任何一次访问 `_connection`，记住这指的是这些属性。

{{< note "important" >}}
您可以根据需要组织服务器连接，只要确保它可以被其他类访问。
{{< / note >}}

## Facebook 身份验证

设备身份验证使玩家可以轻松地直接进入游戏，但是将帐户链接到设备意味着如果设备丢失或重置，则帐户将丢失。通过外部社交服务提供商（如 Facebook）连接到用户帐户，可以缓解此问题并获取其他用户信息。

{{< note "important" >}}
如果您克隆了项目存储库，Pirate Panic 代码中包含了 Facebook SDK。如您启动了自己的项目，您需要[获取 SDK](https://developers.facebook.com/docs/unity)。
{{< / note >}}

将 Facebook SDK 加入场景脚本，并按如下方式对其进行初始化：

```csharp
using Facebook.Unity;
..
private void InitializeFacebook() {
    FB.Init(() =>
    {
        FB.ActivateApp();
    });
}
```

然后，以下片段处理登录：

**ProfileUpdatePanel.cs**

```csharp
private void LinkFacebook()
{
    List<string> permissions = new List<string>();
    permissions.Add("public_profile");

    FB.LogInWithReadPermissions(permissions, async result =>
    {
        string facebookToken = result.AccessToken.TokenString;
        await _connection.Client.AuthenticateFacebookAsync(facebookToken);
    });
}
```

{{< note "important" >}}
您可以选择将 `LinkFacebook` 绑定到 **Connect Facebook** 按钮，以便玩家可以选择何时登录，而不是被迫立即登录。
{{< / note >}}

此处我们给出了 `public_profile` 权限，因此 Nakama 可以访问用户名或个人资料图片等基本的用户信息。您还可以添加其他权限和功能，这些都包含在 [Facebook SDK 文档](https://developers.facebook.com/docs/unity)中。

登录 Facebook，将自动将玩家的 Facebook 好友添加到其游戏内[好友](../friends/)列表。

## 初始化新玩家

在玩家有了登录的方法后，接下来是设置初始玩家信息，以便他们可以开始添加好友、收集宝石、完成任务或其他需要不断存储用户数据的内容。

这可以使用 [register 挂钩](../../../../server-framework/introduction/#hooks)，在服务器端完成。对于每个身份验证方法，有一个不同的挂钩。例如，因为我们设置了设备和 Facebook 身份验证，我们应当使用 `registerAfterAuthenticateDevice` 和 `registerAfterAuthenticateFacebook`。

我们将一个函数绑定到它们，触发此挂钩时，函数即运行：

**main.ts**

```typescript
initializer.registerAfterAuthenticateDevice(afterAuthenticateDeviceFn);

const afterAuthenticateDeviceFn: nkruntime.AfterHookFunction<nkruntime.Session, nkruntime.AuthenticateDeviceRequest> = function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.Session, req: nkruntime.AuthenticateDeviceRequest) {
    afterAuthenticate(ctx, logger, nk, data);

    if (!data.created) {
        return; // Account already exists.
    }
    ...
    // Player initialization goes here. Write initial stats, add items to inventory, etc.
}
```

## 会话令牌

出于安全原因，在您在 [Nakama 配置](../../../../getting-started/configuration/#common-properties)中定义的时间段后，玩家会话将自动过期。为了避免强制玩家不断重新登录，我们可以设置一个方法，在旧会话即将到期时自动请求新会话。

其实现方法是将身份验证**令牌**保存到客户端，定期将其传回服务器，以请求新的令牌。

令牌有两种：

- **访问令牌**：将客户端标识告诉服务器，让服务器信任此客户端。
- **刷新令牌**：用于请求新的访问令牌。

通过 Nakama，我们可以用 `client` 对象通过 `SessionRefreshAsync` 请求新的令牌：

**Scene01MainMenuController.cs**

```csharp
string authToken = PlayerPrefs.GetString("nakama.authToken", null);
string refreshToken = PlayerPrefs.GetString("nakama.refreshToken", null);

session = Session.Restore(authToken, refreshToken);

// Check whether a session is close to expiry.
if (session.HasExpired(DateTime.UtcNow.AddDays(1))) {
    try {
        // get a new access token
        session = await client.SessionRefreshAsync(session);
    } catch (ApiResponseException) {
        // get a new refresh token
        session = await client.AuthenticateDeviceAsync(deviceId);
        PlayerPrefs.SetString("nakama.refreshToken", session.RefreshToken);
    }

    PlayerPrefs.SetString("nakama.authToken", session.AuthToken);
}
```

此处我们使用 Unity 的内置 `PlayerPrefs` 引擎存储访问令牌，并将令牌分别刷新到 `nakama.authToken` 和 `nakama.refreshToken`。
如果会话离到期还有不到一天，脚本将尝试用 `SessionRefreshAsync` 请求新的访问令牌。如果刷新令牌无效，这将失败，我们将需要从头开始重新进行身份验证。

## 下一主题

[好友和聊天](../friends/)