Django 中间件与CSRF跨站请求
引入
1.什么是中间件
中间件是一个很大的概念, 它介于两个事务之间
- 服务器中间件:服务器的调优,例:Java的Tomcat
 - 消息队列中间件:消息队列,在应用程序与应用程序之间,
 - 数据库中间件:应用程序与数据库之间
 
一.Django中间件 (middleware)
1.什么Django中间件
- 请求来的时候需要先经过中间件才能到真正的Django后端
 - 响应走的时候也需要经过中间件才能发送出去
 - 通俗的讲 : 中间件相当于是Django的门户, 你进来时要经过它, 出去的时候也要经过它
 - 介于request与response处理之间的一道处理过程, 并且在全局上改变django的输入与输出
 
2.Django自带的中间件
- Django自带的中间件有七个, 在 setting.py 配置文件中, 每个中间件其实就是一个类
 
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # 处理session
    'django.middleware.common.CommonMiddleware',  # 处理路由匹配是否带斜杠
    'django.middleware.csrf.CsrfViewMiddleware',  # 跨站请求伪造处理
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
3.中间件的作用
- 既然请求来和响应走都经过中间件, 那么我们就可以对所有的请求做一些预处理, 对所有返回的响应也可以做处理
 
- 每一个中间件都有具体的功能
 
4.中间件的主要方法介绍
- 可以通过查看Django自带中间件的源码查看他们一般都有什么方法 : (五种)
 
| 中间件方法 | 描述 | 
|---|---|
| process_request(self,request)(常用) | 所有请求来时都会运行的方法 | 
| process_view(self, request, callback, callback_args, callback_kwargs) | 所有路由匹配成功之后,跳转执行视图函数之前都会运行该方法 | 
| process_exception(self, request, exception) | 所有视图中有异常发生时运行的方法 | 
| process_response(self, request, response)(常用) | 所有返回页面响应时运行的方法 | 
| process_template_response(self,request,response) | 返回的HttpResponse对象具有render属性时才会触发该方法 | 
ps : 并不是每个中间件都有这五个方法, 只是需要什么方法就有什么方法
- 
process_request( ), process_response( ) 重点
 
process_request
    1. 请求来的时候会依此走 MIDDLEWARE 中定义的该方法, 如果中间件没有定义就跳过
        - 执行顺序:  MIDDLEWARE 中从上到下
    2. 如果返回 HttpResponse 对象, 请求将不会继续往后执行, 而是原路返回
        - 目的: 用来做全局相关的所有限制功能
        - 应用: 校验失败不允许访问
process_response
    1. 响应走的时候会依此走 MIDDLEWARE 中定义的该方法, 如果中间件没有定义就跳过
        - 执行顺序:  MIDDLEWARE 中从下到上
    2. 该方法必须返回  HttpResponse 对象
        - 默认返回形参response
        - 返回自定义的
- 
process_view( ), process_template_response( ), process_exception( ) 了解
 
process_view
    - 路由匹配之前或者执行视图函数之前时触发
    - 执行顺序: MIDDLEWARE 中从上到下
process_template_response
    - 视图函数返回结果中含有render属性是触发
    - 执行顺序: MIDDLEWARE 中从下到上
process_exception
    - 视图函数出现异常时触发
    - 执行顺序: MIDDLEWARE 中从下到上
5.自定义中间件流程
- 首先要知道中间件的执行顺序
 
1.请求来时,自上而下顺序执行
2.响应走时,自下而上顺序执行
- 自定义中间件步骤
 
1.先在项目名目录或者应用名目录下创建一个任意名字的文件夹(最好见名知意)
2.在该文件夹下创建任意名字的py文件(最好见名知意)
3.在该py文件内书写类, 在类中书写中间件的五种方法
    - 类必须继承MiddlewareMixin, 五种方法并不是全都要写,需要几种写几种
4.将类的路径以字符串的形式注册到settings.py配置文件中的MIDDLEWARE下(与app注册原理类似)
6.自定义中间件操作
- 创建文件夹和py文件,再书写类
 
from django.utils.deprecation import MiddlewareMixin  # 导入Middleware类
from django.shortcuts import HttpResponse, redirect, render
class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print("请求来了11111")
    def process_response(self, request, response):
        print("响应走了11111")
        return response  # 必须返回response
class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print("请求来了22222")
    def process_response(self,request,response):
        print("响应走了22222")
        return response  # 必须返回response
- 到 setting.py 中的 MIDDLEWARE 配置中去注册
 
# 一字符串的形式添加自定义中间件的路径
'app01.middleware.mymiddle.FirstMiddleware',
'app01.middleware.mymiddle.SecondMiddleware',
注意上面两个中间件的顺序是 First 在上, Second 在下
- 启动项目发起任意请求查看输出
 
- 如果我们在 First 中的 process_request 方法中添加 返回值 HttpResponse 对象
 
class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print("请求来了11111")
        return HttpResponse('1')  # 添加HttpResponse返回值
    ....
- 再查看实验效果
 
