语聊房接入 RTC 必须使用 Token 鉴权,App Certificate 只能存在服务端,客户端每次进入频道前向服务端请求 Token,声网服务端验证 Token 合法后才允许用户加入。把 App Certificate 写进客户端代码,反编译就能取走,等于把整个 RTC 账户开放给任何人。
这篇文章覆盖语聊房 Token 的完整流程:服务端用 AgoraDynamicKey 生成 Token(含 Java 和 Node.js 代码示例)、Android 和 iOS 客户端如何传入 Token 加入频道、Token 即将过期时如何无感续期,以及常见鉴权错误排查和生产环境的安全注意事项。
一. 为什么 App Certificate 不能放在客户端
声网 RTC 鉴权需要两样东西:App ID(公开的,客户端可以持有)和 App Certificate(私密的,只能在服务端持有)。
Token 是用这两样东西合并生成的,带有用户 ID、频道名、权限类型和有效期信息,客户端用 Token 加入频道,声网服务端验证 Token 的合法性。
如果把 App Certificate 直接写在客户端代码里,任何人反编译 APK 或 IPA 都能拿到它,用你的 Certificate 生成任意 Token,以你的账号名义使用 RTC 服务。费用会被别人消耗,而且难以追溯。
正确的做法:App Certificate 只存在服务端,客户端每次加入频道前向服务端请求一个 Token。
二. Token 里包含什么信息
声网的 RTC Token(AccessToken2 格式)包含以下字段:
| 字段 | 说明 |
|---|---|
| App ID | 应用标识 |
| 频道名(Channel Name) | Token 只对指定频道有效 |
| 用户 ID(UID) | Token 只对指定用户 ID 有效 |
| Token 有效期 | Token 本身的过期时间 |
| 权限有效期 | 加入频道、发布音频/视频权限各自的有效期 |
重要:加入频道时使用的用户 ID 和频道名,必须和生成 Token 时填入的一致,否则鉴权会失败。
三. 服务端生成 Token
声网提供了开源的 Token 生成库 AgoraDynamicKey,支持 Java、Go、Node.js、Python、PHP、C++ 等语言,可以直接集成到你的后端服务里。
Java 示例(使用 RtcTokenBuilder2):
package io.agora.sample;
import io.agora.media.RtcTokenBuilder2;
import io.agora.media.RtcTokenBuilder2.Role;
public class TokenServer {
// 从环境变量读取,不要硬编码在代码里
static String appId = System.getenv("AGORA_APP_ID");
static String appCertificate = System.getenv("AGORA_APP_CERTIFICATE");
public static String generateToken(String channelName, int uid) {
// token 本身的有效期(秒),建议 24 小时以内
int tokenExpiration = 3600;
// 频道内权限的有效期,可以和 token 有效期不同
int privilegeExpiration = 3600;
RtcTokenBuilder2 tokenBuilder = new RtcTokenBuilder2();
String token = tokenBuilder.buildTokenWithUid(
appId,
appCertificate,
channelName,
uid,
Role.ROLE_PUBLISHER, // 语聊房上麦用户用 PUBLISHER
tokenExpiration,
privilegeExpiration
);
return token;
}
}
角色说明:
Role.ROLE_PUBLISHER:有发布音频权限,对应麦位上的用户Role.ROLE_SUBSCRIBER:仅订阅权限,对应观众席用户(部分场景用)
语聊房里,通常统一生成 ROLE_PUBLISHER 的 Token,由客户端通过设置 setClientRole 来控制实际的发布行为,而不是在 Token 级别区分。这样用户上麦时不需要重新获取 Token。
Node.js 示例:
const { RtcTokenBuilder, RtcRole } = require('agora-access-token');
function generateRtcToken(channelName, uid) {
const appId = process.env.AGORA_APP_ID;
const appCertificate = process.env.AGORA_APP_CERTIFICATE;
const expirationTimeInSeconds = 3600;
const currentTimestamp = Math.floor(Date.now() / 1000);
const privilegeExpiredTs = currentTimestamp + expirationTimeInSeconds;
const token = RtcTokenBuilder.buildTokenWithUid(
appId,
appCertificate,
channelName,
uid,
RtcRole.PUBLISHER,
privilegeExpiredTs
);
return token;
}
两种语言的逻辑完全一样,选你们服务端用的语言对应的库就行。Token 生成库代码可以从声网官方 GitHub 仓库下载。
四. 客户端加入频道时传入 Token
生成好 Token 之后,客户端通过 API 请求向服务端获取,再用 Token 加入 RTC 频道。
Android:
// 1. 先从服务端获取 Token(示例用伪代码表示网络请求)
String token = fetchTokenFromServer(channelName, uid);
// 2. 用 Token 加入频道
int result = engine.joinChannel(
token, // 从服务端获取的 Token
channelName, // 频道名,必须和生成 Token 时一致
null,
uid // 用户 ID,必须和生成 Token 时一致
);
iOS(Swift):
// 1. 从服务端获取 Token
fetchTokenFromServer(channelName: channelName, uid: uid) { token in
// 2. 用 Token 加入频道
let options = AgoraRtcChannelMediaOptions()
options.channelProfile = .liveBroadcasting
options.clientRoleType = .audience // 默认观众,上麦时再切换
self.agoraKit.joinChannel(
byToken: token,
channelId: channelName,
uid: uid,
mediaOptions: options
)
}
五. Token 过期怎么处理
Token 有效期到了,用户会被踢出频道。不处理 Token 过期,会导致用户在使用过程中突然掉线,体验很差。
SDK 会在 Token 过期前 30 秒触发回调,这时候去服务端换一个新 Token,再调 renewToken 更新就行,用户不会感知到任何中断。
Android 处理示例:
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
// Token 即将过期(过期前 30 秒触发)
@Override
public void onTokenPrivilegeWillExpire(String token) {
// 向服务端请求新 Token
String newToken = fetchTokenFromServer(currentChannelName, currentUid);
// 更新 Token,用户不会掉线
engine.renewToken(newToken);
}
// Token 已过期(过期时触发,说明 onTokenPrivilegeWillExpire 没有及时处理)
@Override
public void onRequestToken() {
// 重新获取 Token 并重新加入频道
String newToken = fetchTokenFromServer(currentChannelName, currentUid);
engine.joinChannel(newToken, currentChannelName, null, currentUid);
}
};
iOS(Swift)处理示例:
extension YourViewController: AgoraRtcEngineDelegate {
// Token 即将过期
func rtcEngine(_ engine: AgoraRtcEngineKit,
tokenPrivilegeWillExpire token: String) {
fetchTokenFromServer(channelName: currentChannelName, uid: currentUid) { newToken in
engine.renewToken(newToken)
}
}
// Token 已过期
func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) {
fetchTokenFromServer(channelName: currentChannelName, uid: currentUid) { newToken in
engine.joinChannel(
byToken: newToken,
channelId: self.currentChannelName,
uid: self.currentUid,
mediaOptions: AgoraRtcChannelMediaOptions()
)
}
}
}
onTokenPrivilegeWillExpire 和 onRequestToken 的区别:前者是”快过期了,提前换”,后者是”已经过期了,没办法只能重连”。两个回调都要处理,提前换可以保证用户无感,但网络不好或者服务端响应慢时可能没来得及,这时候 onRequestToken 是兜底。
六. 测试阶段:临时 Token
开发初期,每次调试都搭一套 Token 服务太麻烦。声网控制台提供临时 Token 生成工具,可以生成最长 24 小时有效的临时 Token,只用于测试环境。
注意:临时 Token 只能用于开发测试,正式上线时必须换成自己服务端生成的 Token。临时 Token 的有效期最长 24 小时,且无法设置超过这个时间。
七. 常见错误排查
ERR_TOKEN_EXPIRED(109)
Token 过期。通常是开发阶段用了临时 Token 过期了,或者 onTokenPrivilegeWillExpire 回调没有正确处理。
ERR_INVALID_TOKEN(110)
Token 无效。可能原因:
- 生成 Token 时的 App ID / App Certificate 和当前 App ID 不匹配
- 频道名或用户 ID 和加入频道时填的不一致
- Token 字符串传输过程中被截断或编码错误
加入频道返回负数错误码
检查 App ID 格式是否正确(32位小写字母+数字),以及网络是否能正常访问声网服务器。
八. 生产环境的注意事项
Token 有效期设置:建议 1 ~ 24 小时,根据你的用户使用时长来定。语聊房用户一次可能待 2-3 小时,Token 有效期设短了频繁续期,设太长了安全性降低。一般设置 2-6 小时,配合 onTokenPrivilegeWillExpire 自动续期。
UID 分配:UID 是 32 位无符号整数(0 ~ 4294967295)。UID 0 表示让 SDK 自动分配,但建议在服务端分配具体的 UID,方便你关联业务用户 ID 和 RTC 用户 ID。
App Certificate 的安全存储:App Certificate 不要写在代码里,通过环境变量或密钥管理服务(如 AWS Secrets Manager、阿里云 KMS)注入到服务端程序。