一、问题
1.前端用ajax get访问接口,报错
:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
![](https://img-blog.csdnimg.cn/direct/31022085cb394cd4afcfccfb1bb299df.png)
![](https://img-blog.csdnimg.cn/direct/afa7665a6b8c482a99ec93f0c3eb36d1.png)
二、原因
javascript处于安全考虑,不允许跨域访问,CORS导致的。
![](https://img-blog.csdnimg.cn/direct/8d1e160c743d4bd28f55b6e439634738.png)
三、CORS
3.1、什么是CORS?
CORS(跨域资源共享,Cross-Origin Resource Sharing)是一种跨域访问的机制,可以让Ajax实现跨域访问。是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其他源,使得浏览器允许这些域加载自己的资源。
-
服务器:标识哪些源可以访问自己的资源
-
HTTP头:Access-Control-Allow-Origin: * # 星号允许所有的源访问
Origin: https://bilibili.com # 表明该请求来自哪个源
-
浏览器:必须实现CORS相关的规范;是否载入都有浏览器控制
在服务器的response header中,加入"Access-Control-Allow-Origin:*" 即可以支持CORS,apache/nginx等怎么配置,见参考文档。
举个例子:
* API部署在DomainA上;
* Ajax文件部署在DomainB上,Ajax文件会向API发送请求,返回数据;
* 用户通过DomainC访问DomainB的Ajax文件,请求数据
以上过程就发生了跨域访问。如果直接使用Ajax来请求就会失败,就像Chrome提示的:No 'Access-Control-Allow-Origin' header is present on the requested resource.
3.2、CORS的工作机制
先打开百度首页,然后在浏览器console下直接使用fetch打开淘宝的首页:
fetch('https://www.taobao.com')
![](https://img-blog.csdnimg.cn/direct/df077ca3da094a2586ee86c7564eff5a.png)
浏览器清楚这是一个跨域请求,用CORS机制的,所以浏览器会检查返回内容的头部,是否包含了
access control allow origin这个标头
切换到Network查看接口,可知这个例子中,访问淘宝的响应头中没有
access control allow origin,也就是不允许任何跨域的请求,因此,浏览器拒绝载入该内容。
Network中有一个请求:
![](https://img-blog.csdnimg.cn/direct/bdbe356286ce4ee4b5a7382c407c8f2a.png)
![](https://img-blog.csdnimg.cn/direct/9fc136b5717548ed9dca877712e44f23.png)
同样在百度首页载入淘宝的页面,但这次请求加入定制的标头,并且内容改成了XML:
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://www.taobao.com");
xhr.setRequestHeader("X-PINGOTHER", "pingpong");
xhr.setRequestHeader("Content-Type", "apptication/xml");
xhr.send("<person><name>Arun</name></person>");
![](https://img-blog.csdnimg.cn/direct/52771cb4652a4686a0f312acebeb1b8e.png)
Network中有两个请求:
![](https://img-blog.csdnimg.cn/direct/c8f6b35dfc694a54b647a2e977352e31.png)
![](https://img-blog.csdnimg.cn/direct/b8a4217f7c324c3099390c3369e79b5a.png)
![](https://img-blog.csdnimg.cn/direct/f49d4cb7cd6a4044b9fc3c28499b9683.png)
得到了同样的CORS错误,在预检阶段失败了。
总结:
对于
简单请求,浏览器会直接请求服务端,然后检查返回内容头部的
access control allow origin,如果允许当前域,则正常的载入并展示返回内容,否则拒绝载入。
对于
非简单请求,浏览器会发出一个option方法,来询问服务器端, 是否允许来自当前域的请求访问该资源,服务器会返回允许访问的域HTTP方法以及允许的标头header,浏览器会进行判断后,决定是否发出真正的请求,这个询问的过程称为预检
3.3、简单请求与非简单请求的概念
![](https://img-blog.csdnimg.cn/direct/02e25b512e79443491e11605ebdc8adf.png)
设计后端请求时,将占用服务器过多资源的请求,通过预检可以降低服务器的负载,应该设计成非简单请求的服务。而轻量服务,则使用简单请求,提升响应速度。
# 简单请求请求
fetch('https://www.taobao.com')
# 预检请求,因为改动了请求头,不满足CORS简单请求的第二条要求
fetch('https://www.taobao.com', {
headers: {
a: 1,
},
});
# POST请求,简单请求
fetch('https://www.taobao.com', {
method: 'POST',
body: JSON.stringify({a: 1, b: 2}),
});
# 预检请求,因为改动了请求头content-type,不满足CORS简单请求的第三条要求
fetch('https://www.taobao.com', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({a: 1, b: 2}),
});
3.4、为什么需要CORS?
-
保护服务器, 控制哪些域有权访问资源,有助于减轻服务器的负载与风险;
-
保护隐私,CORS限制了来自不同域的敏感数据的访问。例如,一个网站不能在没有用户授权的情况下,从其他域获取用户的个人信息;
-
安全考虑, 防止盗取用户数据或进行 CSRF攻击;
-
因为有实际的跨域请求需求,所以也不能完全禁止跨域请求;
四、如何解决Ajax跨域访问问题?
解决跨域问题有以下几种方法:
-
同源策略(增加反向代理)
-
使用jsonp
-
使用django-cors-headers
-
修改response header中,加入"Access-Control-Allow-Origin:*"
-
nginx层配置“Access-Control-Allow-Origin *”
使用jsonp方法,需让服务端返回jsonp格式的response,如:Returning JSON/JSONP from a Django view with a little decorator help (Example)
使用CORS:这个用起来比较方便,现在大多数浏览器都支持了,且我web服务器完全开放给别人调用,所以比较推荐CORS。
4.1、使用JSONP
使用Ajax获取json数据时,存在跨域的限制。不过,在Web页面上调用js的script脚本文件时却不受跨域的影响,JSONP就是利用这个来实现跨域的传输。因此,我们需要将Ajax调用中的dataType从JSON改为JSONP(相应的API也需要支持JSONP)格式。
JSONP只能用于GET请求。
4.2、使用django-cors-headers
pip install django-cors-headers
修改setting.py:
INSTALLED_APPS = [
'corsheaders' # 尽量放在前面
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware' # 需要添加在CommonMiddleware中间件前面(必须)
]
# 添加白名单:设置可以任意访问,默认为False
CORS_ORIGIN_ALLOW_ALL =True
# 也可以通过白名单列表配置允许跨域访问的地址
CORS_ORIGIN_WHITELIST = (
'localhost:3306',
'http://localhost:8080'
)
# CORS_ALLOW_CREDENTIALS允许跨域时携带Cookie,默认为False
CORS_ALLOW_CREDENTIALS = True
4.3、在服务器的response header中,加入“Access-Control-Allow-Origin: *”
修改视图函数view.py,允许其他域通过Ajax请求数据:
def myview(_request):
response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
4.4、nginx配置
location / {
# 跨域请求
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers '*';
# 发起预检请求,从而获知服务器是否允许该跨域请求
if ($request_method = 'OPTIONS') {
return 204;
}
}
总结一下预检请求通过的条件:
-
OPTIONS 方法获取的Response header 字段中,有Access-Control-* 的字段。表示服务器告知浏览器服务端支持跨域访问的方法和header 字段等。
-
复杂请求中的request method、request header、Origin等满足第1点中服务端告知给客户端的服务器端接受的条件。
-
当前两点都满足的时候,浏览器会才会发起跨域请求,否则浏览器直接拦截跨域请求,该请求不会走到服务器端。
其他补充
-
Access-Control-Max-Age 可以指定将预检请求的结果缓存多长时间,在这个时间范围内就不用再重复发起预检请求了
-
Access-Control-Allow-Credentials true 是否允许在跨域请求中传递cookie ,默认false
-
Access-Control-Allow-Origin 要根据实际允许跨域的origin填写,* 表示允许所有origin 跨域访问
五、参考
Python的Django应用程序解决AJAX跨域访问问题的方法_python_脚本之家 (jb51.net)
(5条消息) Django解决AJAX跨域访问问题的方法_kan2016的博客-CSDN博客_ajax访问django找不到接口
掌握CORS跨域请求,看这个视频就够了【渡一教育】_哔哩哔哩_bilibili
彻底搞懂CORS(跨域资源共享)相关内容 什么是CORS 为什么需要CORS 如何解决CORS问题 简单请求 预检_哔哩哔哩_bilibili