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

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

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
## 鉴权原理 [#鉴权原理]

本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。下图展示了鉴权的基本流程：

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

Token 在 App 服务器上生成，其最大有效期为 24 小时。当用户从你的 App 客户端连接声网频道时，声网平台会读取该 Token 中包含的信息，并进行校验。Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 过期的 Unix 时间戳

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

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的声网账户及已开启 App 证书的声网项目。请参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)。
- [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 Generator 会为客户端 App 生成请求所需的 Token，从而实现对声网平台的安全访问。为了提供 Token，你需要在安全基础设施中部署一个 Token Generator。

为了展示鉴权的工作流程，本节介绍如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

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/RtcTokenBuilder"
       "fmt"
       "log"
       "net/http"
       "time"
       "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>"
       // Token 过期的时间，单位为秒
       // 为作演示，在此将过期的时间戳设为 40 秒。当 Token 即将过期时，你可以看到客户端自动更新 Token 的过程
       expireTimeInSeconds := uint32(40)
       // 获取当前时间戳
       currentTimestamp := uint32(time.Now().UTC().Unix())
       // Token 过期的 Unix 时间戳
       expireTimestamp := currentTimestamp + expireTimeInSeconds

       result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
       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 0:
                       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAttendee
                   case 1:
                       role = rtctokenbuilder.RolePublisher
                   case 2:
                       role = rtctokenbuilder.RoleSubscriber
                   case 101:
                       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAdmin
                   }
       }
       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-对用户鉴权]

本节以 Android 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上使用 Android 模拟器搭建并运行一个 Android 客户端。

1. 基于你在实现互动直播时创建的项目，在 `/Gradle Scripts/build.gradle(Module: <projectname>.app)` 路径下添加如下依赖：

   ```java
   dependencies {
    ...
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.google.code.gson:gson:2.8.4'
    ...
   }
   ```

2. 将 `MainActivity.java` 中的内容替换为如下代码。将 `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 重新加入频道。

   ```java
   package com.example.rtcquickstart;

   import androidx.appcompat.app.AppCompatActivity;

   import android.os.Bundle;

   import androidx.core.app.ActivityCompat;
   import androidx.core.content.ContextCompat;

   import android.Manifest;
   import android.content.pm.PackageManager;
   import android.view.SurfaceView;
   import android.widget.FrameLayout;
   import android.widget.Toast;
   import io.agora.rtc2.Constants;
   import io.agora.rtc2.IRtcEngineEventHandler;
   import io.agora.rtc2.RtcEngine;
   import io.agora.rtc2.video.VideoCanvas;
   import io.agora.rtc2.ChannelMediaOptions;

   import okhttp3.MediaType;
   import okhttp3.OkHttpClient;
   import okhttp3.Request;
   import okhttp3.RequestBody;
   import okhttp3.Response;
   import okhttp3.Call;
   import okhttp3.Callback;

   import com.google.gson.Gson;

   import java.util.Map;

   import org.json.JSONException;
   import org.json.JSONObject;

   import java.io.IOException;

   public class MainActivity extends AppCompatActivity {

       // 填入在声网控制台创建项目时生成的 App ID
       private String appId = "Your App ID";
       // 填入频道名称
       private String channelName = "1234";

       private String token = "";

       private RtcEngine mRtcEngine;

       private int joined = 1;

       private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
           @Override
           // 监听远端用户加入频道，并获取远端用户的用户 ID
           public void onUserJoined(int uid, int elapsed) {
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       // 在 onUserJoined 回调中获取用户 ID 后，调用 setupRemoteVideo 设置远端用户视图
                       setupRemoteVideo(uid);
                   }
               });
           }

           @Override
           public void onTokenPrivilegeWillExpire(String token) {
               fetchToken(1234, channelName, 1);
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       Toast toast = Toast.makeText(MainActivity.this, "Token renewed", Toast.LENGTH_SHORT);
                       toast.show();
                   }
               });
               super.onTokenPrivilegeWillExpire(token);
           }

           @Override
           public void onRequestToken() {
               joined = 1;
               fetchToken(1234, channelName, 1);
               super.onRequestToken();
           }

       };

       private static final int PERMISSION_REQ_ID = 22;

       private static final String[] REQUESTED_PERMISSIONS = {
               Manifest.permission.RECORD_AUDIO,
               Manifest.permission.CAMERA
       };

       private boolean checkSelfPermission(String permission, int requestCode) {
           if (ContextCompat.checkSelfPermission(this, permission) !=
                   PackageManager.PERMISSION_GRANTED) {
               ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
               return false;
           }
           return true;
       }

      // 获取 RTC Token
      private void fetchToken(int uid,String channelName,int tokenRole){
          OkHttpClient client = new OkHttpClient();
          MediaType JSON = MediaType.parse("application/json; charset=utf-8");
          JSONObject json = new JSONObject();
          try {
              json.put("uid", uid);
              json.put("ChannelName", channelName);
              json.put("role", tokenRole);
          } catch (JSONException e) {
              e.printStackTrace();
          }

           RequestBody requestBody = RequestBody.create(JSON, String.valueOf(json));
           Request request = new Request.Builder()
                   .url("http://<Your Host URL and port>/fetch_rtc_token")
                   .header("Content-Type", "application/json; charset=UTF-8")
                   .post(requestBody)
                   .build();
           Call call = client.newCall(request);
           call.enqueue(new Callback() {
               @Override
               public void onFailure(Call call, IOException e) {

               }

               @Override
               public void onResponse(Call call, Response response) throws IOException {
                   if(response.isSuccessful()){
                       Gson gson = new Gson();
                       String result = response.body().string();
                       Map map = gson.fromJson(result, Map.class);
                       new Thread(new Runnable() {
                           @Override
                           public void run() {
                               token = map.get("token").toString();
                               // 如果用户未加入频道，使用 token 加入频道
                               ChannelMediaOptions options = new ChannelMediaOptions();
                               if (joined != 0){joined = mRtcEngine.joinChannel(token, channelName, 1234, options);}
                               // 如果用户已加入频道，调用 renewToken 重新生成 token
                               else {mRtcEngine.renewToken(token);}
                           }
                       });
                   }
               }
           });
       }

       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);

           // 如果所有权限都被授予，则初始化 RtcEngine 对象并加入频道
           if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
                   checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
               initializeAndJoinChannel();
           }
       }

       protected void onDestroy() {
           super.onDestroy();

           mRtcEngine.leaveChannel();
           mRtcEngine.destroy();
       }

       private void initializeAndJoinChannel() {
           try {
               mRtcEngine = RtcEngine.create(getBaseContext(), appId, mRtcEventHandler);
           } catch (Exception e) {
               throw new RuntimeException("Check the error.");
           }

           // 在互动直播中，设置频道场景为 BROADCASTING
           mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
           // 根据实际情况，设置用户角色为 BROADCASTER 或 AUDIENCE
           mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);

           // SDK 默认关闭视频。调用 enableVideo 开启视频
           mRtcEngine.enableVideo();

           FrameLayout container = findViewById(R.id.local_video_view_container);
           // 调用 CreateRendererView 创建一个 SurfaceView 对象并将其作为子项添加到 FrameLayout
           SurfaceView surfaceView = new SurfaceView (getBaseContext());
           container.addView(surfaceView);
           // 将 SurfaceView 对象传给声网，渲染本地视频
           mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
           // 开始本地视频预览
           mRtcEngine.startPreview();
           // 从 Token 服务器获取 Token
           fetchToken(1234, channelName, 1);
       }

       private void setupRemoteVideo(int uid) {
           FrameLayout container = findViewById(R.id.remote_video_view_container);
           SurfaceView surfaceView = new SurfaceView (getBaseContext());
           surfaceView.setZOrderMediaOverlay(true);
           container.addView(surfaceView);
           mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
       }

   }
   ```

### 运行项目 [#运行项目]

在本地设备的 Android 模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### Token 生成器代码 [#token-生成器代码]

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

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder.h) | [RtcTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilderSample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder/RtcTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtcTokenBuilder/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder.java) | [RtcTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilderSample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder.js) | [RtcTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilderSample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder.php) | [RtcTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilderSample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilderSample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilderSample.py) |

### API 参考 [#api-参考]

本节介绍生成 Token 的 API 参数和描述。 以 C++ 为例：

```cpp
static std::string buildTokenWithUid(
    const std::string& appId,
    const std::string& appCertificate,
    const std::string& channelName,
    uint32_t uid,
    UserRole role,
    uint32_t privilegeExpiredTs = 0);
