如何在.NET Core上建立Agora的AccessToken服务

随着大量公司将办公会议、产品发布等改为网上进行后, 视频通信的安全性越来越成为受关注的重点。声网平台在 2.1.0 版本之后,通过使用 AccessToken 认证统一了视频通话RTC、录制、消息通讯RTM等各 SDK 的安全认证形式,相较于原先的 DynmicKey 更加方便于用户使用。

.NET Core 是微软的跨平台开发框架,可运行在 Windows、Linux、macOS 等操作系统之上,通过命令行工具就可以方便的创建、编译、运行,并可搭配 Docker 容器使用,方便嵌入微服务架构中。

本文将基于 .NET Core 3.1 版本说明如何建立一个 Agora RTC Token 服务,同样这个服务也可以用于录制和 RTM SDK中。

预备知识

  • 本文默认读者了解基本的 C# 编程知识,如果有需要可以访问C#文档 进行了解。

  • 本文需要 ASP.NET Core 及相关的 WebAPI 知识,如果有需要可以访问ASP.NET 文档进行了解。

  • 本文会有一点点 Git 相关的使用,但不是必要的。

本文所需工具

  • .NET Core SDK - 包括 .NET Core 运行时、开发包及命令行工具。

  • Visual Studio Code - 微软推出的跨平台开发工具,你也可以使用自己喜欢或习惯的开发工具。

  • .NET Core开发环境配置 - 如果你刚开始使用 Visual Studio Code,推荐阅读这个链接中的安装配置。

  • Git - 本文会使用到 Git 但不是必要条件,在相应章节会进行说明。

项目创建

  1. 打开终端,进入你平时开发目录

  2. 运行以下命令


dotnet new webapi -o AgoraTokenServer

code -r AgoraTokenServer

  1. 如果你正确的安装了 Visual Studio Code 的话,这时系统应该会打开 Visual Studio Code 程序并将 AgoraTokenServer 这个项目显示在左侧,如下图所示:
image

为了方便起见,以下 Visual Studio Code 将简称为 vscode。此时整个项目的目录结构应该如下图所示:

image-20200913165358515

我们将 WeatherForecast.cs 与 Controllers/WeatherForecastController.cs 删除,稍后我们将建立起自己的服务。

开发

引入工具代码

Agora 在其AgoraIO in GitHub中提供了 AccessToken 的 C# 实现,我们可以直接使用它。

  1. 进入AgoraIO in GitHub,点击页面上那个绿色的Code按钮
image-20200913170057183
  1. 如果你会 Git 那么可以直接在其他目录中,注意不要直接在上一章节建立的 AgoraTokenServer 项目目录中,把项目克隆下来。

git clone https://github.com/AgoraIO/Tools.git

​ 如果你不会 Git ,可以直接点击 Download ZIP 将其下载下来并解压缩。

image-20200913172902977
  1. 进入刚刚 Git 克隆或者下载解压缩后的目录

cd Tools/DynamicKey/AgoraDynamicKey/csharp/src/AgoraIO

将其中的 Common、Extensions、Media、Utils 四个目录直接拷贝至你创建的 AgoraTokenServer 目录下,之后你的 AgoraTokenServer 目录结构应该是如下图这样子的:

image-20200913174340000

解决依赖

你会发现上图中 Media/AccessToken.cs 是红色的,那是因为这个项目依赖于Crc32.NET这个包,如果你正确的安装了 .NET Core 的运行时和命令行工具的话 我们直接使用命令行将其安装就可以了。

进入 AgoraTokenServer 项目的根目录下,运行如下命令:


dotnet add package Crc32.NET

这样子我们唯一一个外部依赖包就解决了。

设置 AppID 与 AppCertificate

  1. 在通常环境中 AppCertificate 应当保存在安全性较高的服务端,不宜通过客户端请求进行传输,在 .NET Core 中这种设置通常可以保存在 appsetting.json 中。下面 appsetting.json 代码中的 AppID 和 AppCertificate 为示例,请在使用中替换为自己使用的对应 AppID 和 AppCertificate。

