【问题标题】:How can you authenticate requests made to a Discord interactions webhook in .NET?如何在 .NET 中验证对 Discord 交互 webhook 发出的请求?
【发布时间】:2021-09-18 13:11:07
【问题描述】:

根据Discord documentation,webhook 必须验证每个请求的标头才能被接受。文档提供了以下代码示例:

const nacl = require('tweetnacl');

// Your public key can be found on your application in the Developer Portal
const PUBLIC_KEY = 'APPLICATION_PUBLIC_KEY';

const signature = req.get('X-Signature-Ed25519');
const timestamp = req.get('X-Signature-Timestamp');
const body = req.rawBody; // rawBody is expected to be a string, not raw bytes

const isVerified = nacl.sign.detached.verify(
  Buffer.from(timestamp + body),
  Buffer.from(signature, 'hex'),
  Buffer.from(PUBLIC_KEY, 'hex')
);

if (!isVerified) {
  return res.status(401).end('invalid request signature');
}

您如何在 .NET 5.0 中做到这一点?我找不到任何 Ed25519 验证示例。

【问题讨论】:

    标签: asp.net .net asp.net-web-api discord


    【解决方案1】:

    此实现需要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 路由到我的开发环境很有帮助。

    【讨论】:

      猜你喜欢
      • 2015-12-31
      • 2023-02-15
      • 1970-01-01
      • 1970-01-01
      • 2021-12-21
      • 1970-01-01
      • 2021-08-07
      • 2021-03-13
      • 2018-10-07
      相关资源
      最近更新 更多