那些年的跨域问题
同源策略
- 同源策略是由浏览器给的
- 浏览器不允许我们向别人发送请求,只能向自己的服务器发送请求
- 当我们想向别人的服务器发送请求的时候,就会被浏览器阻止了
- 什么是 “别人的服务器” 呢?
- 当 请求协议/域名/端口号 有任意一个不同的时候,那么就算是别人的服务器
- 这个时候就会触发同源策略
- 我们管触发了 同源策略 的请求叫做 跨域请求
跨域
跨域(非同源策略请求)
- 同源策略请求 ajax/fetch
- 跨域传输
早期前部署到web服务器上:同源策略
补充:js是一门客户端语言 :在客户端执行的语言
1、jsonp解决跨域 :前提是服务端的支持
2、cors 解决跨域 :前提是服务端的支持
3、服务端代理解决跨域 :目标服务器有接口,并且没有权限
为什么解决跨域
因为有时候不可能仅仅使用自己的数据,当我们想要使用别人的数据接口时,就比如后端给我们的数据接口,说不定使用过程中就跨域了,所以必须解决跨域问题。
xampp
假如本地服务地址是 http://127.0.0.1:8080/index.html
服务端请求数据的地址 http://api.xxxx.com/getData
协议是一样的,但是域名不一样,端口也不一样,这样就导致访问不到数据了
之前的解决方法是:
修改本地的host文件,如127.0.0.1:8080然后定位到http://api.xxxx.com/
服务器拆分
web服务器:静态资源 xx.xx.xx.com
data服务器:业务逻辑和数据分析 api.xx.xx.xx.com
图片服务器
三者都一样就是同源,只要有一个不同就是跨域
- 协议
- 域名
- 端口号
web服务器地址:http:127.0.0.1:8080/index.html(想在这个地址里面有数据请求)
数据接口地址:http://127.0.0.1:8000/list
协议一样,域名一样,端口号不一样 —— 跨域
1、JSONP跨域解决
先说下简单理解jsonp的跨域问题
<!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>
<script>
// jsonp json
// jsonp 是被包裹的 json 数据;
// json :{"a" : 10}
// jsonp :callback({"a" : 10})
// 跨域 : xhr被限制了;
// 除了xhr,还有没有其他的可以发送http请求的方式那 ?
// 发现 带有 src 的标签可以发起http请求,同时不受同源策略的限制。
// img ×
// script √
// 这里为什么不能 用img
// 因为浏览器之中带标签 加载数据之后都会按照标签所表示的信息进行解析;
// 因为我们目标 : 加载数据在javascript之中使用;
// 我们需要使用 script 发请求,让代码当成script标签进行解析。
//当前的标签要用数据, 外部的标签进行数据的传入;
// 1. 全局变量可以访问;
// 2. 回调函数;
// 3. 函数存在参数传递;
// jsonp的跨域方案 :
// 1. 前端定义一个全局函数;
// 2. script标签使用src发起一个跨域请求;
// 3. 请求的内容调用前端函数。
// 4. 调用时传入实参,在前端全局函数使用形参接受;
// 定义一个函数
function callback(data) {
console.log(data)
}
</script>
<!-- 访问 -->
<!-- 拆分出去; 拆到任意服务器之中进行请求处理; -->
<script src="http://127.0.0.1:80/0713/jsonp.js"></script>
<script src="http://127.0.0.1:80/0713/jsonp.php"></script>
</body>
</html>
//jsonp.js文件
callback("这是jsonp.js")
//jsonp.php文件
<?php
echo "callback('这是jsonp.php')";
?>
上面所说的标签中带有src的有这些:
- script
- img
- link
- iframe
- …
=>不存在跨域请求的限制
再说明一下上述情况的其他变换
<!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>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js"></script>
<script src="jsonp.js"></script>
</body>
</html>
// jsonp.js文件
$.ajax({
url: 'http:127.0.0.1:8080/list',
method: 'get',
// 如果走jsonp需要加下面的代码,执行的是jsonp的请求
dataType: 'jsonp',
success: res=> {
console.log(res);
}
});
// serverJSONP.js文件
let express = require('express'),
app = express();
app.listen(8080,_=> {
console.log('ok!');
});
app.get('/list',(req,res) => {
let {
callback=Function.prototype
// Function.prototype是匿名空函数
} = req.query;
let data = {
code: 0,
message:'你好世界'
};
// 1、这里需要把data数据转换为字符串,用到JSON.stringify() 先在后端执行了,而在前端拿到的是字符串,
// 不是对象数据 , 注意这段代码应该先在后端处理好,然后发到前端去执行。
// 2、注意全局函数的名字,因为这个名字虽然是后端发给我的,但是我想要改这个名字,我不知道后端的名字,
// 所以这时,当我script 标签中src发请求时,把这个我定义的名字 传给后端 告诉后端这是我定义的名字,
// 注意,这只是一个函数名字,并不是一个函数。
res.send(`${callback}(${JSON.stringify(data)})`);
});
npm serverJSONP
显示OK!
注意:jsonp只能处理GET请求
补充jsonp一些知识点:
利用script中的src发送请求,这里的src中可以是.js,也可以不是.js,不管是什么类型的文件,只要响应返回的数据就可以。script请求返回的一定是js代码,还要注意一个全局函数的传递,因为js是需要下载到本地 ,然后在本地执行,那个函数也必须是全局函数。。。
2、CORS跨域资源共享
解决:后端允许服务器访问
第一种:先说明一种简单的情况,为了方便说明,代码比较简单明了
<!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>
<script>
// xhr请求
var xhr = new XMLHttpRequest()
xhr.open("GET", "http://127.0.0.1/0710/05_CORS.php")
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}/.test(xhr.status)) {
console.log(xhr.responseText)
}
}
</script>
</body>
</html>
PHP文件
<?php
//简单的一句打印
echo "hello";
?>
报错信息如下:
通过http://127.0.0.1/0710/05_CORS.php 访问 XMLHttpRequest, from origin 'http://localhost’被CORS策略阻止:被请求的资源上没有 ‘Access-Control-Allow-Origin’ 头文件。
这里只需要加一句即可解决:
<?php
//被请求的资源上没有'Access-Control-Allow-Origin'头文件,那就加上这句,让它有头文件
header("Access-Control-Allow-Orign:http://localhost");
echo "hello";
?>
另一种如下:
使用npm 下载axios npm install --save axios
- 客户端(发送ajax/fetch请求)
axios.defaults.baseURL = 'http://127.0.0.1:8888';
axios.defaults.withCredentials = true;
axios.defaults.header['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = function (data){
if(!data) return data;
let result = ``;
for(let attr in data){
if(!data.hasOwnProperty(attr)) break;
result +=`&${attr}=${data[attr]}`;
}
return result.substing(1);
};
axios.interceptors.response.use(function onFulfilled(response){
return response.data;
},function onRejected(reason) {
return Promise.reject(reason);
});
axios.defaluts.validateStatus = function (status) {
return /^(2|3)\d{2}$/.test(status);
}
- 服务端设置相关的头信息(需要处理option试探性请求)
app.use((req,res,next) => {
res.header("Access-Control-Allow-Origin","http://localhost:8000");
res.header("Access-Control-Allow-Credentials",true);
res.header("Access-Control-Allow-Header",
"Content-Type,Content-Length,Authorization,Accept,X-Require-With");
res.header("Access-Control-Allow-Methods",
"PUT,POST,GET,DELETE,HEAD,OPTIONS");
if (req.method === 'OPTIONS'){
res.send('ok');
return;
}
next();
});
3、ngnix 反向代理(服务端代理)
注意我这里服务器代理是nginx服务器代理
这里需要操作配置
找到nginx配置文件 : nginx.conf
phpstudy_pro\Extensions\nginx.1.15.11\conf\vhosts\0localhost_端口号.conf
修改里面的代码段即可
4、后端代理(比如PHP)
注意:这里以聚合数据为例 ,因为可以使用或者选择的余地比较大。
里面有教程讲解使用,看官网说明即可
<!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>
<!--
1. 数据来源 : 聚合数据;
2. 聚合数据接口需要跨域请求 : 后端代理。
3. 前端用 xhr 对象请求就行;
-->
<!--
用户交互设计 :
输入框
查询按钮
当用户点击查询按钮的时候,获取输入框之中的数据,把数据用xhr发送到聚合数据接口之中,接受响应数据,根据数据渲染页面;
-->
<p><input type="text" placeholder="请输入需要查询天气的城市" id="cityname"></p>
<p><button id="search">查询</button></p>
<div id="container">
查询结果
</div>
<script>
// 字段名 : city
// 前端流程 : 事件 => 获取用户数据并处理成GET请求发送的形式 =>发送xhr请求 => 接收到响应之后渲染页面。
var cityname = document.querySelector("#cityname");
document.querySelector("#search").onclick = function () {
// 重复触发 ;
var cityname_value = cityname.value;
// console.log(cityname_value); 已测试没有问题;
// 字段名=字段值
var get_msg = "?city=" + cityname_value;
// console.log(get_msg); 已测试没有问题;
var xhr = new XMLHttpRequest();
var url = "./01_weather.php";
xhr.open("GET", url + get_msg);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}/.test(xhr.status)) {
var data = JSON.parse(xhr.responseText);
console.log(data)
}
}
}
</script>
</body>
</html>
注意里面的字段名 : city,这里字段名是后端写好的,前端使用的时候,必须保持一致,不然请求时,数据得不到
<?php
header('Content-type:text/html;charset=utf-8');
//配置您申请的appkey
$appkey = "7395d3dc2048cba91aee204208fb3ec8";
$city = $_GET["city"];
//************1.根据城市查询天气************
$url = "http://op.juhe.cn/onebox/weather/query";
$params = array(
"cityname" => "$city",//要查询的城市,如:温州、上海、北京
"key" => $appkey,//应用APPKEY(应用详细页查询)
"type" => "top",//返回数据的格式,xml或json,默认json
);
$paramstring = http_build_query($params);
$content = juhecurl($url,$paramstring);
$result = json_decode($content,true);
if($result){
if($result['error_code']=='0'){
echo json_encode($result);
}else{
echo $result['error_code'].":".$result['reason'];
}
}else{
echo "请求失败";
}
//**************************************************
/**
* 请求接口返回内容
* @param string $url [请求的URL地址]
* @param string $params [请求的参数]
* @param int $ipost [是否采用POST形式]
* @return string
*/
function juhecurl($url,$params=false,$ispost=0){
$httpInfo = array();
$ch = curl_init();
curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );
curl_setopt( $ch, CURLOPT_USERAGENT , 'JuheData' );
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 60 );
curl_setopt( $ch, CURLOPT_TIMEOUT , 60);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true );
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if( $ispost )
{
curl_setopt( $ch , CURLOPT_POST , true );
curl_setopt( $ch , CURLOPT_POSTFIELDS , $params );
curl_setopt( $ch , CURLOPT_URL , $url );
}
else
{
if($params){
curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params );
}else{
curl_setopt( $ch , CURLOPT_URL , $url);
}
}
$response = curl_exec( $ch );
if ($response === FALSE) {
//echo "cURL Error: " . curl_error($ch);
return false;
}
$httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE );
$httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) );
curl_close( $ch );
return $response;
}
?>
5、http proxy
webpack 和 webpack-dev-server
在webpack.config.js中代理方式
devServer:{
port:3000,
progress: true,
contentBase:'./buid',
proxy:{
'./':{
target:'http://127.0.0.1:3001',
changeOrign:true
}
}
}
6、postMessage
window.postMessage() 方法可以安全地实现跨源通信。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
7、websocket io 协议跨域
实时通信的协议等
8、基于iframe方案 —document.domain
只能实现同一个主域,不同的子域之间的操作等
总之以上问题有些还没有在项目中真正搞明白,后期再慢慢补充吧
补充:怎么获取外部接口数据(这里是4、服务端代理)
前提的必须解决 4、ngnix 反向代理
中修改里面的代码段。。。
<!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>
<div id="container"></div>
<script>
var xhr = new XMLHttpRequest()
var url ="/dt?include_fields=top_comments%2Cis_root%2Csource_link%2Citem%2Cbuyable%2Croot_id%2Cstatus%2Clike_count%2Csender%2Calbum%2Creply_count&filter_id=%E6%90%9E%E7%AC%91%E8%90%8C%E5%AE%A0&start=24&_=1594378037461"
xhr.open("GET", url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}/.test(xhr.status)) {
console.log(xhr.responseText)
}
}
</script>
</body>
</html>
成功获取数据,就可以开心的渲染数据到页面
既然拿到数据了,那就随便渲染一下吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<title>Document</title>
<style>
ul,
li {
list-style: none;
}
img {
width: 300px;
height: 300px;
display: block;
}
.lis {
width: 300px;
height: 400px;
float: left;
}
</style>
</head>
<body>
<div id="container">
<ul class="list-ul">
<!-- <li class="lis">
<h2></h2>
<img src="" alt="">
</li> -->
</ul>
</div>
<script>
var cont = document.querySelector("#container")
var list_ul = document.querySelector(".list-ul")
var xhr = new XMLHttpRequest()
var url = "/dt?include_fields=top_comments%2Cis_root%2Csource_link%2Citem%2Cbuyable%2Croot_id%2Cstatus%2Clike_count%2Csender%2Calbum%2Creply_count&filter_id=%E6%90%9E%E7%AC%91%E8%90%8C%E5%AE%A0&start=24&_=1594378037461"
xhr.open("GET", url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}/.test(xhr.status)) {
// console.log(xhr.responseText)
var data = JSON.parse(xhr.responseText)
// console.log(data)
var list = data.data.object_list
console.log(list)
var html = ""
list.forEach(function (item, index) {
// let { photo, msg } = item
html += `
<li class="lis">
<h2>${item.msg}</h2>
<img src="${item.photo.path}" alt="">
</li>`
})
cont.innerHTML = html
}
}
</script>
</body>
</html>
注意:本地服务器路径下打开html文件查看效果
感谢堆糖的支持,这里郑重承诺:仅做数据的获取,不做有损堆糖网站的操作。
同源策略是什么,跨域解决办法,cookie可以跨域吗?
Q:为什么会出现跨域问题?
A:出于浏览器的同源策略限制,浏览器会拒绝跨域请求。
注:严格的说,浏览器并不是拒绝所有的跨域请求,实际上拒绝的是跨域的读操作。浏览器的同源限制策略是这样执行的:
通常浏览器允许进行跨域写操作(Cross-origin writes),如链接,重定向;
通常浏览器允许跨域资源嵌入(Cross-origin embedding),如 img、script 标签;
通常浏览器不允许跨域读操作(Cross-origin reads)。
Q:什么情况才算作跨域?
A:非同源请求,均为跨域。名词解释:同源 —— 如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin)。
Q:为什么有跨域需求?
A:场景 —— 工程服务化后,不同职责的服务分散在不同的工程中,往往这些工程的域名是不同的,但一个需求可能需要对应到多个服务,这时便需要调用不同服务的接口,因此会出现跨域。
方法:JSONP,CORS,postmessage,webscoket,反向代理服务器等。
引用文章:https://juejin.im/post/6857800782276902919
cookie可以跨域吗?
正常情况下,浏览器禁止跨域获取cookie;
一般通过sso服务可以实现取得跨域cookie;
思路如下:
- 域A页面访问位于域A的服务器,对权限进行验证
- 域A服务器于域B服务器通信,记录一个唯一的加密串用作身份验证域(并将cookie信息发送给域B服务器)
- 域A服务器返回302跳转,跳转到域B下,并将加密串作为url的一部分
- 页面由域A跳转到域B,域B服务器通过加密串获取到事先从域A服务器上得到的cookie信息,并在响应头中添加set-cookie字段设置cookie