{

"AppSettings": {

"AppID": "970CA35de60c44645bbae8a215061b33",

"AppCertificate": "5CFd2fd1755d40ecb72977518be15d3b"

},

"Logging": {

"LogLevel": {

"Default": "Information",

"Microsoft": "Warning",

"Microsoft.Hosting.Lifetime": "Information"

}

},

"AllowedHosts": "*"

}

  1. 建立配置类

在 Utils 目录下创建一个名为 AppSettings.cs 的文件,文件内容为:


namespace AgoraTokenServer.Utils

{

public class AppSettings

{

public string AppID { get; set; }

public string AppCertificate { get; set; }

}

}

  1. 注入配置类

ASP.NET Core 使用依赖注入来解决整个程序的依赖问题,通过这个机制我们可以很方便的把上面定义的配置注入进去。依赖注入需要在 Startup.cs 文件中添加自定义的配置类,添加后 Startup.cs 文件内容如下:


using AgoraTokenServer.Utils;

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Hosting;

namespace AgoraTokenServer

{

public class Startup

{

public Startup(IConfiguration configuration)

{

Configuration = configuration;

}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.

public void ConfigureServices(IServiceCollection services)

{

services.AddCors(); //添加跨域请求

services.AddControllers();

services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); //添加程序配置

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors(x => x

.AllowAnyOrigin()

.AllowAnyMethod()

.AllowAnyHeader());

app.UseAuthorization();

app.UseEndpoints(endpoints =>

{

endpoints.MapControllers();

});

}

}

}

创建 Model

我们先定义两个对象来描述接受的内容和返回的结果。首先建立一个名为 Models 的目录,再在目录下创建两个文件。

  1. 请求对象文件

Path: /Models/AuthenticateRequest.cs

在 Models 目录下创建 AuthenticateRequest.cs 文件,文件内容为:


using System.ComponentModel.DataAnnotations;

namespace AgoraTokenServer.Models

{

public class AuthenticateRequest

{

[Required]

public string channel { get; set; }

[Required]

public dynamic uid { get; set; }

public uint expiredTs { get; set; } = 0;

public int role { get; set; } = 1;

}

}

因为 Agora 的用户标识有两种类型,一种是 uint 型,一种是 string 型的,所以这里直接使用 dynamic 类型来同时兼容两种类型。

  1. 回应对象

Path: /Models/AuthenticateResponse.cs

在 Models 目录下创建 AuthenticateResponse.cs 文件,文件内容为:


namespace AgoraTokenServer.Models

{

public class AuthenticateResponse

{

public string channel { get; set; }

public dynamic uid { get; set; }

public string token { get; set; }

}

}

  1. 现在项目的结构如下图:
image-20200913194237623

创建服务

  1. 现在我们创建一个控制器来提供服务,首先在 AgoraTokenServer 项目的 Controllers 目录下建立一个名为 AccessTokenController.cs 的文件。

Path: /Controllers/AccessTokenController.cs


using AgoraTokenServer.Models;

using Microsoft.AspNetCore.Mvc;

namespace AgoraTokenServer.Contollers

{

[ApiController]

[Route("[controller]")]

public class AccessTokenController : ControllerBase

{

}

}

  1. 添加程序配置

using AgoraTokenServer.Utils;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Options;

namespace AgoraTokenServer.Contollers

{

[ApiController]

[Route("[controller]")]

public class AccessTokenController : ControllerBase

{

private readonly AppSettings appSettings;

public AccessTokenController(IOptions<AppSettings> appSettings)

{

this.appSettings = appSettings.Value;

}

}

}

  1. 添加请求处理部分

using System.Net;

using System.Text.Json;

using AgoraIO.Media;

using AgoraTokenServer.Models;

using AgoraTokenServer.Utils;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Options;

namespace AgoraTokenServer.Contollers

