🍖中间件与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.中间件的作用

  • 既然请求来和响应走都经过中间件, 那么我们就可以对所有的请求做一些预处理, 对所有返回的响应也可以做处理

image-20210404155034529

  • 每一个中间件都有具体的功能

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.响应走时,自下而上顺序执行

image-20210404161237597

  • 自定义中间件步骤
1.先在项目名目录或者应用名目录下创建一个任意名字的文件夹(最好见名知意)
2.在该文件夹下创建任意名字的py文件(最好见名知意)
3.在该py文件内书写类, 在类中书写中间件的五种方法
    - 类必须继承MiddlewareMixin, 五种方法并不是全都要写,需要几种写几种
4.将类的路径以字符串的形式注册到settings.py配置文件中的MIDDLEWARE下(与app注册原理类似)

6.自定义中间件操作

  • 创建文件夹和py文件,再书写类

image-20210404163859367

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 在下

  • 启动项目发起任意请求查看输出

image-20210404165229402

  • 如果我们在 First 中的 process_request 方法中添加 返回值 HttpResponse 对象
class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print("请求来了11111")
        return HttpResponse('1')  # 添加HttpResponse返回值
    ....
  • 再查看实验效果

image-20210404174617877

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})
  • 演示效果(没有做登入校验)

ddddd

二.CSRF跨站请求伪造

1.什么是 CSRF (简介)

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性

  • 简单理解就是黑客盗用你的身份, 以你的身份来进行一系列操作(发送恶意请求,发消息,购物,转账等等), 这些操作对于服务器来说是完全合法的, 因为你的身份就是一个正常的用户

2.CSRF 攻击原理示例

  • 以银行转账为例

img

  • 正常用户登入受信任的网站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)

image-20210404190922185

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 来进行试验

xxxxxxxxxx

  • 添加 csrf 随机码来进行校验

sasasasa

四.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}元成功')
posted @ 2021-04-04 22:59  给你骨质唱疏松  阅读(216)  评论(0编辑  收藏  举报