Angular由一个bug说起之十三:Cross Origin

跨域

想要了解跨域,首要要了解源

什么是源,源等于协议加域名加端口号

只有这三个都相同,才是同源,反之则是非同源。

比如下面这四个里,只有第4个是同源

而浏览器给服务器发送请求时,他们的源一样,就是同源请求,反之就是非同源请求,非同源请求又称为跨域。

当你是跨域时,浏览器为了确保资源安全,会对跨域的访问资源做出一些限制,也就是浏览器的同源策略。

这是W3C上对同源策略的说明:https://www.w3.org/Security/wiki/Same_Origin_Policy

浏览器会对跨域做出哪些限制?

例如:源a和源b,它们是非同源的,则浏览器会有如下限制:

1. DOM访问限制:源a的脚本不能读取和操作源b的DOM。

页面1

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <h1>我是页面1</h1>
10.	    <button onclick="showDOM()">获取页面2的DOM</button>
11.	    <br>
12.	    <iframe id="framePage" src="./demo.html"></iframe>
13.	    <!-- <iframe id="framePage" src="https://www.baidu.com"></iframe> -->
14.	
15.	    <script type="text/javascript" >
16.	        function showDOM(){
17.	            const framePage = document.getElementById('framePage')
18.	            console.log(framePage.contentWindow.document.body) //同源的可以获取,非同源的无法获取
19.	        }
20.	    </script>
21.	    </body>
22.	</html>

页面2

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <h2>我是页面2</h2>
10.	</body>
11.	</html>

页面是这样的

点击获取

换一个不同源的页面

1.	<body>
2.	    <h1>我是页面1</h1>
3.	    <button onclick="showDOM()">获取页面2的DOM</button>
4.	    <br>
5.	    <!-- <iframe id="framePage" src="./demo.html"></iframe> -->
6.	    <iframe id="framePage" src="https://www.bilibili.com"></iframe>
7.	
8.	    <script type="text/javascript">
9.	        function showDOM() {
10.	            const framePage = document.getElementById('framePage')
11.	            console.log(framePage.contentWindow.document.body) //同源的可以获取,非同源的无法获取
12.	        }
13.	    </script>
14.	</body>

获取DOM

获取不到

2. 第二个限制,源a不能访问源b的cookie

3.	<!DOCTYPE html>
4.	<html lang="en">
5.	
6.	<head>
7.	    <meta charset="UTF-8">
8.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
9.	    <title>Document</title>
10.	</head>
11.	
12.	<body>
13.	    <h1>我是页面1</h1>
14.	    <button onclick="getCookie()">获取页面2的cookie</button>
15.	    <br>
16.	    <!-- <iframe id="framePage" src="./demo.html"></iframe> -->
17.	    <iframe id="framePage" src="https://www.bilibili.com"></iframe>
18.	
19.	    <script type="text/javascript">
20.	        function getCookie() {
21.	            const framePage = document.getElementById('framePage')
22.	            console.log(framePage.contentWindow.document.cookie) //同源的可以获取,非同源的无法获取
23.	        }
24.	    </script>
25.	</body>
26.	
27.	</html>

还是一样的页面,这次获取的是cookie

也是获取不到的,因为你在获取document这一步就已经失败了

3. ajax响应数据限制:源a可以给源b发请求,但是无法获取源b响应的数据。

这是一个获取头条新闻的页面

代码

28.	<!DOCTYPE html>
29.	<html lang="en">
30.	<head>
31.	    <meta charset="UTF-8">
32.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
33.	    <title>Document</title>
34.	</head>
35.	<body>
36.	    <button onclick="getNews()">获取头条新闻</button>
37.	
38.	    <script>
39.	        async function getNews() {
40.	            const result = await fetch('https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc');
41.	            const data = await result.json();
42.	            console.log('data');
43.	        }
44.	    </script>
45.	</body>
46.	</html>

页面

获取不到

备注:在上述限制中,浏览器对 Ajax 获取数据的限制是影响最大的一个,且实际开发中经常遇到。

几个注意点

  1. 跨域限制仅存在浏览器端,服务端不存在跨域。
  2. 即使跨域了,ajax请求也可以正常发出,但响应数据不会交给开发者。
  3. link,script,img这些标签发出的请求也可能跨域,只不过浏览器对标签跨域不做严格限制,对开发几乎无影响。