{

[ApiController]

[Route("[controller]")]

public class AccessTokenController : ControllerBase

{

private readonly AppSettings appSettings;

public AccessTokenController(IOptions<AppSettings> appSettings)

{

this.appSettings = appSettings.Value;

}

[HttpPost]

public ActionResult<AuthenticateResponse> index(AuthenticateRequest request)

{

if (string.IsNullOrEmpty(appSettings.AppID) || string.IsNullOrEmpty(appSettings.AppCertificate))

{

return new StatusCodeResult((int)HttpStatusCode.PreconditionFailed);

}

var uid = request.uid.ValueKind == JsonValueKind.Number ? request.uid.GetUInt64().ToString() : request.uid.GetString();

var tokenBuilder = new AccessToken(appSettings.AppID, appSettings.AppCertificate, request.channel, uid);

tokenBuilder.addPrivilege(Privileges.kJoinChannel, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishAudioStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishVideoStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishDataStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kRtmLogin, request.expiredTs);

return Ok(new AuthenticateResponse

{

channel = request.channel,

uid = request.uid,

token = tokenBuilder.build()

});

}

}

}

在请求处理中,直接调用了从 AgoraIO 上下载的代码,并且在没有配置 AppID 和 AppCertificate 情况下会回报 412 错误。

同时,这个示例代码中直接将[kJoinChannel, kPublishAudioStream, kPublishVideoStream, kPubishDataStream, kRtmLogin] 的权限一次性给出来,你可以根据直接的需要,在 AuthenticateRequest 中添加权限申请的字段, 实现权限的申请功能。


[HttpPost]

public ActionResult<AuthenticateResponse> index(AuthenticateRequest request)

{

if (string.IsNullOrEmpty(appSettings.AppID) || string.IsNullOrEmpty(appSettings.AppCertificate))

{

return new StatusCodeResult((int)HttpStatusCode.PreconditionFailed);

}

var uid = request.uid.ValueKind == JsonValueKind.Number ? request.uid.GetUInt64().ToString() : request.uid.GetString();

var tokenBuilder = new AccessToken(appSettings.AppID, appSettings.AppCertificate, request.channel, uid);

tokenBuilder.addPrivilege(Privileges.kJoinChannel, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishAudioStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishVideoStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kPublishDataStream, request.expiredTs);

tokenBuilder.addPrivilege(Privileges.kRtmLogin, request.expiredTs);

return Ok(new AuthenticateResponse

{

channel = request.channel,

uid = request.uid,

token = tokenBuilder.build()

});

}

编译并运行

.NET Core 的编译和运行只需要通过命令行既可以解决,在 AgoraTokenServer 目录下,直接在命令行中运行


dotnet build

就可以编译整个工程了。

运行也很直接,直接在命令行中运行


dotnet run

就可以在 https://localhost:5001http://localhost:5000 上运行服务了。

如果你想改缺省的运行端口,推荐直接修改 Path: /Properties/launchSettings.json 文件中的 AgoraTokenServer 这一节的 applicationUrl 参数,其内容如下:


"AgoraTokenServer": {

"commandName": "Project",

"launchBrowser": true,

"launchUrl": "weatherforecast",

"applicationUrl": "https://localhost:5001;http://localhost:5000",

"environmentVariables": {

"ASPNETCORE_ENVIRONMENT": "Development"

}

}

因为修改过的 launchSettings.json 本身也会做为一个配置文件发布在最终运行目录中,这样子就不用吧端口写死在源代码中,或者在 Program.cs 中额外添加代码了。

测试结果

在本文中,使用Postman对服务进行测试,大家可以使用自己习惯的工具。在具体的请求中,因为 expiredTS 和 role 在程序中有缺省值,所以请求中就可以忽略,并且在现阶段,role 只有一个值,所以推荐可以暂时忽略这个。而 expiredTS 的具体用法,可以参考Agora官方网站的生成Token一文中的说明。

image-20200913214230336

具体的 Postman 请求结果如下图所示。

image-20200913213711509

如果你在使用 Postman 发送请求的时候发生了下图的错误:

image-20200913214838643

是因为你现在访问的 https 链接使用的证书是无效的,实际使用中你需要部署真实的证书,测试阶段你可以通过下图的 Settings 按钮将第一个 Enable SSL certificate verification 关闭

image-20200913215607065

完成

到现在为止,基于 .NET Core 的 Agora Token 服务已经开发完成。在实际使用中,还需要添加安全机制,这个可以根据你自己的具体架构情况进行完善。

.NET Core 的 docker 化可以参考微软的 Docker 容器 这编文章。

本文的所有代码都可以在 GitHub 上下载。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351