求助:Twilio Authy推送通知Webhook的C#验证实现方案
我之前在集成Twilio Authy Webhook验证的时候也踩过同样的坑——官方文档只有Node.js和Ruby示例,转C#的时候因为字符编码、请求体读取这些细节卡了好久。好在最终整理出了一套能在生产环境稳定运行的验证逻辑,分享给你:
完整的C#验证实现(ASP.NET Core)
这个实现包含了所有关键的安全细节,比如常量时间比较防止时序攻击,严格读取原始请求体避免签名不匹配:
using System; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Http; public static class AuthyWebhookValidator { /// <summary> /// 验证Twilio Authy Webhook的X-Authy-Signature签名 /// </summary> /// <param name="request">当前HTTP请求</param> /// <param name="authyApiKey">你的Authy API密钥</param> /// <returns>验证是否通过</returns> public static async Task<bool> ValidateSignatureAsync(HttpRequest request, string authyApiKey) { // 1. 检查请求头是否包含签名 if (!request.Headers.TryGetValue("X-Authy-Signature", out var signatureHeader)) { return false; } string receivedSignature = signatureHeader.ToString(); // 2. 读取原始请求体(必须是未被解析的原始内容,不能用Request.Form/Request.BodyReader的已处理数据) request.Body.Position = 0; // 重置流指针,确保从头读取 using var streamReader = new StreamReader(request.Body, Encoding.UTF8); string rawRequestBody = await streamReader.ReadToEndAsync(); request.Body.Position = 0; // 重置指针,让后续中间件能正常读取 // 3. 生成HMAC-SHA256哈希签名 byte[] keyBytes = Encoding.UTF8.GetBytes(authyApiKey); byte[] bodyBytes = Encoding.UTF8.GetBytes(rawRequestBody); using var hmacSha256 = new HMACSHA256(keyBytes); byte[] computedHash = hmacSha256.ComputeHash(bodyBytes); // 4. 转换为Twilio要求的小写十六进制格式 string computedSignature = BitConverter.ToString(computedHash) .Replace("-", string.Empty) .ToLowerInvariant(); // 5. 常量时间对比,防止时序攻击 return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedSignature), Encoding.UTF8.GetBytes(receivedSignature)); } }
在控制器中的使用示例
把验证逻辑集成到你的Webhook控制器里,确保在处理业务逻辑前先验证签名:
[ApiController] [Route("webhooks/authy")] public class AuthyWebhookController : ControllerBase { private readonly IConfiguration _config; public AuthyWebhookController(IConfiguration config) { _config = config; } [HttpPost] public async Task<IActionResult> ProcessWebhook() { string authyApiKey = _config["Authy:ApiKey"]; // 从配置/密钥管理服务读取密钥 // 执行签名验证 bool isSignatureValid = await AuthyWebhookValidator.ValidateSignatureAsync(Request, authyApiKey); if (!isSignatureValid) { return Unauthorized("Invalid Authy webhook signature"); } // 这里处理Webhook的业务逻辑 // ... return Ok(); } }
关键注意事项
- 原始请求体的重要性:一定要读取未被解析的原始请求体,不能用
Request.Form或者已被Json序列化的内容——Twilio是基于原始POST内容生成签名的,任何解析后的格式变化都会导致签名不匹配。 - 密钥安全:绝对不要把Authy API密钥硬编码在代码里,用环境变量、Azure Key Vault或者AWS Secrets Manager这类工具存储和读取。
- 编码一致性:全程使用UTF-8编码,和Twilio的实现保持一致,避免因编码差异导致签名失败。
- 时序攻击防护:用
CryptographicOperations.FixedTimeEquals代替普通的字符串相等判断,防止攻击者通过时序差异猜测签名。
我这套代码已经在生产环境跑了快一年,没出过签名验证的问题,你可以放心用。
内容的提问来源于stack exchange,提问作者Manight
相关产品推荐
相关产品推荐

