Twilio 请求验证对于语音呼叫总是失败(但适用于 SMS)



我已经根据官方 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获得,也许这会以某种方式帮助您

最新更新