跨域请求详解
跨域请求详解
一 同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同.
比如:我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001一段数据
浏览器上就会报错,个就是同源策略的保护,如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险
已拦截跨源请求:同源策略禁止读取位于 http://127.0.0.1:8001/SendAjax/ 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。
但是注意,项目2中的访问已经发生了,说明是浏览器对非同源请求返回的结果做了拦截
二 CORS(跨域资源共享)简介
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
三 CORS基本流程
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
浏览器发出CORS简单请求,只需要在头信息之中增加一个Origin字段。
浏览器发出CORS非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
四 CORS两种请求详解
1 只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
2 支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
3 支持跨域,复杂请求
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
4 区别?(重点)
* 简单请求和非简单请求的区别?
简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
* 关于“预检”
- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
=> 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
Access-Control-Request-Method
=> 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
Access-Control-Request-Headers
五 Django项目中支持CORS
在返回的结果中加入允许信息(简单请求)
def test(request):
import json
obj=HttpResponse(json.dumps({'name':'lqz'}))
# obj['Access-Control-Allow-Origin']='*'
obj['Access-Control-Allow-Origin']='http://127.0.0.1:8004'
return obj
放到中间件处理复杂和简单请求:
`from` `django.utils.deprecation ``import` `MiddlewareMixin``class` `CorsMiddleWare(MiddlewareMixin):`` ``def` `process_response(``self``,request,response):`` ``if` `request.method``=``=``"OPTIONS"``:`` ``#可以加*`` ``response[``"Access-Control-Allow-Headers"``]``=``"Content-Type"`` ``response[``"Access-Control-Allow-Origin"``] ``=` `"http://localhost:8080"`` ``return` `response`
六 总结
#一切都是浏览器控制的,浏览器一旦发现你访问的不是自己的源
1 跨域请求
2 是简单请求还是复杂请求
#如果是简单请求 只需要把跨域的源加上就行
#如果是复杂请求 浏览器会发两次请求
第一次是options请求,所有造成是复杂请求的内容,你都需要做返回
第二次是正常的请求,
所以如果我们自己要在headers里面携带token发post请求回去,就造成了复杂请求,所以我们要处理这个问题。
七 解决安装的跨域组件问题
问题:
如果我们想在前端页面发起header里面携带token:xxx的请求,corsheaders组件不会帮你做通过的返回,需要自定定制。
思路
1 前端在访问后端接口的时候首先是访问了后端的域构成了跨域,并且会在headers里面加入了自定义的token,所以又是一个复杂请求,
2 所以我们根据我们使用跨域模块需要重新定制一下参数要做到Response["Access-Control-Allow-Headers"] = 'xxx,xx,token,xx'
。
3 这样前端options请求得到了肯定,继续发原本的post就不会产生问题
下面这段代码就可以控制跨域模块返回 Response["Access-Control-Allow-Headers"] = ''xxx,xx,token,xx'' 从而前端第一个opentions的请求就会通过
dev.py
# 跨域中间件
default_headers = (
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
“token”)
八 自定义封装跨域中间件(推荐)
dev.py
MIDDLEWARE = [
# 自定义跨域中间件
'utils.my_corsheaders.MyCorsMiddle',
。。。
]
#### 自定义跨域需要的参数
# 定制的复杂跨域返回的请求头
CORS_HEADERS = ("accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken",
"x-requested-with","token")
# 定制复杂跨域返回的方法
CORS_METHODS = ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT")
# 定制的跨域ip+端口
CORS_ORIGIN_WHITELISTS = [
# 线上这俩是无效的。
'http://127.0.0.1:8080',
'http://127.0.0.1:80',
# 'http://localhost:8080',
# 'http://localhost:80',
# 'http://101.132.159.228' # ps:针对80端口,浏览器对比的时候不会对比端口号非80端口会对比端口号
]
prod.py
MIDDLEWARE = [
# 自定义跨域中间件
'utils.my_corsheaders.MyCorsMiddle',
。。。
]
# 自定义跨域需要的参数
# 定制的复杂跨域返回的请求头
CORS_HEADERS = ("accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken",
"x-requested-with","token")
# 定制复杂跨域返回的方法
CORS_METHODS = ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT")
# 定制的跨域ip+端口
CORS_ORIGIN_WHITELISTS = [
# 线上这俩是无效的。
#'http://127.0.0.1:8080',
#'http://127.0.0.1:80',
'http://101.132.159.228' # ps:针对80端口,浏览器对比的时候不会对比端口号非80端口会对比端口号
]
utils.py/my_corsheaders.py
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from .logging import logger
from django.http import HttpResponse
class MyCorsMiddle(MiddlewareMixin):
def process_response(self,request,response):
# 简单请求:
# 允许所有人向我发请求
origin = request.META.get("HTTP_ORIGIN")
# logger.critical(f'【跨域问题】origin:{origin},CORS_ORIGIN_WHITELIST:{settings.CORS_ORIGIN_WHITELISTS}')
if not origin or not (origin in settings.CORS_ORIGIN_WHITELISTS):
return HttpResponse('你是小爬爬吗?')
# 解析出主机名(待定)
# url = urlparse(origin)
# 是否允许后续请求携带认证信息(cookies)
if settings.CORS_ORIGIN_WHITELISTS:
response["Access-Control-Allow-Credentials"] = "true"
# 处理简单请求的需要返回域参数
response['Access-Control-Allow-Origin'] = origin
# 复杂请求预检发起时间间隔
response["Access-Control-Max-Age"] = 86400 # 1天,1天内地改复杂请求都不会进行options验证
# 处理复杂请求:
# 1处理复杂请求 如headers里面有 content-type=json 如headers里面有 token
# 2处理复杂请求 如请求put patch delete请求
if request.method == 'OPTIONS':
# 处理复杂请求头信息
response["Access-Control-Allow-Headers"] = ", ".join(settings.CORS_HEADERS)
# 处理复杂请求put patch方法
response["Access-Control-Allow-Methods"] = ", ".join(settings.CORS_METHODS)
return response
Access-Control-Max-Age是什么?
答:
浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求(比如异步请求GET, POST, PUT, DELETE, OPTIONS等等),所以浏览器会向所请求的服务器发起两次请求,第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求,第一次的预检请求获知服务器是否允许该跨域请求:如果允许,才发起第二次真实的请求;如果不允许,则拦截第二次请求。
Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求。
例如:
resp.addHeader("Access-Control-Max-Age", "0"),表示每次异步请求都发起预检请求,也就是说,发送两次请求。
resp.addHeader("Access-Control-Max-Age", "1800"),表示隔30分钟才发起预检请求。也就是说,发送两次请求