```

| 参数 | 描述 |
| --- | --- |
| `appId` | 你在声网控制台创建项目时生成的 App ID。 |
| `appCertificate` | 你的项目的 App 证书。 |
| `channelName` | [token-server-upgrade-android-windows-markdown-1-3-1 details](#table-slot-token-server-upgrade-android-windows-markdown-1-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | [token-server-upgrade-android-windows-markdown-1-5-1 details](#table-slot-token-server-upgrade-android-windows-markdown-1-5-1) |
| `privilegeExpiredTs` | Token 过期的 Unix 时间戳，单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如，如果你将 `privilegeExpiredTs` 设为当前时间戳再加 600 秒，则 Token 会在 10 分钟内过期。Token 的最大有效期为 24 小时。 如果你将此参数设为 0，或时间长度超过 24 小时，Token 有效期依然为 24 小时。 |

<a id="table-slot-token-server-upgrade-android-windows-markdown-1-3-1"></a>

**token-server-upgrade-android-windows-markdown-1-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

<a id="table-slot-token-server-upgrade-android-windows-markdown-1-5-1"></a>

**token-server-upgrade-android-windows-markdown-1-5-1 details**

用户权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。

- `Role_Publisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
- `Role_Subscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[如何使用连麦鉴权功能？](/zh-CN/reference/faq/integration/token_cohost)

## 升级至 AccessToken2 [#升级至-accesstoken2]

### 升级 Token 服务器 [#升级-token-服务器]

1. 将导入 `rtctokenbuilder` 声明替换为以下代码，并且删除导入 "time" 的声明：

   ```go
   import (
       // 替换
       // rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
       rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
       "fmt"
       "log"
       "net/http"
       // 删除
       // "time"
       "encoding/json"
       "errors"
       "strconv"
   )
   ```

2. 删除生成时间戳的声明，添加 `tokenExpireTimeInSeconds` 和 `privilegeExpireTimeInSeconds` 相关代码：

   ```go
   // expireTimeInSeconds := uint32(40)
   // 获取当前时间戳
   // currentTimestamp := uint32(time.Now().UTC().Unix())
   // Timestamp when the token expires.
   // expireTimestamp := currentTimestamp + expireTimeInSeconds
   tokenExpireTimeInSeconds := uint32(40)
   privilegeExpireTimeInSeconds := uint32(40)
   ```

3. 更新 `tokenbuilder` 方法代码：

   ```go
   // 更新前代码
   // result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
   // 更新后代码
   // 将 BuildTokenWithUID 更改为 BuildTokenWithUid
   // 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
   result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
   ```

4. 删除不支持的角色：

   ```go
   switch role_num {
       // 删除
       // case 0:
       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
       // role = rtctokenbuilder.RoleAttendee
       case 1:
           role = rtctokenbuilder.RolePublisher
       case 2:
           role = rtctokenbuilder.RoleSubscriber
       // 删除
       // case 101:
       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
       // role = rtctokenbuilder.RoleAdmin
   }
   ```

### 测试 AccessToken2 服务器 [#测试-accesstoken2-服务器]

:::error[警告]
如果你同时在使用 RTC 拓展产品或服务，如云端录制、旁路推流，声网推荐你在升级到 AccessToken2 前联系[技术支持](https://ticket.shengwang.cn/)。
:::

为了测试 AccessToken2 服务器是否正常运行，在本地设备的 Android 模拟器中构建并运行项目，App 会执行如下操作：

- 成功加入频道。
- 每 10 秒更新一次 Token。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。
## 鉴权原理 [#鉴权原理-1]

下图展示了鉴权的基本流程：

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

Token 在 App 服务器上生成，其最大有效期为 24 小时。当用户从你的 App 客户端连接至声网频道时，声网平台会读取该 Token 中包含的信息，并进行校验。Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 过期的 Unix 时间戳

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

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/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 Generator 会为客户端 App 生成请求所需的 Token，从而实现对声网平台的安全访问。为了提供 Token，你需要在安全基础设施中部署一个 Token Generator。

为了展示鉴权的工作流程，本节介绍如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

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/RtcTokenBuilder"
       "fmt"
       "log"
       "net/http"
       "time"
       "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>"
       // Token 过期的时间，单位为秒
       // 为作演示，在此将过期的时间戳设为 40 秒。当 Token 即将过期时，你可以看到客户端自动更新 Token 的过程
       expireTimeInSeconds := uint32(40)
       // 获取当前时间戳
       currentTimestamp := uint32(time.Now().UTC().Unix())
       // Token 过期的 Unix 时间戳
       expireTimestamp := currentTimestamp + expireTimeInSeconds

       result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
       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 0:
                       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAttendee
                   case 1:
                       role = rtctokenbuilder.RolePublisher
                   case 2:
                       role = rtctokenbuilder.RoleSubscriber
                   case 101:
                       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAdmin
                   }
       }
       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]

本节以 iOS 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上使用 iOS 模拟器搭建并运行一个 iOS 客户端。

将 `ViewController.swift` 中的内容替换为如下代码。将 `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 会触发 `tokenPrivilegeWillExpire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 将新生成的 Token 传给 SDK。
- Token 过期时，SDK 会触发 `rtcEngineRequestToken` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `joinChannel` 方法，再使用新的 Token 重新加入频道。

  ```swift
  //
  //  ViewController.swift
  //  RteQuickstart
  //
  //  Created by macoscatalina on 2021/8/12.
  //  Copyright © 2021 macoscatalina. All rights reserved.
  //

  import UIKit
  import AgoraRtcKit
  import Foundation

  public enum TokenError: Error{
      case noData
      case invalidData
  }

  class ViewController: UIViewController {
      var localView: UIView!
      var remoteView: UIView!

      var agoraKit: AgoraRtcEngineKit!

      override func viewDidLoad() {
          super.viewDidLoad()
          // 加载完视图后，可进行其他设置
          initView()
          initializeAgoraEngine()
          setClientRole()
          setupLocalVideo()
          fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
              case .success(let token):
                  print("token is: \(token)")
                  self.joinChannel(token: token)
              case .failure(let err):
                  print("Could not fetch token: \(err)")
              }
          }
      }

      override func viewDidLayoutSubviews(){
          super.viewDidLayoutSubviews()
          remoteView.frame = self.view.bounds
          localView.frame = CGRect(x: self.view.bounds.width - 90, y: 0, width: 90, height: 160)
      }

      override func viewDidDisappear(_ animated: Bool) {
          super.viewDidDisappear(true)
          leaveChannel()
          destroy()
      }

      func initView(){
          remoteView = UIView()
          self.view.addSubview(remoteView)
          localView = UIView()
          self.view.addSubview(localView)
      }

      func initializeAgoraEngine(){
          let config = AgoraRtcEngineConfig()
          config.appId = "Your App ID"
          config.channelProfile = .liveBroadcasting
          agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
          if agoraKit != nil{
              print("Initialization successful")
          }
          else{
              print("Initialization failed")
          }
      }

      func setClientRole(){
          agoraKit.setClientRole(.broadcaster)
      }

      func setupLocalVideo(){
          agoraKit.enableVideo()
          agoraKit.startPreview()
          let videoCanvas = AgoraRtcVideoCanvas()
          videoCanvas.uid = 0
          videoCanvas.renderMode = .hidden
          videoCanvas.view = localView
          agoraKit.setupLocalVideo(videoCanvas)
      }

      func joinChannel(token:String){
          let option = AgoraRtcChannelMediaOptions()
          agoraKit.joinChannel(byToken: token, channelId: "test", uid: 123456, mediaOptions: option)
      }

      func leaveChannel(){
          agoraKit.stopPreview()
          agoraKit.leaveChannel(nil)
      }

      func destroy(){
          AgoraRtcEngineKit.destroy()
      }

      func fetchToken(channelName: String, userId: UInt, role: UInt,
          callback: @escaping (Result<String, Error>) -> Void
      ){
          let url = URL(string: "http://<Your Host URL and port>/fetch_rtc_token")
          let parameters = ["uid":userId,"channelName": channelName, "role": role] as [String : Any]

          print(parameters.self)

          var request = URLRequest(
              url: url!,
              timeoutInterval: 10
          )

          request.httpMethod = "POST"

          do {
              request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
          }
          catch let error {
              print(error.localizedDescription)
          }

          URLSession.shared.dataTask(with: request){data, _, err in
              guard let data = data else {
                  if let err = err {
                      callback(.failure(err))
                  }
                  else {
                      callback(.failure(TokenError.noData))
                  }
              return
          }

          let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])

          if let responseDict = responseJSON as? [String: Any], let token = responseDict["token"] as? String {
              callback(.success(token))
          } else {
              callback(.failure(TokenError.invalidData))
          }

      }.resume()
  }
  }

  extension ViewController: AgoraRtcEngineDelegate{
      func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int){
          let videoCanvas = AgoraRtcVideoCanvas()
          videoCanvas.uid = uid
          videoCanvas.renderMode = .hidden
          videoCanvas.view = remoteView
          agoraKit.setupRemoteVideo(videoCanvas)
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
          self.fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
              case .success(let token):
                  print("token is: \(token)")
                  self.agoraKit.renewToken(token)
                  print("Renewed the token")
              case .failure(let err):
                  print("Could not fetch token: \(err)")
              }
          }
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, connectionStateChanged state: AgoraConnectionState, reason: AgoraConnectionChangedReason) {
          print("Connection state changed to")
          print(state.rawValue)
      }

      func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) {
          fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
                  case .success(let token):
                      print("token is: \(token)")
                      self.joinChannel(token: token)
                  case .failure(let err):
                      print("Could not fetch token: \(err)")
                  }
          }
      }
  }
  ```

### 运行项目 [#运行项目-1]

在本地设备的 iOS 模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-1]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### Token 生成器代码 [#token-生成器代码-1]

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

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder.h) | [RtcTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilderSample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder/RtcTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtcTokenBuilder/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder.java) | [RtcTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilderSample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder.js) | [RtcTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilderSample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder.php) | [RtcTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilderSample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilderSample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilderSample.py) |

### API 参考 [#api-参考-1]

本节介绍生成 Token 的 API 参数和描述。 以 C++ 为例：

```c++
static std::string buildTokenWithUid(
    const std::string& appId,
    const std::string& appCertificate,
    const std::string& channelName,
    uint32_t uid,
    UserRole role,
    uint32_t privilegeExpiredTs = 0);
