一、同源策略
1、同源策略是对javascript代码能够操作哪些web内容的一条完整的安全限制。
脚本只能读取和所属文档来源相同的窗口和文档的属性。
文档的属性包括:协议,主机,以及载入文档的URL端口
2、同源策略应用
a.脚本可以打开或者关闭一个窗口,但是不能以任何方式查看窗口内部。
b.使用XMLHttpRequest生成Http请求。XMLHttpRequest对象允许客户端脚本生成的http请求到与所属文档来源相同的web服务器,但是不允许脚本和其他web服务器通信。
c.防止脚本窃取私有的信息
二、ajax跨域
1.什么是ajax跨域
前端代码:文件在本地,vscode可以安装live server插件,让本地的html文件以服务器模式在浏览器打开
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cross-domain-test</title>
<link rel="stylesheet" href="http://dwedg.cn/tools/css/common.css">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<style>
.content-area{
width: 500px;
height: 300px;
word-wrap: normal;
}
#result_area{
width: 100%;
height: 200px;
font-size: 20px;
color:black;
border:2px solid black ;
}
#get_data_btn{
margin-top: 5px;
width: 100px;
height: 40px;
text-align: center;
background: rgba(241, 236, 236, 0.9);
border: solid 1px coral;
border-radius: 10px;
float: right;
color: coral;
}
</style>
</head>
<body>
<div class="content-area">
<div id="result_area"></div>
<button type="button" id="get_data_btn">获取数据</button>
</div>
</body>
<script>
let _dom1=null;
let _dom2=null;
function request(dom){
axios({
method:'get',
url:'http://dwedg.cn/php/rank/interface/getZone.php?action=getZone',
data:{
action:"getZone"
}
})
.then(function(res){
if(res && res.status==200 && res.data){
let str = '';
for(let [key,val] of Object.entries(res.data)){
if(typeof val === "object"){
str+=`<div>${key}=======${JSON.stringify(val)}</div>`
}
else if(typeof val === "string" || typeof val === "number"){
str+=`<div>${key}=======${val}</div>`
}
}
dom.innerHTML=str;
}
})
.catch(function(err){
console.log("出现了错误,错误原因是"+err);
})
}
function init(){
_dom1=document.getElementById("result_area");
_dom2=document.getElementById("get_data_btn");
_dom2.addEventListener("click",function(event){
request(_dom1)
})
}
init();
</script>
</html>
后端代码:php写的接口,放在服务器上
<?php
$action = $_GET["action"];
switch($action)
{
case "getZone":
get_zone();
break;
default:
echo "1111111";
break;
}
function get_zone(){
$temp_data = file_get_contents('../test_data/zone_data.json');
$json = json_encode(array(
"resultCode"=>200,
"message"=>"查询成功!",
"data"=>json_decode($temp_data,true),
),JSON_UNESCAPED_UNICODE);
//转换成字符串JSON
echo($json);
}
?>
当本地文件发送一个http请求时,浏览器报错,如下图:
浏览器报错:服务器由于CORS(跨域资源共享)策略阻止了来自127.0.0.1:5500的http请求.这个http请求没有通过预检查.请求的服务器文件头部没有出现”Access-Control-Allow-Origin“.说明出现了ajax跨域问题。
2.出现的原因
a、浏览器的限制
b、跨域。即协议,域名,端口有一个不一样
c、XHR请求
以上abc同时满足即会产生Ajax跨域问题
3.解决思路
a.浏览器限制=======》浏览器设置允许跨域
b.XHP请求========》JSONP
c.跨域=======》调用接口的一方,设置白名单(cors),调用接口的一方设置代理
三、ajax跨域解决方案
- 浏览器禁止检查
- 新建一个名为chromeDevData的文件夹,并且设置禁止检查
终端命令行:open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/zhangdan/cache/chromeDevData
设置成功后重新打开的浏览器窗口提示稳定性和安全性有所下降,如下图所示:
- 打开前端页面重新发起请求,浏览器没有报错,请求成功
该方法需要客户端进行修改,适用于开发阶段开发人员调试时使用。正式环境下不可能让每一个用户都这样设置。
2.JSONP
- 利用script标签请求资源可以跨域来解决跨域
前端代码:
<script>
//省略重复代码
function axios_extend_jsonp(){
axios.jsonp = (url,data)=>{
if(!url){
throw new Error('url is not exit')
}
const callback = 'callback'+Math.random().toString().substring(3,9);
const JSONP = document.createElement('script');
JSONP.setAttribute('type','text/javascript');
const headEle = document.getElementsByTagName('head')[0];
//参数拼接
let search = '';
if(data){
if(typeof data == 'string'){
search+="&"+data;
}
else if(typeof data == 'object'){
for(let key in data){
search+="&"+key+"="+encodeURIComponent(data[key]);
}
}
search+='&_time='+ Date.now();
}
JSONP.src=`${url}?callback=${callback}${search}`;
return new Promise((resolve,reject)=>{
//将jsonp返回的脚本挂在window上
window[callback]=result =>{
resolve(result)
headEle.removeChild(JSONP);
delete window[callback]
}
headEle.appendChild(JSONP)
})
}
}
function init(){
_dom1=document.getElementById("result_area");
_dom2=document.getElementById("get_data_btn");
//给axios扩展一个jsonp方法
axios_extend_jsonp();
_dom2.addEventListener("click",function(event){
// request(_dom1);
axios.jsonp("http://dwedg.cn/php/rank/interface/getZone_jsonp.php")
.then((res)=>{
let temp_obj = {};
temp_obj.data = res;
temp_obj.status = res.status;
renderResult(temp_obj,_dom1)
})
.catch((err)=>{
console.log("接口异常:"+err)
})
})
}
init();
</script>
后台代码:
<?php
//默认返回text/html类型script标签应该返回的是js脚本类型
header('Content-Type: application/x-javascript;charset=utf-8');
$temp_data = file_get_contents('../test_data/zone_data.json');
$result = json_encode(array(
"status"=>200,
"message"=>"jsonp方法查询成功!",
"data"=>json_decode($temp_data,true),
),JSON_UNESCAPED_UNICODE);
$callback = $_GET['callback'];
echo $callback."($result)";
?>
输出结果:
jsonp原理:
抓出接口:
http://dwedg.cn/php/rank/interface/getZone_jsonp.php?callback=callback248058
浏览器刷接口的返回值
callback248058({"status":200,"message":"jsonp方法查询成功!","data":{"zone":["LPL","LCK","LDL","CK","LEC","LCS","其他"]}})
由此可见:
- 前端和后台共同约定了一个callback参数,前端在发送请求之前,会将callback参数的值callback248058作为全局对象的一个属性名,它的值是一个方法。当后台接收到callback参数时,知道这是一个jsonp请求,会返回js脚本。不一定要用callback,也可以约定其他参数值。
- 后台callback参数的值callback248058(随机数是为了防止缓存)作为函数名,接口要返回的数据作为函数的参数,返回的全部内容其实是一个函数的调用。
- 前端接收js脚本开始解析执行。
jsonp优缺点:
优点:1、不受同源限制2、兼容性更好
缺点:1、xhr很多新的特性,以及事件没有
2、只支持get请求
3、安全性
3.CORS
原理:后台添加允许访问的域名信息
<?php
header('Access-Control-Allow-Origin: http://127.0.0.1:5500');//支持哪些跨域来源的请求
header('Access-Control-Allow-Methods: *');//服务器支持跨域请求的方法
header('Access-Control-Allow-Credentials: true'); // 设置是否允许发送 cookies
header('Access-Control-Max-Age:3000');//制定本次预检查的有效期,单位秒
header('Access-Control-Expose-Headers: *');//Access-Control-Expose-Headers里面指定
//getResponseHeader()方法拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、
//Last-Modified、Pragma之外其他字段
header('Access-Control-Allow-Headers: *');//如果浏览器请求包括Access-Control-Request-Headers字段,
//则Access-Control-Allow-Headers字段是必需的,允许的头部信息字段
$action = $_GET["action"];
switch($action)
{
case "getZone":
get_zone();
break;
default:
echo "1111111";
break;
}
function get_zone(){
$temp_data = file_get_contents('../test_data/zone_data.json');
$json = json_encode(array(
"resultCode"=>200,
"message"=>"查询成功!",
"data"=>json_decode($temp_data,true),
),JSON_UNESCAPED_UNICODE);
//转换成字符串JSON
echo($json);
}
?>
执行结果:
请求接口及响应详情:
补充:
- 如果是非简单请求,会先发送预请求,判断通过再执行
- 如果是带cookie的请求,origin必须是全匹配,且必须增加“Access-Control-Allow-Credentials:true”字段
- 常见简单请求:a.方法为get,post,head b.请求head里1.没有自定义头2.content-type 为text/plain,multipart/form-data,application/x-www-form-urlencode中的一种
- 常见非简单请求:put/delete方法的ajax请求,json格式的ajax请求,带自定义头的Ajax请求
4.nginx配置
1.登录服务器,查找(命令:ps -ef | grep nginx)到nginx目录下的conf目录
2.查看nginx的配置文件,Nginx引入的配置文件放在如下目录
3.查看接口域名的那个配置文件:
5.打开这个配置文件,添加如下请求头信息:(也就是把之前接口添加的请求头信息添加到这个配置文件中来)
6.测试配置文件是否有误:nginx -t
7.进入sbin目录下,重载Nginx服务 :./nginx -s reload
8.测试结果:成功
5.Nginx反向代理:
需要调用方提供一个nginx服务器,在该服务器上配置该服务,使得页面所有的请求都被nginx监听。在服务上配置多个前缀,然后将匹配到的http/https请求转发到多个真实的服务器。这样对于浏览器来说,这些url都是同源的,没有跨域限制。
三、总结:
在实际开发项目中,前端开发人员大部分业务设置浏览器不要检查跨域就可进行开发,正式环境下,前端代码和后台接口,一般是放在同一台服务器上的。
如果确实需要解决跨域,也需要前端开发人员和后台开发对接。
nginx配置一般是运维人员操作。