深度理解跨域问题

防盗标识:本文源文地址,未授权禁止转载https://blog.csdn.net/weixin_44331765/article/details/122759020

本文你将了解:

什么是同源、什么是跨域,什么是源,如何跨域

1. 软件描述

演示使用的web服务:
django3.2 
python3.9
vue 2

2. 问题描述

  1. 什么是跨域CORS
  2. 什么是同源策略
  3. 如何实现跨域

3. 问题解决

3.1 什么是同源策略

想了解跨域就要先了解什么是同源策略,就好比你要了解什么苹果手机”越狱“,首先要了解什么是ios操作系统
了解以下名词:

3.1.1 源:(Origin)就是协议、域名和端口。
源就是https://www.baidu.com:80
3.1.2 同源:如果地址里面的协议、端口号和域名都是相同的就是属于同源

举例子:

不同源1:端口号不同
https://www.csdn.net:80
https://www.csdn.net:8080
不同源2:协议不同
http://www.csdn.net:80
https://www.csdn.net:80
不同源3:域名不同
https://www.csdn.net:80
https://aaa.csdn.net:80
同源:域名/端口号/协议均相同,后面的index.html与index.php不同,这个只是路由不同,不影响。
http://www.csdn.net/index.html:80
https://aaa.csdn.net/index.php:80
3.1.3 什么是同源策略

同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
所以,当我们做前后端分离的时候,把前端部署在a.com上,把后端部署在b.com上,当使用a.com上的js使用ajax请求的时候出现

  1. 如图我们从CSDN上找一个接口
    在这里插入图片描述
  2. 我们在自己的一个a.html中使用ajax调用接口
    在这里插入图片描述
alert('跨域请求alert弹窗');
axios.get('https://bizapi.csdn.net/im-manage/v1.0/dispatch/do', {
	responseType: 'json'
})
.then(response => {
	console.log(response.data)
})
.catch(error => {
	console.log(error.response.data);
})

点击发送请求后,可以看到CORS error的提示了。
在这里插入图片描述

再次测试一个

alert('跨域请求alert弹窗');
axios.get('https://adv.xinnet.com/jsonp/list?callback=jQuery1124003561040491544598_1643606327196&groupCodes%5B%5D=sydh-domain&groupCodes%5B%5D=sydh-cloud&groupCodes%5B%5D=sydh-site&groupCodes%5B%5D=sydh-virtualhost&groupCodes%5B%5D=sydh-mail&groupCodes%5B%5D=sy-operation&groupCodes%5B%5D=sy-cloudfloor&groupCodes%5B%5D=float&_=1643606327197', {
responseType: 'json'
})
.then(response => {
console.log(response.data)
})
.catch(error => {
console.log(error.response.data);
})

在这里插入图片描述

我们打开仔细查看,发现请求成功了,只不过没有返回结果
在这里插入图片描述
以上就是跨域的测试,那么当我们使用其他的工具进行测试时,是没有问题的,比如Postman,可以看到一点问题没有
在这里插入图片描述
这也即使同源策略,同源策略是浏览器的一个策略,也即是说你使用浏览器就必须要遵守同源规则。
那么好,如果我们不遵守呢我硬是要给ajax加上header origin头,
在这里插入图片描述

可以看到浏览器提示大概意思是不安全的设置:
axios-0.18.0.min.js:8 Refused to set unsafe header “Origin”
在这里插入图片描述
并且自动给我们改为了我们js所在的主机域名就是我们当前部署js的域名
在这里插入图片描述
所以这就是浏览器自己个的一个策略,那么有同学说,我们不用浏览器不就行了,当然可以,上面我们也用了postman做的就可以请求了。

3.2 什么是跨域

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
知道是上面的同源,那么通过部署js的机器去请求其他机器上的资源,这就是跨域。
跨域:不同的源的脚本操作其他源下面的对象。
比如

a.com机器上部署的前端接口
后端接口部署在b.com上,使用a.com上的js的请求ajax去请求b.com上的资源就是跨域

