跨域,是指浏览器不能执行其它网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript实施的安全限制。想要了解为什么跨域,我们需要知道什么是同源策略
1、同源策略
同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。如果两个页面的协议,域名和端口其中有任何一项不相同,则称为跨域
同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。
MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
通俗的理解:浏览器规定,A 网站的 JavaScript,不允许和非同源的网站 C 之间,进行资源的交互,例如:
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 Ajax 请求、
注意: http://localhost:8080与http://127.0.0.1:8080不属于同源,也就是说,即使IP地址一致,但是一个是域名,一个是IP地址,也不属于同源。即属于跨域
跨域:同源指的是两个 URL 的协议、域名、端口一致,反之,则是跨域。
出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。
下面是浏览器对跨域请求的拦截:
注意:浏览器允许发起跨域请求,但是,跨域请求回来的数据,会被浏览器拦截,无法被页面获取到!
下表给出了相对于 http://www.test.com/index.html 页面的同源检测,供大家了解同源和跨域:
URL | 是否同源 | 原因 |
---|---|---|
http://www.test.com/other.html | 是 | 同源(协议、域名、端口相同) |
https://www.test.com/about.html | 否 | 协议不同(http 与 https) |
http://blog.test.com/movie.html | 否 | 域名不同(www.test.com 与 blog.test.com) |
http://www.test.com:7001/home.html | 否 | 端口不同(默认的 80 端口与 7001 端口) |
http://www.test.com:80/main.html | 是 | 同源(协议、域名、端口相同) |
2、跨域解决方案
现如今,实现跨域数据请求,主要有三种解决方案,分别是 JSONP 、CORS 、代理跨域proxy。
2.1、JSONP
JSONP(JSON with Padding)出现的早,兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。缺点是只支持 GET 请求,不支持 POST 请求。
原理:<script>
标签不受浏览器同源策略的影响,可以通过src属性,请求非同源的js 脚本
下面用具体例子来讲解它的具体实现方式:
首先定义一个success回调函数
<script>
function success(data) {
console.log('获取到了data数据:')
console.log(data)
}
</script>
通过 <script> 标签,请求接口数据【这里使用黑马的接口去请求数据】:
<script src="http://api.xiaohigh.com/duanzi"></script>
这样就能够请求到对应路径下的数据了
注意:
- JSONP和Ajax之间没有任何关系,不能把JSONP请求数据的方式叫做Ajax,因为JSONP没有用到XMLHttpRequest这个对象。
- 通过href src所请求下来的, js脚本, css文件, 或者image图片文件, 视频文件, 都不存在跨域问题. 只有通过ajax请求才存在跨域问。所以Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有src这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>)
使用jQuery发起JSONP的请求
<body>
<script src="./jquery-3.6.1.min.js"></script>
<script>
$(function(){
// 发起JSONP的请求
$.ajax({
url:'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType:'jsonp',
success:function(res){
console.log(res);
}
})
})
</script>
</body>
2.2、CORS
跨源/域资源共享(CORS,Cross-Origin Resource Sharing)出现的较晚,是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器。
原理:CORS是浏览器为 AJAX 请求设置的一种跨域机制,让其可以在服务端允许的情况下进行跨域访问。主要通过 HTTP 响应头来告诉浏览器服务端是否允许当前域的脚本进行跨域访问。
注意:CORS要求在后端服务器配置相应的HTTP请求头,以允许来自特定域的跨域请求。对于前端应用在发送跨域请求时,需要确保请求头中包含适当的信息。一般情况下,浏览器会自动在请求头中添加所需的信息,但有时需要手动设置。
什么情况下需要CORS?
- 由 XMLHttpRequest 或 Fetch API 发起的跨源 HTTP 请求
- Web 字体(CSS 中通过
@font-face
使用跨源字体资源) - WebGL 贴图
- 使用 drawImage() 将图片或视频画面绘制到 canvas
- 来自图像的 CSS 图形 (en-US)等等
内容有点多,如果下面内容不想了解,可以自行通过目录直接跳转到【CORS跨域请求简单例子】
跨域资源共享将 AJAX 请求分成了两类:简单请求和非简单请求。其中简单请求符合下面 2 个特征。
简单请求【了解】
通常发起的请求中只有请求方法和目标地址,这就是一个简单请求
MDN上对简单请求的介绍:
- 请求方法为 GET、POST、HEAD 其中之一。
- 请求头只能使用下面的字段且不能超过四个:
- Accept(浏览器能够接受的响应内容类型)
- Accept-Language(浏览器能够接受的自然语言列表)
- Content-Type (请求对应的类型,只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded)
- Content-Language(浏览器希望采用的自然语言)
- Range(只允许简单的范围标头值 如
bytes=256-
或bytes=127-255
)【FireFox没有Range】
【注意:Content-Type 的属性值 application/json 不在其中,一旦加上对于简单请求的跨域就会失效】
非简单请求【了解】
简单请求中任意一条要求不符合的即为非简单请求。有些跨域请求需要在请求头中添加额外的参数【后端配置】,这样就超出的简单请求的范围,浏览器在检测该跨域请求的时候出现一个错误,这个错误就是在发送 预检请求(OPTIONS请求)的时候出现的。
预检请求【了解】
浏览器在发送跨域请求的时候会发送一个预检请求【它是浏览器对非简单跨域的一种探测行为】,请求方法为OPTIONS,预检请求的 Access-Control-Allow-Headers 中会添加上我们请求时额外的请求头,Access-Control-Allow-Methods 是本次请求的请求方法。
注意:预检请求不是我们能控制的,它是浏览器对非简单跨域的一种探测行为,同时跨域请求会发送本体请求和预检请求两次请求
所以服务端要注意,当请求方式为 "OPTIONS" 时只需要返回HTTP头即可,也就是预检请求时只需要返回HTTP头,遇到本体请求再做业务处理
对于预检请求我们知道是何物即可,重点在于前端的配置
关于Access-Control-Allow-Origin
对于跨域请求,服务器返回的头信息中必须包含这个字段
在前端开发时,我们经常会在浏览器网络请求中看到,Access-Control-Allow-Origin
响应标头指定了该响应的资源是否被允许与给定的来源(origin)共享。
其中下面字段表明,该资源可以被任意外源访问。
Access-Control-Allow-Origin: *
如果资源所有者想限制他的资源只能通过 https://foo.example
来访问,那么我们可以这样做:
Access-Control-Allow-Origin: https://foo.example
除了Access-Control-Allow-Origin之外还有下面的服务器返回的允许跨域标识:
Access-Control-Allow-Methods【请求方法】
Access-Control-Allow-Credentials【是否允许浏览器发起cookie,针对不同方式发起请求需要在前端或者稍微进行些配置】
Access-Control-Allow-Headers【请求头】
Access-Control-Expose-Headers【服务端允许跨域请求能够访问的HTTP头信息】
Access-Control-Max-Age【控制预检请求有效期,单位秒】
大家如果想要详细了解可以查看MDN文档
CORS跨域请求简单例子
前后端如何搭配进行CORS设置,假设我们有一个前端应用部署在 http://frontend.example.com
,需要访问后端API部署在 http://backend.example.com
。
后端设置(服务器端):
在后端服务器上,你需要配置相应的HTTP头部,以允许来自特定域的跨域请求。这通常在服务器的响应头中进行设置。以下是一个示例,使用Node.js和Express框架:
const express = require('express');
const app = express();
// 允许来自 frontend.example.com 的跨域请求
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://frontend.example.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
// 处理实际的API逻辑
// ...
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
示例中,Access-Control-Allow-Origin
表示允许的跨域来源,Access-Control-Allow-Methods
表示允许的HTTP方法,Access-Control-Allow-Headers
表示允许的请求头。
前端设置:
前端应用在发送跨域请求时,需要确保请求头中包含适当的信息。一般情况下,浏览器会自动在请求头中添加所需的信息,但有时需要手动设置。
fetch('http://backend.example.com/api/data', {
method: 'GET',
headers: {
'Authorization': 'Bearer token', // 示例授权头部
},
})
.then(response => response.json())
.then(data => {
// 处理返回的数据
})
.catch(error => {
// 处理错误
});
示例中,fetch
请求会自动附带上请求头中的授权信息,同时浏览器会在请求中添加 Origin
头部,用于指示请求的源。
总结
- 在简单请求中,只要满足简单请求的要求,一般情况下,是不怎么需要服务端去进行配置的,除了
Access-Control-Allow-Origin
这个字段是必备的 - OPTIONS请求作为预检请求,就算请求方法没有在
Access-Control-Allow-Methods
规定范围内也不会出现错误,但是我们不能自己发送OPTIONS请求 - CORS跨域设置涉及到后端和前端的配合。后端通过设置响应头来允许特定的跨域请求,而前端则需要确保请求头中包含适当的信息。这样可以保障安全地进行跨域资源访问
参考文档:MDN
参考视频:【前端CORS跨域手把手教程】https://www.bilibili.com/video/BV1M84y1v7qH?vd_source=f18af6bf744c2e89a221022960ec1abe
2.3、代理跨域proxy
代理跨域就是在客户端【前端】和服务器【后端】之间放一个用于代理的服务器【与发送请求的客户端同源】,不存在跨域问题
原理:客户端与代理服务器同源,不存在跨域;服务器之间的通信不使用 Ajax技术,不会出现跨域问题,服务器之间通信使用传统的 http 请求就行了(不受浏览器同源策略的限制)
Ajax 是前端技术,你得有浏览器,才有window对象,才有xhr,才能发ajax请求,服务器之间通信就用传统的 http 请求就行了
可以选取的代理服务器:
-
nginx:Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好。
-
vue-cli开启代理服务器 devServer.proxy
- 由于我们前端使用 nginx 的开销较大,所以这里只讲解使用 vue-cli 脚手架开启服务器,如果有需要的使用 nginx 的同学,可以去官方文档查看
vue-cli 代理服务器配置
方法一
在vue.config.js中添加如下配置:
// CommonJS模块化规范中 使用require引入
const { defineConfig } = require('@vue/cli-service')
// module.export是NodeJS模块化中CommonJS模块化规范的暴露
module.exports = defineConfig({
...
// 开启代理服务器,解决跨域问题
devServer:{
// 端口问题:因为使用vue-cli开启的代理服务器,端口号已经指定为当前服务器端口,所以proxy指定的是后端服务器的端口
proxy:'http://localhost:5000'
}
})
修改完配置文件,需要修改接收请求的服务器为代理服务器【App.vue】
import axios from 'axios';
export default {
name: "App",
methods:{
getStudents(){
// response和error都是对象,需要通过具体属性获取我们想要的值
axios.get('http://localhost:8080/students').then(response=>{
console.log('请求成功,数据:',response.data);
},error=>{
// error:AxiosError{message: 'Network Error',name:'AxiosError',code: 'ERR_NETWORK',config: {…}, request: XMLHttpRequest,…}
// error.message:Network Error
console.log('请求失败,失败的原因:',error,error.message);
})
}
}
};
这样就能通过代理服务器得到目标服务器的数据,从而实现跨域
注意:
-
proxy指定的URL和端口是后端服务器的URL和端口,因为代理服务器的URL和端口与我们项目开启的服务器一致,不需要再指定,只需要指定需要接收请求的后端服务器即可
-
修改完vue.config.js配置文件需要重新启动服务器
-
虽然我们修改接收请求的服务器为http://localhost:8080,但是由于我们目标服务器带有/students,也就是需要获取students数据,所以代理服务器也需要带上students,写为http://localhost:8080/students
-
优点:配置简单
-
缺点:1、不能配置多个代理【指定为URL和端口,请求就不能够转发给别人】,2、不能灵活的控制请求是否走代理。
方法二
解决方法一的两个缺点:1、不能配置多个代理【指定为URL和端口,请求就不能够转发给别人】,2、不能灵活的控制请求是否走代理。
编写vue.config.js配置具体代理规则:
module.exports = defineConfig({
...
// 开启代理服务器【方式二】
devServer: {
proxy: {
// '/api'为请求前缀,匹配所有'/api'开头的请求路径
'/api': {
// target为目标地址
target: 'http://localhost:5000',
// pathRewrite重写路径,使用正则表达式 "^/api"以/api开头的替换为""空字符串
pathRewrite:{'^/api':''},
ws: true, // websocket
changeOrigin: true // 用于控制请求头中的host值 建议开启 不告诉服务器真实的请求路径和端口
// vue中ws和changeOrigin不写,默认是true,但是react不写为false
},
'/riluo': {
target: 'http://localhost:5001',
pathRewrite:{'^/riluo':''},
}
}
}
})
App.vue
<template>
<div id="root">
<button @click="getStudents">获取学生信息</button>
<button @click="getCars">获取汽车信息</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: "App",
methods:{
getStudents(){
// response和error都是对象,需要通过具体属性获取我们想要的值
axios.get('http://localhost:8080/api/students').then(response=>{
console.log('请求成功,数据:',response.data);
},error=>{
// error:AxiosError{message: 'Network Error',name:'AxiosError',code: 'ERR_NETWORK',config: {…}, request: XMLHttpRequest,…}
// error.message:Network Error
console.log('请求失败,失败的原因:',error,error.message);
})
},
getCars(){
axios.get('http://localhost:8080/riluo/cars').then(response=>{
console.log('请求成功,数据:',response.data);
},error=>{
console.log('请求失败,失败的原因:',error.message);
})
}
}
};
</script>
注意:
- 因为我们在请求路径中添加了请求前缀,所以后端服务器收到时会同样收到带请求前缀的路径,所以为了后端服务器还收到原先的不带请求前缀的路径,需要对请求前缀进行过滤,我们就需要在Vue.config.js中进行配置,为请求前缀添加配置项pathRewrite路径重写
- ws配置用于支持websocket
- changeOrigin配置项:这个配置项的设置true、false不会影响请求的完成,只是说对于后端服务器能不能识别出请求来源于哪个服务器。
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
-
缺点:配置略微繁琐,请求资源时必须加前缀。
看到这里,你应该对同源策略和代理跨域的解决有了大致的了解,后面可以通过项目进行自身检测。如果有错,欢迎大家的指正。