那么要如何解决跨域

想要解决跨域,一般有三种方法

第一种,用CORS解决ajax跨域问题

CORS 概述

CORS 全称:Cross-Origin Resource Sharing(跨域资源共享),是用于控制浏览器校验跨域请求的一套规范,服务器依照 CORS 规范,添加特定响应头来控制浏览器校验,大致规则如下:

  • 服务器明确表示拒绝跨域请求,或没有表示,则浏览器校验不通过
  • 服务器明确表示允许跨域请求,则浏览器校验通过

了解了CORS之后,还要了解一下简单请求和复杂请求

CORS 会把请求分为两类,分别是:1.简单请求、2.复杂请求。

简单请求要满足这个三个条件

  1. 请求方法要为 get, head或者post。
  2. 请求头字段要符合CORS安全规范(https://fetch.spec.whatwg.org/#cors-safelisted-request-header), 一般只要不手动修改请求头,都能符合改规范。
  3. 请求头的Content-Type的值只能是这三种:

text/plain

multipart/form-data

application/x-www-form-urlencoded

不满足这三个要求的请求都是复杂请求,而复杂请求会自动发送预检请求

预检请求是一种在实际跨域请求发出之前,由浏览器自动发出的一种请求。主要用于向服务器确认是否允许接下来的跨域请求。基本流程是这样的,浏览器先发起一个options请求,携带这个几个请求头,Origin(发起请求的源),Access-Control-Request-Method(实际请求的HTTP方法),Access-Control-Request-Headers(实际请求中使用的自定义头)。如果通过预检,才会继续发起实际的跨域请求。

这就是预检请求

CORS 解决简单请求跨域

前端

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getData()">获取数据</button>
10.	
11.	    <script>
12.	        async function getData() {
13.	            const url = 'http://127.0.0.1:8081/';
14.	            const result = await fetch(url);
15.	            const data = await result.json();
16.	            console.log('data');
17.	        }
18.	    </script>
19.	</body>
20.	</html>

服务端代码(以express框架为例):

1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/', (req, res) => {
5.	    res.send([{ name: '张三', age: 19 }])
6.	})
7.	
8.	app.listen(8081, () => {
9.	    console.log('服务器成功启动');
10.	})

这个时候是获取不到数据的

设置下cors

1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/', (req, res) => {
5.	    res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')
6.	    res.send([{ name: '张三', age: 19 }])
7.	})
8.	
9.	app.listen(8081, () => {
10.	    console.log('服务器成功启动');
11.	})

这样就能获取到数据了

整体思路:服务器在给出响应时,通过添加Access-Control-Allow-Origin响应头,来明确表达允许某个源发起跨域请求,随后浏览器在校验时,直接通过。

像这样的设置是允许某个源跨域

1.	// 允许 http://127.0.0.1:5500 这个源发起跨域请求
2.	res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')

而这样则是允许所有源跨域

1.	// 允许所有源发起跨域请求
2.	res.setHeader('Access-Control-Allow-Origin', '*')

CORS 解决复杂请求跨域

第一步:服务器先通过浏览器的预检请求,服务器需要返回如下响应头

Access-Control-Allow-Origin(允许的源)

Access-Control-Allow-Methods(允许的方法)

Access-Control-Allow-Headers(允许的自定义头)

Access-Control-Max-Age(预检请求的结果缓存时间(可选))

第二步:处理实际的跨域请求(与处理简单请求跨域的方式相同)

服务器代码

1.	const express = require('express');
2.	const app = express();
3.	
4.	app.options('/', (req, res) => {
5.	    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
6.	    res.setHeader('Access-Control-Allow-Methods', 'GET')
7.	    res.setHeader('Access-Control-Allow-Headers', 'city')
8.	    res.send()
9.	})
10.	
11.	app.get('/', (req, res) => {
12.	    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
13.	    res.send([{ name: '张三', age: 19 }])
14.	})
15.	
16.	app.listen(8081, () => {
17.	    console.log('服务器成功启动');
18.	})

前端代码

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getData()">获取数据</button>
10.	
11.	    <script>
12.	        async function getData() {
13.	            const url = 'http://127.0.0.1:8081/';
14.	            const result = await fetch(url, { method: 'GET', headers: { city: 'beijing' } });
15.	            const data = await result.json();
16.	            console.log('data', data);
17.	        }
18.	    </script>
19.	</body>
20.	</html>