```

| 参数 | 描述 |
| --- | --- |
| `appId` | 你在声网控制台创建项目时生成的 App ID。 |
| `appCertificate` | 你的项目的 App 证书。 |
| `channelName` | [token-server-upgrade-ios-macos-markdown-1-3-1 details](#table-slot-token-server-upgrade-ios-macos-markdown-1-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | [token-server-upgrade-ios-macos-markdown-1-5-1 details](#table-slot-token-server-upgrade-ios-macos-markdown-1-5-1) |
| `privilegeExpiredTs` | Token 过期的 Unix 时间戳，单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如，如果你将 `privilegeExpiredTs` 设为当前时间戳再加 600 秒，则 Token 会在 10 分钟内过期。Token 的最大有效期为 24 小时。 如果你将此参数设为 0，或时间长度超过 24 小时，Token 有效期依然为 24 小时。 |

<a id="table-slot-token-server-upgrade-ios-macos-markdown-1-3-1"></a>

**token-server-upgrade-ios-macos-markdown-1-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

<a id="table-slot-token-server-upgrade-ios-macos-markdown-1-5-1"></a>

**token-server-upgrade-ios-macos-markdown-1-5-1 details**

用户权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。

- `Role_Publisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
- `Role_Subscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[如何使用连麦鉴权功能？](/zh-CN/reference/faq/integration/token_cohost)

## 升级至 AccessToken2 [#升级至-accesstoken2-1]

### 升级 Token 服务器 [#升级-token-服务器-1]

1. 将导入 `rtctokenbuilder` 声明替换为以下代码，并且删除导入 "time" 的声明：

   ```go
   import (
       // 替换
       // rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
       rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
       "fmt"
       "log"
       "net/http"
       // 删除
       // "time"
       "encoding/json"
       "errors"
       "strconv"
   )
   ```

2. 删除生成时间戳的声明，添加 `tokenExpireTimeInSeconds` 和 `privilegeExpireTimeInSeconds` 相关代码：

   ```go
      // expireTimeInSeconds := uint32(40)
      // 获取当前时间戳
      // currentTimestamp := uint32(time.Now().UTC().Unix())
      // Timestamp when the token expires.
      // expireTimestamp := currentTimestamp + expireTimeInSeconds
   tokenExpireTimeInSeconds := uint32(40)
   privilegeExpireTimeInSeconds := uint32(40)
   ```

3. 更新 `tokenbuilder` 方法代码：

   ```go
      // 更新前代码
      // result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
      // 更新后代码
      // 将 BuildTokenWithUID 更改为 BuildTokenWithUid
      // 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
   result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
   ```

4. 删除不支持的角色：

   ```go
   switch role_num {
      // 删除
      // case 0:
         // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
         // role = rtctokenbuilder.RoleAttendee
         case 1:
            role = rtctokenbuilder.RolePublisher
         case 2:
            role = rtctokenbuilder.RoleSubscriber
      // 删除
      // case 101:
         // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
         // role = rtctokenbuilder.RoleAdmin
   }
   ```

### 测试 AccessToken2 服务器 [#测试-accesstoken2-服务器-1]

:::error[警告]
如果你同时在使用 RTC 拓展产品或服务，如云端录制、旁路推流，声网推荐你在升级到 AccessToken2 前联系[技术支持](https://ticket.shengwang.cn/)。
:::

为了测试 AccessToken2 服务器是否正常运行，在本地设备的 iOS 模拟器中构建并运行项目，App 会执行如下操作：

- 成功加入频道。
- 每 10 秒更新一次 Token。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。
## 鉴权原理 [#鉴权原理-2]

下图展示了鉴权的基本流程：

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

Token 在 App 服务器上生成，其最大有效期为 24 小时。当用户从你的 App 客户端连接至声网频道时，声网平台会读取该 Token 中包含的信息，并进行校验。Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 过期的 Unix 时间戳

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

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的[声网账户](/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 Generator 会为客户端 App 生成请求所需的 Token，从而实现对声网平台的安全访问。为了提供 Token，你需要在安全基础设施中部署一个 Token Generator。

为了展示鉴权的工作流程，本节介绍如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

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/RtcTokenBuilder"
       "fmt"
       "log"
       "net/http"
       "time"
       "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>"
       // Token 过期的时间，单位为秒
       // 为作演示，在此将过期的时间戳设为 40 秒。当 Token 即将过期时，你可以看到客户端自动更新 Token 的过程
       expireTimeInSeconds := uint32(40)
       // 获取当前时间戳
       currentTimestamp := uint32(time.Now().UTC().Unix())
       // Token 过期的 Unix 时间戳
       expireTimestamp := currentTimestamp + expireTimeInSeconds

       result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
       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 0:
                       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAttendee
                   case 1:
                       role = rtctokenbuilder.RolePublisher
                   case 2:
                       role = rtctokenbuilder.RoleSubscriber
                   case 101:
                       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAdmin
                   }
       }
       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]

本节以 iOS 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上使用 iOS 模拟器搭建并运行一个 iOS 客户端。

将 `ViewController.swift` 中的内容替换为如下代码。将 `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 会触发 `tokenPrivilegeWillExpire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 将新生成的 Token 传给 SDK。
- Token 过期时，SDK 会触发 `rtcEngineRequestToken` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `joinChannel` 方法，再使用新的 Token 重新加入频道。

  ```swift
  //
  //  ViewController.swift
  //  RteQuickstart
  //
  //  Created by macoscatalina on 2021/8/12.
  //  Copyright © 2021 macoscatalina. All rights reserved.
  //

  import UIKit
  import AgoraRtcKit
  import Foundation

  public enum TokenError: Error{
      case noData
      case invalidData
  }

  class ViewController: UIViewController {
      var localView: UIView!
      var remoteView: UIView!

      var agoraKit: AgoraRtcEngineKit!

      override func viewDidLoad() {
          super.viewDidLoad()
          // 加载完视图后，可进行其他设置
          initView()
          initializeAgoraEngine()
          setClientRole()
          setupLocalVideo()
          fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
              case .success(let token):
                  print("token is: \(token)")
                  self.joinChannel(token: token)
              case .failure(let err):
                  print("Could not fetch token: \(err)")
              }
          }
      }

      override func viewDidLayoutSubviews(){
          super.viewDidLayoutSubviews()
          remoteView.frame = self.view.bounds
          localView.frame = CGRect(x: self.view.bounds.width - 90, y: 0, width: 90, height: 160)
      }

      override func viewDidDisappear(_ animated: Bool) {
          super.viewDidDisappear(true)
          leaveChannel()
          destroy()
      }

      func initView(){
          remoteView = UIView()
          self.view.addSubview(remoteView)
          localView = UIView()
          self.view.addSubview(localView)
      }

      func initializeAgoraEngine(){
          let config = AgoraRtcEngineConfig()
          config.appId = "Your App ID"
          config.channelProfile = .liveBroadcasting
          agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
          if agoraKit != nil{
              print("Initialization successful")
          }
          else{
              print("Initialization failed")
          }
      }

      func setClientRole(){
          agoraKit.setClientRole(.broadcaster)
      }

      func setupLocalVideo(){
          agoraKit.enableVideo()
          agoraKit.startPreview()
          let videoCanvas = AgoraRtcVideoCanvas()
          videoCanvas.uid = 0
          videoCanvas.renderMode = .hidden
          videoCanvas.view = localView
          agoraKit.setupLocalVideo(videoCanvas)
      }

      func joinChannel(token:String){
          let option = AgoraRtcChannelMediaOptions()
          agoraKit.joinChannel(byToken: token, channelId: "test", uid: 123456, mediaOptions: option)
      }

      func leaveChannel(){
          agoraKit.stopPreview()
          agoraKit.leaveChannel(nil)
      }

      func destroy(){
          AgoraRtcEngineKit.destroy()
      }

      func fetchToken(channelName: String, userId: UInt, role: UInt,
          callback: @escaping (Result<String, Error>) -> Void
      ){
          let url = URL(string: "http://<Your Host URL and port>/fetch_rtc_token")
          let parameters = ["uid":userId,"channelName": channelName, "role": role] as [String : Any]

          print(parameters.self)

          var request = URLRequest(
              url: url!,
              timeoutInterval: 10
          )

          request.httpMethod = "POST"

          do {
              request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
          }
          catch let error {
              print(error.localizedDescription)
          }

          URLSession.shared.dataTask(with: request){data, _, err in
              guard let data = data else {
                  if let err = err {
                      callback(.failure(err))
                  }
                  else {
                      callback(.failure(TokenError.noData))
                  }
              return
          }

          let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])

          if let responseDict = responseJSON as? [String: Any], let token = responseDict["token"] as? String {
              callback(.success(token))
          } else {
              callback(.failure(TokenError.invalidData))
          }

      }.resume()
  }
  }

  extension ViewController: AgoraRtcEngineDelegate{
      func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int){
          let videoCanvas = AgoraRtcVideoCanvas()
          videoCanvas.uid = uid
          videoCanvas.renderMode = .hidden
          videoCanvas.view = remoteView
          agoraKit.setupRemoteVideo(videoCanvas)
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
          self.fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
              case .success(let token):
                  print("token is: \(token)")
                  self.agoraKit.renewToken(token)
                  print("Renewed the token")
              case .failure(let err):
                  print("Could not fetch token: \(err)")
              }
          }
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, connectionStateChanged state: AgoraConnectionState, reason: AgoraConnectionChangedReason) {
          print("Connection state changed to")
          print(state.rawValue)
      }

      func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) {
          fetchToken(channelName: "test", userId: 1234, role: 1){ result in
              switch result {
                  case .success(let token):
                      print("token is: \(token)")
                      self.joinChannel(token: token)
                  case .failure(let err):
                      print("Could not fetch token: \(err)")
                  }
          }
      }
  }
  ```

### 运行项目 [#运行项目-2]

在本地设备的 iOS 模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-2]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### Token 生成器代码 [#token-生成器代码-2]

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

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder.h) | [RtcTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilderSample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder/RtcTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtcTokenBuilder/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder.java) | [RtcTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilderSample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder.js) | [RtcTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilderSample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder.php) | [RtcTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilderSample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilderSample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilderSample.py) |

### API 参考 [#api-参考-2]

本节介绍生成 Token 的 API 参数和描述。 以 C++ 为例：

```c++
static std::string buildTokenWithUid(
    const std::string& appId,
    const std::string& appCertificate,
    const std::string& channelName,
    uint32_t uid,
    UserRole role,
    uint32_t privilegeExpiredTs = 0);
