浏览器的安全策略会阻止网页向另一个站点发送ajax请求,同时也会阻止恶意站点从另一个站点读取数据。这种限制被称作“同源策略”。然而有时我们需要从一个站点访问另一个站点,比如从一个站点访问你的WebApi接口。
跨域资源共享-Cross Origin Resource Sharing(CORS)是一项W3C标准,允许服务端释放同源策略,使得服务端在接受一些跨域请求的同时拒绝其他的跨域请求。相比较早期的JSONP等技术而言,CORS更加安全更加灵活。
1.什么是“跨域”?
如果两个URL的协议、域名、端口相同,那么这两个URL就是同源。不是同源的就是跨域的,也就是说凡是发送请求URL的协议、域名、端口三者中任意一个与当前页面的URL不同即为跨域。
下面的URL和上面两个都不是同源的,也即是跨域的
URL | 说明 |
---|---|
http://example.net | 不同的域名 |
http://example.com:9080/page1.html | 不同的端口 |
https://example.com/page1.html | 不同的协议 |
http://www.example.com/page1.html | 不同的子域名 |
注意:IE在比较是否同源时不考虑端口号
2.CORS如何工作
CORS特性提供了多个Http请求头以供跨域请求使用。如果一个浏览器支持CORS,它会自动的为跨域请求加上相应的http请求头,并不需要在javaScript中添加额外代码。
下面是一个跨域请求的示例,在Http请求头的“Origin”项中是发起请求的站点域名。
GET http://myservice.net/api/test HTTP/1.1
Referer: http://myclient.net/
Accept: * / *
Accept-Language: en-US
Origin: http://myclient.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.net
如果服务端允许了这个跨域请求,它会在http的Response的头部设置项Access-Control-Allow-Origin。只有当Response中Access-Control-Allow-Origin的值与Request中Origin的值相匹配时,这个请求才会成功。如果Access-Control-Allow-Origin的值是”*”,则意味着任意跨域访问都是被允许的。
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17
如果在HttpResponse的头部中没有包含Access-Control-Allow-Origin项,跨域请求会失败。即使服务端已经返回了正确的反馈,浏览器也不会将这个返回传递给客户端应用。
No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://myclient.net’ is therefore not allowed access. The response had HTTP status code 500.
对于一些CORS请求,浏览器会在发送实际需要的请求之前发送一个额外的请求,我们称之为“前置请求”。前置请求使用了Http Options方法,它包含了两个特殊的请求头Access-Control-Request-Method和Access-Control-Request-Headers。
OPTIONS http://myservice.net/api/test HTTP/1.1
Accept: * / *
Origin: http://myclient.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.net
Content-Length: 0
也就是说浏览器发送了两次请求,首先发送前置请求,验证当前发送请求的站点是否在允许访问的域的范围内,若验证通过就发送真正的请求获取数据,否则请求就会被拒绝。在浏览器的调试界面(F12-Network)我们也可以看到请求是有两次。
在下列情况下,浏览器会跳过前置请求:
1.Http请求方法是GET、HEAD或POST
2.Http请求的Content-Type是application/x-www-form-urlencoded、multipart/form-data、text/plain
3.Http请求头包含了Accept, Accept-Language, Content-Language, Content-Type
3.如何使用CORS
在ASP.NET应用中,可以使用NuGet获取多个关于CORS的类库:
这些类库分别位于Owin和AspNet命名空间下,是CORS标准的不同实现,最终实现的效果是一样的,只是使用环境和方式不太一样。
3.1为WebApi添加CORS
通过NuGet安装Microsoft.AspNet.WebApi.Cors为WebApi添加CORS。使用[EnableCros]属性可以全局设置CORS,也可以为Controller或Action单独设置CORS。
全局(Global)设置
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
}
}
Controller设置
[EnableCors]
public class ValuesController : ApiController
{
public HttpResponseMessage Post() { ... }
[DisableCors]
public HttpResponseMessage PutItem(int id) { ... }
}
Action设置
public class ValuesController : ApiController
{
public HttpResponseMessage Post() { ... }
[EnableCors]
public HttpResponseMessage PutItem(int id) { ... }
}
其中[EnableCros]属性有多个项可以设置,可以实现指定的站点才允许跨域访问:[EnableCros(origin:”“,headers:”“,methods:”*”)]
3.2通过Owin实现CORS
Owin是微软推广的.NET Web应用程序与Web服务器之间的接口,通过Owin实现CORS需要使用NuGet安装Microsoft.Owin.Cros。在项目中添加Owin的Startup类:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
//允许跨域访问
app.UseCors(CorsOptions.AllowAll);
}
}
或者在web.config的system.webServer项下添加:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Headers" value="*"/>
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE"/>
</customHeaders>
</httpProtocol>
对于WebApi或MVC,如果已经引用并实现了Owin中的CROS,就不需要在WebApiConfig中设置config.EnableCros()了。
3.3.其他实现CORS的方式
除了在代码和配置文件中添加CORS外,还可以在网站发布后通过修改IIS配置来实现CORS
打开IIS管理器,找到你的站点中的“HTTP响应标头”:
添加HTTP响应标头:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:origin,x-requested-with,content-type
Access-Control-Allow-Methods:POST,GET,OPTIONS
Access-Control-Allow-Origin:*
注意:如果应用程序包含上述两种以上情况时,只需在一处设置CORS,否则访问应用时会出错(Access-Control-Allow-Origin的值只能有一个):
Response to preflight request doesn’t pass access control check:The ‘Access-Control-Allow-Origin’ header contains multiple values ‘http://myclient.net,*’,but only one is allowed. Origin ‘http://myclient.net’ is therefore not allowed access.