数据获取到了

预检请求返回的响应头

借助 cors 库快速完成配置

上述的配置中需要自己配置响应头,比较麻烦,借助cors库,可以更方便完成配置

安装cors

npm i cors

配置cors

1.	const express = require('express');
2.	const cors = require('cors');
3.	const app = express();
4.	
5.	// 使用cors中间件
6.	app.use(cors({
7.	    origin: 'http://127.0.0.1:5500', // 允许的源
8.	    methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], // 允许的方法
9.	    allowedHeaders: ['city'], // 允许的自定义头
10.	    exposedHeaders: ['abc'], // 要暴露的响应头
11.	    optionsSuccessStatus: 200 // 预检请求成功的状态码
12.	}))
13.	
14.	app.get('/', (req, res) => {
15.	    res.setHeader('abc', '123')
16.	    res.send([{ name: '张三', age: 19 }])
17.	})
18.	
19.	app.listen(8081, () => {
20.	    console.log('服务器成功启动');
21.	})
22.	

成功获取数据

第二种,JSONP 解决跨域问题

JSONP 概述: JSONP 是利用了<script>标签可以跨域加载脚本,且不受严格限制的特性,可以说是程序员智慧的结晶,早期一些浏览器不支持 CORS 的时,可以靠 JSONP 解决跨域(现在基本不用了)。

基本流程:

    • 第一步:客户端创建一个<script>标签,并将其src属性设置为包含跨域请求的 URL,同时准备一个回调函数,这个回调函数用于处理返回的数据。
    • 第二步:服务端接收到请求后,将数据封装在回调函数中并返回。
    • 第三步:客户端的回调函数被调用,数据以参数的形势传入回调函数。

示例代码

前端

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <script>
10.	        function callback(data) {
11.	            console.log('data', data)
12.	        }
13.	    </script>
14.	    <script src="http://127.0.0.1:8081/users"></script>
15.	</body>
16.	</html>

后端

1.	const express = require('express');
2.	const app = express();
3.	
4.	app.get('/users', (req, res) => {
5.	    res.send(`callback([{ name: '张三', age: 19 }])`)
6.	})
7.	
8.	app.listen(8081, () => {
9.	    console.log('服务器成功启动');
10.	})

结果

优化下代码

前端

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	
4.	<head>
5.	    <meta charset="UTF-8">
6.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7.	    <title>Document</title>
8.	</head>
9.	
10.	<body>
11.	    <button onclick="getUsers()">获取用户</button>
12.	
13.	    <script>
14.	        function test(data) {
15.	            console.log('data', data)
16.	        }
17.	        function getUsers() {
18.	            // 创建script元素
19.	            const script = document.createElement('script')
20.	            // 指定script的src属性
21.	            script.src = 'http://127.0.0.1:8081/users?callback=test'
22.	            // 将script元素添加到body中触发脚本加载
23.	            document.body.appendChild(script)
24.	            // script标签加载完毕后移除该标签
25.	            script.onload = () => {
26.	                script.remove()
27.	            }
28.	        }
29.	    </script>
30.	</body>
31.	
32.	</html>

后端

1.	const express = require('express');
2.	const app = express();
3.	
4.	const users = [
5.	    { name: '张三', age: 19 },
6.	    { name: '李四', age: 20 }
7.	]
8.	
9.	app.get('/users', (req, res) => {
10.	    const { callback } = req.query
11.	    res.send(`${callback}(${JSON.stringify(users)})`)
12.	})
13.	
14.	app.listen(8081, () => {
15.	    console.log('服务器成功启动');
16.	})

效果

用jQuery封装jsonp

1.	$.getJSON('http://127.0.0.1:8081/teachers?callback=?', (data) => {
2.	            console.log(data)
3.	        })

第三种,配置代理服务器解决跨域

这里需要用到http-proxy-middleware这个包来配置代理服务器

下载http-proxy-middleware

npm i http-proxy-middleware

前端

