前后端分离是如何做的
在前后端分离架构中,后端只需要负责按照约定的数据格式向前端提供可调用的 API 服务即可。
前后端之间通过 HTTP 请求进行交互,前端获取到数据后,进行页面的组装和渲染,最终返回给浏览器。
同源政策
Ajax请求限制
Ajax 只能向自己的服务器发送请求。
比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求。
但是 A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。
同源:两个文档同源需满足
- 协议相同
- 域名相同
- 端口相同
http://www.example.com/dir/page.html(http协议,域名www.example)
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
同源政策的目的
同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。
随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax 请求,如果请求,浏览器就会报错。
跨域的方法
跨域,指的是浏览器执行了其他网站的脚本。
跨域是前后端分离,出现的问题。前端自己部署服务器 nginx + docker
同源策略已经不常用了,都是跨域
CORS 与 JSONP 的比较
CORS 与 JSONP 的使用目的相同,但是CORS 比 JSONP 更强大。
JSONP 只支持 GET 请求,CORS 支持所有类型的 HTTP 请求。
JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
1)使用 JSONP 解决同源限制问题(客户端和服务端配合完成)
jsonp 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求(将json数据作为填充内容,在服务器端将json数据作为函数的参数)
< script >不受同源策略的影响,可以访问其他域,将加载到的内容作为js代码执行
缺点:
大小限制:ie: 4k
只能发送get请求(因为< script>标签只能get)
有安全性问题,容易遭受xss攻击
需要服务端配合jsonp进行一定程度的改造
步骤:
- 动态创建一个script标签,把向服务器请求发送的地址 赋值给script的src属性,这时就会发送一个
get请求
到src指向的地址 - 通过
?callback传参
的方式,告诉服务器自己需要的数据 - 服务器拿到请求后,准备数据,把要返回的数据拼成字符串
- 浏览器帮我们把func函数执行,参数就是data
本质:
在客户端定义并调用函数执行,服务器端提供的是函数调用需要的参数
虽然服务器端返回的是字符串型的函数调用,但由于代码被写在script标签里面,所以这段字符串型的函数调用,会被当做真实的js代码执行。客户端在加载完响应内容之后,函数就会调用
- 客户端将不同源的服务器端请求地址写在 script 标签的 src 属性中
<script src="www.example.com"></script>
<script src=“https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
- 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
服务端代码
const data = 'fn({name: "张三", age: "20"})';
res.send(data);
- 在客户端全局作用域下定义函数 fn(写在< script >的前面,这样服务器返回后,调用时能找到函数的定义)
function fn (data) { }
- 在 fn 函数内部对服务器端返回的数据进行处理
function fn (data) { console.log(data); }
JSONP 代码优化
- 客户端需要将
函数名称
传递到服务器端(客户端可能需要修改函数名称,所以不能硬编码)
<!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
<script src="http://localhost:3001/better?callback=fn2"></script>
服务器端:
app.get('/better', (req, res) => {
// 接收客户端传递过来的函数的名称
const fnName = req.query.callback;
// 将函数名称对应的函数调用代码返回给客户端
const data = JSON.stringify({name: "张三"});
const result = fnName + '('+ data +')';
setTimeout(() => {
res.send(result);
}, 1000)
});
- 将 script 请求的发送变成
动态请求
。(请求在页面加载的过程中被发送,希望这个请求在想发送的时候再发送:动态创建script标签,将script标签追加到页面中)
<button id="btn">点我发送请求</button>
<script type="text/javascript">
// 获取按钮
var btn = document.getElementById('btn');
// 为按钮添加点击事件
btn.onclick = function () {
// 创建script标签
var script = document.createElement('script');
// 设置src属性
script.src = 'http://localhost:3001/better?callback=fn2';
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
// script标签加载完成后,就没有用了:将body中的script标签删除掉
document.body.removeChild(script);
}
}
</script>
- 封装 jsonp 函数,方便请求发送:不用起名字fn,随机产生函数名字,用success函数显示响应结果
<script>
btn2.onclick = function () {
jsonp({
// 请求地址
url: 'http://localhost:3001/better',
data:{
name:'tom',
age:30
}
success: function (data) {
console.log(456789)
console.log(data)
}
})
}
function jsonp (options) {
// 动态创建script标签
var script = document.createElement('script');
//可能传递多个请求参数,所以传入是一个data对象。需要拼接成get请求的参数
// 拼接字符串的变量 &name=tom&age=30
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}
//0.122741---> myJsonp0124741 使函数名字不一样,避免多次调用时,前一个success函数还没会返回,就被之后的覆盖(window.fn2)
var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
// 它已经不是一个全局函数了, 我们要想办法将它变成全局函数
window[fnName] = options.success;
// 为script标签添加src属性
script.src = options.url + '?callback=' + fnName + params;
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
document.body.removeChild(script);
}
}
</script>
- 服务器端代码优化之 res.jsonp 方法。
app.get('/better', (req, res) => {
// 接收客户端传递过来的函数的名称
//const fnName = req.query.callback;
// 将函数名称对应的函数调用代码返回给客户端
//const data = JSON.stringify({name: "张三"});
//const result = fnName + '('+ data +')';
// setTimeout(() => {
// res.send(result);
// }, 1000)
res.jsonp({name: 'lisi', age: 20});
});
另:jquery实现jsonp跨域
客户端
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp', //执行的是JSONP的请求
success: res =>{
console.log(res);
}
})
</script>
jquery在处理jsonp类型的ajax时(虽然jquery也把jsonp归入了ajax,但其实它们真的不是一回事儿),自动帮你生成回调函数并把数据取出来供success属性方法来调用
后台
let express = require('express'),
app = express();
app.listen(8000, _ =>{ //_为了占位
console.log('ok');
});
app.get('/list', (req, res) => {
let {
callback = Function.prototype //解构并赋默认值是一个匿名空函数
} = req.query;
let data = {
code : 0,
message: 'Tom'
};
res.send('${callback}(${JSON.stringify(data)})');
})
针对ajax与jsonp的异同:
1、ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装。
2、但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加
2) CORS 跨域资源共享
CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。
-
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信 -
CORS跨域,是服务器的事情
,让服务器指定谁能访问(若允许所有源访问,不安全,此时也不能允许携带资源凭证了)
服务端设置 Access-Control-Allow-Origin这样的一个HTTP响应头部,自动允许连接的连接来源, 就可以开启 CORS。 -
客户端必须带凭证
对于附带身份凭证的请求(即服务器设置Access-Control-Allow-Credentials: true),服务器不能设置 Access-Control-Allow-Origin 的值为“*”,否则请求将会失败。Access-Control-Allow-Credentials表示用户代理是否应该在跨域请求的情况下从其他域发送cookies -
CORS跨域请求之前,浏览器会帮我们发送一个options试探性请求:看一下我和服务器能不能连上
![](https://i-blog.csdnimg.cn/blog_migrate/4c01fc59ce68c835b0124ffbb5ea6feb.png)
Access-Control-Allow-Origin: ‘http://localhost:3000’
Access-Control-Allow-Origin: ‘*’
Node 服务器端设置响应头示例代码:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST');
next();
})
CORS预检请求
preflight请求,就是在发生CORS请求时,浏览器检测到跨域请求,会自动发出一个OPTIONS请求来检测本次请求是否被服务器接受。一个OPTIONS请求一般会携带下面两个与CORS相关的头:
Access-Control-Request-Method : 本次预检请求的请求方法。
Access-Control-Request-Headers:本次请求所携带的自定义首部字段。这些字段是导致产生OPTIONS请求的一个原因。后面会讲到。
服务端收到该预检请求后,会返回与CORS相关的响应头。主要会包括下面几个,但可能还会有其他的有关CORS字段:
Access-Control-Allow-Origin: 服务器允许的跨域请求源
Access-Control-Allow-Methods: 服务器允许的请求方法
Access-Control-Allow-Headers : 服务器允许的自定义的请求首部字段
服务器通过CORS跨域请求后,下面浏览器就会发生正式的数据请求。
在上面的两次请求中,预检请求只是一个检查的过程,它不会携带任何请求的参数;预检通过后的请求才会真正的携带请求参数与服务器进行数据通信。
发生preflight请求的条件
上面的预检请求并不是CORS请求的必须的请求过程,在一定的条件下并不需要发生预检请求。
发生预检请求的条件:是否是简单请求。简单请求则直接发送具体的请求而不会产生预检请求。
满足下面的所有条件就不会产生预检请求,也就是该请求是简单请求:
- 请求方法是GET、POST、HEAD其中任意一个
- 必须是下面定义对CORS安全的首部字段集合,不能是集合之外的其他首部字段。
Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width。 - Content-Type的值必须是text/plain、multipart/form-data、application/x-www-form-urlencoded中任意一个值
3) proxy代理 (适用于本地开发)
访问非同源数据 服务器端解决方案
同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制
。
![](https://i-blog.csdnimg.cn/blog_migrate/7435b200f9ef5087c626baffd0f4a189.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3378a6d03b99b97fcb75259213afae85.png)
开发时用http proxy,真实部署的时候让服务器做nginx反向代理
在webpack的devserver设置:所有给自己源发请求的,都代理到目标源
![](https://i-blog.csdnimg.cn/blog_migrate/fedd86f1b24d9dd14ecb5a1738034718.png)
cookie
![](https://i-blog.csdnimg.cn/blog_migrate/27b25ccde900d93b3edd03ddaa8cbbba.png)
withCredentials属性
在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie
4) iframe
iframe也能忽略域的影响,可以在自己的页面里,把百度嵌入进来
必须主域相同,子域不同