为啥大多数浏览器的预检请求中不包含 TLS 客户端证书?

Posted

技术标签:

【中文标题】为啥大多数浏览器的预检请求中不包含 TLS 客户端证书?【英文标题】:Why is the TLS client certificate not being included in preflight request on most browsers?为什么大多数浏览器的预检请求中不包含 TLS 客户端证书? 【发布时间】:2018-03-28 18:04:48 【问题描述】:

我正在构建的网络应用程序出现问题。 Web 应用程序由一个 Angular 4 前端和一个 dotnet core RESTful api 后端组成。要求之一是对后端的请求需要使用 SSL 相互认证进行认证;即客户端证书。

目前,我将前端和后端都作为 Azure 应用服务托管,它们位于不同的子域中。

按照本指南将后端设置为需要客户端证书,我认为这是为 Azure 应用服务执行此操作的唯一方法: https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth

当前端向后端发出请求时,我将 withCredentials 设置为 true — [根据文档][1],这也应该与客户端证书一起使用。

XMLHttpRequest.withCredentials 属性是一个布尔值,指示是否应使用 cookie、授权标头或 TLS 客户端证书等凭据进行跨站点访问控制请求。设置 withCredentials 对同站点请求没有影响。

来自前端的相关代码:

const headers = new Headers( 'Content-Type': 'application/json' );
const options = new RequestOptions( headers, withCredentials: true );

let apiEndpoint = environment.secureApiEndpoint + '/api/transactions/stored-transactions/';

return this.authHttp.get(apiEndpoint, JSON.stringify(transactionSearchModel), options)
    .map((response: Response) => 
         return response.json();
     )
     .catch(this.handleErrorObservable);

这在 Chrome 上有效,当发出请求时,浏览器会提示用户输入证书,它会包含在预检请求中,一切正常。

对于所有其他主要浏览器,但情况并非如此。 Firefox、Edge 和 Safari 都无法通过预检请求,因为当请求中不包含客户端证书时服务器会关闭连接。

直接浏览到 api 端点会使每个浏览器都提示用户输入证书,所以我很确定这与大多数浏览器如何处理带有客户端证书的预检请求明确相关。

做错了什么?还是其他浏览器在发出请求时不提示输入证书而做错了?

我需要支持 Chrome 以外的其他浏览器,所以我需要以某种方式解决这个问题。

我已经看到通过让后端允许而不是要求证书来解决类似的问题。唯一的问题是我还没有找到一种方法来实际使用 Azure 应用程序服务来做到这一点。要么需要,要么不需要。

有人对我如何继续前进有任何建议吗?

【问题讨论】:

请求是使用XMLHttpRequest()还是fetch() 如果请求在 Chrome 中按预期工作,请使用 ***.com/posts/46783506/edit 编辑/更新问题并添加 Chrome OPTIONS 请求的请求和响应详细信息(因为问题已经针对Firefox 请求)和随后的 POST 请求。 @sideshowbarker 你说得对,我用相关的屏幕截图更新了问题。 @guest271314 根据 Angular 4 文档 (angular.io/api/http/Http),它是 XMLHttpRequest。 您的 Chrome 屏幕截图显示了一个针对 GET 请求的 OPTIONS 请求,以及一个后续的 GET 请求——而不是来自您的代码的 POST 请求。 【参考方案1】:

请参阅https://bugzilla.mozilla.org/show_bug.cgi?id=1019603 和我在CORS with client https certificates 的答案中的评论(我忘记了我之前曾看到过同样的问题报告……)。

总而言之,您所看到的差异的原因是 Chrome 中的一个错误。我已经在https://bugs.chromium.org/p/chromium/issues/detail?id=775438 提交了一个错误。

问题在于 Chrome 没有遵循规范要求,该规范要求浏览器不要在预检请求中发送 TLS 客户端证书;因此 Chrome 会在预检中发送您的 TLS 客户端证书。

Firefox/Edge/Safari 遵循规范要求,不要在预检中发送 TLS 客户端证书。


更新:在对问题的编辑中添加的 Chrome 屏幕截图显示了对 GET 请求的 OPTIONS 请求,以及随后的 GET 请求 - 而不是来自您的代码的 POST 请求.所以也许问题是服务器禁止POST请求。


https://i.stack.imgur.com/GD8iG.png 中显示的请求是一个 CORS preflight OPTIONS request,浏览器会在您的代码中尝试 POST 请求之前自动发送。

您的代码添加的Content-Type: application/json 请求标头是触发浏览器发出预检OPTIONS 请求的原因。

重要的是要了解浏览器在该预检 OPTIONS 请求中永远不会包含任何 credentials — 因此,请求被发送到的服务器必须配置为不需要任何凭据/身份验证来处理 OPTIONS 到 @987654341 的请求@。

然而,从https://i.stack.imgur.com/GD8iG.png 看来,服务器正在禁止OPTIONS/api/transactions/own-transactions/ 发出请求。可能是因为请求缺少服务器期望的凭据,或者可能是因为服务器配置为禁止所有 OPTIONS 请求,无论如何。

所以结果是,浏览器断定预检不成功,因此它就停在那里,永远不会继续尝试来自您的代码的 POST 请求。

鉴于https://i.stack.imgur.com/GD8iG.png 中显示的内容,很难理解这实际上是如何在 Chrome 中按预期工作的 - 特别是考虑到没有浏览器在预检请求中发送任何类型的 credentials,因此任何可能的浏览器在处理方面的差异就预检而言,证书的数量没有任何区别。

【讨论】:

感谢您的帖子。我以为我了解 CORS 预检请求。但我猜 Chrome 实际上是罪魁祸首,并且通过在预检请求中包含 TLS 客户端证书来做错事。服务器停止预检选项,因为它需要一个它没有获得的 TLS 客户端证书。我想唯一的解决方案是让请求从 saim 子域提供服务,或者找到一种方法让 Azure 将客户端证书作为可选,以便 OPTIONS 请求实际上得到响应。 我使用的解决方案之一是使用 iframe 和 postMessage。适用于所有浏览器。

以上是关于为啥大多数浏览器的预检请求中不包含 TLS 客户端证书?的主要内容,如果未能解决你的问题,请参考以下文章

预检 CORS 请求在 Chrome 60 中不起作用

为啥会有OPTIONS请求

如果我在使用 cors 时添加标准 http 标头,为啥会预检请求?

预检请求在IIS中不起作用

使用 window.fetch api 的预检请求失败

Apollo graphQL 客户端中的 CORS 异常“Access-Control-Allow-Headers 在预检响应中不允许请求标头字段内容类型”