我已经根据官方 Twilio 教程实现了AuthorizationHandler
,但它仅适用于与短信相关的请求,而不适用于与语音相关的请求(始终无法通过验证(。
以下是应用于接受来自 Twilio 的 POST 请求的不同控制器的唯一AuthorizationHandler
,以通知我的 API 入站和出站语音呼叫、入站短信和出站短信的状态更改:
public class TwilioInboundRequestAuthorizationHandler : AuthorizationHandler<TwilioInboundRequestRequirement>
{
private readonly RequestValidator _requestValidator;
public TwilioInboundRequestAuthorizationHandler(IOptionsSnapshot<AppOptions> options)
{
// Initialize the validator
_requestValidator = new RequestValidator(options.Value.TwilioAuthToken);
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TwilioInboundRequestRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext mvcContext)
{
// Examine MVC-specific things like routing data.
HttpRequest httpRequest = mvcContext.HttpContext.Request;
if (IsValidRequest(httpRequest))
{
context.Succeed(requirement);
}
else
{
/* Omitted some code that logs the error to a cloud service */
context.Fail();
}
}
else
{
throw new NotImplementedException();
}
// Check if the requirement is fulfilled.
return Task.CompletedTask;
}
private bool IsValidRequest(HttpRequest request) {
// The Twilio request URL
var requestUrl = RequestRawUrl(request);
var parameters = ToDictionary(request.Form);
// The X-Twilio-Signature header attached to the request
var signature = request.Headers["X-Twilio-Signature"];
return _requestValidator.Validate(requestUrl, parameters, signature);
}
private static string RequestRawUrl(HttpRequest request)
{
return $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
}
private static IDictionary<string, string> ToDictionary(IFormCollection collection)
{
return collection.Keys
.Select(key => new { Key = key, Value = collection[key] })
.ToDictionary(p => p.Key, p => p.Value.ToString());
}
}
public class TwilioInboundRequestRequirement : IAuthorizationRequirement
{
}
编辑:
根据 Twilio 支持的建议,我应该更改RequestRawUrl
以从 URL 中删除端口号。但是,这会导致验证仅适用于语音呼叫,而对于SMS,它不再有效(与原始问题相反(。我怀疑Twilio在语音或短信的请求标头中设置了不正确的签名。
我将RequestRawUrl
函数从
private static string RequestRawUrl(HttpRequest request)
{
return $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
}
自
private static string RequestRawUrl(HttpRequest request)
{
return $"{request.Scheme}://{request.Host.Host}{request.Path}{request.QueryString}";
}
我们也有类似的问题。归根结底,对于某些方法(如SMS(,请求URL以https的形式出现,而其他方法(如TwiML语音脚本回调(则以http的形式出现请求。
如果请求以 http 格式编写,则 .Validate(...( 方法将失败,即使它是一个有效的请求。
因此,为了让 Twilio 请求验证器正常工作,我们只需重写请求 URL。
private bool IsValidRequest(HttpRequestBase request)
{
var signature = request.Headers["X-Twilio-Signature"];
Debug.WriteLine(request.Headers["X-Twilio-Signature"]);
var requestUrl = rewriteUri(request.Url.AbsoluteUri);
Debug.WriteLine("URI is: " + rewriteUri(request.Url.AbsoluteUri));
return _requestValidator.Validate(requestUrl, request.Form, signature);
}
private string rewriteUri(string absoluteUri)
{
//check to make sure we're not replacing 'https' with 'httpss'
if (!absoluteUri.Contains("https"))
{
return Regex.Replace(absoluteUri, @"http", "https");
}
return absoluteUri;
}
在 ngrok 后面本地运行时,我遇到了完全相同的问题,但在部署时却没有。我发现 RequestValidator 需要原始的 ngrok URL,但默认情况下我们正在传递本地主机。我最终像这样解决它:
private bool IsValidRequest(HttpRequest request)
{
var requestUrl = RequestRawUrl(request);
var parameters = ToDictionary(request.Form);
var signature = request.Headers["X-Twilio-Signature"];
// Check if we are running locally and need to pass ngrok through for validation to succeed.
if (request.Headers.ContainsKey("X-Original-Host") && request.Headers["X-Original-Host"][0].Contains("ngrok"))
{
requestUrl = requestUrl.Replace(request.Headers["Host"][0], request.Headers["X-Original-Host"][0]);
}
return _requestValidator.Validate(requestUrl, parameters, signature);
}
<</div>
div class="one_answers"> 如果您使用 ngrok,验证者需要 ngrok URL(我认为与其他代理相同(在PHP中,原始主机可以从HTTP_X_ORIGINAL_HOST获得,也许这会以某种方式帮助您