# 使用 Token 鉴权 (/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2，如果你使用的是 AccessToken，请先参考 [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)将 AccessToken 升级至 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

<DocsImage alt="token 流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg" displayWidth="85%" />

## 前提条件 [#前提条件]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权]

本节以生成 Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannel`[2/2] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```java
// 频道名
String channelId = "xxx";
// 用户 ID
int uid = 0;
// 请求服务端生成 channelId 和 uid 对应的 token
String token = getToken();
// 设置频道媒体选项
ChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// 加入频道
engine.joinChannel(token, channelId, 0, option);
```

### Token 过期处理 [#token-过期处理]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/android/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/android/channel#onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。

- 调用 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/android/channel#updatechannelmediaoptions) 更新 Token。

- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/android/channel#leavechannel-22) 离开当前频道，然后在调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2) 时传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/android/channel#joinchannelex) 加入多个频道，需要调用 [`updateChannelMediaOptionsEx`](/zh-CN/api-reference/rtc/android/channel#updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```java
class RtcEngineEventHandlerImpl extends IRtcEngineEventHandler {
    // Token 即将过期时回调
    @Override
    public void onTokenPrivilegeWillExpire(String token) {
        super.onTokenPrivilegeWillExpire(token);

        // 重新请求生成 Token
        String token = getToken();
        // 更新 Token
        engine.renewToken(token);
    }
}
```

## 拓展功能（可选） [#拓展功能可选]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如果你需要在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_setclientrole) 方法将用户角色设为主播（`BROADCASTER`），或是在调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2) 加入频道时通过 `options` 将 `clientRoleType` 设为 `CLIENT_ROLE_BROADCASTER`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-1]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-1]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2，如果你使用的是 AccessToken，请先参考 [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)将 AccessToken 升级至 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

<DocsImage alt="token 流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg" displayWidth="85%" />

## 前提条件 [#前提条件-1]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-1]

本节以生成 Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-2]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-2]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannelbyToken`[2/4] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```swift
// 频道名
let channelId: String = "xxxx"
// 用户 ID
let uid: UInt = 0
// 请求服务端生成 channelId 和 uid 对应的 token
let token = getToken()
// 设置频道媒体选项
let mediaOption = AgoraRtcChannelMediaOptions()
// 设置用户角色为主播
mediaOption.clientRoleType = .broadcaster
// 使用 Token 加入频道
agoraKit.joinChannel(byToken: token, channelId: channelId, uid: uid, mediaOptions: mediaOption)
```

### Token 过期处理 [#token-过期处理-1]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/ios/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/ios/channel#callback_irtcengineeventhandler_onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。
- 调用 [`updateChannelWithMediaOptions`](/zh-CN/api-reference/rtc/ios/channel#updatechannelwithmediaoptions) 更新 Token。
- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/ios/channel#leavechannel) 离开当前频道，然后在调用 [`joinChannelByToken`[2/4]](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2) 时传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengineex_joinchannelex) 加入多个频道，需要调用 [`updateChannelExWithMediaOptions`](/zh-CN/api-reference/rtc/ios/channel#api_irtcengineex_updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```swift
extension JoinChannelVideoToken: AgoraRtcEngineDelegate {
    // Token 即将过期时回调
    func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        // 重新请求生成 Token
        let token = getToken()
        //更新 Token
        engine.renewToken(token)
    }
}
```

## 拓展功能（可选） [#拓展功能可选-1]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-1]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-1]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如果你需要在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/ios/channel#setclientrole) 方法将用户角色设为主播（`AgoraClientRoleBroadcaster`），或是在调用 [`joinChannelByToken`[2/4]](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2) 加入频道时通过 `mediaOptions` 将 `clientRoleType` 设为 `AgoraClientRoleBroadcaster`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-3]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-3]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-1]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-1]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-1]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-1]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-1]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-1]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-1]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-1]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-1]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档-1]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2，如果你使用的是 AccessToken，请先参考 [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)将 AccessToken 升级至 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

<DocsImage alt="token 流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg" displayWidth="85%" />

## 前提条件 [#前提条件-2]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-2]

本节以生成 Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-4]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-4]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannelbyToken`[2/4] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```swift
// 频道名
let channelId: String = "xxxx"
// 用户 ID
let uid: UInt = 0
// 请求服务端生成 channelId 和 uid 对应的 token
let token = getToken()
// 设置频道媒体选项
let mediaOption = AgoraRtcChannelMediaOptions()
// 设置用户角色为主播
mediaOption.clientRoleType = .broadcaster
// 使用 Token 加入频道
agoraKit.joinChannel(byToken: token, channelId: channelId, uid: uid, mediaOptions: mediaOption)
```

### Token 过期处理 [#token-过期处理-2]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/macos/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/macos/channel#callback_irtcengineeventhandler_onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/macos/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。
- 调用 [`updateChannelWithMediaOptions`](/zh-CN/api-reference/rtc/macos/channel#updatechannelwithmediaoptions) 更新 Token。
- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/macos/channel#leavechannel) 离开当前频道，然后在调用 [`joinChannelByToken`[2/4]](/zh-CN/api-reference/rtc/macos/channel#api_irtcengine_joinchannel2) 时传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/macos/channel#api_irtcengineex_joinchannelex) 加入多个频道，需要调用 [`updateChannelExWithMediaOptions`](/zh-CN/api-reference/rtc/macos/channel#api_irtcengineex_updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```swift
extension JoinChannelVideoToken: AgoraRtcEngineDelegate {
    // Token 即将过期时回调
    func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        // 重新请求生成 Token
        let token = getToken()
        //更新 Token
        engine.renewToken(token)
    }
}
```

## 拓展功能（可选） [#拓展功能可选-2]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-2]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-2]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如果你需要在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/macos/channel#setclientrole) 方法将用户角色设为主播（`AgoraClientRoleBroadcaster`），或是在调用 [`joinChannelByToken`[2/4]](/zh-CN/api-reference/rtc/macos/channel#api_irtcengine_joinchannel2) 加入频道时通过 `mediaOptions` 将 `clientRoleType` 设为 `AgoraClientRoleBroadcaster`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-5]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-5]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-2]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-2]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-2]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-2]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-2]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-2]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-2]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-2]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-2]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档-2]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2，如果你使用的是 AccessToken，请先参考 [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)将 AccessToken 升级至 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

![token 流程](https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg)

## 前提条件 [#前提条件-3]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-3]

本节以生成 Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-6]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-6]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名、App ID 等信息，通过 `join` 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID、频道名和 App ID 必须与生成 Token 时保持一致。
:::

```javascript
// 将获取到的 Token 赋值给 join 方法中的 token 参数，然后加入频道。
let token = await fetchToken(uid, options.channel, 1);
await client.join(options.appId, options.channel, token, uid);
```

## 进阶功能 [#进阶功能]

本节介绍 Token 相关的进阶功能，包括 Token 过期后的处理方式、如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### Token 过期处理 [#token-过期处理-3]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`token-privilege-will-expire `](/zh-CN/api-reference/rtc/web/interfaces/iagorartcclient#event_token_privilege_will_expire) 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 方法将新生成的 Token 传给 SDK。

示例代码如下：

```javascript
// 收到 token-privilege-will-expire 回调时，从服务器重新申请一个 Token，并调用 renewToken 将新的 Token 传给 SDK。
client.on("token-privilege-will-expire", async function () {
    let token = await fetchToken(uid, options.channel, 1);
    await client.renewToken(token);
});
```

Token 过期时，SDK 会触发 [`token-privilege-did-expire`](/zh-CN/api-reference/rtc/web/interfaces/iagorartcclient#token-privilege-did-expire) 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `join` 方法，再使用新的 Token 重新加入频道。

示例代码如下：

```javascript
// 收到 token-privilege-did-expire 回调时，从服务器重新申请一个 Token，并调用 join 重新加入频道。
client.on("token-privilege-did-expire", async function () {
    console.log("Fetching the new Token")
    let token = await fetchToken(uid, options.channel, 1);
    console.log("Rejoining the channel with new Token")
    await client.join(options.appId, options.channel, token, uid);
});
```

### 生成通配 Token [#生成通配-token-3]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-3]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 只有用户角色为主播时可以在频道内发流。调用 [`setClientRole`](/zh-CN/api-reference/rtc/web/interfaces/iagorartcclient#setclientrole) 方法，将角色 role 设为 "host" （主播）。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-7]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-7]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-3]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-3]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-3]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-3]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-3]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-3]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-3]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-3]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-3]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

### 相关文档 [#相关文档-3]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)
- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2，如果你使用的是 AccessToken，请先参考 [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade)将 AccessToken 升级至 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

![token 流程](https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg)

## 前提条件 [#前提条件-4]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-4]

本节以生成 RTC Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-8]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-8]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannel`[2/2] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```cpp
// 频道名
const char* channelId;
// 用户 ID
int uid = 0
// 请求服务端生成 channelId 和 uid 对应的 token
const char* token = getToken();
// 设置频道媒体选项
ChannelMediaOptions options;
// 将用户角色设为主播
options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
// 加入频道
m_rtcEngine->joinChannel(token, channelId, uid, options);
```

### Token 过期处理 [#token-过期处理-4]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。

- 调用 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#updatechannelmediaoptions) 更新 Token。

- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#leavechannel-22) 离开当前频道，然后再调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#api_irtcengine_joinchannel2) 并传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#joinchannelex) 加入多个频道，需要调用 [`updateChannelMediaOptionsEx`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```cpp
class CJoinChannelVideoByTokenRtcEngineEventHandler
    : public IRtcEngineEventHandler
{
public:
    // Token 即将过期时回调
    virtual void onTokenPrivilegeWillExpire(const char* token) {
        // 重新请求生成 Token
        const char* token = getToken();
        // 更新 Token
        m_rtcEngine->renewToken(token);
    }
}
```

## 拓展功能（可选） [#拓展功能可选-3]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-4]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-4]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如需在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#api_irtcengine_setclientrole) 方法将用户角色设为主播（`BROADCASTER`），或是在调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#api_irtcengine_joinchannel2) 加入频道时通过 `options` 将 `clientRoleType` 设为 `CLIENT_ROLE_BROADCASTER`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-9]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-9]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-4]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-4]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-4]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。

<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将生成 Token 的示例代码贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-4]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-4]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-4]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-4]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-4]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-4]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档-4]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="harmonyos" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

:::info[信息]
本文仅适用于 AccessToken2。
:::

下图展示声网使用 Token 对用户鉴权的流程：

<DocsImage alt="token 流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg" displayWidth="85%" />

## 前提条件 [#前提条件-5]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-5]

本节以生成 Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-10]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-10]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannel` 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```arkts
// 设置频道媒体选项
let mediaOption: ChannelMediaOptions = new ChannelMediaOptions();
mediaOption.publishCameraTrack = true;
mediaOption.publishMicrophoneTrack = true;
mediaOption.autoSubscribeVideo = true;
mediaOption.autoSubscribeAudio = true;
mediaOption.channelProfile = Constants.ChannelProfile.LIVE_BROADCASTING;
mediaOption.clientRoleType = Constants.ClientRole.BROADCASTER;
// 使用 token 加入频道
let ret = this.rtcEngine?.joinChannelWithOptions(this.token, this.channelName, 0, mediaOption)
if (ret != Constants.ErrorCode.ERR_OK) {
    ShowToast.longToast("joinChannelWithOptions error " + ret + " : " + RtcEngine.getErrorDescription(ret))
    return
}
```

### Token 过期处理 [#token-过期处理-5]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/harmonyos/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/harmonyos/channel#onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/harmonyos/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。

- 调用 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/harmonyos/channel#updatechannelmediaoptions) 更新 Token。

