django---django-cors-headers跨域源码分析

Posted 进击的小猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django---django-cors-headers跨域源码分析相关的知识,希望对你有一定的参考价值。

库配置

这篇笔记,是关于跨越的 django—CORS跨域

今天我们来学习下一个开源库django-cors-headers

github上有详细的配置文档说明

1、Install from pip:

pip install django-cors-headers

2、and then add it to your installed apps:

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

3、You will also need to add a middleware class to listen in on responses:

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

CORSMIDID餐具应尽可能高的放置,尤其是在任何能产生响应的中间件之前,比如Django的通用中间件或白化的白皮书。如果不是以前,它将无法向这些响应添加CORS头。

此外,如果使用CLSYRePaseHTTPSY引用器,它应该放在Django的CSRFIEWVIEW中间件之前

4、在Django设置中配置中间件的行为。必须将允许跨站点请求的主机添加到CLSIOrthPosixWalelistor,或者将CLSSOrthPosialApple全部设置为true,以允许所有主机。

CORS_ORIGIN_ALLOW_ALL = True

5、另外还有一些白名单、正则url配置,我这里就省略了,我就直接CORS_ORIGIN_ALLOW_ALL = True

示例展示

我接下来利用一个示例,来展示下功能效果
项目配置:


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig',
    'rest_framework',
    'corsheaders',
]

from corsheaders.middleware import CorsMiddleware

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',

    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True

vue前端代码
contentType: “application/json”发送j’son数据

     doLogin() 
        var _this = this
        $.ajax(
          url: "http://127.0.0.1:8000/api/login/",
          type: 'post',
          data: JSON.stringify(
            "username": this.username,
            "password": this.password,
          ),
          dataType: 'json',
          contentType: "application/json",

          // headers: 
          //   "Content-Type": 'application/json',
          // ,
          success: function (newAllDatas) 
            console.info(newAllDatas, "~~~~~~newAllDatas~~~~~~~~")

            if (newAllDatas.data.token) 
              _this.$store.commit('saveToken', username: newAllDatas.data.username, token: newAllDatas.data.token)
              _this.$router.push(name: 'index')
             else 
              _this.error = '用户名或密码错误'
            
          
        )
      

后端api

class LoginView(APIView):
    def generate_key(self,account):
        """根据用户名和时间生成唯一标识"""
        username = account.username
        now = str(datetime.datetime.now()).encode('utf-8')
        md5 = hashlib.md5(username.encode('utf-8'))
        md5.update(now)
        return md5.hexdigest()

    # def options(self, request, *args, **kwargs):
    #     print('进行预检')
    #     obj = HttpResponse()
    #     # obj['Access-Control-Allow-Headers'] = 'Content-Type'
    #     # obj['Access-Control-Allow-Origin'] = '*'
    #     return obj



    def post(self,request,*args,**kwargs):
        try:
            response = BaseResponse()

            with transaction.atomic():
                print(request.data,"request,data")
                print(request._request.POST,"request,POST")

                account = Account.objects.create(username=request.data.get("username"),password=request.data.get("password"))
                if account:
                    token = self.generate_key(account)
                    u_token = UserAuthToken.objects.create(user=account,token=token,created=datetime.datetime.now())
                    if u_token:

                        usdata = UserSerializer(instance=account)

                        response.data = usdata.data
                        response.error = "1000"
                        response.msg = "登录成功"

                        ret = Response(response.dict)
                        # ret['Access-Control-Allow-Origin'] = '*'
                        print("[response.dict]", response.dict)
                        return ret

        except IndexError as e:
            response.error = "2000"
            response.msg = "登录失败"
            ret = Response(response.dict)
            # ret['Access-Control-Allow-Origin'] = '*'
            print("[Exception type %s][response.dict%s]"%(str(e),response.dict))
            return ret

源码分析

我先贴出来该开源库的目录结构

在该库的__init__.py模块下from .checks import check_settings导入该模块
checks.py中经过@checks.register进行装饰过滤,主要是判断我们的配置有没有问题,例如
我们之前在配置文件中进行的配置CORS_ORIGIN_ALLOW_ALL = True,是否是bool类型的

 if not isinstance(conf.CORS_ORIGIN_ALLOW_ALL, bool):
        errors.append(
            checks.Error(
                "CORS_ORIGIN_ALLOW_ALL should be a bool.",
                id="corsheaders.E005"
            )
        )

checks.py模块头部,还引入from .conf import conf,我们看看这个配置是做什么处理工作的?
我这里就列举出来,该类中的某些方法,我们依然拿CORS_ORIGIN_ALLOW_ALL = True举例,该类的主要左用就是通过反射作用,获取配置的值

class Settings(object):
      @property
    def CORS_ORIGIN_ALLOW_ALL(self):
        return getattr(settings, 'CORS_ORIGIN_ALLOW_ALL', False)

conf.py模块中,头部引入了from .defaults import default_headers, default_methods
是对请求头、及其请求方式做了列举

default_headers = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

default_methods = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
)

我们接下来就看下middleware.py如何实现跨域的

还记得我们在配置中配置的中间件么

'corsheaders.middleware.CorsMiddleware'

代码不多,我就直接贴出来了

