我以前就这个主题发表过文章,但在处理了一年其他事情之后,我又一次陷入了困境。我将尝试简要概述该场景和当前使事情正常运行的尝试:
- IIS web服务器在主机上托管HTML、JS等:IIS.mycompany.com(称为foo)
- WCF RESTful web服务通过主机上的Windows服务托管:WCF.mycompany.com(称为bar)
foo提供的Javascript通过对bar上的WCF服务进行RESTful ajax调用(GET
或POST
取决于操作)来工作,显然这些都是跨域调用,因为它们不在同一主机上。
Javascript使用jQuery(1.7.2)框架来操作DOM,并对bar执行ajax调用,POSTS
的预期内容类型为JSON
,来自GETS
的响应也预期为JSON
(application/json)。
Bar的WCF服务使用TransportCredentialOnly
作为安全模式进行配置,并且传输客户端凭据类型为NTLM
,因此只有经过身份验证的用户才能联系这些服务。
CORS支持已添加到bar的WCF服务中,使用WCF的扩展:
http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx
我们添加了额外的标题,并根据大量互联网文章对帖子中已经包含的一些内容进行了修改:
property.Headers.Add("Access-Control-Allow-Headers", "Accept, Content-Type");
property.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
property.Headers.Add("Access-Control-Max-Age", "172800");
property.Headers.Add("Access-Control-Allow-Origin", "http://iis.mycompany.com");
property.Headers.Add("Access-Control-Allow-Credentials", "true");
property.Headers.Add("Content-type", "application/json");
提供启用CORS信息的站点建议Access-Control-Allow-Origin
响应标头应设置为"*"
。然而,在我们的情况下,这是不可能的,因为我们使用以下设置进行jQuery ajax调用:
$.ajaxSetup({
cache: "false",
crossDomain: true,
xhrFields: {
withCredentials: true
}
});
事实证明,当您在ajax调用中使用"withCredentials"
时,您不能将"*"
用于接受的原点:
https://developer.mozilla.org/en/http_access_control
"重要提示:在响应认证请求时,服务器必须指定域,并且不能使用通配符。">
目前在我们的开发实验室中,这并不重要,因为我们可以将请求硬编码到IIS(foo)服务器URL。
现在的主要问题似乎是尝试POST
请求(GET
正在使用上述配置)。当浏览器尝试POST
进程时,它首先向服务器发送OPTIONS
报头,请求允许的OPTIONS
用于后续发布。这是我们希望看到我们在CORS支持WCF扩展中配置的头被传递回来的地方,但我们还没有走到这一步;在响应返回为"401 Unauthorized">之前,我认为这与请求NTLM的传输安全绑定配置有关,但我不确定。
此外,我对此没有太多经验,但在执行跨域请求时,我没有看到太多关于使用application/json
内容类型的POST
的信息,而不是text/plain
。
我知道人们可能会建议JSONP
作为一个真正的解决方案,我并不反对不同的方法,事实上,我鼓励任何人提出最佳实践,因为这将有助于其他人稍后阅读这个问题。然而,在建议替代方案之前,请尝试回答这个问题。
非常感谢任何有贡献的人。
佩特斯基:)
更新:
Chrome(20.x.x)似乎没有遇到不协商NTLM以从服务器检索OPTIONS
标头响应的问题,但Firefox(13.0.1)遇到了。
我们还注意到,有人已经在Firefox论坛上发布了一个错误,我们已将信息添加到:
http://bugzilla.mozilla.org/show_bug.cgi?id=751552
请投票支持在bugzilla网站上修复此错误
使用以下代码,我们可以查看网络跟踪,以查看Firefox失败和Chrome正常工作:
var url = "http://myWebServiceServer/InstantMessagingService/chat/message/send";
var data = '{ "remoteUserUri" : "sip:foo.bar@mydomain.com", "message" : "This is my message" }';
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.withCredentials = true;
request.setRequestHeader("Content-Type", "application/json");
request.send(data);
console.log(request);
另一方面,IE8不支持跨域调用的XMLHttpRequest
,这有利于它自己神奇的XDomainRequest
对象,所以我们要做一些工作来更改客户端代码,以处理IE8与世界的情况。(感谢IE8)。
/me祈祷Mozilla修复Firefox漏洞
更新2:
经过一番挖掘,IE8的XDomainRequest
似乎无法用于进行必须协商NTLM的跨域请求,这基本上意味着由于web浏览器中的限制,我们WCF绑定上的安全性无法使用。
http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
"请求时不会发送任何身份验证或cookie">
所以,我想我们现在已经做到了。。看起来我们必须创建自己的自定义令牌身份验证,并将其以cookie的形式传递给WCF服务,或者在IE8的情况下,将其与JSON一起传递给POST
。WCF服务将不得不处理解密数据并使用它,而不是我们以前使用NTLM身份验证访问的ServiceSecurityContext.Current.WindowsIdentity
。
我知道你说过你宁愿问题本身得到解决,但你可以考虑使用"反向代理">
我不知道您使用的是什么技术,但我们使用的是Apache web服务器,并且有一个Java RESTful API运行在另一台需要身份验证的服务器上。有一段时间,我们搞砸了JSONP和CORS,但并不满意。
最后,我们设置了一个Apache反向代理,它创造了奇迹。网络浏览器认为它正在与自己的域进行通信,并采取了适当的行动。RESTful API不知道它是通过代理使用的。因此,一切都很正常。阿帕奇拥有所有的魔法。
希望所有的web服务器都有类似Apache的反向代理的功能。以下是有关该功能的一些文档:http://httpd.apache.org/docs/2.2/mod/mod_proxy.html
我们所要做的就是确保mod_proxy模块已经安装,然后在Apache配置文件中添加以下行:
ProxyPass /restapi http://restfulserver.com/restapi
ProxyPassReverse /restapi http://restfulserver.com/restapi
然后重新启动网络服务器,瞧!