12 中间件与CSRF

一. django中间件

1. 什么是django中间件

# django中间件是django的门户
        1. 请求来的时候需要先经过中间件才能到达真正的django后端
        2. 响应走的时候最后也需要经过中间件才能发送出去

2. django中间件的个数

django自带七个中间件, 分别是SecurityMiddleware, SessionMiddleware,
        CommonMiddleware, CsrfViewMiddleware, AuthenticationMiddleware,
        MessageMiddleware, XFrameOptionMiddleware

3. django请求生命周期流程图

img

4. 研究django中间件代码规律

# settings.py配置文件中
MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]

class SessionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        ...
    def process_response(self, request, response):
        ...

class CsrfViewMiddleware(MiddlewareMixin):
    def process_request(self, request):
        ...
    def process_view(self, request, callback, callback_args, callback_kwargs):
        ...

    def process_response(self, request, response):
        ...

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        ...   

5. 自定义中间件

# django支持程序员自定义中间件, 并且暴露给程序员五个可以自定义的方法
    # 掌握
        process_request
        process_response
    # 了解
        process_view
        process_template_response
        process_exception

5-1. 自定义中间件流程

1. 在项目名或者应用名下创建一个任意名称的文件夹
2. 在该文件夹内创建一个任意名称的py文件
3. 在该py文件内需要书写类(注意: 这个类必须继承MiddlewareMixin)
    然后在这个类里面就可以自定义五个方法了
    提示: 这五个方法并不是全部都需要书写,用几个写几个
4. 需要将类的路径以字符串的形式注册到settings.py配置文件中的MIDDLEWARE下才能生效
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 注册自己的中间件(在应用下创建路径有提示,但是如果在项目下创建就没有提示了 你就需要自己比对着书写)
    '你自己写的中间件的路径1',
    '你自己写的中间件的路径2',
    '你自己写的中间件的路径3',
]

5-2. 二种掌握自定义: process_request, process_response

'''
process_request
    1. 请求来的时候会依此走 MIDDLEWARE 中定义的该方法. 如果没有定义就跳过.
        执行顺序:  MIDDLEWARE 中从上到下
    2. 如果返回 HttpResponse 对象, 请求将不会继续往后执行, 而是原路返回
        目的: 用来做全局相关的所有限制功能
        应用: 校验失败不允许访问

process_response
    1. 响应走的时候会依此走 MIDDLEWARE 中定义的该方法. 如果没有定义就跳过.
        执行顺序:  MIDDLEWARE 中从下到上
    2. 该方法必须返回  HttpResponse 对象
        2.1 默认返回形参response
        2.2 返回自定义的
    拓展: 


综合2者: 如果 process_request 方法指定返回 HttpResponse对象.
     1. 后续 process_request 方法不执行.
     2. 从同级别 process_response 方法开始按照 MIDDLEWARE 从下到上依此返回.
     3. 如果 process_response 方法没有指定自定义返回结果, 默认返回的就是 process_request 中返回的结果. 否则返回 process_response 方法的结果
     
拓展: flask框架也有一个中间件但是它的规律
    只要返回数据了就必须经过所有中间件里面的类似process_reponse方法     
'''
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        """
        1. 请求来的时候需要经过每一个中间件里面的process_request方法
            结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
        2. 如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
        3 .如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
            而是直接原路返回(校验失败不允许访问...)
            process_request方法就是用来做全局相关的所有限制功能
        """
        print('我是 FirstMiddleware 类中的定义的 process_request 方法')

    def process_response(self, request, response):
        """
        1.响应走的时候需要结果每一个中间件里面的process_response方法
        该方法有两个额外的参数request,response
        2. 该方法必须返回一个HttpResponse对象. 不返回抛出异常: 'NoneType' object has no attribute 'get'
            2-1. 默认返回的就是形参response
            2-2. 你也可以自己返回自己的
        3. 顺序是按照配置文件中注册了的中间件从下往上依次经过
            如果你没有定义的话 直接跳过执行下一个
        :param response: 就是django后端返回给浏览器的内容
        """
        print('我是 FirstMiddleware 类中的定义的 process_response 方法')
        return response


class SecondMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print('我是 SecondMiddleware 类中的定义的 process_request 方法')
        # return HttpResponse(
        #     '研究2: 如果 process_request 方法返回了 HttpResponse 对象,那么请求将不再继续往后执行而是直接原路返回. 响应走的时候是经过同级别的 process_response 返回')

    def process_response(self, request, response):
        print('我是 SecondMiddleware 类中的定义的 process_response 方法')
        # return HttpResponse('研究1: 如果 process_response 方法返回了 HttpResponse 对象,那么响应按照自己的返回结果作为标准返回.')
        return response

5-3. 三种了解自定义: process_view, process_template_response, process_exception

'''
process_view
    路由匹配之前 或者 执行视图函数之前 时触发
    执行顺序: MIDDLEWARE 中从上到下
process_template_response
    视图函数返回结果中含有render属性是触发
    执行顺序: MIDDLEWARE 中从下到上
process_exception
    视图函数出现异常时触发
    执行顺序: MIDDLEWARE 中从下到上
'''
from django.utils.deprecation import MiddlewareMixin


class FirstMiddleware(MiddlewareMixin):
    def process_view(self, request, view_name, *args, **kwargs):
        """
        路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该放法
        顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
        """
        print(view_name, args, kwargs)    # <function my_index at 0x000002345C28BEA0> ((), {}) {}
        print('我是第一个自定义中间件里面的 process_view')

    def process_template_response(self, request, response):
        """
        返回的HttpResponse对象有render属性的时候才会触发
        顺序是按照配置文件中注册了的中间件从下往上依次经过
            def index(request):
                print('我是视图函数index')
                obj = HttpResponse('index')

                def render():
                    print('内部的render')
                    return HttpResponse("O98K")

                obj.render = render
                return obj
        """
        print('我是第一个自定义中间件里面的 process_template_response')
        return response

    def process_exception(self, request, exception):
        """
        当视图函数中出现异常的情况下触发
        顺序是按照配置文件中注册了的中间件从下往上依次经过
        """
        print('我是第一个中间件里面的 process_exception')
        print(exception)


class SecondMiddleware(MiddlewareMixin):
    def process_view(self, request, view_name, *args, **kwargs):
        print(view_name, args, kwargs)
        print('我是第二个自定义中间件里面的 process_view')

    def process_template_response(self, request, response):
        print('我是第二个自定义中间件里面的 process_template_response')
        return response

    def process_exception(self, request, exception):
        print('我是第二个中间件里面的process_exception')
        print(exception)

二. csrf跨站请求伪造

1. csrf介绍

# csrf 全称 cross site request forgery 跨站请求伪造

# 举一个实例来描述什么是csrf:
    我搭建一个跟正规网站一模一样的界面(中国银行)
    用户不小心进入到了我们的网站,用户给某个人打钱
    打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
    但是唯一不同的时候打钱的账户不适用户想要打的账户变成了一个莫名其妙的账户

# 内部本质
    我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
    然后我们在内部隐藏一个已经写好name和value的input框

# 如何规避上述问题: csrf跨站请求伪造校验
    网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识, 斌且这个唯一标识永远不一样
    当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行

2. CSRF跨站请求伪造及解决思路流程图

3. 争对form与ajax的符合校验设置来解决CSRF问题

