什么同源策略?
同源策略是浏览器的一个安全策略,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
例如:页面a是从服务器A上请求下来的,那么因为同源策略的规定,页面a请求的任何数据,只能从服务器A上请求,不能从其他服务器请求。
注意: 静态资源不受同源策略的限制(比如:页面中的链接,重定向以及表单提交是不会受到同源策略限制的,还有HTML、CSS、JS、图片、音频、视频、iframe等静态资源)。
同源策略限制了以下行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 JS 对象无法获取
- Ajax请求发送不出去
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
下表给出了与 URL http://a.com/dir/page.html 的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://a.com/dir2/page2.html | 成功 | 同源 |
http://a.com/dir/inner/page1.html | 成功 | 同源 |
https://a.com/page3.html | 失败 | 不同协议(http和https) |
http://a.com:81/page4.html | 失败 | 不同端口(80和81) |
http://b.com/page.html | 失败 | 不同域名(a和b) |
同源策略作用
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
但是,我们有时候确实需要从一个服务器访问另一个服务器的数据,我们称“从一个服务器请求另一个服务器资源的行为”叫做 跨域请求
什么是跨域?
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
只要URL中的协议、域名、端口号三者有任何一个不一致,就叫做跨域。
如何实现跨域?
JSONP方式
jsonp跨域是JavaScript设计模式中的一种代理模式。在HTML页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的(从js中调用不行),所以我们可以通过这个“漏洞”来实现跨域。一般,我们可以动态的创建script
标签,再去请求一个带参数网址来实现跨域通信。局限:只能是get请求。
下面是前端两种实现方式:
- 原生js实现方式:
let script = document.createElement("script");
script.src = "http://www.xxx.com/xxx.js?callback=callback";
document.body.appendChild(script);
function callback(res){
console.log(res);
}
- jquery实现方式:
$.ajax({
url: 'http://www.xxx.com/login',
type: 'GET',
dataType: 'jsonp',
jsonpCallback: 'callback'
})
function callback(res){
console.log(res);
}
特别注意的是,上面代码里传入的callback参数是服务器传回数据后需要调用的方法,上面代码中方法是callback();
服务器端传回的数据格式应该是callback(数据)
另外有时会出现406
错误,这时候就需要在响应头中添加Access-Control-Allow-Origin,值为*,表示的是允许所有域名访问:response.setHeader("Access-Control-Allow-Origin", "*");
注意: 只能是get请求。
为了方便,我们可以将jsonp封装到,方便调用。在这里,将实现jsonp的方式封装到上一篇文章中的AJAX函数里面:
var AJAX = {
// 封装get,参数一表示一个url路径,参数二表示前端传递过来的数据,是一个对象,参数三表示一个回调函数
get(url, data, callback){
// 假如data是:{a: 1, b: 2, c: 3, d: 4}
// 需要将它格式转化成:a=1&b=2&c=3&d=4,这种querystring字符串形式
// 定义一个变量,用于存放转化后的字符串
var querystring = "";
for (let i in data){
querystring += i + "=" + data[i] + "&";
// 第一次循环结束以后 => a=1&
// 第二次循环结束以后 => a=1&b=2&
// 第三次循环结束以后 => a=1&b=2&c=3&
// 第四次循环结束以后 => a=1&b=2&c=3&d=4&
}
// 将最后面的&去掉,即只要前面部分
querystring = querystring.slice(0, -1);
// 1、初始化对象
let xhr = new XMLHttpRequest();
// 2、监听状态
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)){
// JSON.parse:将符合JSON格式的字符串转为对象
// 将返回内容转为对象
let obj = JSON.parse(xhr.responseText);
// 回调函数
callback(obj);
}
}
// 3、open
xhr.open("get", url + "?" + querystring, true);
// 4、send
xhr.send();
},
// 封装post,参数一表示一个url路径,参数二表示前端传递过来的数据,是一个对象,参数三表示一个回调函数
post(url, data, callback){
// 假如data是:{a: 1, b: 2, c: 3, d: 4}
// 需要将它格式转化成:a=1&b=2&c=3&d=4,这种querystring字符串形式
// 定义一个变量,用于存放转化后的字符串
var querystring = "";
for (let i in data){
querystring += i + "=" + data[i] + "&";
// 第一次循环结束以后 => a=1&
// 第二次循环结束以后 => a=1&b=2&
// 第三次循环结束以后 => a=1&b=2&c=3&
// 第四次循环结束以后 => a=1&b=2&c=3&d=4&
}
// 将最后面的&去掉,即只要前面部分
querystring = querystring.slice(0, -1);
// 1、初始化对象
let xhr = new XMLHttpRequest();
// 2、监听状态
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)){
// 回调函数
callback(JSON.parse(xhr.responseText));
}
}
// 3、open
xhr.open("post", url, true);
// 将请求头内容更改为表单的内容类型
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded;charset=utf-8");
// 4、send
xhr.send(querystring);
},
// 封装jsonp,参数一表示接口路径,参数二表示前端传递过来的数据,是一个对象,参数三是一个回调函数
jsonp(url, data, callback){
// 假如data是:{a: 1, b: 2, c: 3, d: 4}
// 需要将它格式转化成:a=1&b=2&c=3&d=4,这种querystring字符串形式
// 定义一个变量,用于存放转化后的字符串
var querystring = "";
for (let i in data){
querystring += i + "=" + data[i] + "&";
// 第一次循环结束以后 => a=1&
// 第二次循环结束以后 => a=1&b=2&
// 第三次循环结束以后 => a=1&b=2&c=3&
// 第四次循环结束以后 => a=1&b=2&c=3&d=4&
}
// 将最后面的&去掉,即只要前面部分
querystring = querystring.slice(0, -1);
// 1、创建script标签
let script = document.createElement("script");
// 2、设置script的src
script.src = url + "?" + querystring;
// 3、script只有在树上的时候才会发送请求
document.body.appendChild(script);
// 4、将callback作为回调函数,注册到全局
window[data.callbackName] = callback;
}
}
接下来我们使用封装好的函数,来实现跨域通信:
演示1:简单实现跨域注册
html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>跨域注册</h2>
<!-- 实现跨域注册 -->
<form action="">
<label for="">用户名:</label>
<input type="text" name="username" id="username">
<label for="">密码:</label>
<input type="password" name="password" id="password">
<button id="btn" type="button">注册</button>
</form>
<script src="../js/AJAX.js"></script>
<script>
let username = document.getElementById("username");
let password = document.getElementById("password");
btn.onclick = function(){
let un = username.value;
let psw = password.value;
AJAX.jsonp("http://10.3.131.79/05_JSONP_PHP/regist_jsonp.php", {username:`${un}`, password: `${psw}`, callbackName: "callback"}, function(data) {
alert(data.msg)
})
}
</script>
</body>
</html>
http://10.3.131.79/05_JSONP_PHP/regist_jsonp.php
是另一个服务器的接口,{username:${un}
, password: ${psw}
, callbackName: “callback”}对象是我们传递给后端的数据,username表示用户名,password表示密码,callbackName表示回调函数名。
regist_jsonp.php文件:
<?php
header("content-type: text/html;charset=utf-8");
// 1 接收前端传递过来的数据
$username = $_GET["username"];
$password = $_GET["password"];
$callbackName = $_GET["callbackName"];
// 2 连接数据库
mysql_connect("localhost", "root", "123456");
// 3 选择数据库
mysql_select_db("gx2006");
// 4 定义sql语句
$sql = "INSERT INTO user VALUES('$username', '$password')";
// 5 执行sql语句
$result = mysql_query($sql);
// 6 获取执行结果
if ($result) {
echo $callbackName."(".json_encode(array("error" => 0, "msg" => "注册成功")).")";
} else {
echo $callbackName.'('.json_encode(array("error" => 1, "msg" => "注册失败")).")";
}
?>
演示2:简单实现跨域获取数据
html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
button{
display: block;
width: 400px;
height: 30px;
margin: 0 auto;
}
table{
width: 400px;
height: auto;
margin: 0 auto;
text-align: center;
border: 1px solid #ccc;
}
td{
border: 1px solid #ccc;
width: 50%;
}
</style>
</head>
<body>
<button id="btn" class="btn">跨域请求</button>
<table>
<thead>
<tr>
<td>用户名</td>
<td>密码</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script src="../js/AJAX.js"></script>
<script>
let btn = document.getElementById("btn");
let tbody = document.querySelector("tbody");
btn.onclick = function(){
AJAX.jsonp("http://localhost/05_JSONP_PHP/select_all.php", {callbackName: "callback"}, function(data) {
let arr = data.data;
arr.forEach((value) => {
tbody.innerHTML += `<tr><td>${value.username}</td><td>${value.password}</td></tr>`;
})
})
}
</script>
</body>
</html>
select_all.php文件:
<?php
header("content-type: text/html;charset=utf-8");
$callbackName = $_GET["callbackName"];
// 连接数据库
mysql_connect("localhost", "root", "123456");
// 选择数据库
mysql_select_db("gx2006");
// 创建SQL语句
$sql = "SELECT * FROM user";
// 执行SQL语句
$result = mysql_query($sql);
// 定义一个空数组
$arr = array();
// 将每一条数据放入数组
while($row = mysql_fetch_array($result)){
array_push($arr, $row);
}
$arr1 = array("erorr" => 0, "data" => $arr);
echo $callbackName.'('.json_encode($arr1).")";
?>
以上是jsonp实现跨域的方式。还有两种常见的跨域方式:
跨域资源共享CORS
nginx代理跨域