Skip to content

Commit cb3b065

Browse files
authored
Merge pull request #8 from hawaiyon/feature/token_server
feat(server): add permission key examples
2 parents 87eb6f2 + b765f1d commit cb3b065

35 files changed

+1341
-73
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "server/token_server/cpp/thirdparty/rapidjson"]
2+
path = server/token_server/cpp/thirdparty/rapidjson
3+
url = https://github.com/Tencent/rapidjson.git

server/README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
# 云信 RTC 鉴权 token 生成示例
1+
# 云信 RTC token 生成示例
2+
3+
因为安全性考虑,RTC 鉴权 token 应该由客户端调用客户业务服务器获取。因此本目录下提供了多种服务端语言的实现方式。
4+
5+
官方文档:
6+
* 基础token:https://doc.yunxin.163.com/nertc/docs/TQ0MTI2ODQ?platform=android
7+
* 高级 token:https://doc.yunxin.163.com/nertc/docs/zA3MjAwNzM?platform=android
28

3-
官方文档:https://doc.yunxin.163.com/nertc/docs/TQ0MTI2ODQ?platform=android
49

510
包括以下编程语言:
611

@@ -9,5 +14,7 @@
914
- NodeJS
1015
- PHP
1116
- Python3
17+
- C++
18+
- C#(dotnet)
1219

13-
请查看对应 token_server 目录下对应的语言目录
20+
请查看 token_server 目录下对应的语言目录,每个子目录下都有 README 说明使用方式。

