跨域请求详解

跨域请求详解

一 同源策略

同源策略(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分钟才发起预检请求。也就是说,发送两次请求
posted @ 2019-12-20 13:27  张明岩  阅读(563)  评论(0编辑  收藏  举报