- 调用 [`leaveChannel`](/zh-CN/api-reference/rtc/harmonyos/channel#api_irtcengine_leavechannel2) 离开当前频道，然后在调用 [`joinChannel`](/zh-CN/api-reference/rtc/harmonyos/channel#api_irtcengine_joinchannel2) 时传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/harmonyos/channel#joinchannelex) 加入多个频道，需要调用 [`updateChannelMediaOptionsEx`](/zh-CN/api-reference/rtc/harmonyos/channel#updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```arkts
config.mEventHandler.onTokenPrivilegeWillExpire =async  (oldToken: string) => {
    Logger.info(TAG, `onTokenPrivilegeWillExpire oldToken = ${oldToken}`);
    // 请求服务端重新生成 Token
    this.token = getTokenFromServer()
    // 更新 Token
    this.rtcEngine?.renewToken(this.token)
}
```

## 拓展功能（可选） [#拓展功能可选-4]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-5]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-5]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如果你需要在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/harmonyos/channel#setclientrole) 方法将用户角色设为主播（`BROADCASTER`），或是在调用 [`joinChannelWithOptions`](/zh-CN/api-reference/rtc/harmonyos/channel#api_irtcengine_joinchannel2) 加入频道时通过 `options` 将 `clientRoleType` 设为 `CLIENT_ROLE_BROADCASTER`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-11]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-11]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-5]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-5]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-5]

以 Go 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 相关信息 [#相关信息]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-5]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-5]

以 Go 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-5]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位有符号整数，取值范围为 -231 - 1 到 231 - 1， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-5]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位有符号整数，取值范围为 -231 - 1 到 231 - 1， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-5]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

### 相关文档 [#相关文档-5]

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="mini-program" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

下图展示声网使用 Token 对用户鉴权的流程：

![token 流程](https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg)

## 前提条件 [#前提条件-6]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现加入频道鉴权 [#实现加入频道鉴权]

本节介绍如何使用 Token 对加入频道的用户鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-12]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-12]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `join` 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```javascript
// 频道名
const channel = 'xxx'
// 用户 ID
const uid = 1
// 请求服务端生成 channel 和 uid 对应的 token
const token = getToken()
// 是否为纯音频场景
const isAudioOnly = false
// 用户 ID 类型  0: int 1: string
const uidType = 0
// 加入频道
await client.join(
  token,
  channel,
  uid,
  isAudioOnly,
  uidType
);
```

### Token 过期处理 [#token-过期处理-6]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 `token-privilege-will-expire` 事件；Token 过期时，SDK 会触发 `token-privilege-did-expire` 事件。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后调用 `renewToken` 更新 Token。

下列示例代码展示在收到 `token-privilege-will-expire` 事件提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```javascript
client.on("token-privilege-will-expire", async () => {
  // 请求服务端生成 channel 和 uid 对应的新 token
  const token = getToken()
  // 重新鉴权
  await client.renewToken(token)
})
```

## 部署与调试 [#部署与调试-6]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-6]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-6]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

:::warning[注意]
在开始前，请确保你的 [Golang](https://golang.org/dl/) 版本是 1.14 或以上版本。
:::
<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将使用 Golang [生成 Token 的示例代码](#token-code)贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

<a id="api"></a>
## 参考信息 [#参考信息-5]
### API 参考 [#api-参考-6]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

#### BuildTokenWithUid [#buildtokenwithuid-6]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

### 相关文档 [#相关文档-6]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />
鉴权是指在用户访问你的系统前，对其进行身份校验。用户在使用声网服务，如加入音视频通话或登录信令系统时，声网使用 Token 对其鉴权。

本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器，以及如何搭建一个使用 Token 鉴权的客户端。

## 鉴权原理 [#鉴权原理]

下图展示了鉴权的基本流程：

![鉴权流程图](https://web-cdn.agora.io/docs-files/1673942117005)

1. 客户端根据需要，向 App 服务端申请 Token。
2. App 服务端生成并返回 Token。
3. 客户端以用户 ID、频道名以及获取到的 Token 加入频道。
4. 声网平台读取该 Token 中包含的信息，并进行校验。
5. 客户端收到加入频道成功回调，并获取用户 ID。
6. Token 最大有效期为 24 小时。当即将过期时，客户端会收到 Token 即将过期的回调。
7. 此时，如果客户端需要继续进行音视频互动，需要申请新的 Token。
8. App 服务端生成并返回 Token。
9. 客户端更新 Token。

:::info[信息]
Token 过期时，SDK 会触发 Token 过期回调。收到该回调后，客户端需要从服务器获取新的 Token，再使用新的 Token 重新加入频道。
:::

你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。

Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 的过期时间

## 前提条件 [#前提条件-7]

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/zh-CN/introduction/quickstart#注册账号)。
- 已开启 [App 证书](/zh-CN/introduction/user-guides/manage-authentication#启用主要证书)的声网项目。
- [Golang](https://golang.org/) 1.14 或以上版本，GO111MODULE 设置为开启。

  :::warning[注意]
  如果你使用的是 Go 1.16 或以上版本，GO111MODULE 已默认开启。详情请参考 [New module changes in Go 1.16](https://blog.golang.org/go116-module-changes)。
  :::

- [npm](https://www.npmjs.com/get-npm) 以及[支持的浏览器](/zh-CN/realtime-media/rtc/reference/browser-compatibility)。

## 实现鉴权流程 [#实现鉴权流程]

本节介绍如何使用声网提供的[代码](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)生成并提供 Token，对用户及其权限进行校验。

### 获取 App ID 及 App 证书 [#获取-app-id-及-app-证书]

本节介绍如何获取生成 Token 所需的安全信息，如你的项目的 App ID 及 App 证书。

#### 获取 App ID [#获取-app-id]

声网会给每个项目自动分配一个 App ID 作为项目唯一标识。

在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击 App ID 右侧的图标，即可获取项目的 App ID。

#### 获取 App 证书 [#获取-app-证书]

参考以下步骤获取 App 证书：

1. 在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击**配置**。
  ![编辑项目](https://web-cdn.agora.io/docs-files/1641871111769)

2. 点击**主要证书**下面的复制图标，即可获取项目的 App 证书。
  ![获取 App 证书](https://web-cdn.agora.io/docs-files/1637637672988)

### 部署 Token 服务器 [#部署-token-服务器]

Token 需要在你的服务端部署生成。当客户端发送请求时，服务端部署的 Token 生成器会生成相应的 Token，再发送给客户端。

本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

此示例服务器使用 `BuildTokenWithUid` [1/2]。

:::warning[注意]
此示例服务器仅用于演示，请勿用于生产环境中。
:::

1. 创建一个 `server.go` 文件，然后贴入如下代码。将其中的 `<Your App ID>` 和 `<Your App Certificate>` 替换为你的 App ID 和 App 证书。

```go
package main
import (
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "errors"
    "strconv"
)
type rtc_int_token_struct struct{
    Uid_rtc_int uint32 `json:"uid"`
    Channel_name string `json:"ChannelName"`
    Role uint32 `json:"role"`
}
var rtc_token string
var int_uid uint32
var channel_name string
var role_num uint32
var role rtctokenbuilder.Role
// 使用 RtcTokenBuilder 来生成 RTC Token
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role){
    appID := "<Your App ID>"
    appCertificate := "<Your App Certificate>"
    // AccessToken2 过期的时间，单位为秒
    // 当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 回调
    // 但一旦用户和频道断开连接，用户将无法使用该 Token 加入同一频道。请确保 AccessToken2 的过期时间晚于权限过期时间
    tokenExpireTimeInSeconds := uint32(40)
    // 权限过期的时间，单位为秒
    // 权限过期30秒前会触发 token-privilege-will-expire 回调
    // 权限过期时会触发 token-privilege-did-expire 回调
    // 为作演示，在此将过期时间设为 40 秒。你可以看到客户端自动更新 Token 的过程
    privilegeExpireTimeInSeconds := uint32(40)
    result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with uid: %s\n", result)
        fmt.Printf("uid is %d\n", int_uid )
        fmt.Printf("ChannelName is %s\n", channelName)
        fmt.Printf("Role is %d\n", role)
    }
    rtc_token = result
}
func rtcTokenHandler(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
    w.Header().Set("Access-Control-Allow-Headers", "*");
    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }
    if r.Method != "POST" && r.Method != "OPTIONS" {
        http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
        return
    }
    var t_int rtc_int_token_struct
    var unmarshalErr *json.UnmarshalTypeError
    int_decoder := json.NewDecoder(r.Body)
    int_err := int_decoder.Decode(&t_int)
    if (int_err == nil) {
                int_uid = t_int.Uid_rtc_int
                channel_name = t_int.Channel_name
                role_num = t_int.Role
                switch role_num {
                    case 1:
                        role = rtctokenbuilder.RolePublisher
                    case 2:
                        role = rtctokenbuilder.RoleSubscriber
                }
    }
    if (int_err != nil) {
        if errors.As(int_err, &unmarshalErr){
                errorResponse(w, "Bad request. Wrong type provided for field " + unmarshalErr.Value  + unmarshalErr.Field + unmarshalErr.Struct, http.StatusBadRequest)
                } else {
                errorResponse(w, "Bad request.", http.StatusBadRequest)
            }
        return
    }
    generateRtcToken(int_uid, channel_name, role)
    errorResponse(w, rtc_token, http.StatusOK)
    log.Println(w, r)
}
func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.WriteHeader(httpStatusCode)
    resp := make(map[string]string)
    resp["token"] = message
    resp["code"] = strconv.Itoa(httpStatusCode)
    jsonResp, _ := json.Marshal(resp)
    w.Write(jsonResp)
}
func main(){
    // 使用 int 型 uid 生成 RTC Token
    http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
    fmt.Printf("Starting server at port 8082\n")
    if err := http.ListenAndServe(":8082", nil); err != nil {
        log.Fatal(err)
    }
}
```

2. `go.mod` 文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建 `go.mod` 文件：

```go
$ go mod init sampleServer
```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

```go
$ go get
```

4. 运行如下命令行启动服务器：

```go
$ go run server.go
```

### 使用 Token 对用户鉴权 [#使用-token-对用户鉴权]

本节以 Windows 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

基于你在[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)时创建的项目，在如下代码示例中，你可以看到 Token 与客户端的如下代码逻辑有关：

 - 调用 `joinChannel` 方法，使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。
 - 在 Token 过期前 30 秒，SDK 会触发 `onTokenPrivilegeWillExpire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 将新生成的 Token 传给 SDK。
 - Token 过期时，SDK 会触发 `onRequestToken` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `joinChannel` 方法，再使用新的 Token 重新加入频道。

```typescript
const {
    createAgoraRtcEngine,
    VideoMirrorModeType,
    VideoSourceType,
    RenderModeType,
    ChannelProfileType,
    ClientRoleType,
} = require("agora-electron-sdk");

let rtcEngine;
let localVideoContainer;
let remoteVideoContainer;
let isJoined = false;

const EventHandles = {
    // 监听本地用户加入频道事件
    onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
        console.log('成功加入频道：' + channelId);
        isJoined = true;
        // 本地用户加入频道后，设置本地视频窗口
        rtcEngine.setupLocalVideo({
            sourceType: VideoSourceType.VideoSourceCameraPrimary,
            view: localVideoContainer,
            mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled,
            renderMode: RenderModeType.RenderModeFit,
        });
    },

    onLeaveChannel: ({ channelId, localUid }, stats) => {
        console.log('成功退出频道：' + channelId);
        isJoined = false;
    },

    // 监听远端用户加入频道事件
    onUserJoined: ({ channelId, localUid }, remoteUid, elapsed) => {
        console.log('远端用户 ' + remoteUid + ' 已加入');
        // 远端用户加入频道后，设置远端视频窗口
        rtcEngine.setupRemoteVideoEx(
            {
                sourceType: VideoSourceType.VideoSourceRemote,
                uid: remoteUid,
                view: remoteVideoContainer,
                mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled,
                renderMode: RenderModeType.RenderModeFit,
            },
            { channelId },
        );
    },

    // 监听 Token 即将过期事件
    onTokenPrivilegeWillExpire: ({ channelId, localUid }, token) => {
        console.log('token ' + token + '即将过期');
        // Token 过期后重新从服务器获取新的 Token
        fetchToken(channelId, localUid, ClientRoleType.ClientRoleBroadcaster)
    },

    // 监听 Token 已过期事件
    onRequestToken: ({ channelId, localUid }) => {
        console.log('token ' + '已过期');
        token = ''
    }
};

// 向 Token 服务器发送获取 Token 请求
function fetchToken(channelName, uid, role) {
    // Token 服务器 URL 示例：http://12.123.1.123:8082/fetch_rtc_token
    const url = '<Your Host URL and port>/fetch_rtc_token';
    const body = JSON.stringify({
        uid,
        ChannelName: channelName,
        role
    });
    fetch(url, {
        method: 'POST',
        body,
    })
        .then((res) => res.json())
        .then((res) => {
            console.log('token ' + res.token);
            if (+res.code === 200) {
                if (isJoined) {
                    rtcEngine.renewToken(res.token)
                } else {
                    rtcEngine.joinChannel(res.token, channelName, uid, { clientRoleType: role })
                }
            }
            return res;
        });
}

window.onload = () => {
    const os = require("os");
    const path = require("path");

    // 填入你的 App ID
    const APPID = "<Your App ID>";
    // 填入生成 Token 时使用的频道名
    const channel = "Test";

    localVideoContainer = document.getElementById("join-channel-local-video");
    remoteVideoContainer = document.getElementById("join-channel-remote-video");
    const sdkLogPath = path.resolve(os.homedir(), "./test.log");

    // 创建 RtcEngine 实例
    rtcEngine = createAgoraRtcEngine();


    // 初始化 RtcEngine 实例
    rtcEngine.initialize({
        appId: APPID,
        logConfig: { filePath: sdkLogPath }
    });

    // 注册事件回调
    rtcEngine.registerEventHandler(EventHandles);

    // 设置频道场景为直播场景
    rtcEngine.setChannelProfile(ChannelProfileType.ChannelProfileLiveBroadcasting);

    // 设置用户角色，主播设为 ClientRoleBroadcaster，观众设为 ClientRoleAudience
    rtcEngine.setClientRole(ClientRoleType.ClientRoleBroadcaster);

    // 视频默认禁用，你需要调用 enableVideo 启用视频流
    rtcEngine.enableVideo();

    // 开启摄像头预览
    rtcEngine.startPreview();

    // 使用 Token 加入频道
    // 你需要自行指定用户 ID，并确保其在频道内的唯一性
    fetchToken(channel, 123456, ClientRoleType.ClientRoleBroadcaster);
};
```

### 运行项目 [#运行项目]

在本地设备中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 40 秒更新 Token。

## 参考 [#参考]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-6]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）:

| SDK                  | 版本     |
| :------------------- | :------- |
| RTC Native 4.x SDK        | &ge; v4.0.0 |
| RTC Electron 4.x SDK     |&ge; v4.0.0 |
| RTC Unity 4.x SDK        | &ge; v4.0.0 |
| RTC React Native 4.x SDK |&ge; v4.0.0 |
| RTC Flutter 4.x SDK     | &ge; v6.0.0 |
| RTC Native 3.x  SDK       | &ge; 3.6.0 |
| RTC Electron 3.x SDK     | &ge; 3.6.0 |
| RTC Unity 3.x SDK        | &ge; 3.6.0 |
| RTC React Native 3.x SDK | &ge; 3.6.0 |
| RTC Flutter 3.x SDK      | &ge; 5.10  |
| RTC Web SDK         | &ge; v4.8.0 |

使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。

### Token 生成器代码 [#token-生成器代码]

声网在 GitHub 上提供一个开源的 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) 仓库，支持使用 C++、Java、Go 等语言在你自己的服务器上生成 AccessToken2。

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

### BuildTokenWithUid API 参考 [#buildtokenwithuid-api-参考]

本节介绍生成 AccessToken2 的核心方法 `BuildTokenWithUid`。AccessToken2 生成器代码中提供两个 `BuildTokenWithUid` 方法：

- `BuildTokenWithUid` [1/2]：生成 AccessToken2，并设置 AccessToken2 和所有权限的过期时间。

- `BuildTokenWithUid` [2/2]：生成 AccessToken2，并设置如下过期时间：

  - AccessToken2 过期时间
  - 加入频道权限的过期时间
  - 频道内发布音频流权限的过期时间
  - 频道内发布视频流权限的过期时间
  - 频道内发布数据流权限的过期时间

#### BuildTokenWithUid [1/2] [#buildtokenwithuid-12]

该方法使用 `token_expire` 参数设置 AccessToken2 的过期时间，使用 `privilege_expire` 参数设置所有权限的过期时间。

```cpp
// 以 C++ 为例
static std::string BuildTokenWithUid(const std::string& app_id,
                                       const std::string& app_certificate,
                                       const std::string& channel_name,
                                       uint32_t uid,
									   UserRole role,
                                       uint32_t token_expire,
                                       uint32_t privilege_expire = 0);
```