# form表单符合校验: 只需要指定一个 {% load csrf_token %} 即可
    <head>
        {% load static %} {# 方式三 #}
        <script src="{% static 'js/pass_csrf.js' %}"></script>
    </head>
    <body>
    <h3>我是正规网站</h3>
    <form action="" method="post">
        {% csrf_token %}
        <p>转账账户: <input type="text" name="user"></p>
        <p>目标账户: <input type="text" name="target_user"></p>
        <p>转账金额: <input type="text" name="money"></p>
        <input type="submit" value="form表单提交">
    </form>
    <br>

# ajax符合校验设置
'''
方式一: 利用标签查找获取页面上的随机字符串
    'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()
方式二:  利用模版语法提供的快捷书写
    'csrfmiddlewaretoken': '{{ csrf_token }}'
    注意: 这里是 '{{ csrf_token }}' 而不是 {% csrf_token %}
方式三: 通用直接拷贝js代码并应用到自己的html页面上即可
    {% load static %}
    <script src="{% static 'js/pass_csrf.js' %}"></script>
'''
<button id="d1">ajax方式提交</button>
<script>
    $('#d1').on('click', function () {
        $.ajax({
            url: '',
            type: 'post',
            data: {
                'user': 'jason',
                {#'user': $("[name='user']").val(),#}
                {#'target_user': $("[name='target_user']").val(),#}
                'target_user': 'egon',
                {#'money': $("[name='money']").val(),#}
                'money': '100000',
                {#'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),#}      // 方式一
                {#'csrfmiddlewaretoken': '{{ csrf_token }}',#}  // 方式二
            },
            success: function () {

            },
        })
    })
</script>

ajax符合校验设置第三种方法的js代码拷贝

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

4. {% csrf_token%} 与 {{ csrf_token }}的表现形式及两者之间的关系

# {% csrf_token%}
    <input type="hidden" name="csrfmiddlewaretoken" value="Fuih8hBFfSw7usOBVz1FF8yWYlBVeqmJ59O0HGGZp9Rko4Ovm9F5QnS2Zm0dKV5K">

# {{ csrf_token }}
    # 对应的就是{% csrf_token %}input标签中的value值
    BTYOUHdtrTRzOKnSFn1yzu4hnbs2J88h1yuxt6iNBacMImnM6XFYKJonocRkfDRi

三. csrf相关装饰器

# 需求
'''
1. 网站整体都不校验csrf,就单单几个视图函数需要校验
2. 网站整体都校验csrf,就单单几个视图函数不校验
'''

1. 争对FBV

from django.views.decorators.csrf import csrf_protect, csrf_exempt


# @csrf_exempt
# @csrf_protect
def my_transfer(request):
    if request.method == "POST":
        user = request.POST.get('user')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print(f'{user}给{target_user}转了{money}元')
    return render(request, 'my_transfer.html')

2. 争对CBV

"""
csrf_protect  需要校验
    针对csrf_protect符合装饰器的三种玩法
csrf_exempt   忽视校验
    针对csrf_exempt只能给dispatch方法加才有效
"""
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.utils.decorators import method_decorators


# @method_decorator(csrf_protect, name='post')
class MySelfCsrfToken(View):
    # @method_decorator(csrf_protect)
    def dispatch(self, request, *args, **kwargs):
        return super(MySelfCsrfToken, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('get 请求')

    # @method_decorator(csrf_protect)
    def post(self, request):
        return HttpResponse('post 请求')

# @method_decorator(csrf_exempt, name='post')
class MySelfCsrfToken(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(MySelfCsrfToken, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('get 请求')

    # @method_decorator(csrf_exempt)
    def post(self, request):
        return HttpResponse('post 请求')

四. 总结

# django中间件
    # 什么是django中间件: 它是django的大门
        请求来的时候要先过中间件才能到达真正的django后端
        响应走的时候也需要经过中间件才能发送出去

    # django中间件个数: 自带七个
        SecurityMiddleware
        SessionMiddleware
        CommonMiddleware
        CsrfViewMiddleware
        AuthenticationMiddleware
        MessageMiddleware
        XFrameOptionMiddleware

    # django提供了5种自定义中间件方法
        process_request
        process_response
        process_view
        process_template_response
        process_exception

    # 自定义中间件流程
        1. 应用 或 项目 下创建任意文件夹
        2. 在创建的任意文件夹下创建任意的.py文件
        3. 在创建的任意的.py文件中书写类. 该类要继承MiddlewareMixin
        4. 到settings.py配置文件中找到MIDDLEWARE注册

    # 二种掌握自定义: process_request, process_response
        # process_request
            1. 请求来的时候会从上到下依此执行注册了中间件类中的方法. 没有定义就跳过
            2. 如果返回了HttpResponse对象, 请求将不会往下执行, 而是原路返回
                功能: 用来做全局相关的所有限制功能
                应用: 效验用户权限功能

        # process_response
            1. 响应走的时候会从下到上依此执行注册了中间件类中的方法. 没有定义就跳过
            2. 该方法必须返回HttpResponse对象
                2-1. 返回参数response
                2-2. 返回自定义HttpResponse对象

        # 二者综合:
            如果process_request方法返回的是HttpResponse对象. 请求将不会往下继续执行,
            开始按照同级别定义的process_response方法, 按照注册了中间件类中的顺序从下到上依此返回.
            如果在返回的途中的process_response方法没有使用自定义的HttpResponse对象的形式返回, 那么最终返回的结果就按照process_request方法返回的HttpResponse对象为准
            如果在返回的途中的process_response方法自定义的自己的HttpResponse对象,
            那么将会按照当前HttpResponse返回的结果作为标准. 同时如果上一层也定义了,
            就按照上一层的结果返回.

        # 基本使用
            from django.utils.deprecation import MiddlewareMixin

            class MyMiddleware(MiddlewareMixin):
                def process_request(self, request)
                    ...

                def process_response(self, request, response):
                    ...
                    return response

    # 三种了解自定义: process_view, process_template_response, process_exception
        # process_view
            在路由匹配之前视图函数执行之前触发
            执行顺序: 在注册了的中间件类中从上到下
        # process_template_response
            在视图函数返回的HttpResponse对象含有render属性时触发
            执行顺序: 在注册了的中间件类中从下到上
        # process_exception
            在视图函数执行过程中抛出异常时触发
            执行顺序: 在注册了的中间件类中从下到上

# csrf跨站请求伪造
    # csrf 全称 cross site request forgery  跨站请求伪造
    # 什么是CSRF
        就是基于不同的服务端发送出相同的展示页面, 假的页面与真的页面展示给用户看时候一模一样,
        在用户输入敏感信息的时候, 提交的不是用户想提交的内容, 而是隐藏在标签中的内容,
        虽然都是提交到同一个正规服务器, 但是提交的数据内容不是用户指定的了. 而是隐藏的内容.
    # 本质:
        钓鱼网站不提供用户输入的name属性值, 而是提供自己指定的隐藏的写好的name以及对应的value
    # 规避:
        在给用户返回一个具有提交数据的功能页面的时候附加一个唯一标识,
        在用户提交请求过来的时候, 服务端效验这个请求中是否含有对应的唯一标识,
        如果有则正常执行 如果没有则拒绝并返回403 forbbiden
    # 争对form与ajax的符合校验设置来解决CSRF问题
        # 争对form
            在返回的页面书写 {% csrf_token %}
        # 争对ajax: 在data参数中指定
            方式一: data: {'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken].val()')}
            方式二: {'csrfmiddlewaretoken': `{{ csrf_token }}`}
            方式三: 自定义js文件copy代码. 再配置静态文件导入.

    # csrf相关装饰器
        from django.utils.decorators.csrf import csrf_protect, csrf_exempt
        # 争对FBV
            @csrf_protect
            # @csrf_exempt
            def index(request):
                if request.method == 'POST':
                    return HttpResponse('index')
                return render(request, 'index.html')
                    
            
        # 争对CBV
            csrf_protect三种方式都可以
            csrf_exempt只争对dispatch方法有效
posted @ 2020-06-09 02:15  给你加马桶唱疏通  阅读(233)  评论(0编辑  收藏  举报