会话 #

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

在Nakama中,会话由客户端对象表示,在访问服务器功能时该对象证明客户端已通过身份验证

在配置中,可通过token_expiry_sec属性设置会话持续时间。建议将会话持续时间设置为游戏平均游戏会话长度的2到3倍。

会话会过期,在该设置的时间段后失效。当客户端进行身份验证时,会提供一个刷新令牌,只要刷新令牌保持有效,就可以获取新的身份验证令牌。刷新令牌过期后,需要与服务器进行重新身份验证以获取新会话。

会话详细信息 #

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

Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
4
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));
Client
1
2
3
4
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);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);
Client
1
2
3
var deviceid = SystemInfo.deviceUniqueIdentifier;
Session session = client.authenticateDevice(deviceid).get();
System.out.format("Session %s", session.getAuthToken());
Client
1
2
3
4
5
6
7
8
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)
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Client
1
client->authenticateRefresh(session);
Client
1
client.session_refresh(session.refresh_token)
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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"
  }
}
Client
1
session = yield(client.session_refresh_async(session), "completed)
Client
1
session = await client.sessionRefresh(session);
Client
1
session = await client.SessionRefreshAsync(session);

在刷新会话时,可以选择新的会话变量。这些新的值将替换当前存储在会话对象中的会话变量。

会话注销 #

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

Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Client
1
client.session_logout(session.refresh_token, session.token)
Client
1
2
3
4
5
6
7
8
9
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>"
}
Client
1
yield(client.session_logout_async(session), "completed")
Client
1
await client.sessionLogout(session);
Client
1
await client.SessionLogoutAsync(session);

注销用户的会话将取消其身份验证和刷新令牌,但_并不_端口其开放套接字连接。这可以通过服务器端sessionDisconnect函数完成。请参阅对应的函数参考

会话变量 #

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

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

在刷新会话时,可以选择新的会话变量。这些新的值将替换当前存储在会话对象中的会话变量。

客户端设置 #

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

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language gdscript has not been found. Please choose another language to show equivalent examples.

Client
1
2
3
local vars = { key = "value" }
local create = true
local result = client.authenticate_email(email, password, vars, create, "mycustomusername")
Client
1
2
3
4
5
Map<String, String> vars = new HashMap<>();
vars.put("Key", "Value");
vars.put("Key2", "Value2");

Session session = client.authenticateEmail("<email>", "<password>", vars).get();
Client
1
2
3
4
5
6
7
var vars = {
    "key" : "value",
    "key2" : "value2"
    # ...
}

var session : NakamaSession = yield(client.authenticate_email_async("<email>", "<password>", null, true, vars), "completed")
Client
1
2
3
4
5
var vars = new Dictionary<string, string>();
vars["key"] = "value";
vars["key2"] = "value2";
// ...
var session = await client.AuthenticateEmailAsync("<email>", "<password>", null, true, vars);
Client
1
2
3
const create = true;
const session = await client.authenticateEmail(email, password, create, "mycustomusername", {key: "value"});
console.info("Successfully authenticated:", session);
Client
1
2
3
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" }}'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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"
    }
}

本示例演示通过电子邮件身份验证设置会话变量,但对于所有其他身份验证选项和在所有客户端库中都可以使用此功能。

服务器设置 #

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

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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")
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Server
1
2
3
4
5
6
7
8
9
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")
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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
}