那么问题来了,既然浏览器不让跨域,那怎么实现跨域?

  • 不受同源策略限制的:
  1. 页面中的重定向,表单提交,页面中的链接,比如a标签,script标签。
  • 细心的同学可能已经发现了,我们之前说的,就算是跨域我们也可以请求成功。上面有图,返回了200说明我们成功了,只不过服务器哪里没通过Origin,所以没有返回结果,而是返回的结果是让浏览器报错。
  • 其实浏览器是支持CORS的,主要让不让跨域在于服务器本身,CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,有些可能需要新版本的浏览器比如ie。
  • 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
  • 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
  1. 如何实现跨域
    以django为例
第一步:安装插件
pip install django-cors-headers
第二步:导入
INSTALLED_APPS = [
	……
    'corsheaders',
	……
]
导入'corsheaders.middleware.CorsMiddleware',放在SessionMiddleware后面以及CommonMiddleware前面,最好直接放在最上边。
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
]
注释的就是非必要的
# 1. 如果为True,则将不使用白名单,并且将接受所有来源。默认为False
# CORS_ORIGIN_ALLOW_ALL = True	

# 2. CORS_ALLOW_CREDENTIALS允许跨域时携带Cookie,默认为False
CORS_ALLOW_CREDENTIALS = True
# 3. CORS_ORIGIN_WHITELIST指定能够访问后端接口的ip或域名,白名单
CORS_ORIGIN_WHITELIST = (
    'http://api.xxx.com',
    'http://api.xxx.com:8001',
)
# 4. 允许的方法
# CORS_ALLOW_METHODS = ('DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW',)
# 5. 允许的请求头
# CORS_ALLOW_HEADERS = (
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
    'x-token',
)

设置完后,就可以访问了
在这里插入图片描述
以上就是跨域的问题,如果需要更加深层的了解,可以继续往下读。
防盗标识:本文源文地址,未授权禁止转载https://blog.csdn.net/weixin_44331765/article/details/122759020

3.3 跨域请求的原理

我们接着看上面的请求方法,这个是可以跨域的,可以看到我们返回头中包含有两个字段,可以通过翻译分析出大概的意思就是说,http://www.meiduo.site服务器允许这个Origin的请求,他的资格证书是正确的。正式这个,我们的www.meiduo.site是我们前端的部署。

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://www.meiduo.site

在这里插入图片描述
跨域请求分两种,一种是简单请求,一个是非简单请求

  1. 简单请求,就是浏览器直接发送CORS请求
  2. 非简单请求,就是需要先发送一个预检查(OPTIONS的请求)然后再发送请求(PUT/DELETE等)
    那么什么是简单请求和复杂请求呢?

功能概述

官方话:跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME类型 的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP认证 相关数据)。
人话:为了防止对服务器产生副作用,需要再发送请求时,发送一个预检请求(OPTIONS),特别是GET以外的请求,需要通过OPTIONS的预检请求获取浏览器是否同意该请求。

CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

然而简单请求不会触发预检的

什么是简单请求

不会触发预检的请求都是简单请求
若请求 满足所有下述条件,则该请求可视为“简单请求”:


1. 使用下列方法之一:
	GET
	HEAD
	POST
2. 除了被用户代理自动设置的首部字段(例如 Connection,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (需要注意额外的限制)
Content-Type 的值仅限于下列三者之一:
	text/plain
	multipart/form-data
	application/x-www-form-urlencoded
3, 请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。
4. 请求中没有使用 ReadableStream 对象。

在这里插入图片描述

HTTP响应头

1. Access-Control-Allow-Origin 指定源
# origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符[*],表示允许来自所有域的请求。
Access-Control-Allow-Origin:*
# 例如
Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin
如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。
2. Access-Control-Expose-Headers 指定响应头
在跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头
3. Access-Control-Max-Age 指定预检的缓存时间,多久之后再次预检,在缓存时间内不在发起预检,单位秒。
Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久,请参考本文在前面提到的preflight例子。
Access-Control-Max-Age: <delta-seconds>
delta-seconds 参数表示 preflight 预检请求的结果在多少秒内有效。
4. Access-Control-Allow-Credentials是否允许携带凭证。凭证是 Cookie ,授权标头或 TLS 客户端证书。
指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。
5. Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
6. Access-Control-Allow-Headers首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

参考文献

1. 跨域资源共享 CORS
2. django-cors-headers

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