# 会话

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/session/
**Summary:** 会话表示用户经过了服务器的身份验证的一段时间。该会话由客户端对象表示，该对象证明客户端已通过身份验证。会话会过期，在您的服务器配置中设定的时间之后失效。

---


# 会话

一般来说，“会话”是指用户通过系统认证的一段时间。

在Nakama中，会话由客户端对象表示，在访问服务器功能时该对象证明客户端[已通过身份验证](../authentication/)。

在配置中，可通过[token_expiry_sec](../../getting-started/configuration/#common-properties)属性设置会话持续时间。建议将会话持续时间设置为游戏平均游戏会话长度的2到3倍。

会话会过期，在该设置的时间段后失效。当客户端进行身份验证时，会提供一个刷新令牌，只要刷新令牌保持有效，就可以获取新的身份验证令牌。刷新令牌[过期](../../getting-started/configuration/#session.refresh_token_expiry_sec)后，需要与服务器进行重新身份验证以获取新会话。

## 会话详细信息

您可以使用以下代码访问用户的id、名称以及他们的会话是否过期：

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

{{< code type="client" >}}
```javascript
const id = "3e70fd52-7192-11e7-9766-cb3ce5609916";
const session = await client.authenticateDevice(id)
console.info("id:", session.user_id, "username:", session.username);
console.info("Session expired?", session.isexpired(Date.now() / 1000));
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string id = "3e70fd52-7192-11e7-9766-cb3ce5609916";
var session = await client.AuthenticateDeviceAsync(id);
System.Console.WriteLine("Id '{0}' Username '{1}'", session.UserId, session.Username);
System.Console.WriteLine("Session expired? {0}", session.IsExpired);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto loginFailedCallback = [](const NError& error)
{
};

auto loginSucceededCallback = [](NSessionPtr session)
{
    cout << "id " << session->getUserId() << " username " << session->getUsername() << endl;
    cout << "Session expired? " << (session->isExpired() ? "yes" : "no") << endl;
};

std::string deviceId = "3e70fd52-7192-11e7-9766-cb3ce5609916";

client->authenticateDevice(deviceId, opt::nullopt, opt::nullopt, {}, loginSucceededCallback, loginFailedCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
var deviceid = SystemInfo.deviceUniqueIdentifier;
Session session = client.authenticateDevice(deviceid).get();
System.out.format("Session %s", session.getAuthToken());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var session : NakamaSession = yield(client.authenticate_device_async(deviceid), "completed")

if session.is_exception():
    print("An error occurred: %s" % session)
    return

print("Id '%s' Username '%s'" % [session.id, session.username])
print("Session expired? %s" % session.expired)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = client.authenticate_device(defold.uuid())

if result.error then
  print(result.message)
  return
end

print(("Id '%s' Username '%s' "):format(result.user_id, result.username))

if os.time() > result.expires then
  print("Session has expired")
end
```
{{< / code >}}

## 会话刷新

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="java" />}}

{{< code type="client" >}}
```cpp
client->authenticateRefresh(session);
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
client.session_refresh(session.refresh_token)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/session/refresh/
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "<Refresh token>",
  "vars": {
    "key": "value",
    "key2": "value2"
  }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
session = yield(client.session_refresh_async(session), "completed)
```
{{< / code >}}

{{< code type="client" >}}
```javascript
session = await client.sessionRefresh(session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
session = await client.SessionRefreshAsync(session);
```
{{< / code >}}

