16 【跨域】
admin
2024-03-08 02:09:36
0

16 【跨域】

1.什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。浏览器从一个域名的网页去请求另一个域名的资源时,出现域名、端口、协议任一不同,都属于跨域。

同源策略限制了一下行为:

Cookie、LocalStorage 和 IndexDB 无法读取

DOM 和 JS 对象无法获取

Ajax请求发送不出去

下面举个例子:

http://www.yyy.cn/index.html 调用 http://www.xxxyyy.cn/server.php 非跨域http://**www.xxxyyy.cn**/index.html 调用  http://**www.xxx.cn**/server.php  跨域,主域不同http://**abc**.xxxyyy.cn/index.html 调用  http://**def**.xxx.cn/server.php  跨域,子域名不同http://www.xxx.cn:**8080**/index.html 调用  http://www.xxx.cn/server.php  跨域,端口不同**https**://www.xxx.cn/index.html 调用  **http**://www.xxx.cn/server.php  跨域,协议不同

2.跨域资源共享CORS

这是目前主流的解决方案。

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 因此,实现CORS通信的关键是服务器

基本只需要对服务器动手脚,前端代码跟同源时一样,也就是不跨域时一样。

这种方式分为两种请求:

一种是简单请求,另一种是非简单请求。只要满足下面条件就是简单请求

请求方式为HEAD、POST 或者 GET

http头信息不超出以下字段:

Accept、Accept-Language Content-LanguageLast-Event-IDContent-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)

为什么要分为简单请求和非简单请求,因为浏览器对这两种请求方式的处理方式是不同的。

2.1 简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。 下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。 浏览器发现,这个响应头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。

注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:

  Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: FooBarContent-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头

**Access-Control-Allow-Origin *:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个,表示接受任意域名的请求

Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容// 前端设置是否带cookiexhr.withCredentials = true;xhr.open('post', 'http://www.domain2.com:8080/login', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.send('user=admin');xhr.onreadystatechange = function() {if (xhr.readyState == 4 && xhr.status == 200) {alert(xhr.responseText);}};
// jquery$.ajax({...xhrFields: {withCredentials: true    // 前端设置是否带cookie},crossDomain: true,  // 会让请求头中包含跨域的额外信息,但不会含cookie...});

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。 但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。

**需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号*,必须指定明确的、与请求网页一致的域名。**同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

2.2 非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)——浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

var url = 'http://api.alice.com/cors';var xhr = new XMLHttpRequest();xhr.open('PUT', url, true);xhr.setRequestHeader('X-Custom-Header', 'value');xhr.send();

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。

  OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,"预检"请求头信息包括两个特殊字段。

Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

预检请求的回应

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应

HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出报错信息。

服务器回应的其他CORS相关字段如下:

Access-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderAccess-Control-Allow-Credentials: trueAccess-Control-Max-Age: 1728000

Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

Access-Control-Allow-Credentials: 该字段与简单请求时的含义相同。

Access-Control-Max-Age: 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

浏览器正常请求回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

PUT /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comX-Custom-Header: valueAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...

浏览器的正常CORS请求。上面头信息的Origin字段是浏览器自动添加的。下面是服务器正常的回应。

Access-Control-Allow-Origin: http://api.bob.comContent-Type: text/html; charset=utf-8

Access-Control-Allow-Origin字段是每次回应都必定包含的

3.Nodejs 跨域处理

3.1 设置允许域名跨域

  • 设置允许所有域名跨域
var express = require('express');
var app = express();// 设置允许所有域名跨域:
app.all("*",function(req,res,next){//设置允许跨域的域名,*代表允许任意域名跨域res.header("Access-Control-Allow-Origin","*");//允许的header类型res.header("Access-Control-Allow-Headers","content-type");//跨域允许的请求方式 res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS");if (req.method.toLowerCase() == 'options')res.send(200);  //让options尝试请求快速结束elsenext();
})
  • 设置允许多个域名跨域:
app.all("*",function(req,res,next){var orginList=["http://www.bibi.com","http://www.qq.com","http://www.baidu.com"]// 防止undefined 报错if(!req.headers.origin){return}if(orginList.includes(req.headers.origin.toLowerCase())){//允许的header类型res.header("Access-Control-Allow-Headers", "content-type");//跨域允许的请求方式res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");//设置允许跨域的域名,*代表允许任意域名跨域res.header("Access-Control-Allow-Origin",req.headers.origin);if (req.method.toLowerCase() == 'options'){res.sendStatus(200);  //让options尝试请求快速结束}else{next();}} else {res.sendStatus(500)}
})

3.2 利用中间件 cors

安装 npm i cors -S

使用方式一 : 为所有源启用跨域

const  cors = require('cors')app.use(cors())app.listen(8000, function () {console.log('start')})

使用方式二 : 按条件配置

 var express = require('express')var cors = require('cors')var app = express()var whitelist = ['http://example1.com', 'http://example2.com']
//  异步配置var corsOptions;var corsOptionsDelegate = function (req, callback) {if (whitelist.indexOf(req.header('Origin')) !== -1) {corsOptions = { origin: true} //在CORS响应中反映(启用)请求的起源} else {corsOptions = { origin: false} // 拦截请求}callback(null, corsOptions) //  error  options
}app.all("*", cors(corsOptionsDelegate), function (req, res, next) {if(corsOptions.origin  === true){if (req.method.toLowerCase() == 'options'){res.sendStatus(200);  //让options尝试请求快速结束}else{next();}} else {res.sendStatus(500); //被拦截}})app.post('/cors',(req,res)=> {res.send('ok')
})app.listen(8000, function () {console.log('start')})

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...