First中process_request有返回HttpResponse对象, 那么接下来的中间件都不执行, 直接原路返回
7.自定义中间件示例
- 统计所有登入用户的用户名、ip、时间、平台、客户端类型
 - 中间件 mymiddle.py 文件
 
from django.utils.deprecation import MiddlewareMixin
from app01 import models
class MyMiddleware(MiddlewareMixin):
    def process_request(self, request):
        name = request.POST.get('name')
        if name is not None:
            IP = request.META.get('REMOTE_ADDR')
            OS = request.META.get('OS').split('_')[0]
            client = request.META.get('HTTP_USER_AGENT').split(' ')[-1].split('/')[0]
            if client == 'Safari':
                client = 'Chrome'
            models.Visits.objects.create(name=name, IP=IP, OS=OS, client=client)
    def process_response(self, request, response):
        return response
    def process_view(self, request, view_name, *args, **kwargs):
        print("")
- 在 setting.py 中注册
 
'app01.middleware.mymiddle.MyMiddleware',
- 路由层 urls.py 文件
 
# 访问统计
path('', views.visit_login),
re_path('^visit_data/', views.visit_data),
- 视图层 views.py 文件
 
# 访问统计登入页面
def visit_login(request):
    if request.method == "GET":
        return render(request, 'visit_login.html')
    elif request.method == "POST":
        return render(request, 'visit_login.html')
# 访问统计页面(分页模板)
from app01 import models
from django.core.paginator import Paginator
def visit_data(request):
    #1.分页后的paginator对象
    current_page = int(request.GET.get('page_num', 1))
    #2.页码列表
    if paginator.num_pages > 9:
        if current_page - 4 < 1:
            page_range = range(1, 10)
        elif current_page + 4 > paginator.num_pages:
            page_range = range(paginator.num_pages - 8, paginator.num_pages + 1)
        else:
            page_range = range(current_page - 4, current_page + 4)
    else:
        page_range = paginator.page_range
    #3.page对象
    try:
        page = paginator.page(current_page)
    except Exception as E:
        page = paginator.page(current_page)
    return render(request, 'visit_data.html', {'page_range': page_range, 'page': page, 'current_page': current_page})
- 演示效果(没有做登入校验)
 
二.CSRF跨站请求伪造
1.什么是 CSRF (简介)
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性
- 简单理解就是黑客盗用你的身份, 以你的身份来进行一系列操作(发送恶意请求,发消息,购物,转账等等), 这些操作对于服务器来说是完全合法的, 因为你的身份就是一个正常的用户
 
2.CSRF 攻击原理示例
- 以银行转账为例
 
- 正常用户登入受信任的网站A, 并在本地生成Cookie
 - 在没有登出网站A的情况下(也就是没有清除Cookie), 访问了黑客网站B
 - B网站中有一个虚假按钮, 背后对应的就是向别人转账, 但用户并不知道, 点击之后会带着用户的Cookie进行转账
 
3.CSRF 攻击防范之 Referer
- 验证 HTTP Referer 字段
 
在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址, 服务器只要校验来源地址是不是自己的地址, 如果来源地址是其他网站的域名, 则很有可能是黑客的CSRF攻击, 直接拒绝该请求
- 优缺点
 
优点 : 简单, 易于实现
缺点 : Referer的值是浏览器提供的, 我们不能保证浏览器自身的漏洞, 这样依赖于第三方的校验显然是不安全的, 并且某些浏览器目前已经有一些方法可以篡改 Referer 值, 比如 IE6 或 FF2
4.CSRF攻击防范之 token
- 验证 token 随机字符串
 
在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求
三.针对 from表单 及 ajax 的 csrf_token 校验提交
1.CSRF校验的中间件
- 在 setting.py 中开启, 默认是打开的
 
'django.middleware.csrf.CsrfViewMiddleware',
2.在前端进行请求
- 如果没有携带 csrf 随机码, 
POST将会请求失败(403) 
3.在页面form表单中加上 {% csrf_token %} (这是方法之一)
<form action="/csrf_test/" method="post">
    {% csrf_token %}
    ....
    ....
</form>
添加之后会在页面上生成一个隐藏的 input 标签, 里面的 name 对应
csrfmiddlewaretoken, value 对应一串随机码, 请求发送的时候就回去校验这个随机码这个时候进行 POST 请求就可以成功了
<input type="hidden" name="csrfmiddlewaretoken" value="Fuih8hBFfSw7usOBVz1FF8yWYlBVeqmJ59O0HGGZp9Rko4Ovm9F5QnS2Zm0dKV5K">
4.基于from表单提交 CSRF 请求
- 实现的方法就是上面介绍的, 在form表单中添加 
{% csrf_token %} 
5.基于ajax提交 CSRF 请求
- 方式一 (1): 在 data 中放入 csrf 随机字符串
 
# 首先需要在form表单中写上 {% csrf_token %},(不然后面取不到)
# 然后直接通过jQuery语法获取到页面中隐藏的input框内的name和value,将其放入data中
data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()}
- 方式一 (2)
 
# 直接拿到 csrf_token 值进行渲染,需要引号(不需要在form表单中写csrf_token)
data:{'csrfmiddlewaretoken':'{{ csrf_token }}'}
- 方式二(1、2) : 将其放在请求头中
 