```

| 参数 | 描述 |
| --- | --- |
| `appId` | 你在声网控制台创建项目时生成的 App ID。 |
| `appCertificate` | 你的项目的 App 证书。 |
| `channelName` | [token-server-upgrade-ios-macos-markdown-1-3-1 details](#table-slot-token-server-upgrade-ios-macos-markdown-1-3-1-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | [token-server-upgrade-ios-macos-markdown-1-5-1 details](#table-slot-token-server-upgrade-ios-macos-markdown-1-5-1-1) |
| `privilegeExpiredTs` | Token 过期的 Unix 时间戳，单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如，如果你将 `privilegeExpiredTs` 设为当前时间戳再加 600 秒，则 Token 会在 10 分钟内过期。Token 的最大有效期为 24 小时。 如果你将此参数设为 0，或时间长度超过 24 小时，Token 有效期依然为 24 小时。 |

<a id="table-slot-token-server-upgrade-ios-macos-markdown-1-3-1-1"></a>

**token-server-upgrade-ios-macos-markdown-1-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

<a id="table-slot-token-server-upgrade-ios-macos-markdown-1-5-1-1"></a>

**token-server-upgrade-ios-macos-markdown-1-5-1 details**

用户权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。

- `Role_Publisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
- `Role_Subscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[如何使用连麦鉴权功能？](/zh-CN/reference/faq/integration/token_cohost)

## 升级至 AccessToken2 [#升级至-accesstoken2-2]

### 升级 Token 服务器 [#升级-token-服务器-2]

1. 将导入 `rtctokenbuilder` 声明替换为以下代码，并且删除导入 "time" 的声明：

   ```go
   import (
       // 替换
       // rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
       rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
       "fmt"
       "log"
       "net/http"
       // 删除
       // "time"
       "encoding/json"
       "errors"
       "strconv"
   )
   ```

2. 删除生成时间戳的声明，添加 `tokenExpireTimeInSeconds` 和 `privilegeExpireTimeInSeconds` 相关代码：

   ```go
      // expireTimeInSeconds := uint32(40)
      // 获取当前时间戳
      // currentTimestamp := uint32(time.Now().UTC().Unix())
      // Timestamp when the token expires.
      // expireTimestamp := currentTimestamp + expireTimeInSeconds
   tokenExpireTimeInSeconds := uint32(40)
   privilegeExpireTimeInSeconds := uint32(40)
   ```

3. 更新 `tokenbuilder` 方法代码：

   ```go
      // 更新前代码
      // result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
      // 更新后代码
      // 将 BuildTokenWithUID 更改为 BuildTokenWithUid
      // 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
   result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
   ```

4. 删除不支持的角色：

   ```go
   switch role_num {
      // 删除
      // case 0:
         // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
         // role = rtctokenbuilder.RoleAttendee
         case 1:
            role = rtctokenbuilder.RolePublisher
         case 2:
            role = rtctokenbuilder.RoleSubscriber
      // 删除
      // case 101:
         // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
         // role = rtctokenbuilder.RoleAdmin
   }
   ```

### 测试 AccessToken2 服务器 [#测试-accesstoken2-服务器-2]

:::error[警告]
如果你同时在使用 RTC 拓展产品或服务，如云端录制、旁路推流，声网推荐你在升级到 AccessToken2 前联系[技术支持](https://ticket.shengwang.cn/)。
:::

为了测试 AccessToken2 服务器是否正常运行，在本地设备的 iOS 模拟器中构建并运行项目，App 会执行如下操作：

- 成功加入频道。
- 每 10 秒更新一次 Token。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。
:::info[信息]
如果你此前并未使用过 AccessToken 鉴权，可只参考[使用 Token 鉴权](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)文档使用 AccessToken2，无需了解 AccessToken。
:::

## 使用 AccessToken 鉴权 [#使用-accesstoken-鉴权]

### 鉴权原理 [#鉴权原理-3]

下图展示了鉴权的基本流程：

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

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
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 过期的 Unix 时间戳

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

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的声网账户及已开启 App 证书的声网项目。请参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)。
- [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 作为项目唯一标识。你可以直接在**总览**页面的**项目信息**栏，点击 App ID 右侧的复制按钮来获取当前项目的 App ID。

你需要保存好复制的 App ID，后续调用 API 进行初始化等操作时需要传入你获取的 App ID。

##### 获取 App 证书 [#获取-app-证书-3]
参考以下步骤获取 App 证书：

1. 在声网控制台的[项目管理](https://console.shengwang.cn/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 Generator 会生成相应的 Token，再发送给客户端。

本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

:::error[警告]
此示例服务器仅用于演示，请勿用于生产环境中。
:::

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/RtcTokenBuilder"
       "fmt"
       "log"
       "net/http"
       "time"
       "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){

       // 填入项目 App ID
       appID := "<Your App ID>"
       // 填入 App 证书
       appCertificate := "<Your App Certificate>"
       // Token 过期的时间，单位为秒
       // 为作演示，在此将过期的时间戳设为 40 秒。当 Token 即将过期时，你可以看到客户端自动更新 Token 的过程
       expireTimeInSeconds := uint32(40)
       // 获取当前时间戳
       currentTimestamp := uint32(time.Now().UTC().Unix())
       // Token 过期的 Unix 时间戳
       expireTimestamp := currentTimestamp + expireTimeInSeconds

       result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
       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 0:
                      // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAttendee
                   case 1:
                       role = rtctokenbuilder.RolePublisher
                   case 2:
                       role = rtctokenbuilder.RoleSubscriber
                   case 101:
                       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAdmin
                   }
       }
       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]

本节以 Web 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

:::error[警告]
此示例仅用于演示，请勿用于生产环境中。
:::

1. 创建一个项目文件夹，其中包含如下文件：
   - `index.html`：用户界面
   - `client.js`：使用声网 RTC Web SDK 4.x 的 App 逻辑

   ```shell
   |
   |-- index.html
   |-- client.js
   ```

2. 在 `index.html` 中加入以下代码，创建用户界面：

   ```html
   <html>
   <head>
      <title>Token demo</title>
   </head>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
   <body>
      <h1>Token demo</h1>
      <script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script>
    <script src="./client.js"></script>

   </body>
   </html>
   ```

3. 将如下代码贴入 `client.js` 文件中，实现客户端鉴权逻辑。
    - 将 `<Your App ID>` 替换为你的 App ID。该 App ID 必须与 Token 生成代码中的 App ID 一致。
    - 将 `<Your Host URL and port>` 替换为你部署好的本地 Golang 服务器的主机 URL 和端口，如 `10.53.3.234:8082`。

    ```javascript
    var rtc = {
        // 设置本地音频轨道和视频轨道
        localAudioTrack: null,
        localVideoTrack: null,
    };

    var options = {
        // 传入 App ID
        appId: "<Your App ID>",
        // 传入频道名
        channel: "ChannelA",
        // 设置用户为 host (可发流) 或 audience（仅可收流）
        role: "host"
    };

    // 从 Golang 服务器获取 Token
    function fetchToken(uid, channelName, tokenRole) {

        return new Promise(function (resolve) {
            axios.post('http://<Your Host URL and port>/fetch_rtc_token', {
                uid: uid,
                channelName: channelName,
                role: tokenRole
            }, {
                headers: {
                    'Content-Type': 'application/json; charset=UTF-8'
                }
            })
                .then(function (response) {
                    const token = response.data.token;
                    resolve(token);
                })
                .catch(function (error) {
                    console.log(error);
                });
        })
    }

    async function startBasicCall() {

        const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
        client.setClientRole(options.role);
        const uid = 123456;

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

        await client.join(options.appId, options.channel, token, uid);
        rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
        rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
        await client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
        const localPlayerContainer = document.createElement("div");
        localPlayerContainer.id = uid;
        localPlayerContainer.style.width = "640px";
        localPlayerContainer.style.height = "480px";
        document.body.append(localPlayerContainer);

        rtc.localVideoTrack.play(localPlayerContainer);

        console.log("publish success!");

        client.on("user-published", async (user, mediaType) => {
            await client.subscribe(user, mediaType);
            console.log("subscribe success");

            if (mediaType === "video") {
                const remoteVideoTrack = user.videoTrack;
                const remotePlayerContainer = document.createElement("div");
                remotePlayerContainer.textContent = "Remote user " + user.uid.toString();
                remotePlayerContainer.style.width = "640px";
                remotePlayerContainer.style.height = "480px";
                document.body.append(remotePlayerContainer);
                remoteVideoTrack.play(remotePlayerContainer);

            }

            if (mediaType === "audio") {
                const remoteAudioTrack = user.audioTrack;
                remoteAudioTrack.play();
            }

            client.on("user-unpublished", user => {
                const remotePlayerContainer = document.getElementById(user.uid);
                remotePlayerContainer.remove();
            });

        });

        // 收到 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-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);
        });

    }

    startBasicCall()
    ```

   在上述代码示例中，你可以看到 Token 与客户端的以下代码逻辑有关：

   - 调用 `join` 方法，使用 Token、用户 ID 和频道名加入频道。用户 ID 和频道名必须和用于生成 Token 的用户 ID 和频道名一致。
   - 在 Token 过期前 30 秒，SDK 会触发 `token-privilege-will-expire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `renewToken` 方法将新生成的 Token 传给 SDK。
   - Token 过期时，SDK 会触发 `token-privilege-did-expire` 回调。收到该回调后，客户端需要从服务器获取新的 Token 并调用 `join` 方法，再使用新的 Token 重新加入频道。