class CorsMiddleware(MiddlewareMixin):

    def _https_referer_replace(self, request):
        """
        When https is enabled, django CSRF checking includes referer checking
        which breaks when using CORS. This function updates the HTTP_REFERER
        header to make sure it matches HTTP_HOST, provided that our cors logic
        succeeds
        """
        origin = request.META.get('HTTP_ORIGIN')

        if request.is_secure() and origin and 'ORIGINAL_HTTP_REFERER' not in request.META:

            url = urlparse(origin)
            if not conf.CORS_ORIGIN_ALLOW_ALL and not self.origin_found_in_white_lists(origin, url):
                return

            try:
                http_referer = request.META['HTTP_REFERER']
                http_host = "https://%s/" % request.META['HTTP_HOST']
                request.META = request.META.copy()
                request.META['ORIGINAL_HTTP_REFERER'] = http_referer
                request.META['HTTP_REFERER'] = http_host
            except KeyError:
                pass

    def process_request(self, request):
        """
        If CORS preflight header, then create an
        empty body response (200 OK) and return it

        Django won't bother calling any other request
        view/exception middleware along with the requested view;
        it will call any response middlewares
        """
        request._cors_enabled = self.is_enabled(request)
        if request._cors_enabled:
            if conf.CORS_REPLACE_HTTPS_REFERER:
                self._https_referer_replace(request)

            if (
                request.method == 'OPTIONS' and
                'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
            ):
                return http.HttpResponse()

    def process_view(self, request, callback, callback_args, callback_kwargs):
        """
        Do the referer replacement here as well
        """
        if request._cors_enabled and conf.CORS_REPLACE_HTTPS_REFERER:
            self._https_referer_replace(request)
        return None

    def process_response(self, request, response):
        """
        Add the respective CORS headers
        """
        origin = request.META.get('HTTP_ORIGIN')
        if not origin:
            return response

        enabled = getattr(request, '_cors_enabled', None)
        if enabled is None:
            enabled = self.is_enabled(request)

        if not enabled:
            return response

        # todo: check hostname from db instead
        url = urlparse(origin)

        if conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'

        if (
            not conf.CORS_ORIGIN_ALLOW_ALL and
            not self.origin_found_in_white_lists(origin, url) and
            not self.origin_found_in_model(url) and
            not self.check_signal(request)
        ):
            return response

        if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
        else:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
            patch_vary_headers(response, ['Origin'])

        if len(conf.CORS_EXPOSE_HEADERS):
            response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(conf.CORS_EXPOSE_HEADERS)

        if request.method == 'OPTIONS':
            response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
            response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
            if conf.CORS_PREFLIGHT_MAX_AGE:
                response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE

        return response

    def origin_found_in_white_lists(self, origin, url):
        return (
            url.netloc in conf.CORS_ORIGIN_WHITELIST or
            (origin == 'null' and origin in conf.CORS_ORIGIN_WHITELIST) or
            self.regex_domain_match(origin)
        )

    def regex_domain_match(self, origin):
        for domain_pattern in conf.CORS_ORIGIN_REGEX_WHITELIST:
            if re.match(domain_pattern, origin):
                return origin

    def origin_found_in_model(self, url):
        if conf.CORS_MODEL is None:
            return False
        model = apps.get_model(*conf.CORS_MODEL.split('.'))
        return model.objects.filter(cors=url.netloc).exists()

    def is_enabled(self, request):
        return (
            bool(re.match(conf.CORS_URLS_REGEX, request.path)) or
            self.check_signal(request)
        )

    def check_signal(self, request):
        signal_responses = check_request_enabled.send(
            sender=None,
            request=request,
        )
        return any(
            return_value for
            function, return_value in signal_responses
        )

我们就简单分析下即可
先经过process_request,先看下这个类方法

   def process_request(self, request):
        """
        If CORS preflight header, then create an
        empty body response (200 OK) and return it

        Django won't bother calling any other request
        view/exception middleware along with the requested view;
        it will call any response middlewares
        """
        request._cors_enabled = self.is_enabled(request)
        if request._cors_enabled:
            if conf.CORS_REPLACE_HTTPS_REFERER:
                self._https_referer_replace(request)

            if (
                request.method == 'OPTIONS' and
                'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
            ):
                return http.HttpResponse()

process_request代码意思就是通过is_enabled方法,通过request.path进行正则判断是否是可用的url,
然后通过 conf.CORS_REPLACE_HTTPS_REFERER(默认falsel),来进行解决CSRFYtrutrudDoad源文图,
ps如下:
在Jjango 1.9中引入了CSRFYtrutrudDoad源,因此早期版本的用户需要一个替代的解决方案。如果COSYRePaseJTHPSPSJORKER是真的,CORSMIDIDWORKES会将引用头更改为当CORS检查通过时将通过Django的CSRF检查的东西。默认为false。

然后判断如下代码,当请求添加自定义头部,或者contentType为json(复杂请求),会执行OPTIONS,我们需要预检信息

 if (
                request.method == 'OPTIONS' and
                'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
            ):
                return http.HttpResponse()

然后直接进入该中间件的process_response,在该方法里面,也就是进行了具体的跨域处理
跨域传输cookie如下

 if conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'

允许不同源的链接访问

  if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
        else:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
            patch_vary_headers(response, ['Origin'])

OPTIONS进行预检

 if request.method == 'OPTIONS':
            response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
            response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
            if conf.CORS_PREFLIGHT_MAX_AGE:
                response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE

以上就是基本的源码分析

以上是关于django---django-cors-headers跨域源码分析的主要内容,如果未能解决你的问题,请参考以下文章