使用客户端HTTPS证书的CORS

10

我的网站有两个https服务器。一个(前端)提供由静态页面构成的UI界面。另一个(后端)提供微服务。它们都恰好使用相同的(测试)X509证书来进行身份验证。分别,我可以通过https连接到它们两个,并需要客户端证书“tester”。

我们一直通过nginx设置来隐藏CORS问题,使前端和后端看起来像是同一个来源。我已经为所有请求实现了“Access-Control-Allow-Origin”、“Access-Control-Allow-Credentials”头;以及为预检查请求(OPTIONS)实现了方法、头。

  • 在Chrome中,跨站点的操作能够正常工作。我可以看到前端URL和后端URL是不同的站点。我看到在后端请求发送之前会发出OPTIONS请求。

  • 即使Chrome似乎不需要,我还是找到了将用于执行请求的XMLHttpRequest对象,并在其上进行了xhr.withCredentials = true操作,因为这似乎是fetch.js在获取"credentials":"include"时内部执行的操作。我注意到有一个xhr.setRequestHeader函数可用,我可能需要用它来让Firefox满意。

  • Firefox对于UI调用的行为与Chrome相同。但对于所有后端调用,我得到了一个405错误。当它这样做时,没有任何网络连接被建立到服务器。浏览器只是决定这是一个405,而没有执行任何https请求。尽管这与Chrome的行为不同,但它有点合理。前端UI和后端服务都需要选择客户端证书。当我连接到UI时,我选择了证书“tester”。当它去进行后端请求时,它可能会假设相同的客户端证书应该用于到达后端。但也许它认为它可能不同,还有其他一些事情我需要告诉Firefox。

有没有人在使用CORS和双向SSL证书的组合时遇到过这个Firefox问题,并在某个地方解决了它。我怀疑这不是服务器端的修复,而是客户端需要做的事情。

编辑:请参见此处的答案:https://dev59.com/lGAf5IYBdhLWcg3wlDUD#74744206


你在Safari和Edge上测试过吗?知道它们是否与Firefox相匹配会很有趣。你看到的行为可能只发生在Chrome中。Firefox的行为似乎符合当前规范。 - sideshowbarker
我也遇到了同样的问题,我们也使用了双向 SSL 设置。但是我没有收到任何状态码 - Firefox 只是中止了 Ajax 调用。你最终解决了这个问题吗? - heez
基本上,在Chrome中它甚至能够工作,这是一个bug。规范表明,当进行请求时,OPTION动词不应使用客户端证书。如果您拒绝所有没有客户端证书的连接,则永远无法获得OPTIONS请求。但是,您可以使nginx为您发送200并防止服务看到OPTION。这就是我要尝试的,因为如果CORS不可移植,它几乎没用。 - Rob
1
规范要求的问题在于浏览器在发送请求之前就知道请求的类型,但服务器直到 TLS 协商之后才知道。 - kbolino
我为此提出了 https://bugs.chromium.org/p/chromium/issues/detail?id=775438(几年前)。尽管该错误报告中的评论表明他们确实打算更改Chrome行为,但目前尚未更改。 - sideshowbarker
请在此处查看答案:https://dev59.com/lGAf5IYBdhLWcg3wlDUD#74744206 - ryenus
2个回答

3

我实际上没有使用客户端证书进行测试,但我似乎记得,如果将 Access-Control-Allow-Origin 设置为 * 通配符而不是实际域名,Firefox 将不会发送凭据。请参阅 MDN 上的此页面

同时,Firefox 在发送 CORS 请求到期望客户端证书在 TLS 握手中被呈现的服务器时也存在问题。基本上,在预检请求期间,Firefox 不会发送证书,从而创建了鸡生蛋的问题。请参见 bugzilla 上的此错误


2
Chrome的一位提交者在https://bugzilla.mozilla.org/show_bug.cgi?id=1019603#c9中的评论表明,这里的错误实际上是在Chrome中,而Firefox的行为是当前规范所要求的。因此,了解Safari和Edge在这里的做法将是有趣的。 - sideshowbarker
确实。如果证明 Firefox 的解释是正确的,那么如何在后端实现客户端证书认证呢?也许可以通过使用匿名 SSL 进行初始连接,然后在预检请求之后重新协商?我不确定那是否可行。 - AfroThundr
这是问题的正确答案。浏览器行为差异的原因就在于Chrome中存在一个bug。Firefox,Edge和Safari都遵循规范要求 - 即它们不在预检请求中包含SSL / TLC客户端证书。请参见https://dev59.com/n6bja4cB1Zd3GeqPZwJv#46783730上的相关答案。 - sideshowbarker
1
Firefox的行为不可能被认真考虑。需要双向身份验证的TLS连接被建立,而http协议层在其中运行。更具体地说,它将是一个nginx服务器,绝不会将未经身份验证的请求转发到其后面的微服务;出于审计原因。无论规范如何,Chrome的行为都是正确的。 - Rob
1
我同意Chrome处理这个问题的方式可能更适合像这样的用例,但遵循规范也很重要。我不认为在编写CORS规范时考虑过这种情况,因此修订该规范以考虑预检请求中的有意身份验证可能是更正确的解决方案。 - AfroThundr
@Rob 我认为像SNI这样的东西是必要的,以在连接协商之前指示它是CORS预检请求,否则在服务器端设置强制客户端证书的TLS会变得更加复杂。 - kbolino

1

使用CORS与凭据(基本认证、cookie、客户端证书等)时:

  • Access-Control-Allow-Credentials必须为true
  • Access-Control-Allow-Origin不能为*
  • Access-Control-Allow-Origin不能为多值(不重复,也不逗号分隔)
  • Access-Control-Allow-Origin必须设置为请求的Origin头中的确切值,以使请求正常工作(可以硬编码为此方式或通过允许值的白名单进行设置)
  • 预检OPTIONS请求不能需要凭据(包括客户端证书)。预检的部分目的是询问CORS请求中允许什么,因此在不知道它们是否被允许之前发送凭据是不正确的。
  • 预检OPTIONS请求必须返回一个200级别的响应,通常为204
注意:对于Access-Control-Allow-Origin,您可能希望考虑允许值null,因为重定向链(例如通常用于OAuth的链)可能会导致浏览器请求中的Origin值。

你有在不同的浏览器中尝试过吗?我上次尝试是大约两年前。它可以在使用PKI客户端证书的Chrome中工作,但无法在Firefox或Safari中工作。这是因为规范规定,如果您使用证书进行身份验证,则在OPTIONS请求期间不会发送DN。因此,mTLS连接将无法到达后端。之所以在Chrome中可以工作,是因为他们(在我看来正确地)违反了规范,使功能正常工作。自从我上次调查以来,情况可能已经改变。 - Rob
1
是的。我们的程序在Chrome、Firefox、IE、Safari和Edge上都可以运行(已经有7-8年了)。有办法配置Apache HTTPD和Nginx,使其不需要客户端验证OPTIONS请求,并且如果未提供证书,则将任何非OPTIONS请求处理为403。在Chrome中进行主要测试是不明智的,因为您正确地指出它违反了规范以获取方便性。 - willsters
在我们的情况下,我们需要使用mTLS。如果您未被识别,边缘服务器将不会将请求转发到后端。这甚至在HTTP解析之前就已经发生了。不幸的是,由于TLS是如何在HTTP上分层的,有很多服务器都是这样工作的。 - Rob
你能不能在边缘处理CORS?那是我们所有CORS处理的地方。如果你想这样做,至少可以在边缘处理OPTIONS并让其他请求由后端处理。 - willsters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接