4. 用支持的浏览器打开 `index.html` 文件，可以看到客户端执行以下操作：
   - 成功加入频道。
   - 每隔 10 秒更新 Token。

### 参考 [#参考-3]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

#### Token 生成器代码 [#token-生成器代码-3]

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

| 语言 | 算法 | 核心方法 | 示例代码 |
| -------- | ----------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| C++ | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder.h) | [RtcTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilderSample.cpp) |
| Go | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder/RtcTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtcTokenBuilder/sample.go) |
| Java | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder.java) | [RtcTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilderSample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder.js) | [RtcTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilderSample.js) |
| PHP | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder.php) | [RtcTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilderSample.php) |
| Python | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilderSample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilderSample.py) |

#### API 参考 [#api-参考-3]

本节介绍生成 Token 的 API 参数和描述。以 C++ 为例：

```cpp
static std::string buildTokenWithUid(
    const std::string& appId,
    const std::string& appCertificate,
    const std::string& channelName,
    uint32_t uid,
    UserRole role,
    uint32_t privilegeExpiredTs = 0);
```

| 参数 | 描述 |
| --- | --- |
| `appId` | 你在声网控制台创建项目时生成的 App ID。 |
| `appCertificate` | 你的项目的 App 证书。 |
| `channelName` | [token-server-upgrade-javascript-markdown-1-3-1 details](#table-slot-token-server-upgrade-javascript-markdown-1-3-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | [token-server-upgrade-javascript-markdown-1-5-1 details](#table-slot-token-server-upgrade-javascript-markdown-1-5-1) |
| `privilegeExpiredTs` | Token 过期的 Unix 时间戳，单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如，如果你将 `privilegeExpiredTs` 设为当前时间戳再加 600 秒，则 Token 会在 10 分钟内过期。 Token 的最大有效期为 24 小时。 如果你将此参数设为 0，或时间长度超过 24 小时，Token 有效期依然为 24 小时。 |

<a id="table-slot-token-server-upgrade-javascript-markdown-1-3-1"></a>

**token-server-upgrade-javascript-markdown-1-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格
- 标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

<a id="table-slot-token-server-upgrade-javascript-markdown-1-5-1"></a>

**token-server-upgrade-javascript-markdown-1-5-1 details**

用户权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。

- `Role_Publisher(1)`：（默认）用户有发流权限，即用户可在频道中发流。
- `Role_Subscriber(2)`：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[如何使用连麦鉴权功能？](/zh-CN/reference/faq/integration/token_cohost)

## 升级至 AccessToken2 [#升级至-accesstoken2-3]

### 升级服务器 [#升级服务器]

1. 将导入 `rtctokenbuilder` 声明替换为以下代码，并且删除导入 `"time"` 的声明：

    ```go
    import (
      // 替换
      // rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
      rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
      "fmt"
      "log"
      "net/http"
      // 删除
      // "time"
      "encoding/json"
      "errors"
      "strconv"
    )
    ```

2. 删除生成时间戳的声明，添加 `tokenExpireTimeInSeconds` 和 `privilegeExpireTimeInSeconds` 相关代码：

    ```go
    // expireTimeInSeconds := uint32(40)
    // 获取当前时间戳
    // currentTimestamp := uint32(time.Now().UTC().Unix())
    // Timestamp when the token expires.
    // expireTimestamp := currentTimestamp + expireTimeInSeconds
    tokenExpireTimeInSeconds := uint32(40)
    privilegeExpireTimeInSeconds := uint32(40)
    ```

3. 更新 `tokenbuilder` 方法代码：

    ```go
    // 更新前代码
    // result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
    // 更新后代码
    // 将 BuildTokenWithUID 更改为 BuildTokenWithUid
    // 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
    result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
    ```

4. 删除不支持的角色：

    ```go
    switch role_num {
      // 删除
      // case 0:
      // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
      // role = rtctokenbuilder.RoleAttendee
      case 1:
         role = rtctokenbuilder.RolePublisher
      case 2:
         role = rtctokenbuilder.RoleSubscriber
      // 删除
      // case 101:
         // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
         // role = rtctokenbuilder.RoleAdmin
                }
    ```

### 升级客户端 [#升级客户端]

:::warning[注意]
如果 Web 客户端使用的 RTC Web SDK 的版本为 4.8.0 或更高版本，则无需升级客户端。AccessToken2 兼容的 SDK 版本详见 [对 SDK 的兼容性要求](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication#对-sdk-的兼容性要求)。
:::

在 `index.html` 中做如下修改：

```html
<html>
<head>
   <title>Token demo</title>
</head>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
   <h1>Token demo</h1>
   <!-- 删除 -->
   <!-- <script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script> -->
   <!-- 将 SDK 升级至与 AccessToken2 兼容的版本。-->
   <script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.8.0.js"></script>
 <script src="./client.js"></script>
</body>
</html>
```

### 测试服务器 [#测试服务器]

:::warning[注意]
如果你同时在使用 RTC 拓展产品或服务，如云端录制、旁路推流，声网推荐你在升级到 AccessToken2 前[联系技术支持](https://ticket.shengwang.cn/)。
:::

为了测试 AccessToken2 服务器是否正常运行，使用支持的浏览器打开 `index.html` 文件并进行以下操作：

- 成功加入频道。
- 每 10 秒更新一次 Token。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
本文介绍如何使用 AccessToken 鉴权以及如何升级至 AccessToken2。
## 鉴权原理 [#鉴权原理-4]

下图展示了鉴权的基本流程：

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

Token 在 App 服务器上生成，其最大有效期为 24 小时。当用户从你的 App 客户端连接声网频道时，声网平台会读取该 Token 中包含的信息，并进行校验。Token 包含以下信息：

- 你在声网控制台创建项目时生成的 App ID
- 你的项目的 App 证书
- 频道名
- 用户 ID
- 用户权限，如是否能发流或收流
- Token 过期的 Unix 时间戳

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

开始前，请确保你的项目或使用的声网产品满足如下条件：

- 一个有效的声网账户及已开启 App 证书的声网项目。请参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)。
- [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)。

## 实现鉴权流程 [#实现鉴权流程-4]

本节介绍如何使用声网提供的[代码](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey)生成并提供 Token，对用户及其权限进行校验。

### 获取 App ID 及 App 证书 [#获取-app-id-及-app-证书-4]

本节介绍如何获取生成 Token 所需的安全信息，如你的项目的 App ID 及 App 证书。

#### 获取 App ID [#获取-app-id-4]

声网会给每个项目自动分配一个 App ID 作为项目唯一标识。

在声网控制台的[项目管理](https://console.shengwang.cn/settings/projects)页面，找到你的项目，点击 App ID 右侧的图标，即可获取项目的 App ID。

#### 获取 App 证书 [#获取-app-证书-4]

参考以下步骤获取 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-服务器-4]

Token Generator 会为客户端 App 生成请求所需的 Token，从而实现对声网平台的安全访问。为了提供 Token，你需要在安全基础设施中部署一个 Token Generator。

为了展示鉴权的工作流程，本节介绍如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。

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/RtcTokenBuilder"
       "fmt"
       "log"
       "net/http"
       "time"
       "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>"
       // Token 过期的时间，单位为秒
       // 为作演示，在此将过期的时间戳设为 40 秒。当 Token 即将过期时，你可以看到客户端自动更新 Token 的过程
       expireTimeInSeconds := uint32(40)
       // 获取当前时间戳
       currentTimestamp := uint32(time.Now().UTC().Unix())
       // Token 过期的 Unix 时间戳
       expireTimestamp := currentTimestamp + expireTimeInSeconds

       result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
       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 0:
                       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAttendee
                   case 1:
                       role = rtctokenbuilder.RolePublisher
                   case 2:
                       role = rtctokenbuilder.RoleSubscriber
                   case 101:
                       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
                       role = rtctokenbuilder.RoleAdmin
                   }
       }
       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-对用户鉴权-4]