server/token_server/cpp/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# 生成云信 RTC 鉴权的 token
2+
3+
* 基础token:https://doc.yunxin.163.com/nertc/docs/TQ0MTI2ODQ?platform=android
4+
* 高级 token:https://doc.yunxin.163.com/nertc/docs/zA3MjAwNzM?platform=android
5+
6+
7+
## 代码目录
8+
9+
* 本目录下是一个完整的 C++ 项目。`./src/token_builder.h` 文件包含了基础token、高级 token 的完整代码
10+
11+
## 使用示例
12+
13+
```cpp
14+
15+
// 建议项目初始化时候创建对象, 通过单例全局维护一个 TokenServer 对象
16+
// appKey、appSecret 请替换成自己的,具体在云信管理后台查看。
17+
// 7200 代表默认有效的时间,单位秒。不能超过 86400,即 24 小时
18+
TokenServer tokenServer(appKey, appSecret, 7200);
19+
20+
// 在需要的时候,提供 channelName(房间名)、uid(用户标识)、ttlSec(有效时间,单位秒) 参数,生成 token
21+
string token = tokenServer.getBasicToken(channelName, uid, ttlSec);
22+
23+
24+
// 高级 token 具体权限说明见函数注释
25+
uint8_t privilege = (uint8_t) (1);
26+
long ttlSec = 1000;
27+
// permSecret 见云信管理后台,具体见文档说明:https://doc.yunxin.163.com/nertc/docs/zA3MjAwNzM?platform=android
28+
string permissionToken = tokenServer.getPermissionKey(channelName, permSecret, uid, privilege, ttlSec);
29+
```
30+
31+
## 代码引入说明
32+
33+
1. 复制 `./src/token_builder.h` 文件到你的项目中. 并且添加依赖的库到编译选项中 `-lssl -lcrypto -lz`
34+
2. 生成 token 的代码中引入文件, 然后调用 `getBasicToken` 方法
35+
3. 如果需要生成高级 token,调用 `getPermissionKey` 方法
36+
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <sstream>
4+
#include <iomanip>
5+
#include <chrono>
6+
#include <stdexcept>
7+
#include <openssl/buffer.h>
8+
#include "../thirdparty/rapidjson/include/rapidjson/document.h"
9+
#include "../thirdparty/rapidjson/include/rapidjson/pointer.h"
10+
#include "../thirdparty/rapidjson/include/rapidjson/stringbuffer.h"
11+
#include "../thirdparty/rapidjson/include/rapidjson/writer.h"
12+
#include <cstring>
13+
#include <algorithm>
14+
#include <openssl/sha.h>
15+
#include <openssl/bio.h>
16+
#include <openssl/evp.h>
17+
#include <openssl/hmac.h>
18+
#include "zlib.h"
19+
20+
class TokenServer
21+
{
22+
public:
23+
TokenServer(const std::string &appKey, const std::string &appSecret, int defaultTTLSec) : appKey(appKey), appSecret(appSecret), defaultTTLSec(defaultTTLSec)
24+
{
25+
if (appKey.empty() || appSecret.empty())
26+
{
27+
throw std::invalid_argument("appKey or appSecret is empty");
28+
}
29+
if (defaultTTLSec <= 0)
30+
{
31+
throw std::invalid_argument("defaultTTLSec must be positive");
32+
}
33+
}
34+
35+
/**
36+
* Generate a token with the given channel name, user ID, and time-to-live (TTL) in seconds.
37+
*
38+
* @param channelName: the name of the channel for which the token is being generated
39+
* @param uid: the user ID associated with the token
40+
* @param ttlSec: the time-to-live (TTL) for the token in seconds
41+
* @return: the token
42+
*/
43+
std::string getBasicToken(const std::string &channelName, long uid, int ttlSec)
44+
{
45+
long long curTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
46+
return getBasicTokenWithCurrentTime(channelName, uid, ttlSec, curTimeMs);
47+
}
48+
49+
std::string getBasicTokenWithCurrentTime(const std::string &channelName, long uid, int ttlSec, long long curTimeMs)
50+
{
51+
if (ttlSec <= 0)
52+
{
53+
ttlSec = defaultTTLSec;
54+
}
55+
DynamicToken tokenModel;
56+
tokenModel.signature = sha1(appKey + std::to_string(uid) + std::to_string(curTimeMs) + std::to_string(ttlSec) + channelName + appSecret);
57+
tokenModel.curTime = curTimeMs;
58+
tokenModel.ttl = ttlSec;
59+
60+
rapidjson::Document sig_doc;
61+
sig_doc.SetObject();
62+
rapidjson::Document::AllocatorType &allocator = sig_doc.GetAllocator();
63+
sig_doc.AddMember("signature", rapidjson::Value(tokenModel.signature.c_str(), allocator), sig_doc.GetAllocator());
64+
sig_doc.AddMember("curTime", tokenModel.curTime, sig_doc.GetAllocator());
65+
sig_doc.AddMember("ttl", tokenModel.ttl, sig_doc.GetAllocator());
66+
rapidjson::StringBuffer s;
67+
rapidjson::Writer<rapidjson::StringBuffer> w(s);
68+
sig_doc.Accept(w);
69+
return base64Encode(s.GetString());
70+
}
71+
72+
/**
73+
* generates a permission key for the given channelName, uid and privilege.
74+
* @param channelName: the name of the channel for which the token is being generated
75+
* @param permSecret: the user ID associated with the token
76+
* @param uid: the user ID associated with the token
77+
* @param privilege: the privilege of the user. privilege is a 8-bit number, each bit represents a permission:
78+
* - bit 1: (0000 0001) = 1, permission to send audio stream
79+
* - bit 2: (0000 0010) = 2, permission to send video stream
80+
* - bit 3: (0000 0100) = 4, permission to receive audio stream
81+
* - bit 4: (0000 1000) = 8, permission to receive video stream
82+
* - bit 5: (0001 0000) = 16, permission to create room
83+
* - bit 6: (0010 0000) = 32, permission to join room
84+
* So, (0010 1100) = 32+8+4 = 44 means permission to receive audio&video stream and join room.
85+
* (0011 1111) = 63 means all permission allowed.
86+
* @param ttlSec: the time-to-live (TTL) for the token in seconds
87+
*
88+
*/
89+
std::string getPermissionKey(const std::string channelName, const std::string permSecret, long uid, uint8_t privilege, long ttlSec)
90+
{
91+
long curTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
92+
return getPermissionKeyWithCurrentTime(channelName, permSecret, uid, privilege, ttlSec, curTime);
93+
}
94+
95+
std::string getPermissionKeyWithCurrentTime(const std::string channelName, const std::string permSecret, long uid, uint8_t privilege, long ttlSec, long curTime)
96+
{
97+
PermissionKey permKey;
98+
permKey.appkey = appKey;
99+
permKey.uid = uid;
100+
permKey.cname = channelName;
101+
permKey.privilege = privilege;
102+
permKey.expireTime = ttlSec;
103+
permKey.curTime = curTime;
104+
105+
// Calculate the signature
106+
std::string checksum = hmacsha256(appKey, std::to_string(uid), std::to_string(curTime), std::to_string(ttlSec), channelName, permSecret, std::to_string(privilege));
107+
permKey.checksum = checksum;
108+
// construct json
109+
rapidjson::Document sig_doc;
110+
sig_doc.SetObject();
111+
rapidjson::Document::AllocatorType &allocator = sig_doc.GetAllocator();
112+
113+
sig_doc.AddMember("appkey", rapidjson::Value(appKey.c_str(), allocator), sig_doc.GetAllocator());
114+
sig_doc.AddMember("checksum", rapidjson::Value(checksum.c_str(), allocator), sig_doc.GetAllocator());
115+
sig_doc.AddMember("cname", rapidjson::Value(channelName.c_str(), allocator), sig_doc.GetAllocator());
116+
sig_doc.AddMember("curTime", int(curTime), sig_doc.GetAllocator());
117+
sig_doc.AddMember("expireTime", int(ttlSec), sig_doc.GetAllocator());
118+
sig_doc.AddMember("privilege", int(privilege), sig_doc.GetAllocator());
119+
sig_doc.AddMember("uid", uint64_t(uid), sig_doc.GetAllocator());
120+
rapidjson::StringBuffer s;
121+
rapidjson::Writer<rapidjson::StringBuffer> w(s);
122+
sig_doc.Accept(w);
123+
std::vector<uint8_t> compressedData = compress(s.GetString());
124+
125+
// Encode the compressed data and return
126+
return base64EncodeUrl(compressedData);
127+
}
128+
129+
private:
130+
std::string appKey;
131+
std::string appSecret;
132+
int defaultTTLSec;
133+
134+
std::string sha1(const std::string &input)
135+
{
136+
unsigned char hash[SHA_DIGEST_LENGTH];
137+
SHA1(reinterpret_cast<const unsigned char *>(input.c_str()), input.length(), hash);
138+
std::ostringstream oss;
139+
oss << std::hex << std::setfill('0');
140+
for (auto c : hash)
141+
{
142+
oss << std::setw(2) << static_cast<int>(c);
143+
}
144+
return oss.str();
145+
}
146+
147+
std::string base64Encode(const std::string &input)
148+
{
149+
BIO *bio, *b64;
150+
BUF_MEM *bufferPtr;
151+
152+
b64 = BIO_new(BIO_f_base64());
153+
bio = BIO_new(BIO_s_mem());
154+
bio = BIO_push(b64, bio);
155+
156+
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
157+
BIO_write(bio, input.c_str(), input.length());
158+
BIO_flush(bio);
159+
160+
BIO_get_mem_ptr(bio, &bufferPtr);
161+
std::string output(bufferPtr->data, bufferPtr->length);
162+
163+
BIO_free_all(bio);
164+
return output;
165+
}
166+
167+
std::string hmacsha256(const std::string &appidStr, const std::string &uidStr, const std::string &curTimeStr, const std::string &expireTimeStr, const std::string &cname, const std::string &permSecret, const std::string &privilegeStr)
168+
{
169+
std::string contentToBeSigned = "appkey:" + appidStr + "\n";
170+
contentToBeSigned += "uid:" + uidStr + "\n";
171+
contentToBeSigned += "curTime:" + curTimeStr + "\n";
172+
contentToBeSigned += "expireTime:" + expireTimeStr + "\n";
173+
contentToBeSigned += "cname:" + cname + "\n";
174+
contentToBeSigned += "privilege:" + privilegeStr + "\n";
175+
176+
unsigned char *digest;
177+
digest = HMAC(EVP_sha256(), permSecret.c_str(), permSecret.length(), reinterpret_cast<const unsigned char *>(contentToBeSigned.c_str()), contentToBeSigned.length(), NULL, NULL);
178+
179+
return base64Encode(std::string((char *)(digest)));
180+
}
181+
182+
std::string base64EncodeUrl(const std::vector<uint8_t> &input)
183+
{
184+
BIO *bmem = BIO_new(BIO_s_mem());
185+
BIO *b64 = BIO_new(BIO_f_base64());
186+
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
187+
BIO_push(b64, bmem);
188+
BIO_write(b64, input.data(), input.size());
189+
BIO_flush(b64);
190+
191+
BUF_MEM *bptr;
192+
BIO_get_mem_ptr(b64, &bptr);
193+
194+
std::string result(bptr->data, bptr->length);
195+
BIO_free_all(b64);
196+
197+
// Replace characters for URL safety
198+
replace(result.begin(), result.end(), '+', '*');
199+
replace(result.begin(), result.end(), '/', '-');
200+
replace(result.begin(), result.end(), '=', '_');
201+
202+
return result;
203+
}
204+
205+
std::vector<uint8_t> compress(const std::string &data)
206+
{
207+
z_stream zs;
208+
memset(&zs, 0, sizeof(zs));
209+
210+
if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK)
211+
{
212+
throw std::runtime_error("deflateInit failed while compressing.");
213+
}
214+
215+
zs.next_in = (Bytef *)(data.data());
216+
zs.avail_in = data.size();
217+
218+
int ret;
219+
std::vector<uint8_t> outBuffer(2048);
220+
221+
do
222+
{
223+
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
224+
zs.avail_out = outBuffer.size();
225+
226+
ret = deflate(&zs, Z_FINISH);
227+
228+
if (outBuffer.size() - zs.avail_out > 0)
229+
{
230+
outBuffer.resize(outBuffer.size() - zs.avail_out);
231+
}
232+
} while (ret == Z_OK);
233+
234+
deflateEnd(&zs);
235+
236+
if (ret != Z_STREAM_END)
237+
{
238+
throw std::runtime_error("deflate failed while compressing.");
239+
}
240+
241+
return outBuffer;
242+
}
243+
244+
public:
245+
struct DynamicToken
246+
{
247+
std::string signature;
248+
long long curTime;
249+
int ttl;
250+
};
251+
struct PermissionKey
252+
{
253+
std::string appkey;
254+
long uid;
255+
std::string cname;
256+
uint8_t privilege;
257+
long expireTime;
258+
long curTime;
259+
std::string checksum;
260+
};
261+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/bash
2+
g++ -std=c++14 -lgtest -lssl -lcrypto -lgtest -lz main.cpp token_build_test.cpp -o main
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include <gtest/gtest.h>
2+
3+
int main(int argc, char* argv[])
4+
{
5+
testing::InitGoogleTest(&argc, argv);
6+
return RUN_ALL_TESTS();
7+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include "../src/token_builder.h"
2+
#include <gtest/gtest.h>
3+
#include <string>
4+
#include <stdint.h>
5+
6+
class TokenServer_test : public testing::Test
7+
{
8+
protected:
9+
virtual void SetUp() override {}
10+
11+
virtual void TearDown() {}
12+
13+
void TestGetToken()
14+
{
15+
TokenServer tokenServer("c37bf7a8758a4ed00000000000000000", "c00000000000", 3600);
16+
std::string token = tokenServer.getBasicTokenWithCurrentTime("room1", 10000, 1800, 1693968975000);
17+
std::cout << "Token: " << token << std::endl;
18+
EXPECT_EQ(token, "eyJzaWduYXR1cmUiOiJlZjRmNGEwOGM1NmZiOWI5MDQ3OTE2YjZlYmZhZGY5NWFjZDc2OGViIiwiY3VyVGltZSI6MTY5Mzk2ODk3NTAwMCwidHRsIjoxODAwfQ==");
19+
20+
token = tokenServer.getBasicToken("room1", 10000, 1800);
21+
std::cout << "Token: " << token << std::endl;
22+
}
23+
24+
void TestGetPermKey()
25+
{
26+
TokenServer tokenServer("c37bf7a8758a4ed00000000000000000", "c00000000000", 3600);
27+
std::string token = tokenServer.getPermissionKeyWithCurrentTime("room1", "45eaeb3c2757c57c1b8e0a25a1f246a476c36ca5ba0cd20da38a154c2adebdab", 10000, 1, 1000, 1696662104);
28+
std::cout << "PermKey: " << token << std::endl;
29+
EXPECT_EQ(token, "eNpdjcEKgkAYhN-lP3vQ1F0LulQQgRCBlHlb1z-dbNt1Y0WL3j2XOjW3*WaGeQHTusURFsBDWl4oS2icsAgr-1-gAW*Qtw8rp3YfXYdzso6fharSUapVR7ddybNyU1tMm2O*O*0Pecjagi-d8s4kTjOjlAyctyYTjgRkTgiZBX7kAQ5aGPzx6dEDbUQvblg74IEV1Tfw3x-fBDcB");
30+
31+
token = tokenServer.getPermissionKey("room1", "45eaeb3c2757c57c1b8e0a25a1f246a476c36ca5ba0cd20da38a154c2adebdab", 10000, 1, 1000);
32+
std::cout << "PermKey2: " << token << std::endl;
33+
}
34+
};
35+
36+
TEST_F(TokenServer_test, testAccessTokenWithIntUid)
37+
{
38+
TestGetToken();
39+
TestGetPermKey();
40+
}
Submodule rapidjson added at f9d5341

0 commit comments

Comments
 (0)