正文
先看一个例子,入口脚本第一行:
if (isset($_SERVER['HTTP_ORIGIN'])) {
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: *, X-Requested-With, Content-Type, jwt-token");
}
出于防范跨站脚本攻击的同源安全策略,浏览器禁止客户端脚本(如Javascript)对不同域名的服务进行跨域调用。
同源策略(Same Origin)中的源有着严格的定义,参见RFC6454,第4章节。一般而言,Origin由{protocol, host, port}三部分组成。
一般我们会认为不同的子域名应该被当做同域名,是安全的调用,但实际上浏览器同源策略甚至禁止了不同子域名和端口的服务之间的调用。
解决方法
有时候上述限制过于严格,为了在两个不同Origin的网页(服务)之间进行通讯,我们可以采用如下的一些技术方法。
1、服务代理模式
即在web服务器上封装第三方服务,然后给自己同源的web页面调用。
浏览器 -> 代理服务器 -> 目标服务器
2、跨子域名的调用
如果想从www.example.com调用api.example.com的html/xml数据服务,
那么可以通过使用iframe,并在document和iframe中均设置相同的document.domain属性,
那么document和iframe所对应的页面直接就可以通信而不会有任何安全冲突(security violation),
注意必须设置相同的一级域名(document.domain="example.com"
),不要设置子域名。
3、跨源资源共享
参阅 https://ibaiyang.github.io/blog/网络协议/2021/02/23/CORS-跨域资源共享.html
这是已经标准化的技术方法,参见W3规范文档Cross Origin Resouce Sharing:http://www.w3.org/TR/cors/
通过在服务端设置Access-Control-Allow-Origin
响应头(一般回填$_SERVER['HTTP_ORIGIN']
),
header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
来告诉浏览器该服务允许来自特定源的访问或者允许所有人访问。
尽管该规范支持使用origin list的形式,但实际浏览器实现只支持了单个origin、*、null,如果要配置多个访问源,可以在代码中处理如下(PHP):
$allowed_origins = array(
"http://www.example.com" ,
"http://app.example.com" ,
"http://cms.example.com" ,
);
if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)){
@header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
}
或者在httpd配置或.htaccess文件(如果使用的是Apache服务器)中添加如下语句:
SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1
Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
如用的是nginx,配置类似如下:
location / {
# this will echo back the origin header
if ($http_origin ~ "example.com$") {
add_header "Access-Control-Allow-Origin" $http_origin;
}
}
4、使用JSONP协议
JSONP是JSON with Padding的缩写,是一种解决跨域数据获取的方案。由于浏览器的同源策略限制, 不同域名之间的前端JS代码不能相互访问到对方的数据,JSONP通过script标签的特性,实现在不同域名的网页间传递数据。 其原理是在客户端页面上定义一个回调函数(callback),然后通过script标签向外部服务器请求数据, 并将定义好的回调函数名称作为参数放在url请求地址里,服务器成功接收请求后,使用该参数将数据传递给定义好的回调函数并返回, 客户端页面中定义好的回调函数接收参数后进行处理。
需要注意的是,由于JSONP使用script标签获取数据,所以只支持GET请求,而不支持POST请求。 此外,JSONP采取的是无状态机制,不支持像HTTP协议那样有状态的操作,故容易被恶意利用。因此,开发中需谨慎使用。
下面看个例子。
JSONP 应用
一、服务端 JSONP 格式数据
如客户想访问 : https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction。
假设客户期望返回数据:["customername1","customername2"]
。
真正返回到客户端的数据显示为: callbackFunction(["customername1","customername2"])
。
服务端文件 jsonp.php 代码为:
<?php
header('Content-type: application/json');
//获取回调函数名
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);
//json数据
$json_data = '["customername1","customername2"]';
//输出jsonp格式的数据
echo $jsoncallback . "(" . $json_data . ")";
二、客户端实现 callbackFunction 函数
<script type="text/javascript">
function callbackFunction(result, methodName)
{
var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('divCustomers').innerHTML = html;
}
</script>
客户端页面完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
</head>
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
function callbackFunction(result, methodName)
{
var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('divCustomers').innerHTML = html;
}
</script>
<script type="text/javascript" src="https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script>
</body>
</html>
三、jQuery 使用 JSONP
以上代码可以使用 jQuery 代码实例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="divCustomers"></div>
<script>
$.getJSON("https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=?", function(data) {
var html = '<ul>';
for(var i = 0; i < data.length; i++)
{
html += '<li>' + data[i] + '</li>';
}
html += '</ul>';
$('#divCustomers').html(html);
});
</script>
</body>
</html>
其他
出于同源策略,HTML禁止在两个页面之间进行通讯,但<script>
元素是个例外,JSONP利用这个开放特性来实现跨域调用,
在客户端调用提供JSONP支持的URL Service,获取JSONP格式数据,然后在callback函数中处理返回的json协议数据:
<script type="text/javascript" src="http://api.example.com/getdata?jsonp=callback"></script>
如使用jQuery的ajax调用方式,示范代码如下:
$.ajax({
dataType: 'jsonp',
data: 'id=10',
jsonp: 'jsonp_callback',
url: 'http://api.example.com/getdata',
success: function () {
// do stuff
},
});
5、跨文档消息(Cross-document Messaging)
这个是HTML5引入的一个在跨域页面之间通讯的方法,参见W3规范 http://dev.w3.org/html5/postmsg/
postMessage API应用范围主要有2个,1是文档和内嵌frame之间的消息通信,2是文档和自身通过脚本打开的windows之间的消息通信。