本节以 Android 客户端为例，展示如何使用 Token 对客户端的用户进行鉴权。

为了展示鉴权的工作流程，本节介绍如何在你的本地开发环境上使用 Android 模拟器搭建并运行一个 Android 客户端。

1. 基于你在实现互动直播时创建的项目，在 `/Gradle Scripts/build.gradle(Module: <projectname>.app)` 路径下添加如下依赖：

   ```java
   dependencies {
    ...
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.google.code.gson:gson:2.8.4'
    ...
   }
   ```

2. 将 `MainActivity.java` 中的内容替换为如下代码。将 `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 重新加入频道。

   ```java
   package com.example.rtcquickstart;

   import androidx.appcompat.app.AppCompatActivity;

   import android.os.Bundle;

   import androidx.core.app.ActivityCompat;
   import androidx.core.content.ContextCompat;

   import android.Manifest;
   import android.content.pm.PackageManager;
   import android.view.SurfaceView;
   import android.widget.FrameLayout;
   import android.widget.Toast;
   import io.agora.rtc2.Constants;
   import io.agora.rtc2.IRtcEngineEventHandler;
   import io.agora.rtc2.RtcEngine;
   import io.agora.rtc2.video.VideoCanvas;
   import io.agora.rtc2.ChannelMediaOptions;

   import okhttp3.MediaType;
   import okhttp3.OkHttpClient;
   import okhttp3.Request;
   import okhttp3.RequestBody;
   import okhttp3.Response;
   import okhttp3.Call;
   import okhttp3.Callback;

   import com.google.gson.Gson;

   import java.util.Map;

   import org.json.JSONException;
   import org.json.JSONObject;

   import java.io.IOException;

   public class MainActivity extends AppCompatActivity {

       // 填入在声网控制台创建项目时生成的 App ID
       private String appId = "Your App ID";
       // 填入频道名称
       private String channelName = "1234";

       private String token = "";

       private RtcEngine mRtcEngine;

       private int joined = 1;

       private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
           @Override
           // 监听远端用户加入频道，并获取远端用户的用户 ID
           public void onUserJoined(int uid, int elapsed) {
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       // 在 onUserJoined 回调中获取用户 ID 后，调用 setupRemoteVideo 设置远端用户视图
                       setupRemoteVideo(uid);
                   }
               });
           }

           @Override
           public void onTokenPrivilegeWillExpire(String token) {
               fetchToken(1234, channelName, 1);
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       Toast toast = Toast.makeText(MainActivity.this, "Token renewed", Toast.LENGTH_SHORT);
                       toast.show();
                   }
               });
               super.onTokenPrivilegeWillExpire(token);
           }

           @Override
           public void onRequestToken() {
               joined = 1;
               fetchToken(1234, channelName, 1);
               super.onRequestToken();
           }

       };

       private static final int PERMISSION_REQ_ID = 22;

       private static final String[] REQUESTED_PERMISSIONS = {
               Manifest.permission.RECORD_AUDIO,
               Manifest.permission.CAMERA
       };

       private boolean checkSelfPermission(String permission, int requestCode) {
           if (ContextCompat.checkSelfPermission(this, permission) !=
                   PackageManager.PERMISSION_GRANTED) {
               ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
               return false;
           }
           return true;
       }

      // 获取 RTC Token
      private void fetchToken(int uid,String channelName,int tokenRole){
          OkHttpClient client = new OkHttpClient();
          MediaType JSON = MediaType.parse("application/json; charset=utf-8");
          JSONObject json = new JSONObject();
          try {
              json.put("uid", uid);
              json.put("ChannelName", channelName);
              json.put("role", tokenRole);
          } catch (JSONException e) {
              e.printStackTrace();
          }

           RequestBody requestBody = RequestBody.create(JSON, String.valueOf(json));
           Request request = new Request.Builder()
                   .url("http://<Your Host URL and port>/fetch_rtc_token")
                   .header("Content-Type", "application/json; charset=UTF-8")
                   .post(requestBody)
                   .build();
           Call call = client.newCall(request);
           call.enqueue(new Callback() {
               @Override
               public void onFailure(Call call, IOException e) {

               }

               @Override
               public void onResponse(Call call, Response response) throws IOException {
                   if(response.isSuccessful()){
                       Gson gson = new Gson();
                       String result = response.body().string();
                       Map map = gson.fromJson(result, Map.class);
                       new Thread(new Runnable() {
                           @Override
                           public void run() {
                               token = map.get("token").toString();
                               // 如果用户未加入频道，使用 token 加入频道
                               ChannelMediaOptions options = new ChannelMediaOptions();
                               if (joined != 0){joined = mRtcEngine.joinChannel(token, channelName, 1234, options);}
                               // 如果用户已加入频道，调用 renewToken 重新生成 token
                               else {mRtcEngine.renewToken(token);}
                           }
                       });
                   }
               }
           });
       }

       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);

           // 如果所有权限都被授予，则初始化 RtcEngine 对象并加入频道
           if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
                   checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
               initializeAndJoinChannel();
           }
       }

       protected void onDestroy() {
           super.onDestroy();

           mRtcEngine.leaveChannel();
           mRtcEngine.destroy();
       }

       private void initializeAndJoinChannel() {
           try {
               mRtcEngine = RtcEngine.create(getBaseContext(), appId, mRtcEventHandler);
           } catch (Exception e) {
               throw new RuntimeException("Check the error.");
           }

           // 在互动直播中，设置频道场景为 BROADCASTING
           mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
           // 根据实际情况，设置用户角色为 BROADCASTER 或 AUDIENCE
           mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);

           // SDK 默认关闭视频。调用 enableVideo 开启视频
           mRtcEngine.enableVideo();

           FrameLayout container = findViewById(R.id.local_video_view_container);
           // 调用 CreateRendererView 创建一个 SurfaceView 对象并将其作为子项添加到 FrameLayout
           SurfaceView surfaceView = new SurfaceView (getBaseContext());
           container.addView(surfaceView);
           // 将 SurfaceView 对象传给声网，渲染本地视频
           mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
           // 开始本地视频预览
           mRtcEngine.startPreview();
           // 从 Token 服务器获取 Token
           fetchToken(1234, channelName, 1);
       }

       private void setupRemoteVideo(int uid) {
           FrameLayout container = findViewById(R.id.remote_video_view_container);
           SurfaceView surfaceView = new SurfaceView (getBaseContext());
           surfaceView.setZOrderMediaOverlay(true);
           container.addView(surfaceView);
           mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
       }

   }
   ```

### 运行项目 [#运行项目-3]

在本地设备的 Android 模拟器中构建并运行项目，App 会执行如下操作：

1. 加入频道。
2. 每隔 10 秒更新 Token。

## 参考 [#参考-4]

本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。

### Token 生成器代码 [#token-生成器代码-4]

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

| 语言    | 算法        | 核心方法                                                     | 示例代码                                                     |
| :------ | :---------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| C++     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtcTokenBuilder.h) | [RtcTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtcTokenBuilderSample.cpp) |
| Go      | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder/RtcTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtcTokenBuilder/sample.go) |
| Java    | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/media/RtcTokenBuilder.java) | [RtcTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtcTokenBuilderSample.java) |
| Node.js | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtcTokenBuilder.js) | [RtcTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtcTokenBuilderSample.js) |
| PHP     | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtcTokenBuilder.php) | [RtcTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtcTokenBuilderSample.php) |
| Python  | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtcTokenBuilderSample.py) |
| Python3 | HMAC-SHA256 | [buildTokenWithUid](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtcTokenBuilder.py) | [RtcTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtcTokenBuilderSample.py) |

### API 参考 [#api-参考-4]

本节介绍生成 Token 的 API 参数和描述。 以 C++ 为例：

```cpp
static std::string buildTokenWithUid(
    const std::string& appId,
    const std::string& appCertificate,
    const std::string& channelName,
    uint32_t uid,
    UserRole role,
    uint32_t privilegeExpiredTs = 0);
