此实现需要NSec.Cryptography NuGet package。
首先,您必须创建一个 ActionFilter 以放置在您的 WebAPI 控制器或端点上。最简单的方法是扩展 ActionFilterAttribute:
public class DiscordAuthorizationActionFilterAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// This is needed to move the request stream to the beginning as it has already been evaluated for model binding
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
var signature = context.HttpContext.Request.Headers["X-Signature-Ed25519"].FirstOrDefault();
var timestamp = context.HttpContext.Request.Headers["X-Signature-Timestamp"].FirstOrDefault();
var body = await new StreamReader(context.HttpContext.Request.Body).ReadToEndAsync();
var key = "{YOUR API KEY HERE}";
var algorithm = SignatureAlgorithm.Ed25519;
var publicKey = PublicKey.Import(algorithm, GetBytesFromHexString(key), KeyBlobFormat.RawPublicKey);
var data = Encoding.UTF8.GetBytes(timestamp + body);
var verified = algorithm.Verify(publicKey, data, GetBytesFromHexString(signature));
if (!verified)
context.Result = new UnauthorizedObjectResult("Invalid request");
else
await next();
}
private byte[] GetBytesFromHexString(string hex)
{
var length = hex.Length;
var bytes = new byte[length / 2];
for (int i = 0; i < length; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
}
注意这条评论:
// This is needed to move the request stream to the beginning as it has already been evaluated for model binding
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
要允许重用请求主体流,您必须在第一次访问请求管道之前在请求管道中显式启用此功能,可能是在模型绑定期间。为此,您可以在 Startup.cs 中添加一个简单的步骤:
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
{
app.UseStaticFiles();
app.UseRouting();
// This is needed to retrieve request body as JSON string in ActionFilter
app.Use(async (context, next) =>
{
var controller = context.Request.RouteValues["controller"] as string;
if (controller == "Discord")
context.Request.EnableBuffering();
await next();
});
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
注意检查控制器名称;由于我将属性放在 DiscordController 上,因此 RouteValues 集合中存储的控制器值是“Discord”。
最后,只需将属性添加到接受 POST 请求的端点:
public class DiscordController : ControllerBase
{
[DiscordAuthorizationActionFilter]
[HttpPost]
public async Task<IActionResult> PostAsync(DiscordInteraction interaction)
{
if (interaction == null)
return BadRequest();
if (interaction.Type == DiscordInteractionType.Ping)
return Ok(new { Type = 1 });
// Request processing here
return Ok();
}
}
请注意,DiscordInteraction 模型是自定义代码,在我知道的任何库中都不可用。按照文档创建它很简单。为了测试这一点,我发现使用 ngrok 将请求从 Discord 路由到我的开发环境很有帮助。