1.	<!DOCTYPE html>
2.	<html lang="en">
3.	<head>
4.	    <meta charset="UTF-8">
5.	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6.	    <title>Document</title>
7.	</head>
8.	<body>
9.	    <button onclick="getNews()">获取头条数据</button>
10.	
11.	    <script>
12.	        async function getNews() {
13.	            const url = 'http://127.0.0.1:8081/api/hot-event/hot-board/?origin=toutiao_pc';
14.	            const result = await fetch(url);
15.	            const data = result.json();
16.	            console.log('data', data);
17.	        }
18.	    </script>
19.	</body>
20.	</html>

后端

1.	const express = require('express');
2.	const app = express();
3.	const { createProxyMiddleware } = require('http-proxy-middleware');
4.	
5.	app.use(express.static('./public'))
6.	
7.	app.use('/api', createProxyMiddleware({ // 拦截所有带有/api的请求
8.	    target:'https://www.toutiao.com', // 转发的目标
9.	    changeOrigin:true, // 允许跨域
10.	    pathRewrite:{
11.	      '^/api':'' // 把路径中的/api去掉
12.	    }
13.	  }))
14.	
15.	app.listen(8081, () => {
16.	    console.log('服务器成功启动');
17.	})
18.	

需要把前端代码当成静态资源部署在服务器上

目录

把服务器启动了,需要去http://127.0.0.1:8081获取页面

点击获取数据

数据成功获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949303.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

x86霸权难动摇!

快科技1月6日消息&#xff0c;根据市场研究机构ABI Research的最新报告&#xff0c;尽管2025年被视为Arm PC市场扩张的关键一年&#xff0c;但搭载Arm架构处理器的PC预计仅占PC总出货量的13%。 ABI Research的分析师指出&#xff0c;尽管高通最新的PC处理器在性能和AI功能上有…

STM32的LED点亮教程:使用HAL库与Proteus仿真

学习目标&#xff1a;掌握使用STM32 HAL库点亮LED灯&#xff0c;并通过Proteus进行仿真验证&#xff01; 建立HAL库标准工程 1.新建工程文件夹 新建工程文件夹建议路径尽量为中文。建立文件夹的目的为了更好分类去管理项目工程中需要的各类工程文件。 首先需要在某个位置建立工…

mongodb==安装prisma连接

官网下载mongodb,解压安装 Download MongoDB Community Server | MongoDB 修改bin/mongod.cfg # mongod.conf# for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/# Where and how to store data. storage:dbPat…

前端工程化之手搓webpack5 --【elpis全栈项目】

前端工程化之手搓webpack5 --【elpis全栈项目】 导读 基本流程&#xff1a;输入 – 编译 – 输出 #mermaid-svg-V8Gi7RFNikCuEhax {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-V8Gi7RFNikCuEhax .error-icon{fil…

云备份项目--服务端编写

文章目录 7. 数据管理模块7.1 如何设计7.2 完整的类 8. 热点管理8.1 如何设计8.2 完整的类 9. 业务处理模块9.1 如何设计9.2 完整的类9.3 测试9.3.1 测试展示功能 完整的代码–gitee链接 7. 数据管理模块 TODO: 读写锁&#xff1f;普通锁&#xff1f; 7.1 如何设计 需要管理…

深入了解 ES6 Map:用法与实践

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

大润发易主,被阿里割肉卖了

文丨白念云 零售行业2025年伊始便迎来一则重磅消息&#xff1a;大润发被卖了。 1月1日晚&#xff0c;阿里巴巴集团发布公告&#xff0c;宣布子公司及NewRetail与德弘资本达成交易&#xff0c;以最高约131.38亿港元出售所持高鑫零售&#xff08;大润发母公司&#xff09;全部股…

VulnHub—potato-suncs

使用命令扫描靶机ip arp-scan -l 尝试访问一下ip 发现一个大土豆没什么用 尝试扫描一下子域名 没有发现什么有用的信息 尝试扫描端口 namp -A 192.168.19.137 -p- 尝试访问一下端口,发现都访问不进去 查看源代码发现了网页的标题 potato&#xff0c;就想着爆破一下密码 hydr…

docker学习记录:commit,制作自己的镜像

1.清除所有 ktkt-SYS-4028GR-TR2:~$ sudo docker rm -f $(sudo docker ps -aq)2.再操作一次tomcat,修改好&#xff0c;再打成一外镜像 ktkt-SYS-4028GR-TR2:~$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat 9.0 3…

macos安装java8

