在.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 这个项目显示在左侧,如下图所示:

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

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

开发

引入工具代码

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

  1. 进入AgoraIO in GitHub,点击页面上那个绿色的Code按钮

  1. 如果你会 Git 那么可以直接在其他目录中, 注意不要直接在上一章节建立的 AgoraTokenServer 项目目录中 ,把项目克隆下来。
git clone https://github.com/AgoraIO/Tools.git

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

  1. 进入刚刚 Git 克隆或者下载解压缩后的目录
 cd Tools/DynamicKey/AgoraDynamicKey/csharp/src/AgoraIO

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

解决依赖

你会发现上图中 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. 现在项目的结构如下图:

创建服务

  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一文中的说明。

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

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

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

完成

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

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

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

1赞