```

| 参数 | 描述 |
| --- | --- |
| `appId` | 你在声网控制台创建项目时生成的 App ID。 |
| `appCertificate` | 你的项目的 App 证书。 |
| `channelName` | [token-server-upgrade-android-windows-markdown-1-3-1 details](#table-slot-token-server-upgrade-android-windows-markdown-1-3-1-1) |
| `uid` | 待鉴权用户的用户 ID 32 位无符号整数，范围为 1 到 (2³² - 1)， 并保证唯一性。 如不需对用户 ID 进行鉴权，即客户端使用任何 `uid` 都可加入频道，请把 `uid` 设为 0。 |
| `role` | [token-server-upgrade-android-windows-markdown-1-5-1 details](#table-slot-token-server-upgrade-android-windows-markdown-1-5-1-1) |
| `privilegeExpiredTs` | Token 过期的 Unix 时间戳，单位为秒。该值为当前时间戳和 Token 有效期的总和。 例如，如果你将 `privilegeExpiredTs` 设为当前时间戳再加 600 秒，则 Token 会在 10 分钟内过期。Token 的最大有效期为 24 小时。 如果你将此参数设为 0，或时间长度超过 24 小时，Token 有效期依然为 24 小时。 |

<a id="table-slot-token-server-upgrade-android-windows-markdown-1-3-1-1"></a>

**token-server-upgrade-android-windows-markdown-1-3-1 details**

频道名称，长度在 64 个字节以内。以下为支持的字符集范围：

- 26 个小写英文字母 a-z；
- 26 个大写英文字母 A-Z；
- 10 个数字 0-9；
- 空格标点符号和其他符号, 包括: "!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "&lt;", "=", ".", "&gt;", "?", "@", "[", "]", "^", "_", "&#123;", "&#125;", "\", "~", ","。

<a id="table-slot-token-server-upgrade-android-windows-markdown-1-5-1-1"></a>

**token-server-upgrade-android-windows-markdown-1-5-1 details**

用户权限，分为发流用户和接收用户。参数决定用户是否能在频道中发流。

- `Role_Publisher`(1)：（默认）用户有发流权限，即用户可在频道中发流。
- `Role_Subscriber`(2)：用户有接收权限，即用户可在频道中接收，但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考[如何使用连麦鉴权功能？](/zh-CN/reference/faq/integration/token_cohost)

## 升级至 AccessToken2 [#升级至-accesstoken2-4]

### 升级 Token 服务器 [#升级-token-服务器-3]

1. 将导入 `rtctokenbuilder` 声明替换为以下代码，并且删除导入 "time" 的声明：

   ```go
   import (
       // 替换
       // rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtcTokenBuilder"
       rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
       "fmt"
       "log"
       "net/http"
       // 删除
       // "time"
       "encoding/json"
       "errors"
       "strconv"
   )
   ```

2. 删除生成时间戳的声明，添加 `tokenExpireTimeInSeconds` 和 `privilegeExpireTimeInSeconds` 相关代码：

   ```go
   // expireTimeInSeconds := uint32(40)
   // 获取当前时间戳
   // currentTimestamp := uint32(time.Now().UTC().Unix())
   // Timestamp when the token expires.
   // expireTimestamp := currentTimestamp + expireTimeInSeconds
   tokenExpireTimeInSeconds := uint32(40)
   privilegeExpireTimeInSeconds := uint32(40)
   ```

3. 更新 `tokenbuilder` 方法代码：

   ```go
   // 更新前代码
   // result, err := rtctokenbuilder.BuildTokenWithUID(appID, appCertificate, channelName, int_uid, role, expireTimestamp)
   // 更新后代码
   // 将 BuildTokenWithUID 更改为 BuildTokenWithUid
   // 将 expireTimestamp 更改为 tokenExpireTimeInSeconds 和 privilegeExpireTimeInSeconds
   result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
   ```

4. 删除不支持的角色：

   ```go
   switch role_num {
       // 删除
       // case 0:
       // 已废弃。RoleAttendee 和 RolePublisher 的权限相同
       // role = rtctokenbuilder.RoleAttendee
       case 1:
           role = rtctokenbuilder.RolePublisher
       case 2:
           role = rtctokenbuilder.RoleSubscriber
       // 删除
       // case 101:
       // 已废弃。RoleAdmin 和 RolePublisher 的权限相同
       // role = rtctokenbuilder.RoleAdmin
   }
   ```

### 测试 AccessToken2 服务器 [#测试-accesstoken2-服务器-3]

:::error[警告]
如果你同时在使用 RTC 拓展产品或服务，如云端录制、旁路推流，声网推荐你在升级到 AccessToken2 前联系[技术支持](https://ticket.shengwang.cn/)。
:::

为了测试 AccessToken2 服务器是否正常运行，在本地设备的 Android 模拟器中构建并运行项目，App 会执行如下操作：

- 成功加入频道。
- 每 10 秒更新一次 Token。
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