下载 dmg方式安装 安装 双击pkg运行 输入java -version验证 配置环境变量 cd ~ ls -a输入 ls -a后查看是否已经存在.bash_profile文件&#xff0c;如果已经存在就不需要创建&#xff0c;如果不存在&#xff0c;继续执行下方命令创建文件 touch .bash_profile /usr/l…

【每日学点鸿蒙知识】自定义键盘光标、Cavas绘制、XComponent触发键盘抬起等

【每日学点鸿蒙知识】24.08.25 【每日学点鸿蒙知识】自定义键盘光标、Cavas绘制、XComponent触发键盘抬起等 1、基于自定义键盘如何设置光标位置&#xff1f; 可以参考如下代码&#xff1a; class MyKeyboardController {public onInputChanged?: (value: string) > vo…

在Mysql环境下对数据进行增删改查

一、插入数据&#xff1a; insert into 表名 [(字段名)] values (字段对应的值1,字段对应的值2,…)[,(字段对应的值1,字段对应的值2,…)]; insert into students (id,name,age,height,gender,cls_id,is_delete) values (0,小明,18,180.00,2,1,0)在学生表中插入“小明”数据的…

Web网页制作之JavaScript的应用

---------------&#x1f4e1;&#x1f50d;K学啦 更多学习资料&#x1f4d5; 免费获取--------------- 实现的功能&#xff1a;1.通过登录界面跳转至主页面&#xff0c;用户名统一为“admin”&#xff0c;密码统一为“admin123”&#xff0c;密码可显示或隐藏&#xff0c;输入…

Markdown编辑器——Typora(Picgo+Github图床)

Markdown编辑器——Typora&#xff08;PicgoGithub图床&#xff09; 文章目录 Markdown编辑器——Typora&#xff08;PicgoGithub图床&#xff09;安装Typora安装PicGoPicGo软件下载PicGo的npm版本下载 GitHub图床配置PicGo配置PicGo的软件配置PicGo的npm版本信息配置 配置Typo…

Unity 3D游戏开发从入门进阶到高级

本文精心整理了Unity3D游戏开发相关的学习资料&#xff0c;涵盖入门、进阶、性能优化、面试和书籍等多个维度&#xff0c;旨在为Unity开发者提供全方位、高含金量的学习指南.欢迎收藏。 学习社区 Unity3D开发者 这是一个专注于Unity引擎的开发者社区&#xff0c;汇聚了众多Un…

Python 21:Debug

1. Debug的作用 当程序的预期结果和实际结果不一致时&#xff0c;可以用Debug模式进行调试来定位问题的位置。 2. Debug使用 1&#xff09;设置断点 点击行号&#xff0c;出现”断点“ 2&#xff09;执行Debug 点击Debug 或者右键&#xff0c;点击debug进入debug模式 3.Debu…

(CICD)自动化构建打包、部署(Jenkins + maven+ gitlab+tomcat)

一、平滑发布与灰度发布 **什么叫平滑&#xff1a;**在发布的过程中不影响用户的使用&#xff0c;系统不会因发布而暂停对外服务&#xff0c;不会造成用户短暂性无法访问&#xff1b; **什么叫灰度&#xff1a;**发布后让部分用户使用新版本&#xff0c;其它用户使用旧版本&am…

强化学习入门谈

之前我们见识到很多机器学习大展手脚的任务场景了&#xff0c;但是机器学习依旧有很多软肋。 回忆一下&#xff0c;我们之前做的机器学习&#xff08;深度学习&#xff09;策略基本都是类似于"supervised learning"的方法&#xff0c;比如你想用CNN实现一个classifi…

colnames看似简单,却能优化数据处理流程

引言 在数据处理和分析中&#xff0c;变量名称是至关重要的&#xff0c;它们决定了数据的可读性和操作的简便性。在R语言中&#xff0c;colnames 函数以其简单的语法设计&#xff0c;提供了高效管理数据框列名的能力&#xff0c;尤其是在复杂的爬虫任务中显得尤为重要。本篇文…

【分布式】Hadoop完全分布式的搭建(零基础)

Hadoop完全分布式的搭建 环境准备&#xff1a; &#xff08;1&#xff09;VMware Workstation Pro17&#xff08;其他也可&#xff09; &#xff08;2&#xff09;Centos7 &#xff08;3&#xff09;FinalShell &#xff08;一&#xff09;模型机配置 0****&#xff09;安…