# 需要在form表单中写上 {% csrf_token %}
headers:{'X-CSRFToken':$('[name="csrfmiddlewaretoken"]').val()}
# 或者直接使用 '{{ csrf_token }}' 取值进行渲染
headers:{'X-CSRFToken':'{{ csrf_token }}'}
6.CSRF 校验示例
- 路由层 urls.py 文件
 
# csrf跨站请求伪造测试
re_path('^csrf_test/',views.csrf_test),
- 视图层 views.py 文件
 
from django.shortcuts import render, HttpResponse, redirect
def csrf_test(request):
    if request.method == 'GET':
        return render(request,'csrf_test.html')
    elif request.method == "POST":
        from_name = request.POST.get('from_name')
        to_name = request.POST.get('to_name')
        money = request.POST.get('money')
        return HttpResponse(f'{from_name}向{to_name}转账{money}元成功')
- 模板层 csrf_test.html 文件
 
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="panel panel-default">
              <div class="panel-heading text-center">
                <h3 class="panel-title">转账</h3>
              </div>
              <div class="panel-body">
                  <form action="/csrf_test/" method="post">
                      {% csrf_token %}  {# 方式一:直接导入csrf_token #}
                      <p>你的账户名: <input type="text" name="from_name" class="form-control b1"></p>
                      <p>对方账户名: <input type="text" name="to_name" class="form-control b2"></p>
                      <p>转账金额: <input type="text" name="money" class="form-control b3"></p>
                      <input type="submit" value="form转账" class="btn btn-block btn-warning">
                  </form>
                  <br>
                  <input type="submit" class="btn btn-danger btn-block a1" value="Ajax转账">
              </div>
            </div>
        </div>
    </div>
</div>
<script>
    $('.a1').click(function () {
        $.ajax({
            url:'/csrf_test/',
            method:'post',
            {#方式二 : 请求体中放随机字符串,两种方式放#}
            {#第一种:直接通过页面获取#}
            {#data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(),'from_name':$('.b1').val(),'to_name':$('.b2').val(),'money':$('.b3').val()},#}
            {#第二种 : 直接拿到随机字符串渲染好,需要加引号#}
            data:{'csrfmiddlewaretoken':'{{ csrf_token }}','from_name':$('.b1').val(),'to_name':$('.b2').val(),'money':$('.b3').val()},
            {#方式三 : 在请求头上加#}
            {#headers:{'X-CSRFToken':$('[name="csrfmiddlewaretoken"]').val()},#}
            success:function (data) {
                alert(data)
            }
        })
    })
</script>
- 我们先不添加 csrf 来进行试验
 
- 添加 csrf 随机码来进行校验
 
四.CSRF 校验局部禁用与局部使用
1.两种装饰器
@csrf_exempt: 全局启用CSRF校验的时候, 使用该装饰器可以使得局部不进行校验@csrf_protect: 全局禁用CSRF校验的时候, 使用该装饰器可以使得局部仍然进行校验
2.两种装饰器的使用示例
- 先导入装饰器
 
from django.views.decorators.csrf import csrf_exempt,csrf_protect
- 为CBV添加装饰器
 
# @csrf_exempt  # 全局启用,csrf_CBV可以不进行校验
@csrf_protect  # 全局禁用(也就是注释掉CSRF校验的中间件),csrf_CBV仍然进行校验
def csrf_CBV(request):
    if request.method == 'GET':
        return render(request,'csrf_CBV.html')
    elif request.method == "POST":
        from_name = request.POST.get('from_name')
        to_name = request.POST.get('to_name')
        money = request.POST.get('money')
        return HttpResponse(f'{from_name}向{to_name}转账{money}元成功')
四.使用中间件定期修改密码
需求:用户每隔半年需要修改密码,确保系统安全性
class PasswordDateMiddleware(MiddlewareMixin):
    '''
    获取密码是否超过规定期限
    '''
    def process_response(self, request, response):
        """
        :param request:
        :param response:
        :return:
        """
        url_info = request.path_info
        # 请求路径白名单
        white_list = ["/api/login/", '/api/wechat_url/', '/api/userinfo/', "/api/wechat_login/",
                      "/api/binding_wechat/"]
        if url_info.startswith("/captcha") or url_info in white_list:
            return response
        try:
            user_id = request.user.user_id
            o_user = User.objects.get(user_id=user_id)
            local_time = datetime.datetime.now()
            passwd_change_time = o_user.passwd_change_time
            expenses_time = local_time - passwd_change_time
            expenses_days = expenses_time.days
            # expenses_days = expenses_time.seconds
            # if expenses_days >= 1:
            # 判断密码有效期是否大于180天
            if expenses_days >= 180:
                return ErrorJsonResponse(code=3002, msg="你的密码已经超过有规定有效时间!", data=None)
        except:
            return response
        return response
                版权声明:
                                    
作者:淘小欣                                    
链接:https://blog.taoxiaoxin.club/171.html
                                    
来源:淘小欣的博客                                    
文章版权归作者所有,未经允许请勿转载。
                                
                









共有 0 条评论