在刷新会话时，可以选择新的[会话变量](#session-variables)。这些新的值将替换当前存储在会话对象中的会话变量。

## 会话注销

使用户能够注销活动会话是一种良好的安全做法。例如，当使用共享设备时，注销可以用于防止他人进行未经授权的访问。

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="java" />}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
client.session_logout(session.refresh_token, session.token)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/session/logout/
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Basic base64(ServerKey:)
{
  "token": "<Session token to log out>",
  "refreshToken": "<Refresh token to invalidate>"
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
yield(client.session_logout_async(session), "completed")
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.sessionLogout(session);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
await client.SessionLogoutAsync(session);
```
{{< / code >}}

注销用户的会话将取消其身份验证和刷新令牌，但_并不_端口其开放套接字连接。这可以通过服务器端`sessionDisconnect`函数完成。请参阅对应的[函数参考](../../server-framework/typescript-runtime/function-reference/#Sessions)。

## 会话变量

会话变量可以让客户端和服务器端代码在游戏服务器生成的会话令牌中嵌入额外的密钥/值对。这使得会话令牌可以充当边缘缓存，只要会话保持活动状态，就可以获得信息。

这些可用于实现不同的功能和工具，如分析、推荐奖金或奖励计划。

在刷新会话时，可以选择新的[会话变量](#session-refresh)。这些新的值将替换当前存储在会话对象中的会话变量。

### 客户端设置

要设置客户端身份验证请求中的变量，请使用：

{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="lua" framework="defold" />}}

{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="gdscript" />}}

{{< code type="client" framework="defold">}}
```lua
local vars = { key = "value" }
local create = true
local result = client.authenticate_email(email, password, vars, create, "mycustomusername")
```
{{< / code >}}

{{< code type="client" >}}
```java
Map<String, String> vars = new HashMap<>();
vars.put("Key", "Value");
vars.put("Key2", "Value2");

Session session = client.authenticateEmail("<email>", "<password>", vars).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var vars = {
    "key" : "value",
    "key2" : "value2"
    # ...
}

var session : NakamaSession = yield(client.authenticate_email_async("<email>", "<password>", null, true, vars), "completed")
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var vars = new Dictionary<string, string>();
vars["key"] = "value";
vars["key2"] = "value2";
// ...
var session = await client.AuthenticateEmailAsync("<email>", "<password>", null, true, vars);
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const create = true;
const session = await client.authenticateEmail(email, password, create, "mycustomusername", {key: "value"});
console.info("Successfully authenticated:", session);
```
{{< / code >}}

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/account/authenticate/email?create=true&username=mycustomusername" \
--user 'defaultkey:' \
--data '{"email": "hello@heroiclabs.com", "password": "password", "vars": { "key": "value" }}'
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/account/authenticate/email?create=true&username=mycustomusername
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
    "email": "hello@heroiclabs.com",
    "password": "password",
    "vars": {
        "key": "value"
    }
}
```
{{< / code >}}

本示例演示通过电子邮件身份验证设置会话变量，但对于所有其他[身份验证选项](../authentication/)和在所有[客户端库](../../client-libraries/)中都可以使用此功能。

### 服务器设置

服务器可以设置会话变量，但是仅在__before__身份验证挂钩中。

{{< code type="server" >}}
```lua
local nk = require("nakama")

local function set_session_vars(context, payload)
  nk.logger_info("User session contains key-value pairs set by the client: " .. nk.json_encode(payload.account.vars))

  payload.account.vars["key_added_in_lua"] = "value_added_in_lua"
  return payload
end

nk.register_req_before(set_session_vars, "AuthenticateEmail")
```
{{< / code >}}

{{< code type="server" >}}
```go
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterBeforeAuthenticateEmail(SetSessionVars); err != nil {
        logger.Error("Unable to register: %v", err)
        return err
    }

    return nil
}

func SetSessionVars(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.AuthenticateEmailRequest) (*api.AuthenticateEmailRequest, error) {
    logger.Info("User session contains key-value pairs set the client: %v", in.GetAccount().Vars)

    if in.GetAccount().Vars == nil {
        in.GetAccount().Vars = map[string]string{}
    }

    in.GetAccount().Vars["key_added_in_go"] = "value_added_in_go"

    return in, nil
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let setSessionVars: nkruntime.BeforeHookFunction<nkruntime.AuthenticateEmailRequest> = function (context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: nkruntime.AuthenticateEmailRequest) {
  payload.account.vars = {
    key: 'key',
    value: 'value',
  }

  return payload;
}

function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerBeforeAuthenticateEmail(setSessionVars);
}
```
{{< / code >}}


### 通过服务器访问

设置后，会话变量将变为只读属性，可以在任何服务器挂钩中进行访问。更改或删除会话变量的唯一方法是强制客户端再次进行身份验证。

{{< missing type="server" lang="typescript" />}}

{{< code type="server" >}}
```lua
local nk = require("nakama")

local function access_session_vars(context, payload)
  nk.logger_info("User session contains key-value pairs set by both the client and the before authentication hook: " .. nk.json_encode(context.vars))

  return payload
end

nk.register_req_before(access_session_vars, "GetAccount")
```
{{< / code >}}

{{< code type="server" >}}
```go
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    if err := initializer.RegisterBeforeGetAccount(AccessSessionVars); err != nil {
        logger.Error("Unable to register: %v", err)
        return err
    }

    return nil
}

func AccessSessionVars(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) error {
    vars, ok := ctx.Value(runtime.RUNTIME_CTX_VARS).(map[string]string)

    if !ok {
        logger.Info("User session does not contain any key-value pairs set")
        return nil
    }

    logger.Info("User session contains key-value pairs set by both the client and the before authentication hook: %v", vars)
    return nil
}
```
{{< / code >}}
