出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequest 对象和Fetch发起 HTTP 请求就必须遵守同源策略。
具体而言,Web 应用程序通过 XMLHttpRequest 对象或Fetch能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。
隶属于 W3C 的 Web 应用工作组( Web Applications Working Group )推荐了一种新的机制,即跨源资源共享(Cross-Origin Resource Sharing (CORS))。这种机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。
Note: 这些跨站请求与以往浏览器发出的跨站请求并无异同。并且,如果服务器不给出适当的响应头,则不会有任何数据返回给请求方。
如果服务器端仅允许来自 http://foo.example 的跨站请求,它可以返回: Access-Control-Allow-Origin: http://foo.example
“预请求”要求必须先发送一个 OPTIONS 请求给目的站点*,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏。 当请求具备以下条件,就会被当成预请求处理:
如果request请求要支持HTTP Cookies和验证信息,那么,XMLHttpRequest
需要将withCredentials
属性设置为true
,而response需要返回Access-Control-Allow-Credentials: true
。
前端代码:
// JS
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true; // 设置
invocation.onreadystatechange = handler;
invocation.send();
}
// JQuery
$.ajax({
...
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
Access-Control-Allow-Origin: | *
origin参数指定一个允许向该服务器提交请求的URI。对于一个不带有credentials的请求,可以指定为'*',表示允许来自所有域的请求。
还可以指定具体的域,比如:
Access-Control-Allow-Origin: http://mozilla.com
如果服务器端指定了域名,而不是'*',那么响应头的Vary值里必须包含Origin.它告诉客户端: 响应是根据请求头里的Origin的值来返回不同的内容的。
在 HTML 中,,
,
,
,
,
等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域。
HTML中a/form/img/css/script/iframe/ajax都可以指向资源
发起请求的资源所在域不同于该请求所指向资源所在的域的HTTP请求就叫做跨域HTTP请求
举个例子:假如一个域名为aaa.cn
的网站,它发起一个资源路径为aaa.cn/books/getBookInfo
的 Ajax 请求,那么这个请求是同域的,因为资源路径的协议、域名以及端口号与当前域一致(例子中协议名默认为http,端口号默认为80)。但是,如果发起一个资源路径为bbb.com/pay/purchase
的 Ajax 请求,那么这个请求就是跨域请求,因为域不一致,与此同时由于安全问题,这种请求会受到同源策略限制。
跨源 HTTP 请求的一个例子:运行在 https://domain-a.com
的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json
的请求。
出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest
和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
协议+域名+端口号都相同才是同域
CORS 机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。
通常,浏览器会对上面提到的跨域请求作出限制。浏览器之所以要对跨域请求作出限制,是出于安全方面的考虑,因为跨域请求有可能被不法分子利用来发动 CSRF攻击。
如果对跨域请求不做限制,会有安全隐患
例1:一个恶意网站的页面通过iframe嵌入了支付宝的登录页面(两者不同域),如果没有任何限制,恶意网页上的javascript脚本就可以在用户登录银行的时候获取用户名和密码
例2:跨域请求别人的AJAX登录接口,也可以获取到用户名密码
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。
CSRF 攻击的原理大致描述如下:有两个网站,其中A网站是真实受信任的网站,而B网站是危险网站。在用户登陆了受信任的A网站是,本地会存储A网站相关的Cookie,并且浏览器也维护这一个Session会话。这时,如果用户在没有登出A网站的情况下访问危险网站B,那么危险网站B就可以模拟发出一个对A网站的请求(跨域请求)对A网站进行操作,而在A网站的角度来看是并不知道请求是由B网站发出来的(Session和Cookie均为A网站的),这时便成功发动一次CSRF 攻击。
因而 CSRF 攻击可以简单理解为:攻击者盗用了你的身份,以你的名义发送而已请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
因此,大多数浏览器都会跨域请求作出限制,这是从浏览器层面上的对 CSRF 攻击的一种防御,但是需要注意的是在复杂的网络环境中借助浏览器来防御 CSRF 攻击并不足够,还需要从服务端或者客户端方面入手防御。详细可以参考这篇文章浅谈CSRF攻击方式
是浏览器的一种约定和安全功能,对跨域请求进行控制
1:限制来自不同源的document或脚本,对当前document读取或设置某些属性
2:禁止ajax直接发起跨域HTTP请求(其实可以发送请求,结果被浏览器拦截,不展示)
3:允许img/css/script(拥有src的标签都有跨域的能力。加载下来就属于当前域了)
4:允许跨域的连接、跳转、表单提交(带来了跨站请求伪造CSRF问题)
解决了例1里面的问题:
恶意网站的JS无法读取iframe里面的内容
因为iframe加载之后属于单独的一个window当然也是一个单独的document
解决了例2里面的问题:
发起的AJAX请求结果如何被浏览器屏蔽了,虽然实际请求已经发送到服务端,但是不知道结果(所以服务端也要有相应的策略)
示例:
server {
#listen 443 ssl;
listen 16443 ;
server_name zjapp6-test.hrfax.cn;
if ($request_method ~* PUT|DELETE) {
return 403;
}
location ~*\.(jsp|php) {
deny all;
return 404;
}
location / {
if ($request_method = 'OPTIONS') {
# 处理预检请求
#add_header 'Access-Control-Allow-Origin' 'https://bocsign-h5-test12.hrfax.cn, http://192.168.12.112:83';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Content-Length' 0;
return 204;
}
#ua_log4j
if ($http_user_agent ~ "jndi|ldap|\$"){
return 403;
}
include proxy.conf;
proxy_pass http://192.168.12.112:9087;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}