很多时候,大中型网站为了静态资源分布式部署,加快访问速度,减轻主站压力,会把静态资源(例如字体文件、图片等)放在独立服务器或者CDN上,并且使用独立的资源域名(例如res.test.com)
但是在实际部署中,会发现浏览器无法载入这些不同域名的资源,firefox控制台会报错:
- <span role="presentation" class="objectBox objectBox-errorMessage "><span class="errorMessage ">已阻止跨源请求:同源策略禁止读取位于 http://xxxxx 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。</span></span>
- 已阻止跨源请求:同源策略禁止读取位于 http://xxxxx 的远程资源。(原因:CORS 请求失败)。
这是因为现代浏览器将其定义为跨域资源而不允许加载
理解跨域首先必须要了解同源策略。同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。
何谓同源:
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
同源策略:
浏览器的同源策略,限制了来自不同源的"document"或脚本,对当前"document"读取或设置某些属性。 (白帽子讲web安全[1])
从一个域上加载的脚本不允许访问另外一个域的文档属性。
那么关键是如何解决呢,其实很简单,只要在静态资源服务器上,增加一个头信息:
Access-Control-Allow-Origin *
本文就apache进行操作,nginx大同小异
首先编辑httpd.conf
找到这行
#LoadModule headers_module modules/mod_headers.so
把#注释符去掉
LoadModule headers_module modules/mod_headers.so
目的是开启apache头信息自定义模块
然后在独立资源域名的虚拟主机添加一行
Header set Access-Control-Allow-Origin *
意思是对这个域名的资源进行访问时,添加一个头信息
重启apache
再访问,OK!
当使用ajax跨域请求时,浏览器报错:XmlHttpRequest error: Origin null is not allowed by Access-Control-Allow-Origin.肯定是跨域的问题,如果用jsonp或者proxy的方式进行修改的话未免需要太大的工程量,所以采用CORS这种比较简单高效的技术。相比JOSP的方式,CORS更为高效。JSONP由于它的原理只能实现GET请求,而CORS支持所有类型的HTTP请求。使用CORS,可以使用普通的ajax实现跨域,这对于前端来说是极大的福音了,这个技术被现在大多数浏览器所普遍支持,因为跨域已经是普遍的要求,浏览器肯定会逐渐流出适当的‘后门'出来专门用以跨域。
浏览器支持情况
经本人测试IE浏览器中IE10及以上才可正常发送请求
1.服务器端对于CORS的支持,是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问,也就是相应的‘后门'。
设置Apache:Apache需要使用mod_headers模块来激活HTTP头的设置,它默认是激活的。你只需要修改Apache配置文件中的httpd.conf文件:
原始代码
<Directory />
AllowOverride none
Require all denied
</Directory>
改为下面代码
<Directory />
Require all denied
Header set Access-Control-Allow-Origin *
</Directory>
在处理请求的PHP文件中设置:
<?php
header("Access-Control-Allow-Origin:*");
//处理请求输出数据
?>
配置的含义是允许任何域发起的请求都可以获取当前服务器的数据。当然,这样有很大的危险性,恶意站点可能通过XSS攻击我们的服务器。所以我们应该尽量有针对性的对限制安全的来源,例如下面的设置使得只有 http://jb51.net/这个域才能跨域访问服务器的API。
httpd.conf中:
Header set Access-Control-Allow-Origin http://www.jb51.net
php文件中:
<?php
header("Access-Control-Allow-Origin:http://www.jb51.net");
前台代码:
<script type="text/javascript">
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// 此时即支持CORS的情况
// 检查XMLHttpRequest对象是否有“withCredentials”属性
// “withCredentials”仅存在于XMLHTTPRequest level 2对象里
} else {
// 否则检查是否支持XDomainRequest
// XDomainRequest仅存在于IE中,是IE用于支持CORS请求的方式
xhr = new XDomainRequest();
}
xhr.open(method, url, true);
xhr.send();
xhr.onload = function(){
alert(xhr.responseText);
}
}
createCORSRequest('GET', "http://192.168.1.58/t.php");
</script>
背景:服务器提供https的api给浏览器ajax调用,并要允许跨域访问:
1. httpd.conf
去掉注释LoadModule headers_module modules/mod_headers.so
2. 修改以下配置
conf/extra/httpd-ssl
<VirtualHost>
...
Header add Access-Control-Allow-Origin *
Header add Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header add Access-Control-Allow-Headers "Content-Type"
</VirtualHost>
这里一定要加Header add Access-Control-Allow-Headers "Content-Type"。
猜测是因为客户端要发送的类型是application/json,
所以在发送的OPTIONS请求头里出现:
Access-Control-Request-Headers:Content-Type, Accept
3. 重启apache
由于前端解决跨域问题的局限性比较大,对于 Ajax 跨域或是 iframe 跨域,建议用服务器端解决方案。
此方案的原理是接受客户端发来的请求后,经由本域服务器代理向目标服务器发送请求,并将响应数据返回给客户端。
用 apache 的 mod_proxy 模块开启反向代理功能来实现:
1 修改 apache 配置文件 httpd.conf ,去掉以下两行前面 # 号
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so
2 在 server config 或 virtual host 中增加:
ProxyRequests Off <Proxy *> Order deny,allow Allow from all </Proxy> ProxyPass /folder http://****.com/floder
重启 apache.
注释:
ProxyRequests Off
指令是指采用反向(reverse)代理,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置;而正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此必须采取安全措施以确保仅为经过授权的客户端提供服务。ProxyPass
指令允许将一个远端服务器映射到本地服务器的 URL 空间中,此时本地服务器并不充当代理角色,而是充当远程服务器的一个镜像。/folder 是一个本地虚拟路径,http://****.com/floder 是一个指向远程服务器的部分 URL
如果不想对某个子目录进行反向代理时,可以用"!"指令。比如说:
ProxyPass /folder/exception ! ProxyPass /folder http://****.com/floder
将会代理除 /folder/exception 之外的所有对 http://****.com/floder 的请求。
也可以用 URL 重写的方法来实现
1 修改 apache 配置文件 httpd.conf ,去掉以下三行前面 # 号
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule rewrite_module modules/mod_rewrite.so
2 在 server config 或 virtual host 中增加:
<Location /folder> SetHandler proxy-server order allow,deny Allow from all </Location> RewriteEngine on RewriteRule ^/folder/(.*)$ http://****.com/floder [L,R=301,P,NC]
注释:
Location
指令提供了基于URL的访问控制,对于本域下的 /folder 目录下的任何资源的访问都会首先由proxy-server这个 handler(mod_proxy模块内部定义的一个 handler)来处理。SetHandler proxy-server
指令是强制所有匹配的文件被一个代理服务器处理。RewriteEngine on
指令是指打开重写引擎。RewriteRule
指令是重写规则。last|L
这个标记用于阻止当前已被重写的 URL 被后继规则再次重写。redirect|R [=code]
若Substitution以http://thishost[:thisport]/(使新的URL成为一个URI)开头,可以强制性执行一个外部重定向,是跨域或定向到外部域的必备良药。默认为 HTTP 响应码为 302, 我通常指定为301。proxy|P
此标记使替换成分被内部地强制作为代理请求发送,表明该 rewrite 是通过 mod_proxy 代理过去,而不是通过外部重定向过去。nocase|NC
忽略大小写,也就是在Pattern与当前 URL 匹配时,’A-Z’和’a-z’没有区别。