跨域访问(Cross Domain)在Silverlight中是个非常讨厌的话题,常常在不经意间就会发现出现跨域访问的问题。在某些特殊的情况下,你的网站以及服务可能部署在HTTPS的安全环境下,例如服务器使用了VMWare或者F5等软件来让所有对于服务的请求都自动变成HTTPS请求,这个时候对于跨域访问的设置以及服务端和客户端的配置都有所不同。本文就着重讲述了这样情况下如何配置跨域访问并如何解决常见的请求异常。
环境:Windows Server 2008 R2 + VMWare Tools(Https) + .NET Framework 3.5 + Silverlight 4. 因为使用了VMWare的软件使得所有外部对本服务器的Web请求均自动变为Https请求。在但IIS仍然可以区分在外部请求时是使用Https还是Http。
问题: An error occurred while trying to make a request to URI 'http://cn.mydomain.com/Allan/Services/ModelService.svc/main'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details.
客户端配置:
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IModelService" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://cn.mydomain.com/Allan/Services/ModelService.svc/main"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IModelService"
contract="ModelServices.IModelService" name="BasicHttpBinding_IModelService" />
</client>
此时我们将服务地址设为http,但得到的结果却是可能Security Error,也可能是跨域访问的问题。启动Fiddler看看到底发生了什么:
很明显,ClientAccessPolicy.xml(以及CrossDomain.xml)无法被访问,而请求结果是301错误:Moved Permanently。因为在Silverlight中设置的地址是http://cn.mydomain.com/...则对应的两个跨域访问的xml文件也应该是http,所以这个请求应该是http://cn.mydomain.com/clientaccesspolicy.xml,但提示我们已经被永久转向到https://cn.mydomain.com/clientaccesspolicy.xml,那说明我们应该将服务请求地址设置为https访问就可以了。怎么解决呢?
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IModelService" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://cn.mydomain.com/Allan/Services/ModelService.svc/main"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IModelService"
contract="ModelServices.IModelService" name="BasicHttpBinding_IModelService" />
</client>
但是很奇怪的是你的请求没有被顺利的传输到服务端,而且在Fiddler中观察的结果也是500错误,无法被请求。有时你也会得到这样的错误:
Could not find a base address that matches scheme https for the endpoint with binding BasicHttpBinding. Registered base address schemes are [http].
那么,问题在哪儿呢?
- 首先,默认情况下https的请求是不被跨域访问策略所接受的,需要特殊设置。
- 需要确保客户端的security mode设置为Transport。(某些环境下server端的security需要设置为None)
- 在服务端设置BaseAddress。
- 完整的ClientAccessPolicy.xml和CrossDomain.xml,正确的路径(位于网站根目录下)
ClientAccessPolicy.xml文件:
<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="http://*"/>
<domain uri="https://*" />
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
CrossDomain.xml文件:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain=""*"" />
<allow-http-request-headers-from domain=""*"" headers=""*"" />
</cross-domain-policy>
客户端配置文件:
<system.serviceModel>
<bindings>
<binding name="BasicHttpBinding_IModelService" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://cn.mydomain.com/Allan/Services/ModelService.svc/main"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IModelService"
contract="ModelServices.IModelService" name="BasicHttpBinding_IModelService" />
</client>
</system.serviceModel>
服务端配置文件:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<behaviors>
<serviceBehaviors>
<behavior name="defaultBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="HyattSite.Services.ModelService.customBinding0">
<security mode="None"></security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="defaultBehavior" name="HyattSite.Services.ModelService">
<endpoint address="main" binding="basicHttpBinding" bindingConfiguration="HyattSite.Services.ModelService.customBinding0"
contract="HyattSite.Services.IModelService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://cn.mydomain.com" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
OK, 大功告成,Silverlight可以正确的得到返回结果:
但是对于每个服务器的环境可能是不一样的,如果你在服务器内部的访问请求需要带上权限的话,那就更为麻烦一些。How To: SSL Passthrough with WCF --or-- TransportWithMessageCredential over plain HTTP 这篇文章可以带给你更多的思路。
有关Cross Domain配置文件的更多信息参阅:Cross-domain Policy File Specification