| 参数 | 描述 |
| --- | --- |
| `app_id` | 你在声网控制台创建项目时生成的 App ID。 |
| `app_certificate` | 你的项目的 App 证书。 |
| `channel_name` | [token-authentication-electron-markdown-2-3-1 details](#table-slot-token-authentication-electron-markdown-2-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为1到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | 权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。`kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。`kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](#cohost)。 |
| `token_expire` | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600，则 AccessToken2 会在生成后 10 分钟过期。AccessToken2 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `privilege_expire` | 从 AccessToken2 生成到所有权限过期的时间长度，单位为秒。例如，如果你将 `privilege_expire` 设为 600，则权限会在生成后 10 分钟过期。如果你将此参数设为 0（默认值），则权限永不过期。 |

<a id="table-slot-token-authentication-electron-markdown-2-3-1"></a>

**token-authentication-electron-markdown-2-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

:::info[信息]
当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但是一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。因此，确保 AccessToken2 的过期时间晚于权限过期时间。
:::

#### BuildTokenWithUid [2/2] [#buildtokenwithuid-22]

为支持在权限级别设置过期时间，声网还提供 `BuildTokenWithUid` 的同名重载方法。支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

```cpp
// 以 C++ 为例
static std::string BuildTokenWithUid(
       const std::string& app_id,
       const std::string& app_certificate,
       const std::string& channel_name,
       uint32_t uid,
       uint32_t token_expire,
       uint32_t join_channel_privilege_expire = 0,
       uint32_t pub_audio_privilege_expire = 0,
       uint32_t pub_video_privilege_expire = 0,
       uint32_t pub_data_stream_privilege_expire = 0);
```

该方法生成 RTC AccessToken2，支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入 RTC 频道
- 在 RTC 频道中发布音频流
- 在 RTC 频道中发布视频流
- 在 RTC 频道中发布数据流

其中，在 RTC 频道中发布音频流、视频流和数据流的权限仅在[开启连麦鉴权服务](#cohost)后才生效。

一个用户可以设置多个权限。每个权限的最大有效时间为 24 小时。权限即将过期或已经过期后，SDK 会分别触发 `onTokenPriviegeWillExpire` 或 `onRequestToken` 回调。你需要在 App 逻辑中添加如下操作：

1. 识别即将过期或已经过期的是哪类权限。
2. App 从 Token 服务器获取新的 AccessToken2。
3. SDK 调用 `renewToken` 以更新 AccessToken2。

:::warning[注意]
你需要根据实际业务场景设置合理的过期时间，并确保 AccessToken2 的过期时间晚于其他权限过期时间。例如，如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户就会被踢出 RTC 频道。当 AccessToken2 过期但加入频道的权限未过期时，用户仍在频道里，并且只要发流权限未过期就可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。
:::

| 参数                               | 描述                                                         |
| :--------------------------------- | :----------------------------------------------------------- |
| `token_expire`                     | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600 ，则 AccessToken2 会在生成后 10 分钟过期。Token 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `join_channel_privilege_expire`    | 从 AccessToken2 生成到加入频道权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则加入频道权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则加入频道权限永不过期。 |
| `pub_audio_privilege_expire`       | 从 AccessToken2 生成到在频道内发布音频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布语音流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布语音流权限永不过期。 |
| `pub_video_privilege_expire`       | 从 AccessToken2 生成到在频道内发布视频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布视频流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布视频流权限永不过期。 |
| `pub_data_stream_privilege_expire` | 从 AccessToken2 生成到在频道内发布数据流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布数据流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布数据流权限永不过期。 |

<a id="cohost"></a>
### 开启连麦鉴权 [#开启连麦鉴权]

请参考以下步骤在声网控制台开启连麦鉴权服务：

1. 登录[声网控制台](https://console.shengwang.cn/)，在**项目列表**区域选择你想要开启的项目，点击配置按钮，进入**编辑项目**页面。
2. 在项目详情页，下滑到功能区域，点击连麦鉴权区域的**启用**。
3. 按照屏幕提示，了解开通该功能的主要事项，勾选后点击**启用**。

在控制台启用连麦鉴权后，该服务会在约 5 分钟后生效。

项目一旦开启了连麦鉴权服务，则用户在频道中发流需要同时满足两个条件：

- 在 `setClientRole` 中设置的 `role` 参数为 `BROADCASTER`(1)。
- 使用 Token 加入频道的用户拥有发流的权限（在 `BuildTokenWithUid `中设置的 `role` 参数为 `kRolePublisher`）。

## 相关文档 [#相关文档-7]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)
- [如何升级到 Token 鉴权方案？](/zh-CN/reference/faq/integration/appid_to_token)
- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
鉴权是指在用户访问你的系统前，对其进行身份校验。用户在使用声网服务，如加入音视频通话或登录信令系统时，声网使用 Token 对其鉴权。

本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器，以及如何搭建一个使用 Token 鉴权的客户端。

## 鉴权原理 [#鉴权原理-1]

下图展示了鉴权的基本流程：

![](https://web-cdn.agora.io/docs-files/1673942117005)

1. 客户端根据需要，向 App 服务端申请 Token。
2. App 服务端生成并返回 Token。
3. 客户端以用户 ID、频道名以及获取到的 Token 加入频道。
4. 声网平台读取该 Token 中包含的信息，并进行校验。
5. 客户端收到加入频道成功回调，并获取用户 ID。
6. Token 最大有效期为 24 小时。当即将过期时，客户端会收到 Token 即将过期的回调。
7. 此时，如果客户端需要继续进行音视频互动，需要申请新的 Token。
8. App 服务端生成并返回 Token。
9. 客户端更新 Token。

:::info[信息]
Token 过期时，SDK 会触发 Token 过期回调。收到该回调后，客户端需要从服务器获取新的 Token，再使用新的 Token 重新加入频道。
:::

你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。

Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 的过期时间

## 前提条件 [#前提条件-8]

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/zh-CN/introduction/quickstart#注册账号)。
- 已开启 [App 证书](/zh-CN/introduction/user-guides/manage-authentication#启用主要证书)的声网项目。
- [Golang](https://golang.org/) 1.14 或以上版本，GO111MODULE 设置为开启。

  :::warning[注意]
  如果你使用的是 Go 1.16 或以上版本，GO111MODULE 已默认开启。详情请参考 [New module changes in Go 1.16](https://blog.golang.org/go116-module-changes)。
  :::

- [npm](https://www.npmjs.com/get-npm) 以及[支持的浏览器](/zh-CN/realtime-media/rtc/reference/browser-compatibility)。

## 实现鉴权流程 [#实现鉴权流程-1]

本节介绍如何使用声网提供的[代码](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)生成并提供 Token，对用户及其权限进行校验。

### 获取 App ID 及 App 证书 [#获取-app-id-及-app-证书-1]

本节介绍如何获取生成 Token 所需的安全信息，如你的项目的 App ID 及 App 证书。

#### 获取 App ID [#获取-app-id-1]

声网会给每个项目自动分配一个 App ID 作为项目唯一标识。

在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击 App ID 右侧的图标，即可获取项目的 App ID。

#### 获取 App 证书 [#获取-app-证书-1]

参考以下步骤获取 App 证书：

1. 在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击**配置**。
  ![编辑项目](https://web-cdn.agora.io/docs-files/1641871111769)

2. 点击**主要证书**下面的复制图标，即可获取项目的 App 证书。
  ![获取 App 证书](https://web-cdn.agora.io/docs-files/1637637672988)

### 部署 Token 服务器 [#部署-token-服务器-1]

Token 需要在你的服务端部署生成。当客户端发送请求时，服务端部署的 Token 生成器会生成相应的 Token，再发送给客户端。

本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

此示例服务器使用 `BuildTokenWithUid` [1/2]。

:::warning[注意]
此示例服务器仅用于演示，请勿用于生产环境中。
:::

1. 创建一个 `server.go` 文件，然后贴入如下代码。将其中的 `<Your App ID> `和 `<Your App Certificate>` 替换为你的 App ID 和 App 证书。

   ```go
   package main

   import (
       rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
       "fmt"
       "log"
       "net/http"
       "encoding/json"
       "errors"
       "strconv"
   )

   type rtc_int_token_struct struct {
       Uid_rtc_int   uint32 `json:"uid"`
       Channel_name  string `json:"ChannelName"`
       Role          uint32 `json:"role"`
   }

   var rtc_token     string
   var int_uid       uint32
   var channel_name  string

   var role_num      uint32
   var role          rtctokenbuilder.Role

   // 使用 RtcTokenBuilder 来生成 RTC Token
   func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role) {

       appID := "<Your App ID>"
       appCertificate := "<Your App Certificate>"
       // AccessToken2 过期的时间，单位为秒
       // 当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 回调
       // 但一旦用户和频道断开连接，用户将无法使用该 Token 加入同一频道。请确保 AccessToken2 的过期时间晚于权限过期时间
       tokenExpireTimeInSeconds := uint32(40)
       // 权限过期的时间，单位为秒
       // 权限过期30秒前会触发 token-privilege-will-expire 回调
       // 权限过期时会触发 token-privilege-did-expire 回调
       // 为作演示，在此将过期时间设为 40 秒。你可以看到客户端自动更新 Token 的过程
       privilegeExpireTimeInSeconds := uint32(40)

       result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
       if err != nil {
           fmt.Println(err)
       } else {
           fmt.Printf("Token with uid: %s\n", result)
           fmt.Printf("uid is %d\n", int_uid)
           fmt.Printf("ChannelName is %s\n", channelName)
           fmt.Printf("Role is %d\n", role)
       }
       rtc_token = result
   }

   func rtcTokenHandler(w http.ResponseWriter, r *http.Request) {
       w.Header().Set("Content-Type", "application/json; charset=UTF-8")
       w.Header().Set("Access-Control-Allow-Origin", "*")
       w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
       w.Header().Set("Access-Control-Allow-Headers", "*")

       if r.Method == "OPTIONS" {
           w.WriteHeader(http.StatusOK)
           return
       }

       if r.Method != "POST" && r.Method != "OPTIONS" {
           http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
           return
       }

       var t_int rtc_int_token_struct
       var unmarshalErr *json.UnmarshalTypeError
       int_decoder := json.NewDecoder(r.Body)
       int_err := int_decoder.Decode(&t_int)
       if int_err == nil {
           int_uid = t_int.Uid_rtc_int
           channel_name = t_int.Channel_name
           role_num = t_int.Role
           switch role_num {
           case 1:
               role = rtctokenbuilder.RolePublisher
           case 2:
               role = rtctokenbuilder.RoleSubscriber
           }
       }
       if int_err != nil {

           if errors.As(int_err, &unmarshalErr) {
               errorResponse(w, "Bad request. Wrong type provided for field "+unmarshalErr.Value+unmarshalErr.Field+unmarshalErr.Struct, http.StatusBadRequest)
           } else {
               errorResponse(w, "Bad request.", http.StatusBadRequest)
           }
           return
       }

       generateRtcToken(int_uid, channel_name, role)
       errorResponse(w, rtc_token, http.StatusOK)
       log.Println(w, r)
   }

   func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
       w.Header().Set("Content-Type", "application/json")
       w.Header().Set("Access-Control-Allow-Origin", "*")
       w.WriteHeader(httpStatusCode)
       resp := make(map[string]string)
       resp["token"] = message
       resp["code"] = strconv.Itoa(httpStatusCode)
       jsonResp, _ := json.Marshal(resp)
       w.Write(jsonResp)
   }

   func main() {
       // 使用 int 型 uid 生成 RTC Token
       http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
       fmt.Printf("Starting server at port 8082\n")

       if err := http.ListenAndServe(":8082", nil); err != nil {
           log.Fatal(err)
       }
   }
   ```

2. `go.mod` 文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建 `go.mod` 文件：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get
   ```

4. 运行如下命令行启动服务器：

   ```go
   $ go run server.go
   ```

### 使用 Token 对用户鉴权 [#使用-token-对用户鉴权-1]

本节展示如何使用 Token 对客户端的用户进行鉴权。

```csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Agora.Rtc;
using Agora.Util;
using Logger = Agora.Util.Logger;

namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.JoinChannelVideoToken
{
    public class JoinChannelVideoToken : MonoBehaviour
    {

        // 填入你的 App ID 和频道名
        public string appID = "";
        public string channelName = "";
        public uint localUid = 0;
        internal static string channelToken = "";

        internal IRtcEngine RtcEngine = null;

        internal CONNECTION_STATE_TYPE _state = CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED;

        private void Start()
        {

            if (CheckAppId())
            {
                InitEngine();
                JoinChannel();
            }
        }

        internal void RenewOrJoinToken(TokenObject tokenObject)
        {
            if (tokenObject ==  null || tokenObject.code != 200) {
                Debug.Log("get token failed");
                return;
            }


            JoinChannelVideoToken.channelToken = tokenObject.token;
            if (_state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
                || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
                || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_FAILED
            )
            {
                // 使用 Token 加入频道
                JoinChannel();
            }
            else
            {
                // 在频道内更新 Token
                UpdateToken();
            }
        }

        private void Update()
        {
            PermissionHelper.RequestMicrophontPermission();
            PermissionHelper.RequestCameraPermission();
        }

        private void UpdateToken()
        {
            RtcEngine.RenewToken(JoinChannelVideoToken.channelToken);
            Debug.Log("renewToken: " + JoinChannelVideoToken.channelToken);
        }

        private bool CheckAppId()
        {
            if (appID.Length < 10) {
                Debug.Log("Please fill in your appId in Inspector");
                return false;
            }
            return true;
        }


        // 初始化
        private void InitEngine()
        {
            RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
            UserEventHandler handler = new UserEventHandler(this);
            RtcEngineContext context = new RtcEngineContext(appID, 0,
                CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING, null,
                AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
            RtcEngine.Initialize(context);
            RtcEngine.InitEventHandler(handler);
        }
        // 加入频道
        private void JoinChannel()
        {
            RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
            RtcEngine.EnableAudio();
            RtcEngine.EnableVideo();

            if (channelToken.Length == 0)
            {
                StartCoroutine(HelperClass.FetchToken(this.localUid, channelName,CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
                return;
            }

            RtcEngine.JoinChannel(channelToken, channelName, "");
            Debug.Log("joinChannel with token:" + channelToken);

        }


        internal void  StartUpdateTokenEveryTenSecond() {
            InvokeRepeating("RequestToken", 10, 10);
        }

        private void RequestToken() {
            StartCoroutine(HelperClass.FetchToken(this.localUid, channelName, CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
        }


        private void OnDestroy()
        {
            Debug.Log("OnDestroy");
            if (RtcEngine == null) return;
            RtcEngine.InitEventHandler(null);
            RtcEngine.LeaveChannel();
            RtcEngine.Dispose();
        }

        internal string GetChannelName()
        {
            return channelName;
        }

        #region -- Video Render UI Logic ---

        internal static void MakeVideoView(uint uid, string channelId = "")
        {
            GameObject go = GameObject.Find(uid.ToString());
            if (!ReferenceEquals(go, null))
            {
                return;
            }

            VideoSurface videoSurface = MakeImageSurface(uid.ToString());
            if (!ReferenceEquals(videoSurface, null))
            {

                if (uid == 0)
                {
                    videoSurface.SetForUser(uid, channelId);
                }
                else
                {
                    videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
                }

                videoSurface.OnTextureSizeModify += (int width, int height) =>
                {
                    float scale = (float)height / (float)width;
                    videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
                    Debug.Log("OnTextureSizeModify: " + width + "  " + height);
                };

                videoSurface.SetEnable(true);
            }
        }

        private static VideoSurface MakePlaneSurface(string goName)
        {
            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);

            if (go == null)
            {
                return null;
            }

            go.name = goName;

            go.transform.Rotate(-90.0f, 0.0f, 0.0f);
            go.transform.position = Vector3.zero;
            go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);

            var videoSurface = go.AddComponent<VideoSurface>();
            return videoSurface;
        }

        private static VideoSurface MakeImageSurface(string goName)
        {
            GameObject go = new GameObject();

            if (go == null)
            {
                return null;
            }

            go.name = goName;
            go.AddComponent<RawImage>();
            go.AddComponent<UIElementDrag>();
            GameObject canvas = GameObject.Find("VideoCanvas");
            if (canvas != null)
            {
                go.transform.parent = canvas.transform;
                Debug.Log("add video view");
            }
            else
            {
                Debug.Log("Canvas is null video view");
            }

            go.transform.Rotate(0f, 0.0f, 180.0f);
            go.transform.localPosition = Vector3.zero;
            go.transform.localScale = new Vector3(3f, 4f, 1f);

            var videoSurface = go.AddComponent<VideoSurface>();
            return videoSurface;
        }

        internal static void DestroyVideoView(uint uid)
        {
            GameObject go = GameObject.Find(uid.ToString());
            if (!ReferenceEquals(go, null))
            {
                Object.Destroy(go);
            }
        }

        #endregion
    }


    internal class UserEventHandler : IRtcEngineEventHandler
    {
        private readonly JoinChannelVideoToken _helloVideoTokenAgora;

        internal UserEventHandler(JoinChannelVideoToken helloVideoTokenAgora)
        {
            _helloVideoTokenAgora= helloVideoTokenAgora;
        }

        public override void OnError(int err, string msg)
        {
            Debug.Log(string.Format("OnError err: {0}, msg: {1}", err, msg));
        }
        // 加入频道成功
        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
        {
            int build = 0;
            Debug.Log(string.Format("sdk version: ${0}",
                _helloVideoTokenAgora.RtcEngine.GetVersion(ref build)));
            Debug.Log(
                string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
                    connection.channelId, connection.localUid, elapsed));
            Debug.Log(string.Format("New Token: {0}",
                JoinChannelVideoToken.channelToken));
            JoinChannelVideoToken.MakeVideoView(0);

            _helloVideoTokenAgora.localUid = connection.localUid;
            _helloVideoTokenAgora.StartUpdateTokenEveryTenSecond();
        }



        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
        {
            Debug.Log(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
                elapsed));
            JoinChannelVideoToken.MakeVideoView(uid, _helloVideoTokenAgora.GetChannelName());
        }

        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
        {
            Debug.Log(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
                (int)reason));
            JoinChannelVideoToken.DestroyVideoView(uid);
        }
        // Token 即将过期
        public override void OnTokenPrivilegeWillExpire(RtcConnection connection, string token)
        {
            _helloVideoTokenAgora.StartCoroutine(HelperClass.FetchToken(_helloVideoTokenAgora.localUid,
                _helloVideoTokenAgora.GetChannelName(), CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, _helloVideoTokenAgora.RenewOrJoinToken));
        }

        public override void OnConnectionStateChanged(RtcConnection connection, CONNECTION_STATE_TYPE state,
            CONNECTION_CHANGED_REASON_TYPE reason)
        {
            _helloVideoTokenAgora._state = state;
        }

        public override void OnConnectionLost(RtcConnection connection)
        {
            Debug.Log(string.Format("OnConnectionLost "));
        }
    }

    #endregion
}



namespace Agora.Util
{
    public static class HelperClass
    {
        public static IEnumerator FetchToken(uint uid, string channelName, CLIENT_ROLE_TYPE role, Action<TokenObject> callback = null)
        {

            Dictionary<string, object> postParams = new Dictionary<string, object>();
            postParams.Add("uid", uid);
            postParams.Add("ChannelName", channelName);
            postParams.Add("role", role);
            string json = AgoraJson.ToJson<Dictionary<string, object>>(postParams);
            var url = "http://127.0.0.1/fetch_rtc_token";

            byte[] postBytes = System.Text.Encoding.Default.GetBytes(json);
            UnityWebRequest request = new UnityWebRequest(url, "POST");

            request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postBytes);
            request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");
            yield return request.SendWebRequest();

            if (request.isNetworkError || request.isHttpError)
            {
                Debug.Log(request.error);
                callback(null);
                yield break;
            }
            Debug.Log("text: " + request.downloadHandler.text);
            TokenObject tokenInfo = JsonUtility.FromJson<TokenObject>(request.downloadHandler.text);
            callback(tokenInfo);
        }
    }
}
```

### 运行项目 [#运行项目-1]

在本地设备的模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-1]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-7]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）:

| SDK                  | 版本     |
| :------------------- | :------- |
| RTC Native 4.x SDK        | &ge; v4.0.0 |
| RTC Electron 4.x SDK     |&ge; v4.0.0 |
| RTC Unity 4.x SDK        | &ge; v4.0.0 |
| RTC React Native 4.x SDK |&ge; v4.0.0 |
| RTC Flutter 4.x SDK     | &ge; v6.0.0 |
| RTC Native 3.x  SDK       | &ge; 3.6.0 |
| RTC Electron 3.x SDK     | &ge; 3.6.0 |
| RTC Unity 3.x SDK        | &ge; 3.6.0 |
| RTC React Native 3.x SDK | &ge; 3.6.0 |
| RTC Flutter 3.x SDK      | &ge; 5.10  |
| RTC Web SDK         | &ge; v4.8.0 |

使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。

### Token 生成器代码 [#token-生成器代码-1]

声网在 GitHub 上提供一个开源的 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) 仓库，支持使用 C++、Java、Go 等语言在你自己的服务器上生成 AccessToken2。

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

### BuildTokenWithUid API 参考 [#buildtokenwithuid-api-参考-1]

本节介绍生成 AccessToken2 的核心方法 `BuildTokenWithUid`。AccessToken2 生成器代码中提供两个 `BuildTokenWithUid` 方法：

- `BuildTokenWithUid` [1/2]：生成 AccessToken2，并设置 AccessToken2 和所有权限的过期时间。

- `BuildTokenWithUid` [2/2]：生成 AccessToken2，并设置如下过期时间：

  - AccessToken2 过期时间
  - 加入频道权限的过期时间
  - 频道内发布音频流权限的过期时间
  - 频道内发布视频流权限的过期时间
  - 频道内发布数据流权限的过期时间

#### BuildTokenWithUid [1/2] [#buildtokenwithuid-12-1]

该方法使用 `token_expire` 参数设置 AccessToken2 的过期时间，使用 `privilege_expire` 参数设置所有权限的过期时间。

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(const std::string& app_id,
                                       const std::string& app_certificate,
                                       const std::string& channel_name,
                                       uint32_t uid,
									   UserRole role,
                                       uint32_t token_expire,
                                       uint32_t privilege_expire = 0);
```

| 参数 | 描述 |
| --- | --- |
| `app_id` | 你在声网控制台创建项目时生成的 App ID。 |
| `app_certificate` | 你的项目的 App 证书。 |
| `channel_name` | [token-authentication-unity-markdown-2-3-1 details](#table-slot-token-authentication-unity-markdown-2-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为1到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | 权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。`kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。`kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](#cohost)。 |
| `token_expire` | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600，则 AccessToken2 会在生成后 10 分钟过期。AccessToken2 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `privilege_expire` | 从 AccessToken2 生成到所有权限过期的时间长度，单位为秒。例如，如果你将 `privilege_expire` 设为 600，则权限会在生成后 10 分钟过期。如果你将此参数设为 0（默认值），则权限永不过期。 |

<a id="table-slot-token-authentication-unity-markdown-2-3-1"></a>

**token-authentication-unity-markdown-2-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

:::info[信息]
当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但是一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。因此，确保 AccessToken2 的过期时间晚于权限过期时间。
:::

#### BuildTokenWithUid [2/2] [#buildtokenwithuid-22-1]

为支持在权限级别设置过期时间，声网还提供 `BuildTokenWithUid` 的同名重载方法。支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(
       const std::string& app_id,
       const std::string& app_certificate,
       const std::string& channel_name,
       uint32_t uid,
       uint32_t token_expire,
       uint32_t join_channel_privilege_expire = 0,
       uint32_t pub_audio_privilege_expire = 0,
       uint32_t pub_video_privilege_expire = 0,
       uint32_t pub_data_stream_privilege_expire = 0);
```

该方法生成 RTC AccessToken2，支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入 RTC 频道
- 在 RTC 频道中发布音频流
- 在 RTC 频道中发布视频流
- 在 RTC 频道中发布数据流

其中，在 RTC 频道中发布音频流、视频流和数据流的权限仅在[开启连麦鉴权服务](#cohost)后才生效。

一个用户可以设置多个权限。每个权限的最大有效时间为 24 小时。权限即将过期或已经过期后，SDK 会分别触发 `onTokenPriviegeWillExpire` 或 `onRequestToken` 回调。你需要在 App 逻辑中添加如下操作：

1. 识别即将过期或已经过期的是哪类权限。
2. App 从 Token 服务器获取新的 AccessToken2。
3. SDK 调用 `renewToken` 以更新 AccessToken2。

:::warning[注意]
你需要根据实际业务场景设置合理的过期时间，并确保 AccessToken2 的过期时间晚于其他权限过期时间。例如，如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户就会被踢出 RTC 频道。当 AccessToken2 过期但加入频道的权限未过期时，用户仍在频道里，并且只要发流权限未过期就可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。
:::

| 参数                               | 描述                                                         |
| :--------------------------------- | :----------------------------------------------------------- |
| `token_expire`                     | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600 ，则 AccessToken2 会在生成后 10 分钟过期。Token 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `join_channel_privilege_expire`    | 从 AccessToken2 生成到加入频道权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则加入频道权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则加入频道权限永不过期。 |
| `pub_audio_privilege_expire`       | 从 AccessToken2 生成到在频道内发布音频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布语音流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布语音流权限永不过期。 |
| `pub_video_privilege_expire`       | 从 AccessToken2 生成到在频道内发布视频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布视频流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布视频流权限永不过期。 |
| `pub_data_stream_privilege_expire` | 从 AccessToken2 生成到在频道内发布数据流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布数据流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布数据流权限永不过期。 |

<a id="cohost"></a>
### 开启连麦鉴权 [#开启连麦鉴权-1]

请参考以下步骤在声网控制台开启连麦鉴权服务：

1. 登录[声网控制台](https://console.shengwang.cn/)，在**项目列表**区域选择你想要开启的项目，点击配置按钮，进入**编辑项目**页面。
2. 在项目详情页，下滑到功能区域，点击连麦鉴权区域的**启用**。
3. 按照屏幕提示，了解开通该功能的主要事项，勾选后点击**启用**。

在控制台启用连麦鉴权后，该服务会在约 5 分钟后生效。

项目一旦开启了连麦鉴权服务，则用户在频道中发流需要同时满足两个条件：

- 在 `setClientRole` 中设置的 `role` 参数为 `BROADCASTER`(1)。
- 使用 Token 加入频道的用户拥有发流的权限（在 `BuildTokenWithUid `中设置的 `role` 参数为 `kRolePublisher`）。

## 相关文档 [#相关文档-8]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)
- [如何升级到 Token 鉴权方案？](/zh-CN/reference/faq/integration/appid_to_token)
- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />
鉴权是指在用户访问你的系统前，对其进行身份校验。用户在使用声网服务，如加入音视频通话或登录信令系统时，声网使用 Token 对其鉴权。

本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器，以及如何搭建一个使用 Token 鉴权的客户端。

## 鉴权原理 [#鉴权原理-2]

下图展示了鉴权的基本流程：

![鉴权流程图](https://web-cdn.agora.io/docs-files/1673942117005)

1. 客户端根据需要，向 App 服务端申请 Token。
2. App 服务端生成并返回 Token。
3. 客户端以用户 ID、频道名以及获取到的 Token 加入频道。
4. 声网平台读取该 Token 中包含的信息，并进行校验。
5. 客户端收到加入频道成功回调，并获取用户 ID。
6. Token 最大有效期为 24 小时。当即将过期时，客户端会收到 Token 即将过期的回调。
7. 此时，如果客户端需要继续进行音视频互动，需要申请新的 Token。
8. App 服务端生成并返回 Token。
9. 客户端更新 Token。

:::info[信息]
Token 过期时，SDK 会触发 Token 过期回调。收到该回调后，客户端需要从服务器获取新的 Token，再使用新的 Token 重新加入频道。
:::

你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。

Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 的过期时间

## 前提条件 [#前提条件-9]

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/zh-CN/introduction/quickstart#注册账号)。
- 已开启 [App 证书](/zh-CN/introduction/user-guides/manage-authentication#启用主要证书)的声网项目。
- [Golang](https://golang.org/) 1.14 或以上版本，GO111MODULE 设置为开启。

  :::warning[注意]
  如果你使用的是 Go 1.16 或以上版本，GO111MODULE 已默认开启。详情请参考 [New module changes in Go 1.16](https://blog.golang.org/go116-module-changes)。
  :::

- [npm](https://www.npmjs.com/get-npm) 以及[支持的浏览器](/zh-CN/realtime-media/rtc/reference/browser-compatibility)。

## 实现鉴权流程 [#实现鉴权流程-2]

本节介绍如何使用声网提供的[代码](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)生成并提供 Token，对用户及其权限进行校验。

### 获取 App ID 及 App 证书 [#获取-app-id-及-app-证书-2]

本节介绍如何获取生成 Token 所需的安全信息，如你的项目的 App ID 及 App 证书。

#### 获取 App ID [#获取-app-id-2]

声网会给每个项目自动分配一个 App ID 作为项目唯一标识。

在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击 App ID 右侧的图标，即可获取项目的 App ID。

#### 获取 App 证书 [#获取-app-证书-2]

参考以下步骤获取 App 证书：

1. 在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击**配置**。
  ![编辑项目](https://web-cdn.agora.io/docs-files/1641871111769)

2. 点击**主要证书**下面的复制图标，即可获取项目的 App 证书。
  ![获取 App 证书](https://web-cdn.agora.io/docs-files/1637637672988)

### 部署 Token 服务器 [#部署-token-服务器-2]

Token 需要在你的服务端部署生成。当客户端发送请求时，服务端部署的 Token 生成器会生成相应的 Token，再发送给客户端。

本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

此示例服务器使用 `BuildTokenWithUid` [1/2]。

:::warning[注意]
此示例服务器仅用于演示，请勿用于生产环境中。
:::

1. 创建一个 `server.go` 文件，然后贴入如下代码。将其中的 `<Your App ID>` 和 `<Your App Certificate>` 替换为你的 App ID 和 App 证书。

```go
package main

import (
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "errors"
    "strconv"
)

type rtc_int_token_struct struct{
    Uid_rtc_int uint32 `json:"uid"`
    Channel_name string `json:"ChannelName"`
    Role uint32 `json:"role"`
}

var rtc_token string
var int_uid uint32
var channel_name string

var role_num uint32
var role rtctokenbuilder.Role

// 使用 RtcTokenBuilder 来生成 RTC Token
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role){

    appID := "<Your App ID>"
    appCertificate := "<Your App Certificate>"
    // AccessToken2 过期的时间，单位为秒
    // 当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 回调
    // 但一旦用户和频道断开连接，用户将无法使用该 Token 加入同一频道。请确保 AccessToken2 的过期时间晚于权限过期时间
    tokenExpireTimeInSeconds := uint32(40)
    // 权限过期的时间，单位为秒
    // 权限过期30秒前会触发 token-privilege-will-expire 回调
    // 权限过期时会触发 token-privilege-did-expire 回调
    // 为作演示，在此将过期时间设为 40 秒。你可以看到客户端自动更新 Token 的过程
    privilegeExpireTimeInSeconds := uint32(40)

    result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with uid: %s\n", result)
        fmt.Printf("uid is %d\n", int_uid )
        fmt.Printf("ChannelName is %s\n", channelName)
        fmt.Printf("Role is %d\n", role)
    }
    rtc_token = result
}


func rtcTokenHandler(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
    w.Header().Set("Access-Control-Allow-Headers", "*");

    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }

    if r.Method != "POST" && r.Method != "OPTIONS" {
        http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
        return
    }


    var t_int rtc_int_token_struct
    var unmarshalErr *json.UnmarshalTypeError
    int_decoder := json.NewDecoder(r.Body)
    int_err := int_decoder.Decode(&t_int)
    if (int_err == nil) {

                int_uid = t_int.Uid_rtc_int
                channel_name = t_int.Channel_name
                role_num = t_int.Role
                switch role_num {
                    case 1:
                        role = rtctokenbuilder.RolePublisher
                    case 2:
                        role = rtctokenbuilder.RoleSubscriber
                }
    }
    if (int_err != nil) {

        if errors.As(int_err, &unmarshalErr){
                errorResponse(w, "Bad request. Wrong type provided for field " + unmarshalErr.Value  + unmarshalErr.Field + unmarshalErr.Struct, http.StatusBadRequest)
                } else {
                errorResponse(w, "Bad request.", http.StatusBadRequest)
            }
        return
    }

    generateRtcToken(int_uid, channel_name, role)
    errorResponse(w, rtc_token, http.StatusOK)
    log.Println(w, r)
}

func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.WriteHeader(httpStatusCode)
    resp := make(map[string]string)
    resp["token"] = message
    resp["code"] = strconv.Itoa(httpStatusCode)
    jsonResp, _ := json.Marshal(resp)
    w.Write(jsonResp)

}

func main(){
    // 使用 int 型 uid 生成 RTC Token
    http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
    fmt.Printf("Starting server at port 8082\n")

    if err := http.ListenAndServe(":8082", nil); err != nil {
        log.Fatal(err)
    }
}
```

2. `go.mod` 文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建 `go.mod` 文件：

```go
$ go mod init sampleServer
```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

```go
$ go get
```

4. 运行如下命令行启动服务器：

```go
$ go run server.go
```

### 使用 Token 对用户鉴权 [#使用-token-对用户鉴权-2]

本节以 Android 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上使用 Android 模拟器搭建并运行一个 Android 客户端。

1. 基于你在实现互动直播时创建的项目，在 `pubspec.yaml` 文件的 `dependencies` 下添加如下依赖：

```yaml
dependencies:
    # 声网 Flutter SDK 依赖项，请使用最新版本的 agora_rtc_engine
    agora_rtc_engine: ^6.0.0
    # 用于 http 请求
    http: ^0.13.5
```

2. 将 `/lib/main.dart` 中的内容替换为如下代码。 将 `<Your App ID>` 替换为你的 App ID，必须与服务器中的 App ID 一致。 您还需要将 `<Your Host URL and port>` 替换为你刚刚部署的本地 Golang 服务器的主机 URL 和端口，例如 10.53.3.234:8082。

在如下代码示例中，你可以看到 Token 与客户端的如下代码逻辑有关：

 - 调用 `joinChannel` 方法，使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。
 - 在 Token 过期前 30 秒，SDK 会触发 `onTokenPrivilegeWillExpire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 将新生成的 Token 传给 SDK。
 - Token 过期时，SDK 会触发 `onRequestToken` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `joinChannel` 方法，再使用新的 Token 重新加入频道。

```dart
import 'dart:convert';

import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(const MyApp());

/// This widget is the root of your application.
class MyApp extends StatefulWidget {
  /// Construct the [MyApp]
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
          appBar: AppBar(
            title: const Text('APIExample'),
          ),
          body: const JoinChannelVideoToken()),
    );
  }
}

class JoinChannelVideoToken extends StatefulWidget {
  const JoinChannelVideoToken({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _State();
}

class _State extends State<JoinChannelVideoToken> {
  late final RtcEngine _engine;
  bool _isReadyPreview = false;

  bool isJoined = false, switchCamera = true, switchRender = true;
  Set<int> remoteUid = {};
  static const String appId = '<Your App ID>'; // 填入在声网控制台创建项目时生成的 App ID
  static const String channelId = '<Your Channel Name>'; // 填入频道名称
  static const String hostUrl = '<Your Host URL and port>'; // 填入服务器的 URL 和端口

  @override
  void initState() {
    super.initState();
    _initEngine();
  }

  @override
  void dispose() {
    super.dispose();
    _dispose();
  }

  Future<void> _dispose() async {
    await _engine.leaveChannel();
    await _engine.release();
  }

  Future<void> _initEngine() async {
    _engine = createAgoraRtcEngine();
    await _engine.initialize(const RtcEngineContext(
      appId: appId,
    ));

    _engine.registerEventHandler(RtcEngineEventHandler(
      onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
        setState(() {
          isJoined = true;
        });
      },
      onUserJoined: (RtcConnection connection, int rUid, int elapsed) {
        setState(() {
          remoteUid.add(rUid);
        });
      },
      onUserOffline:
          (RtcConnection connection, int rUid, UserOfflineReasonType reason) {
        setState(() {
          remoteUid.removeWhere((element) => element == rUid);
        });
      },
      onLeaveChannel: (RtcConnection connection, RtcStats stats) {
        setState(() {
          isJoined = false;
          remoteUid.clear();
        });
      },
      onTokenPrivilegeWillExpire: (RtcConnection connection, String token) {
        _fetchToken(1234, channelId, 1, false);
      },
      onRequestToken: (RtcConnection connection) {
        _fetchToken(1234, channelId, 1, true);
      },
    ));

    await _engine.enableVideo();

    await _engine.startPreview();
    await _fetchToken(1234, channelId, 1, true);

    setState(() {
      _isReadyPreview = true;
    });
  }

  Future<void> _fetchToken(
    int uid,
    String channelName,
    int toeknRole,
    bool needJoinChannel,
  ) async {
    var client = http.Client();
    try {
      Map<String, String> headers = {
        'Content-type': 'application/json',
        'Accept': 'application/json',
      };

      var response = await client.post(Uri.parse(hostUrl),
          headers: headers,
          body: jsonEncode(
              {'uid': uid, 'ChannelName': channelName, 'role': toeknRole}));
      var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;

      final token = decodedResponse['token'];
      if (needJoinChannel) {
        await _engine.joinChannel(
          token: token,
          channelId: channelName,
          uid: uid,
          options: const ChannelMediaOptions(
            channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
            clientRoleType: ClientRoleType.clientRoleBroadcaster,
          ),
        );
      } else {
        await _engine.renewToken(token);
      }
    } finally {
      client.close();
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!_isReadyPreview) return Container();
    return Stack(
      children: [
        AgoraVideoView(
          controller: VideoViewController(
            rtcEngine: _engine,
            canvas: const VideoCanvas(uid: 0),
          ),
        ),
        Align(
          alignment: Alignment.topLeft,
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              children: List.of(remoteUid.map(
                (e) => SizedBox(
                  width: 120,
                  height: 120,
                  child: AgoraVideoView(
                    controller: VideoViewController.remote(
                      rtcEngine: _engine,
                      canvas: VideoCanvas(uid: e),
                      connection: const RtcConnection(channelId: channelId),
                    ),
                  ),
                ),
              )),
            ),
          ),
        )
      ],
    );
  }
}
```

### 运行项目 [#运行项目-2]

在本地设备的 Android 模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-2]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-8]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）:

| SDK                  | 版本     |
| :------------------- | :------- |
| RTC Native 4.x SDK        | &ge; v4.0.0 |
| RTC Electron 4.x SDK     |&ge; v4.0.0 |
| RTC Unity 4.x SDK        | &ge; v4.0.0 |
| RTC React Native 4.x SDK |&ge; v4.0.0 |
| RTC Flutter 4.x SDK     | &ge; v6.0.0 |
| RTC Native 3.x  SDK       | &ge; 3.6.0 |
| RTC Electron 3.x SDK     | &ge; 3.6.0 |
| RTC Unity 3.x SDK        | &ge; 3.6.0 |
| RTC React Native 3.x SDK | &ge; 3.6.0 |
| RTC Flutter 3.x SDK      | &ge; 5.10  |
| RTC Web SDK         | &ge; v4.8.0 |

使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。

### Token 生成器代码 [#token-生成器代码-2]

声网在 GitHub 上提供一个开源的 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) 仓库，支持使用 C++、Java、Go 等语言在你自己的服务器上生成 AccessToken2。

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

### BuildTokenWithUid API 参考 [#buildtokenwithuid-api-参考-2]

本节介绍生成 AccessToken2 的核心方法 `BuildTokenWithUid`。AccessToken2 生成器代码中提供两个 `BuildTokenWithUid` 方法：

- `BuildTokenWithUid` [1/2]：生成 AccessToken2，并设置 AccessToken2 和所有权限的过期时间。

- `BuildTokenWithUid` [2/2]：生成 AccessToken2，并设置如下过期时间：

  - AccessToken2 过期时间
  - 加入频道权限的过期时间
  - 频道内发布音频流权限的过期时间
  - 频道内发布视频流权限的过期时间
  - 频道内发布数据流权限的过期时间

#### BuildTokenWithUid [1/2] [#buildtokenwithuid-12-2]

该方法使用 `token_expire` 参数设置 AccessToken2 的过期时间，使用 `privilege_expire` 参数设置所有权限的过期时间。

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(const std::string& app_id,
                                       const std::string& app_certificate,
                                       const std::string& channel_name,
                                       uint32_t uid,
									   UserRole role,
                                       uint32_t token_expire,
                                       uint32_t privilege_expire = 0);
```

| 参数 | 描述 |
| --- | --- |
| `app_id` | 你在声网控制台创建项目时生成的 App ID。 |
| `app_certificate` | 你的项目的 App 证书。 |
| `channel_name` | [token-authentication-flutter-markdown-2-3-1 details](#table-slot-token-authentication-flutter-markdown-2-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为1到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | 权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。`kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。`kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](#cohost)。 |
| `token_expire` | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600，则 AccessToken2 会在生成后 10 分钟过期。AccessToken2 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `privilege_expire` | 从 AccessToken2 生成到所有权限过期的时间长度，单位为秒。例如，如果你将 `privilege_expire` 设为 600，则权限会在生成后 10 分钟过期。如果你将此参数设为 0（默认值），则权限永不过期。 |

<a id="table-slot-token-authentication-flutter-markdown-2-3-1"></a>

**token-authentication-flutter-markdown-2-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

:::info[信息]
当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但是一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。因此，确保 AccessToken2 的过期时间晚于权限过期时间。
:::

#### BuildTokenWithUid [2/2] [#buildtokenwithuid-22-2]

为支持在权限级别设置过期时间，声网还提供 `BuildTokenWithUid` 的同名重载方法。支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(
       const std::string& app_id,
       const std::string& app_certificate,
       const std::string& channel_name,
       uint32_t uid,
       uint32_t token_expire,
       uint32_t join_channel_privilege_expire = 0,
       uint32_t pub_audio_privilege_expire = 0,
       uint32_t pub_video_privilege_expire = 0,
       uint32_t pub_data_stream_privilege_expire = 0);
```

该方法生成 RTC AccessToken2，支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入 RTC 频道
- 在 RTC 频道中发布音频流
- 在 RTC 频道中发布视频流
- 在 RTC 频道中发布数据流

其中，在 RTC 频道中发布音频流、视频流和数据流的权限仅在[开启连麦鉴权服务](#cohost)后才生效。

一个用户可以设置多个权限。每个权限的最大有效时间为 24 小时。权限即将过期或已经过期后，SDK 会分别触发 `onTokenPriviegeWillExpire` 或 `onRequestToken` 回调。你需要在 App 逻辑中添加如下操作：

1. 识别即将过期或已经过期的是哪类权限。
2. App 从 Token 服务器获取新的 AccessToken2。
3. SDK 调用 `renewToken` 以更新 AccessToken2。

:::warning[注意]
你需要根据实际业务场景设置合理的过期时间，并确保 AccessToken2 的过期时间晚于其他权限过期时间。例如，如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户就会被踢出 RTC 频道。当 AccessToken2 过期但加入频道的权限未过期时，用户仍在频道里，并且只要发流权限未过期就可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。
:::

| 参数                               | 描述                                                         |
| :--------------------------------- | :----------------------------------------------------------- |
| `token_expire`                     | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600 ，则 AccessToken2 会在生成后 10 分钟过期。Token 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `join_channel_privilege_expire`    | 从 AccessToken2 生成到加入频道权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则加入频道权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则加入频道权限永不过期。 |
| `pub_audio_privilege_expire`       | 从 AccessToken2 生成到在频道内发布音频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布语音流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布语音流权限永不过期。 |
| `pub_video_privilege_expire`       | 从 AccessToken2 生成到在频道内发布视频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布视频流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布视频流权限永不过期。 |
| `pub_data_stream_privilege_expire` | 从 AccessToken2 生成到在频道内发布数据流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布数据流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布数据流权限永不过期。 |

<a id="cohost"></a>
### 开启连麦鉴权 [#开启连麦鉴权-2]

请参考以下步骤在声网控制台开启连麦鉴权服务：

1. 登录[声网控制台](https://console.shengwang.cn/)，在**项目列表**区域选择你想要开启的项目，点击配置按钮，进入**编辑项目**页面。
2. 在项目详情页，下滑到功能区域，点击连麦鉴权区域的**启用**。
3. 按照屏幕提示，了解开通该功能的主要事项，勾选后点击**启用**。

在控制台启用连麦鉴权后，该服务会在约 5 分钟后生效。

项目一旦开启了连麦鉴权服务，则用户在频道中发流需要同时满足两个条件：

- 在 `setClientRole` 中设置的 `role` 参数为 `BROADCASTER`(1)。
- 使用 Token 加入频道的用户拥有发流的权限（在 `BuildTokenWithUid `中设置的 `role` 参数为 `kRolePublisher`）。

## 相关文档 [#相关文档-9]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)
- [如何升级到 Token 鉴权方案？](/zh-CN/reference/faq/integration/appid_to_token)
- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />
鉴权是指在用户访问你的系统前，对其进行身份校验。用户在使用声网服务，如加入音视频通话或登录信令系统时，声网使用 Token 对其鉴权。

本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器，以及如何搭建一个使用 Token 鉴权的客户端。

## 鉴权原理 [#鉴权原理-3]

下图展示了鉴权的基本流程：

![](https://web-cdn.agora.io/docs-files/1673942117005)

1. 客户端根据需要，向 App 服务端申请 Token。
2. App 服务端生成并返回 Token。
3. 客户端以用户 ID、频道名以及获取到的 Token 加入频道。
4. 声网平台读取该 Token 中包含的信息，并进行校验。
5. 客户端收到加入频道成功回调，并获取用户 ID。
6. Token 最大有效期为 24 小时。当即将过期时，客户端会收到 Token 即将过期的回调。
7. 此时，如果客户端需要继续进行音视频互动，需要申请新的 Token。
8. App 服务端生成并返回 Token。
9. 客户端更新 Token。

:::info[信息]
Token 过期时，SDK 会触发 Token 过期回调。收到该回调后，客户端需要从服务器获取新的 Token，再使用新的 Token 重新加入频道。
:::

你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。

Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 的过期时间

## 前提条件 [#前提条件-10]

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/zh-CN/introduction/quickstart#注册账号)。
- 已开启 [App 证书](/zh-CN/introduction/user-guides/manage-authentication#启用主要证书)的声网项目。
- [Golang](https://golang.org/) 1.14 或以上版本，GO111MODULE 设置为开启。

  :::warning[注意]
  如果你使用的是 Go 1.16 或以上版本，GO111MODULE 已默认开启。详情请参考 [New module changes in Go 1.16](https://blog.golang.org/go116-module-changes)。
  :::

- [npm](https://www.npmjs.com/get-npm) 以及[支持的浏览器](/zh-CN/realtime-media/rtc/reference/browser-compatibility)。

## 实现鉴权流程 [#实现鉴权流程-3]

本节介绍如何使用声网提供的[代码](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)生成并提供 Token，对用户及其权限进行校验。

### 获取 App ID 及 App 证书 [#获取-app-id-及-app-证书-3]

本节介绍如何获取生成 Token 所需的安全信息，如你的项目的 App ID 及 App 证书。

#### 获取 App ID [#获取-app-id-3]

声网会给每个项目自动分配一个 App ID 作为项目唯一标识。

在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击 App ID 右侧的图标，即可获取项目的 App ID。

#### 获取 App 证书 [#获取-app-证书-3]

参考以下步骤获取 App 证书：

1. 在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击**配置**。
  ![编辑项目](https://web-cdn.agora.io/docs-files/1641871111769)

2. 点击**主要证书**下面的复制图标，即可获取项目的 App 证书。
  ![获取 App 证书](https://web-cdn.agora.io/docs-files/1637637672988)

### 部署 Token 服务器 [#部署-token-服务器-3]

Token 需要在你的服务端部署生成。当客户端发送请求时，服务端部署的 Token 生成器会生成相应的 Token，再发送给客户端。

本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

此示例服务器使用 `BuildTokenWithUid` [1/2]。

:::warning[注意]
此示例服务器仅用于演示，请勿用于生产环境中。
:::

1. 创建一个 `server.go` 文件，然后贴入如下代码。将其中的 `<Your App ID>` 和 `<Your App Certificate>` 替换为你的 App ID 和 App 证书。

```go
package main

import (
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "errors"
    "strconv"
)

type rtc_int_token_struct struct{
    Uid_rtc_int uint32 `json:"uid"`
    Channel_name string `json:"ChannelName"`
    Role uint32 `json:"role"`
}

var rtc_token string
var int_uid uint32
var channel_name string

var role_num uint32
var role rtctokenbuilder.Role

// 使用 RtcTokenBuilder 来生成 RTC Token
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role){

    appID := "<Your App ID>"
    appCertificate := "<Your App Certificate>"
    // AccessToken2 过期的时间，单位为秒
    // 当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 回调
    // 但一旦用户和频道断开连接，用户将无法使用该 Token 加入同一频道。请确保 AccessToken2 的过期时间晚于权限过期时间
    tokenExpireTimeInSeconds := uint32(40)
    // 权限过期的时间，单位为秒
    // 权限过期30秒前会触发 token-privilege-will-expire 回调
    // 权限过期时会触发 token-privilege-did-expire 回调
    // 为作演示，在此将过期时间设为 40 秒。你可以看到客户端自动更新 Token 的过程
    privilegeExpireTimeInSeconds := uint32(40)

    result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with uid: %s\n", result)
        fmt.Printf("uid is %d\n", int_uid )
        fmt.Printf("ChannelName is %s\n", channelName)
        fmt.Printf("Role is %d\n", role)
    }
    rtc_token = result
}


func rtcTokenHandler(w http.ResponseWriter, r *http.Request){
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
    w.Header().Set("Access-Control-Allow-Headers", "*");

    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }

    if r.Method != "POST" && r.Method != "OPTIONS" {
        http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
        return
    }


    var t_int rtc_int_token_struct
    var unmarshalErr *json.UnmarshalTypeError
    int_decoder := json.NewDecoder(r.Body)
    int_err := int_decoder.Decode(&t_int)
    if (int_err == nil) {

                int_uid = t_int.Uid_rtc_int
                channel_name = t_int.Channel_name
                role_num = t_int.Role
                switch role_num {
                    case 1:
                        role = rtctokenbuilder.RolePublisher
                    case 2:
                        role = rtctokenbuilder.RoleSubscriber
                }
    }
    if (int_err != nil) {

        if errors.As(int_err, &unmarshalErr){
                errorResponse(w, "Bad request. Wrong type provided for field " + unmarshalErr.Value  + unmarshalErr.Field + unmarshalErr.Struct, http.StatusBadRequest)
                } else {
                errorResponse(w, "Bad request.", http.StatusBadRequest)
            }
        return
    }

    generateRtcToken(int_uid, channel_name, role)
    errorResponse(w, rtc_token, http.StatusOK)
    log.Println(w, r)
}

func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.WriteHeader(httpStatusCode)
    resp := make(map[string]string)
    resp["token"] = message
    resp["code"] = strconv.Itoa(httpStatusCode)
    jsonResp, _ := json.Marshal(resp)
    w.Write(jsonResp)

}

func main(){
    // 使用 int 型 uid 生成 RTC Token
    http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
    fmt.Printf("Starting server at port 8082\n")

    if err := http.ListenAndServe(":8082", nil); err != nil {
        log.Fatal(err)
    }
}
```

2. `go.mod` 文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建 `go.mod` 文件：

```go
$ go mod init sampleServer
```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

```go
$ go get
```

4. 运行如下命令行启动服务器：

```go
$ go run server.go
```

### 使用 Token 对用户鉴权 [#使用-token-对用户鉴权-3]

本节展示如何使用 Token 对客户端的用户进行鉴权。
为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上搭建并运行一个带鉴权逻辑的 Android 或 iOS 客户端。
基于你在[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)时创建的项目，在 `renderer.js` 中的代码替换为如下代码。将 `<Your App ID>` 替换为你的 App ID，必须与服务器中的 App ID 一致。您还需要将 `<Your Host URL and port>` 替换为你刚刚部署的本地 Golang 服务器的主机 URL 和端口，例如 10.53.3.234:8082。

 - 调用 `joinChannel` 方法，使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。
 - 在 Token 过期前 30 秒，SDK 会触发 `onTokenPrivilegeWillExpire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 将新生成的 Token 传给 SDK。
 - Token 过期时，SDK 会触发 `onRequestToken` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `joinChannel` 方法，再使用新的 Token 重新加入频道。

```typescript
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
  PermissionsAndroid,
  Platform,
  SafeAreaView,
  ScrollView,
  StyleSheet,
  Switch,
  Text,
  View,
} from 'react-native';


import {
  ChannelProfileType,
  ClientRoleType,
  createAgoraRtcEngine,
  IRtcEngine,
  IRtcEngineEventHandler,
  RtcConnection,
  RtcSurfaceView,
} from 'react-native-agora';

// 定义基本信息
const appId = '<Your App ID>';
const channelName = 'test';
const uid = 123; // 本地用户 Uid

const App = () => {
  const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine 实例
  const eventHandlerRef = useRef<IRtcEngineEventHandler>(); // IRtcEngine 实例
  const [token, setToken] = useState<string>(''); // 音视频 Token
  const [isJoined, setIsJoined] = useState(false); // 本地用户是否已加入频道
  const [isHost, setIsHost] = useState(true); // 用户角色
  const [remoteUid, setRemoteUid] = useState(0); // 远端用户的 Uid
  const [message, setMessage] = useState(''); // 用户提示信息

    // 向 Token 服务器发送获取音视频 Token 请求
    const fetchToken = useCallback(() => {
    // Token 服务器 URL 示例：http://12.123.1.123:8082/fetch_rtc_token
    const url = '<Your Host URL and port>/fetch_rtc_token';
    const body = JSON.stringify({
      uid,
      ChannelName: channelName,
      role: isHost
        ? ClientRoleType.ClientRoleBroadcaster
        : ClientRoleType.ClientRoleAudience,
    });
    console.log('fetchToken', url, body);
    return fetch(url, {
      method: 'POST',
      body,
    })
      .then((res) => res.json())
      .then((res) => {
        showMessage('token ' + res.token + '已经刷新');
        if (+res.code === 200) {
          setToken(res.token);
        }
        return res;
      });
  }, [isHost]);

  // 使用获取到的 Token 加入频道
  const join = useCallback(async () => {
    if (!token) {
      await fetchToken();
    } else {
      try {
        // 加入频道后设置频道场景类型为直播
        agoraEngineRef.current?.setChannelProfile(
          ChannelProfileType.ChannelProfileLiveBroadcasting
        );
        if (isHost) {
          agoraEngineRef.current?.startPreview();
          // 以主播角色加入频道
          agoraEngineRef.current?.joinChannel(token, channelName, uid, {
            publishCameraTrack: true,
            clientRoleType: ClientRoleType.ClientRoleBroadcaster,
          });
        } else {
          // 以观众角色加入频道
          agoraEngineRef.current?.joinChannel(token, channelName, uid, {
            clientRoleType: ClientRoleType.ClientRoleAudience,
          });
        }
      } catch (e) {
        console.log(e);
      }
    }
  }, [fetchToken, isHost, token]);

  const leave = () => {
    try {
      agoraEngineRef.current?.leaveChannel();
      setRemoteUid(0);
      setIsJoined(false);
      showMessage('已离开频道');
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    const setupVideoSDKEngine = async () => {
      try {
        if (Platform.OS === 'android') {
          await getPermission();
        }
        agoraEngineRef.current = createAgoraRtcEngine();
        eventHandlerRef.current = {
          onJoinChannelSuccess: () => {
            showMessage('成功加入频道：' + channelName);
            setIsJoined(true);
          },
          onUserJoined: (_connection, Uid) => {
            showMessage('远端用户 ' + Uid + ' 已加入');
            setRemoteUid(Uid);
          },
          onUserOffline: (_connection, Uid) => {
            showMessage('远端用户 ' + Uid + '已离开频道');
            setRemoteUid(0);
          },
          onTokenPrivilegeWillExpire(connection: RtcConnection, token: string) {
            showMessage('token ' + token + '即将过期');
            fetchToken();
          },
          onRequestToken(connection: RtcConnection) {
            showMessage('token ' + '已过期');
            setToken('');
          },
        };
        const agoraEngine = agoraEngineRef.current;
        // 初始化引擎
        agoraEngine.initialize({
          appId: appId,
          channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
        });
        // 注册回调事件
        agoraEngine.registerEventHandler(eventHandlerRef.current);
        // 开启本地视频
        agoraEngine.enableVideo();
      } catch (e) {
        console.error(e);
      }
    };

    setupVideoSDKEngine();

    return () => {
      agoraEngineRef.current?.release();
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    const checkJoined = () => {
      if (isJoined) {
        console.log('renewToken');
        // agoraEngineRef.current?.renewToken(token);
      } else {
        console.log('joinChannel');
        join();
      }
    };
    checkJoined();
  }, [token]); // eslint-disable-line react-hooks/exhaustive-deps

  // 渲染用户界面
  return (
    <SafeAreaView style={styles.main}>
      <Text style={styles.head}>声网互动直播快速开始</Text>
      <View style={styles.btnContainer}>
        <Text onPress={join} style={styles.button}>
          加入频道
        </Text>
        <Text onPress={leave} style={styles.button}>
          离开频道
        </Text>
      </View>
      <View style={styles.btnContainer}>
        <Text>Audience</Text>
        <Switch
          onValueChange={(switchValue) => {
            setIsHost(switchValue);
            if (isJoined) {
              leave();
            }
          }}
          value={isHost}
        />
        <Text>Host</Text>
      </View>
      <ScrollView
        style={styles.scroll}
        contentContainerStyle={styles.scrollContainer}
      >
        {isJoined ? (
          <React.Fragment key={0}>
            <RtcSurfaceView canvas={{ uid: 0 }} style={styles.videoView} />
            <Text>本地用户 uid: {uid}</Text>
          </React.Fragment>
        ) : (
          <Text>加入一个频道</Text>
        )}
        {isJoined && !isHost && remoteUid !== 0 ? (
          <React.Fragment key={remoteUid}>
            <RtcSurfaceView
              canvas={{ uid: remoteUid }}
              style={styles.videoView}
            />
            <Text>远端用户 uid: {remoteUid}</Text>
          </React.Fragment>
        ) : (
          <Text>{isJoined && !isHost ? '等待远端用户加入' : ''}</Text>
        )}
        <Text style={styles.info}>{message}</Text>
      </ScrollView>
    </SafeAreaView>
  );

  // 提示信息
  function showMessage(msg: string) {
    console.info(msg);
    setMessage(msg);
  }
};

// 定义用户界面样式
const styles = StyleSheet.create({
  button: {
    paddingHorizontal: 25,
    paddingVertical: 4,
    fontWeight: 'bold',
    color: '#ffffff',
    backgroundColor: '#0055cc',
    margin: 5,
  },
  main: { flex: 1, alignItems: 'center' },
  scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
  scrollContainer: { alignItems: 'center' },
  videoView: { width: '90%', height: 200 },
  btnContainer: { flexDirection: 'row', justifyContent: 'center' },
  head: { fontSize: 20 },
  info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
});

const getPermission = async () => {
  if (Platform.OS === 'android') {
    await PermissionsAndroid.requestMultiple([
      'android.permission.RECORD_AUDIO',
      'android.permission.CAMERA',
    ]);
  }
};

export default App;
```

### 运行项目 [#运行项目-3]

构建并运行你的 iOS 或 Android 项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-3]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-9]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）:

| SDK                  | 版本     |
| :------------------- | :------- |
| RTC Native 4.x SDK        | &ge; v4.0.0 |
| RTC Electron 4.x SDK     |&ge; v4.0.0 |
| RTC Unity 4.x SDK        | &ge; v4.0.0 |
| RTC React Native 4.x SDK |&ge; v4.0.0 |
| RTC Flutter 4.x SDK     | &ge; v6.0.0 |
| RTC Native 3.x  SDK       | &ge; 3.6.0 |
| RTC Electron 3.x SDK     | &ge; 3.6.0 |
| RTC Unity 3.x SDK        | &ge; 3.6.0 |
| RTC React Native 3.x SDK | &ge; 3.6.0 |
| RTC Flutter 3.x SDK      | &ge; 5.10  |
| RTC Web SDK         | &ge; v4.8.0 |

使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。

### Token 生成器代码 [#token-生成器代码-3]

声网在 GitHub 上提供一个开源的 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) 仓库，支持使用 C++、Java、Go 等语言在你自己的服务器上生成 AccessToken2。

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

### BuildTokenWithUid API 参考 [#buildtokenwithuid-api-参考-3]

本节介绍生成 AccessToken2 的核心方法 `BuildTokenWithUid`。AccessToken2 生成器代码中提供两个 `BuildTokenWithUid` 方法：

- `BuildTokenWithUid` [1/2]：生成 AccessToken2，并设置 AccessToken2 和所有权限的过期时间。

- `BuildTokenWithUid` [2/2]：生成 AccessToken2，并设置如下过期时间：

  - AccessToken2 过期时间
  - 加入频道权限的过期时间
  - 频道内发布音频流权限的过期时间
  - 频道内发布视频流权限的过期时间
  - 频道内发布数据流权限的过期时间

#### BuildTokenWithUid [1/2] [#buildtokenwithuid-12-3]

该方法使用 `token_expire` 参数设置 AccessToken2 的过期时间，使用 `privilege_expire` 参数设置所有权限的过期时间。

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(const std::string& app_id,
                                       const std::string& app_certificate,
                                       const std::string& channel_name,
                                       uint32_t uid,
									   UserRole role,
                                       uint32_t token_expire,
                                       uint32_t privilege_expire = 0);
```

| 参数 | 描述 |
| --- | --- |
| `app_id` | 你在声网控制台创建项目时生成的 App ID。 |
| `app_certificate` | 你的项目的 App 证书。 |
| `channel_name` | [token-authentication-rn-markdown-2-3-1 details](#table-slot-token-authentication-rn-markdown-2-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为1到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | 权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。`kRolePublisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。`kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](#cohost)。 |
| `token_expire` | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600，则 AccessToken2 会在生成后 10 分钟过期。AccessToken2 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `privilege_expire` | 从 AccessToken2 生成到所有权限过期的时间长度，单位为秒。例如，如果你将 `privilege_expire` 设为 600，则权限会在生成后 10 分钟过期。如果你将此参数设为 0（默认值），则权限永不过期。 |

<a id="table-slot-token-authentication-rn-markdown-2-3-1"></a>

**token-authentication-rn-markdown-2-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

:::info[信息]
当 AccessToken2 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但是一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。因此，确保 AccessToken2 的过期时间晚于权限过期时间。
:::

#### BuildTokenWithUid [2/2] [#buildtokenwithuid-22-3]

为支持在权限级别设置过期时间，声网还提供 `BuildTokenWithUid` 的同名重载方法。支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

```C++
// 以 C++ 为例
static std::string BuildTokenWithUid(
       const std::string& app_id,
       const std::string& app_certificate,
       const std::string& channel_name,
       uint32_t uid,
       uint32_t token_expire,
       uint32_t join_channel_privilege_expire = 0,
       uint32_t pub_audio_privilege_expire = 0,
       uint32_t pub_video_privilege_expire = 0,
       uint32_t pub_data_stream_privilege_expire = 0);
```

该方法生成 RTC AccessToken2，支持你设置 AccessToken2 的过期时间以及以下权限的过期时间：

- 加入 RTC 频道
- 在 RTC 频道中发布音频流
- 在 RTC 频道中发布视频流
- 在 RTC 频道中发布数据流

其中，在 RTC 频道中发布音频流、视频流和数据流的权限仅在[开启连麦鉴权服务](#cohost)后才生效。

一个用户可以设置多个权限。每个权限的最大有效时间为 24 小时。权限即将过期或已经过期后，SDK 会分别触发 `onTokenPriviegeWillExpire` 或 `onRequestToken` 回调。你需要在 App 逻辑中添加如下操作：

1. 识别即将过期或已经过期的是哪类权限。
2. App 从 Token 服务器获取新的 AccessToken2。
3. SDK 调用 `renewToken` 以更新 AccessToken2。

:::warning[注意]
你需要根据实际业务场景设置合理的过期时间，并确保 AccessToken2 的过期时间晚于其他权限过期时间。例如，如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户就会被踢出 RTC 频道。当 AccessToken2 过期但加入频道的权限未过期时，用户仍在频道里，并且只要发流权限未过期就可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。
:::

| 参数                               | 描述                                                         |
| :--------------------------------- | :----------------------------------------------------------- |
| `token_expire`                     | AccessToken2 从生成到过期的时间长度，单位为秒。例如，如果你将 `token_expire` 设为 600 ，则 AccessToken2 会在生成后 10 分钟过期。Token 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间，AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0，AccessToken2 立即过期。 |
| `join_channel_privilege_expire`    | 从 AccessToken2 生成到加入频道权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则加入频道权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则加入频道权限永不过期。 |
| `pub_audio_privilege_expire`       | 从 AccessToken2 生成到在频道内发布音频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布语音流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布语音流权限永不过期。 |
| `pub_video_privilege_expire`       | 从 AccessToken2 生成到在频道内发布视频流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布视频流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布视频流权限永不过期。 |
| `pub_data_stream_privilege_expire` | 从 AccessToken2 生成到在频道内发布数据流的权限过期的时间长度，单位为秒。例如，如果你将此参数为 600 ，则发布数据流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0（默认值），则发布数据流权限永不过期。 |

<a id="cohost"></a>
### 开启连麦鉴权 [#开启连麦鉴权-3]

请参考以下步骤在声网控制台开启连麦鉴权服务：

1. 登录[声网控制台](https://console.shengwang.cn/)，在**项目列表**区域选择你想要开启的项目，点击配置按钮，进入**编辑项目**页面。
2. 在项目详情页，下滑到功能区域，点击连麦鉴权区域的**启用**。
3. 按照屏幕提示，了解开通该功能的主要事项，勾选后点击**启用**。

在控制台启用连麦鉴权后，该服务会在约 5 分钟后生效。

项目一旦开启了连麦鉴权服务，则用户在频道中发流需要同时满足两个条件：

- 在 `setClientRole` 中设置的 `role` 参数为 `BROADCASTER`(1)。
- 使用 Token 加入频道的用户拥有发流的权限（在 `BuildTokenWithUid `中设置的 `role` 参数为 `kRolePublisher`）。

## 相关文档 [#相关文档-10]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)
- [如何升级到 Token 鉴权方案？](/zh-CN/reference/faq/integration/appid_to_token)
- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unreal" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

下图展示声网使用 Token 对用户鉴权的流程：

![token 流程](https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg)

## 前提条件 [#前提条件-11]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-6]

本节以生成 RTC Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-13]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-13]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannel`[2/2] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```cpp
// 频道名
const char* channelId;
// 用户 ID
int uid = 0
// 请求服务端生成 channelId 和 uid 对应的 token
const char* token = getToken();
// 设置频道媒体选项
ChannelMediaOptions options;
// 将用户角色设为主播
options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
// 加入频道
m_rtcEngine->joinChannel(token, channelId, uid, options);
```

### Token 过期处理 [#token-过期处理-7]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/unreal-cpp/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/unreal-cpp/channel#onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/unreal-cpp/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。

- 调用 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/unreal-cpp/channel#updatechannelmediaoptions) 更新 Token。

- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-cpp/channel#leavechannel-22) 离开当前频道，然后再调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-cpp/channel#api_irtcengine_joinchannel2) 并传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/unreal-cpp/channel#joinchannelex) 加入多个频道，需要调用 [`updateChannelMediaOptionsEx`](/zh-CN/api-reference/rtc/unreal-cpp/channel#updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```cpp
class CJoinChannelVideoByTokenRtcEngineEventHandler
    : public IRtcEngineEventHandler
{
public:
    // Token 即将过期时回调
    virtual void onTokenPrivilegeWillExpire(const char* token) {
        // 重新请求生成 Token
        const char* token = getToken();
        // 更新 Token
        m_rtcEngine->renewToken(token);
    }
}
```

## 拓展功能（可选） [#拓展功能可选-5]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-6]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-6]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如需在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/unreal-cpp/channel#api_irtcengine_setclientrole) 方法将用户角色设为主播（`BROADCASTER`），或是在调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-cpp/channel#api_irtcengine_joinchannel2) 加入频道时通过 `options` 将 `clientRoleType` 设为 `CLIENT_ROLE_BROADCASTER`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-14]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-14]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-7]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-7]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-7]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。

<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将生成 Token 的示例代码贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-6]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-10]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-7]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-7]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-6]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-6]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档-11]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="blueprint" />
鉴权是指在用户访问你的系统前，对用户权限进行校验，声网用 Token 对用户鉴权。[Token](/zh-CN/introduction/glossary#token) 也称为动态密钥，是在加入频道时用于校验用户权限的一组字符串。当 App 用户使用声网服务，如加入 RTC 频道时传入 Token，声网服务端会根据 Token 中的信息校验用户的权限。

你可以根据你的使用场景，通过下列任意一种方式来设置 Token 的权限：

- **统一设置权限**：统一设置加入频道并发流的权限，适用于绝大多数场景，详见[实现 Token 鉴权](#basic-token)。
- **精细控制发布不同流的权限**：开通连麦鉴权功能，分别设置在频道内发布音频流、视频流、数据流的权限，适用于对安全要求较高的场景，详见[使用连麦鉴权](#advanced)。

下图展示声网使用 Token 对用户鉴权的流程：

![token 流程](https://web-cdn.agora.io/docs-portal/img/rtc/token-authentication.svg)

## 前提条件 [#前提条件-12]

开始前，请确保你的项目满足如下条件：

- 已开启 App 证书的声网项目以及项目的 App ID，可参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)获取。

- 已在你的项目中集成实时互动 SDK 并实现基本的实时互动功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

<a id="basic-token"></a>
## 实现 Token 鉴权 [#实现-token-鉴权-7]

本节以生成 RTC Token 并加入频道为例，介绍如何使用 Token 进行鉴权。为确保你的业务的安全性，声网推荐你对每一个加入频道的客户端都进行鉴权。

<a id="token-code"></a>
### 生成 Token [#生成-token-15]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法                                                     | 示例代码                                                     |
|:------|:----------------------------------------------------------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `privilege_expiration_in_seconds` 来统一设置所有权限的过期时间。

示例代码如下：

<Tabs defaultValue="cpp" groupId="token-sample-code" persist>
<TabsList>
  <TabsTrigger value="cpp">C++</TabsTrigger>
  <TabsTrigger value="Golang">Golang</TabsTrigger>
  <TabsTrigger value="java">Java</TabsTrigger>
  <TabsTrigger value="js">Node.js</TabsTrigger>
  <TabsTrigger value="php">PHP</TabsTrigger>
  <TabsTrigger value="python">Python</TabsTrigger>
  <TabsTrigger value="python3">Python3</TabsTrigger>
</TabsList>

<TabsContent value="cpp">

```cpp
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";
    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时间，单位秒
    uint32_t token_expiration_in_seconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    uint32_t privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }
    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, UserRole::kRolePublisher,
        token_expiration_in_seconds, privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

</TabsContent>

<TabsContent value="Golang">

<a id="sample-code"></a>

```go
package main

import (
    "fmt"
    "os"

    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间，单位秒
    tokenExpirationInSeconds := uint32(3600)
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilegeExpirationInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)
    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }
    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid: %s\n", result)
    }
}
```

</TabsContent>

<TabsContent value="java">

```java
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间，单位秒
    static int tokenExpirationInSeconds = 3600;
    // 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    static int privilegeExpirationInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }
        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, Role.ROLE_PUBLISHER, tokenExpirationInSeconds, privilegeExpirationInSeconds);
        System.out.printf("Token with uid: %s\n", result);
    }
}
```

</TabsContent>

<TabsContent value="js">

```javascript
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;
const RtcRole = require("../src/RtcTokenBuilder2").Role;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = "uid";
// 设为具有发流权限
const role = RtcRole.PUBLISHER;
// Token 的有效时间，单位秒
const tokenExpirationInSecond = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
const privilegeExpirationInSecond = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUid = RtcTokenBuilder.buildTokenWithUid(appId, appCertificate, channelName, uid, role, tokenExpirationInSecond, privilegeExpirationInSecond);
console.log("Token with int uid:", tokenWithUid);
```

</TabsContent>

<TabsContent value="php">

```php
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");
// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间，单位秒
$tokenExpirationInSeconds = 3600;
// 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
$privilegeExpirationInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}
// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUid($appId, $appCertificate, $channelName, $uid, RtcTokenBuilder2::ROLE_PUBLISHER, $tokenExpirationInSeconds, $privilegeExpirationInSeconds);
echo 'Token with int uid: ' . $token . PHP_EOL;
```

</TabsContent>

<TabsContent value="python">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>

<TabsContent value="python3">

```py
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")
    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间，单位秒
    token_expiration_in_seconds = 3600
    # 所有的权限的有效时间，单位秒，声网建议你将该参数和 Token 的有效时间设为一致
    privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid(app_id, app_certificate, channel_name, uid, Role_Publisher,
                                                 token_expiration_in_seconds, privilege_expiration_in_seconds)
    print("Token with int uid: {}".format(token))


if __name__ == "__main__":
    main()
```

</TabsContent>
</Tabs>

:::info[信息]
- 以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
- 如果你需要生成同时具备 RTC 和 RTM 权限的 Token，请参考[如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)
:::

<a id="use"></a>
### 使用 Token [#使用-token-15]

本节介绍如何使用 Token 对客户端加入频道的用户进行鉴权。

使用获取到的 Token 和用户 ID、频道名等信息，通过 `joinChannel`[2/2] 方法加入频道。

:::warning[注意]
加入频道时使用的用户 ID 和频道名必须和生成 Token 时的用户 ID 和频道名一致。
:::

```cpp
// 频道名
const char* channelId;
// 用户 ID
int uid = 0
// 请求服务端生成 channelId 和 uid 对应的 token
const char* token = getToken();
// 设置频道媒体选项
ChannelMediaOptions options;
// 将用户角色设为主播
options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
// 加入频道
m_rtcEngine->joinChannel(token, channelId, uid, options);
```

### Token 过期处理 [#token-过期处理-8]

使用 Token 加入频道后，在 Token 过期前 30 秒，SDK 会触发 [`onTokenPrivilegeWillExpire`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#callback_irtcengineeventhandler_ontokenprivilegewillexpire) 回调。

Token 过期时，SDK 会触发 [`onRequestToken`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#onrequesttoken) 回调。

当收到上述回调后，你需要重新在服务端生成新的 Token，然后通过下列任意一种方式来更新 Token：

**单频道场景**

- （推荐）调用 [`renewToken`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#api_irtcengine_renewtoken) 来传入新生成的 Token。

- 调用 [`updateChannelMediaOptions`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#updatechannelmediaoptions) 更新 Token。

- 调用 [`leaveChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-blueprint/channel#leaveChannel-[2/2]) 离开当前频道，然后再调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-blueprint/channel#api_irtcengine_joinchannel2) 并传入新的 Token 重新加入频道。

**多频道场景**

如果你调用了 [`joinChannelEx`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#joinchannelex) 加入多个频道，需要调用 [`updateChannelMediaOptionsEx`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#updatechannelmediaoptionsex) 方法来更新 Token。

**示例代码**

下列示例代码展示在收到 `onTokenPrivilegeWillExpire` 回调提示 Token 即将过期时，调用 `renewToken` 来更新 Token。

```cpp
class CJoinChannelVideoByTokenRtcEngineEventHandler
    : public IRtcEngineEventHandler
{
public:
    // Token 即将过期时回调
    virtual void onTokenPrivilegeWillExpire(const char* token) {
        // 重新请求生成 Token
        const char* token = getToken();
        // 更新 Token
        m_rtcEngine->renewToken(token);
    }
}
```

## 拓展功能（可选） [#拓展功能可选-6]

本节介绍 Token 相关的拓展功能，包括如何生成通配 Token、如何使用连麦鉴权来精细控制发流权限。

### 生成通配 Token [#生成通配-token-7]

如果用户需要频繁切换频道、或同时加入多个频道，声网提供将**用户 ID** 或**频道名**设为通配符的通配 Token。用户可以复用同一 Token 加入不同频道，既可减少频繁获取 Token 所造成的耗时，从而加快切换、加入频道的速度，同时也能减小你的 Token 服务器的压力，详见[通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)。

<a id="advanced"></a>
### 使用连麦鉴权 [#使用连麦鉴权-7]

声网支持精细控制频道内用户发布不同流的权限，可确保频道内的发流用户都经过授权，从而防止黑客利用业务漏洞或盗取 Token 进行直播间炸房等行为。

:::warning[注意]
- 在设置前，请确保你已开启[连麦鉴权功能](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- 如需在频道内发流，请确保在加入频道前，已调用 [`setClientRole`](/zh-CN/api-reference/rtc/unreal-blueprint/channel#setclientrole) 方法将用户角色设为主播（`BROADCASTER`），或是在调用 [`joinChannel`[2/2]](/zh-CN/api-reference/rtc/unreal-blueprint/channel#api_irtcengine_joinchannel2) 加入频道时通过 `options` 将 `clientRoleType` 设为 `CLIENT_ROLE_BROADCASTER`。
:::

声网提供 `BuildTokenWithUidAndPrivilege` 方法来设置下列权限的过期时间：

- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流

详细参数说明见 [API 参考](#api)。

#### 生成 Token [#生成-token-16]

声网在 GitHub 上提供一个开源的 Token 生成器代码仓库 [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)，支持使用 C++、Java、Golang 等多种语言来在你自己的服务器上生成 Token。在生成时，你可以通过不同参数分别设置发布不同流的权限。

以下列出的语言均基于 HMAC-SHA256 算法生成 Token。

| 语言            | 核心方法   | 示例代码                             |
|:------ |:---------- |:----------------------------------------------------------- |
| C++     | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder2.h) | [RtcTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilder2Sample.cpp) |
| Golang      |[BuildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2/rtctokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/go/sample/rtctokenbuilder2/sample.go) |
| Java    |[buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder2.java) | [RtcTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilder2Sample.java) |
| Node.js |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder2.js) | [RtcTokenBuilder2Sample.js](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilder2Sample.js) |
| PHP     |[buildTokenWithUidAndPrivilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder2.php) | [RtcTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilder2Sample.php) |
| Python  |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilder2Sample.py) |
| Python3 |[build_token_with_uid_and_privilege](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder2.py) | [RtcTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilder2Sample.py) |

以 C++ 为例，你可以通过 `join_channel_privilege_expiration_in_seconds`、`pub_audio_privilege_expiration_in_seconds`、`pub_video_privilege_expiration_in_seconds`、`pub_data_stream_privilege_expiration_in_seconds` 参数来分别设置加入频道、发布音频流、视频流、数据流的有效时间。

示例代码如下：

```cpp tab="C++" tabGroup="token-sample-code"
#include <cstdlib>
#include <iostream>

#include "../src/RtcTokenBuilder2.h"

using namespace agora::tools;

int main(int argc, char const *argv[]) {
    (void)argc;
    (void)argv;

    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    const char *env_app_id = getenv("AGORA_APP_ID");
    std::string app_id = env_app_id ? env_app_id : "";
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    const char *env_app_certificate = getenv("AGORA_APP_CERTIFICATE");
    std::string app_certificate = env_app_certificate ? env_app_certificate : "";

    // 将 channelName 替换为需要加入的频道名
    std::string channel_name = "channelName";
    // 填入你实际的用户 ID
    uint32_t uid = "uid";
    // Token 的有效时长（秒）
    uint32_t token_expiration_in_seconds = 3600;
    // 加入频道权限的有效时长（秒），声网建议将其他权限的有效时间和该参数设为一致
    uint32_t join_channel_privilege_expiration_in_seconds = 3600;
    // 频道内发布音频流权限的有效时长（秒）
    uint32_t pub_audio_privilege_expiration_in_seconds = 3600;
    // 频道内发布视频流权限的有效时长（秒）
    uint32_t pub_video_privilege_expiration_in_seconds = 3600;
    // 频道内发布数据流权限的有效时长（秒）
    uint32_t pub_data_stream_privilege_expiration_in_seconds = 3600;
    std::string result;

    std::cout << "App Id:" << app_id << std::endl;
    std::cout << "App Certificate:" << app_certificate << std::endl;
    if (app_id == "" || app_certificate == "") {
        std::cout << "Need to set environment variable AGORA_APP_ID and "
                     "AGORA_APP_CERTIFICATE"
                  << std::endl;
        return -1;
    }

    // 生成 Token
    result = RtcTokenBuilder2::BuildTokenWithUid(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds,
        pub_audio_privilege_expiration_in_seconds,
        pub_video_privilege_expiration_in_seconds,
        pub_data_stream_privilege_expiration_in_seconds);
    std::cout << "Token With Int Uid:" << result << std::endl;

    return 0;
}
```

```go tab="Golang" tabGroup="token-sample-code"
package main

import (
    "fmt"
    "os"
    rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
)

func main() {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    appId := os.Getenv("AGORA_APP_ID")
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    appCertificate := os.Getenv("AGORA_APP_CERTIFICATE")
    // 将 channelName 替换为需要加入的频道名
    channelName := "channelName"
    // 填入你实际的用户 ID
    uid := uint32(uid)
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    tokenExpirationInSeconds := uint32(3600)
    // 加入频道权限的有效时间（秒）
    joinChannelPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布音频流权限的有效时间（秒）
    pubAudioPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布视频流权限的有效时间（秒）
    pubVideoPrivilegeExpireInSeconds := uint32(3600)
    // 频道内发布数据流权限的有效时间（秒）
    pubDataStreamPrivilegeExpireInSeconds := uint32(3600)

    fmt.Println("App Id:", appId)
    fmt.Println("App Certificate:", appCertificate)

    if appId == "" || appCertificate == "" {
        fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return
    }

    // 生成 Token
    result, err := rtctokenbuilder.BuildTokenWithUidAndPrivilege(
        appId,
        appCertificate,
        channelName,
        uid,
        tokenExpirationInSeconds,
        joinChannelPrivilegeExpireInSeconds,
        pubAudioPrivilegeExpireInSeconds,
        pubVideoPrivilegeExpireInSeconds,
        pubDataStreamPrivilegeExpireInSeconds,
    )

    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("Token with int uid and privilege: %s\n", result)
    }
}
```

```java tab="Java" tabGroup="token-sample-code"
package io.agora.sample;

import io.agora.media.RtcTokenBuilder2;

public class RtcTokenBuilder2Sample {
    // 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    static String appId = System.getenv("AGORA_APP_ID");
    // 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");

    // 将 channelName 替换为需要加入的频道名
    static String channelName = "channelName";
    // 填入你实际的用户 ID
    static int uid = 2082341273;
    // Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    static int tokenExpirationInSeconds = 3600;
    // 加入频道权限的有效时间（秒）
    static int joinChannelPrivilegeExpireInSeconds = 3600;
    // 频道内发布音频流权限的有效时间（秒）
    static int pubAudioPrivilegeExpireInSeconds = 3600;
    // 频道内发布视频流权限的有效时间（秒）
    static int pubVideoPrivilegeExpireInSeconds = 3600;
    // 频道内发布数据流权限的有效时间（秒）
    static int pubDataStreamPrivilegeExpireInSeconds = 3600;

    public static void main(String[] args) {
        System.out.printf("App Id: %s\n", appId);
        System.out.printf("App Certificate: %s\n", appCertificate);
        if (appId == null || appId.isEmpty() || appCertificate == null || appCertificate.isEmpty()) {
            System.out.printf("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE\n");
            return;
        }

        // 生成 Token
        RtcTokenBuilder2 token = new RtcTokenBuilder2();
        String result = token.buildTokenWithUid(appId, appCertificate, channelName, uid, tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds,
                pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds);
        System.out.printf("Token with uid and privilege: %s\n", result);
    }
}
```

```javascript tab="Node.js" tabGroup="token-sample-code"
const RtcTokenBuilder = require("../src/RtcTokenBuilder2").RtcTokenBuilder;

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
const appId = process.env.AGORA_APP_ID;
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
const appCertificate = process.env.AGORA_APP_CERTIFICATE;

// 将 channelName 替换为需要加入的频道名
const channelName = "channelName";
// 填入你实际的用户 ID
const uid = 2082341273;
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
const tokenExpirationInSecond = 3600;
// 加入频道权限的有效时间（秒）
const joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
const pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
const pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
const pubDataStreamPrivilegeExpireInSeconds = 3600;

console.log("App Id:", appId);
console.log("App Certificate:", appCertificate);
if (appId == undefined || appId == "" || appCertificate == undefined || appCertificate == "") {
    console.log("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE");
    process.exit(1);
}

// 生成 Token
const tokenWithUidAndPrivilege = RtcTokenBuilder.buildTokenWithUidAndPrivilege(
    appId,
    appCertificate,
    channelName,
    uid,
    tokenExpirationInSecond,
    joinChannelPrivilegeExpireInSeconds,
    pubAudioPrivilegeExpireInSeconds,
    pubVideoPrivilegeExpireInSeconds,
    pubDataStreamPrivilegeExpireInSeconds
);
console.log("Token with int uid and privilege:", tokenWithUidAndPrivilege);
```

```php tab="PHP" tabGroup="token-sample-code"
<?php
include("../src/RtcTokenBuilder2.php");

// 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
$appId = getenv("AGORA_APP_ID");
// 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
$appCertificate = getenv("AGORA_APP_CERTIFICATE");

// 将 channelName 替换为需要加入的频道名
$channelName = "channelName";
// 填入你实际的用户 ID
$uid = "uid";
// Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
$tokenExpirationInSeconds = 3600;
// 加入频道权限的有效时间（秒）
$joinChannelPrivilegeExpireInSeconds = 3600;
// 频道内发布音频流权限的有效时间（秒）
$pubAudioPrivilegeExpireInSeconds = 3600;
// 频道内发布视频流权限的有效时间（秒）
$pubVideoPrivilegeExpireInSeconds = 3600;
// 频道内发布数据流权限的有效时间（秒）
$pubDataStreamPrivilegeExpireInSeconds = 3600;

echo "App Id: " . $appId . PHP_EOL;
echo "App Certificate: " . $appCertificate . PHP_EOL;
if ($appId == "" || $appCertificate == "") {
    echo "Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE" . PHP_EOL;
    exit;
}

// 生成 Token
$token = RtcTokenBuilder2::buildTokenWithUidAndPrivilege($appId, $appCertificate, $channelName, $uid, $tokenExpirationInSeconds, $joinChannelPrivilegeExpireInSeconds, $pubAudioPrivilegeExpireInSeconds, $pubVideoPrivilegeExpireInSeconds, $pubDataStreamPrivilegeExpireInSeconds);
echo 'Token with int uid and privilege: ' . $token . PHP_EOL;
```

```py tab="Python" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))


if __name__ == "__main__":
    main()
```

```py tab="Python3" tabGroup="token-sample-code"
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.RtcTokenBuilder2 import *


def main():
    # 获取环境变量 AGORA_APP_ID 的值。请确保你将该变量设为你在声网控制台获取的 App ID
    app_id = os.environ.get("AGORA_APP_ID")
    # 获取环境变量 AGORA_APP_CERTIFICATE 的值。请确保你将该变量设为你在声网控制台获取的 App 证书
    app_certificate = os.environ.get("AGORA_APP_CERTIFICATE")

    # 将 channel_name 替换为需要加入的频道名
    channel_name = "channel_name"
    # 填入你实际的用户 ID
    uid = "uid"
    # Token 的有效时间（秒），声网建议将其他权限的有效时间和该参数设为一致
    token_expiration_in_seconds = 3600
    # 加入频道权限的有效时间（秒）
    join_channel_privilege_expiration_in_seconds = 3600
    # 频道内发布音频流权限的有效时间（秒）
    pub_audio_privilege_expiration_in_seconds = 3600
    # 频道内发布视频流权限的有效时间（秒）
    pub_video_privilege_expiration_in_seconds = 3600
    # 频道内发布数据流权限的有效时间（秒）
    pub_data_stream_privilege_expiration_in_seconds = 3600

    print("App Id: %s" % app_id)
    print("App Certificate: %s" % app_certificate)
    if not app_id or not app_certificate:
        print("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
        return

    # 生成 Token
    token = RtcTokenBuilder.build_token_with_uid_and_privilege(
        app_id, app_certificate, channel_name, uid, token_expiration_in_seconds,
        join_channel_privilege_expiration_in_seconds, pub_audio_privilege_expiration_in_seconds, pub_video_privilege_expiration_in_seconds, pub_data_stream_privilege_expiration_in_seconds)
    print("Token with int uid and privilege: {}".format(token))

if __name__ == "__main__":
    main()
```

:::info[信息]
以上示例代码中使用的是 `int` 型的用户 ID 来生成 Token。如果你需要使用 `string` 型的用户 ID 来生成 Token，请前往 [Token 生成器代码仓库](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)查看对应语言的相关方法及示例代码。
:::

#### 使用 Token [#使用-token-16]

请参考[使用 Token](#use) 来使用可精细控制发流权限的 Token。

## 部署与调试 [#部署与调试-8]

本节介绍如何在你的本地来快速部署一个 Token 生成器，部署成功之后你可以用生成的 Token 调试你的项目。

### 方式一：通过 Docker 部署 Token 生成器 [#方式一通过-docker-部署-token-生成器-8]

参考以下步骤在本地部署一个基于 Docker 的 Token 生成器并申请 Token：

:::warning[注意]
在开始前，请确保你已安装 [Docker](https://www.docker.com/)。
:::

<Accordions>
<Accordion title="具体步骤">

1. 打开终端，运行下列命令来启动 Token 生成器。将 `[YOUR_APP_ID]` 和 `[YOUR_APP_CERTIFICATE]` 替换为你在声网控制台获取的 App ID 和 App 证书。

   ```shell
   docker run -d -it -p 8080:8080 -e APP_ID=[YOUR_APP_ID] -e APP_CERTIFICATE=[YOUR_APP_CERTIFICATE] --name agora-token-service agoracn/token:0.1.2023053011
   ```

2. 运行下列命令来向你刚刚部署的 Token 生成器请求一个 Token：

   ```shell
   curl --location 'http://localhost:8080/token/generate' \
   --header 'Content-Type: application/json' \
   --data '{
       "channelName": "channel_name_test",
       "uid": "12345678",
       "tokenExpireTs": 3600,
       "privilegeExpireTs": 3600,
       "serviceRtc": {
           "enable": true,
           "role": 1
       }
   }'
   ```

   请求成功后，你的终端会返回生成的 Token。

</Accordion>
</Accordions>

### 方式二：本地手动部署 Token 生成器 [#方式二本地手动部署-token-生成器-8]

以 Golang 语言为例，你可以参考如下步骤来在你本地搭建并运行一个 Token 生成器来生成 Token：

在开始前，请确保你的 [Golang](https://golang.org/dl/ ) 版本是 1.14 或以上版本。

<Accordions>
<Accordion title="具体步骤">

:::warning[注意]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 新建一个 `token-server` 文件夹，在该文件夹下创建一个 `server.go` 文件，将生成 Token 的示例代码贴入到该文件中，将其中的 `appID` 和 `appCertificate` 替换为你的 App ID 和 App 证书。

2. 打开终端，进入到 `token-server` 文件路径下，运行如下命令行来为你的 Token 生成器创建 `go.mod` 文件，该文件定义导入路径及依赖项：

   ```go
   $ go mod init sampleServer
   ```

3. 运行如下命令行安装依赖。你可以使用 Go 镜像进行加速，例如 [https://goproxy.cn](https://goproxy.cn/)。

   ```go
   $ go get github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2
   ```

4. 运行如下命令行启动 Token 生成器：

   ```go
   $ go run server.go
   ```

​   Token 生成器部署成功后，你的终端会返回根据你填入的 App ID 和 App 证书生成的 Token。

</Accordion>
</Accordions>

你也可以直接前往声网控制台获取临时 Token 来调试你的项目，详见[获取临时 Token](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service#获取临时-token)。

## 参考信息 [#参考信息-7]

### 对 SDK 的兼容性要求 [#对-sdk-的兼容性要求-11]

AccessToken2 支持以下版本的声网 SDK（不包括客户端旁路推流功能）：

- 对于 RTC 4.x SDK：Android、iOS、Windows、macOS、Electron、Unity、React Native、Unreal 平台的版本需大于等于 **v4.0.0**，HarmonyOS 平台需大于等于 **v4.4.0**，Web 平台需大于等于 **v4.8.0**，Flutter 平台需大于等于 **v6.0.0**。

:::info[信息]
使用 [AccessToken2](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken2.h) 的 RTC SDK 可与使用 [AccessToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/AccessToken.h) 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
:::

<a id="api"></a>
### API 参考 [#api-参考-8]

以 Golang 语言为例，本节介绍生成 Token 的核心方法。

- `BuildTokenWithUid`：生成 Token，设置 Token 和所有权限的过期时间。

- `BuildTokenWithUidAndPrivilege`：生成 Token 并可精细控制发流权限。可设置 Token 的有效期以及加入频道权限、发布音频、视频、数据流权限的过期时间。

#### BuildTokenWithUid [#buildtokenwithuid-8]

生成 Token 并设置所有的权限过期时间。

```go
func BuildTokenWithUid(appId string, appCertificate string, channelName string, uid uint32, role Role, tokenExpire uint32, privilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `role`：用户权限，决定用户是否能在频道中发流。
  - `kRolePublisher`(1)（默认）：用户有发流权限，即用户可在频道中发流。
  - `kRoleSubscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[开启连麦鉴权](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `privilegeExpire`：所有权限的有效时长（秒）。如设为 0（默认值），表示权限永不过期。

:::warning[注意]
当 Token 过期但权限未过期时，用户仍在频道里并且可以发流，不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接，用户将无法使用该 Token 加入同一频道。声网建议将 Token 的过期时间和权限过期时间设为**一致**。
:::

#### BuildTokenWithUidAndPrivilege [#buildtokenwithuidandprivilege-7]

生成 Token 并设置不同权限的过期时间。

```go
func BuildTokenWithUidAndPrivilege(appId string, appCertificate string, channelName string, uid uint32,
	tokenExpire uint32, joinChannelPrivilegeExpire uint32, pubAudioPrivilegeExpire uint32, pubVideoPrivilegeExpire uint32, pubDataStreamPrivilegeExpire uint32)
```

**参数**

- `appId`：你在声网控制台创建项目时生成的 App ID。
- `appCertificate`：你的项目的 App 证书。
- `channelName`：频道名，长度在 64 个字节以内。
- `uid`：待鉴权用户的用户 ID，32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。
- `tokenExpire`：Token 的有效时长（秒），最长有效期为 24 小时。
- `joinChannelPrivilegeExpire`：加入频道权限的有效时长（秒）。
- `pubAudioPrivilegeExpire`：在频道内发布音频流权限的有效时长（秒）。
- `pubVideoPrivilegeExpire`：在频道内发布视频流权限的有效时长（秒）。
- `pubDataStreamPrivilegeExpire`：在频道内发布数据流权限的有效时长（秒）。

:::warning[注意]
- 上述不同权限的过期时间长度均从 Token 生成开始计算。
- 声网建议将 Token 的过期时间与其他权限的过期时间设为**一致**。
- 在频道中发布音视频流和数据流的权限仅在[开启连麦鉴权服务](/zh-CN/reference/faq/integration/token_cohost#3-开启连麦鉴权)后才生效。如果加入频道的权限过期时间早于发布音频流权限的过期时间，则在加入频道的权限过期后，用户会被踢出频道。
:::

### 常见问题 [#常见问题-7]

- [如何处理 Token 相关错误码？](/zh-CN/reference/faq/integration/token_related_issues)

- [如何开启连麦鉴权？](/zh-CN/reference/faq/integration/token_cohost)

- [如何生成具备 RTC 和 RTM 权限的 Token？](/zh-CN/reference/faq/integration/rtc_rtm_token)

### 相关文档 [#相关文档-12]

- [AccessToken 升级指南](/zh-CN/realtime-media/rtc/build/setup-and-access/token-server-upgrade#升级至-accesstoken2)

- [使用通配 Token](/zh-CN/realtime-media/rtc/build/security-and-auth/wildcard-token)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
