Day59 Django中间件,csrf跨站请求伪造,auth模块,settings插拔式配置

一.Django中间件

1.什么是中间件

Django的中间件类似于是Django的保安

  请求来的时候需要先经过中间件才能到达Django的后端

  响应走的时候也需要经过中间件才能到达web服务网关接口

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

中间件在Django中就位于settings.py文件中的MIDDLEWARE配置项

# 下面这几行代码就是Django的中间件
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',
]

MIDDLEWARE配置项是一个列表(列表是有序的,记住这一点,后面你就知道为什么要强调有序二字),列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。

我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,我们学会使用csrf_token之后就不再注释这个中间件了。

Django中间件可以用来做什么

1.网站全集的身份校验,访问频率限制,权限校验,涉及到全局的校验都可以在中间件中完成

2.Django的中间件是所有web框架中做的最好的

2.自定义中间件

中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

  process_request(self,request)

  process_view(self,request,view_func,view_args,view_kwargs)

  process_template_response(self,request,response)

  process_exception(self,request,exception)

  process_response(self,request,response)

以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

如何自定义我们自己的中间件

  1.如果想让自己写的中间件生效,就必须先继承MiddlewareMixin

  2.在注册自定义中间件的时候,一定要确保路径不能写错

需要掌握的两个方法

process_request(self,request)方法和process_response(self,request,response)方法

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MyMdd(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件里的process_request方法')
    
    def process_response(self,request,response):
        print('我是第一个中间件里的process_response方法')
        return response

class MyMdd1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第二个中间件里的process_request方法')
        return HttpResponse('第二个中间件就返回了')

    def process_response(self,request,response):
        print('我是第一个中间件里的process_response方法')
        return response

class MyMdd2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第三个中间件里的process_request方法')

    def process_response(self,request,response):
        print('我是第一个中间件里的process_response方法')
        return response
下方的代码

process_request

  1.请求来的时候,会经过每个中间件里面的process_request方法,从上往下

  2.如果方法里返回了HTTPResponse对象,那么会直接返回,不再往下执行

  基于该特点就可以做访问频率限制,身份校验,权限校验等

class MyMdd(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件里的process_request方法')

class MyMdd1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第二个中间件里的process_request方法')

class MyMdd2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第三个中间件里的process_request方法')

当在某个process_request方法直接返回了HttpResponse对象就不会继续执行了

# 第一个中间件就返回了
class MyMdd(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件里的process_request方法')
        return HttpResponse('第一个中间件就返回了')

process_response

  1.必须将response形参返回,因为这个形参指代的就是要返回给前端的数据

  2.响应走的时候,会依次经过每一个中间件里面的process_response方法,从下往上

# process_response方法
class MyMdd(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件里的process_request方法')

    def process_response(self,request,response):
        print('我是第一个中间件里的process_response方法')
        return response

class MyMdd1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第二个中间件里的process_request方法')

    def process_response(self,request,response):
        print('我是第二个中间件里的process_response方法')
        return response

class MyMdd2(MiddlewareMixin):
    def process_request(self,request):
        print('我是第三个中间件里的process_request方法')

    def process_response(self,request,response):
        print('我是第三个中间件里的process_response方法')
        return response

需要了解的三个方法

process_view()

  在路由匹配成功之后,执行视图函数之前触发

class MyMdd(MiddlewareMixin):
    def process_view(self,request,view_func,view_args,view_kwargs):
        print(view_func)
        print(view_args)
        print(view_kwargs)
        print('我是第一个中间件里的process_view方法')

...

view_func会拿到视图函数名

process_exception()

  当视图函数报错时,自动执行

class MyMdd(MiddlewareMixin):
    def process_exception(self,request,exception):
        print('我是第一个中间件里的process_exception方法')

...

process_template_response()

  当你返回的HTTPResponse对象中必须包含render属性才会触发

# 视图函数层
def index(request):
    print('我是index函数')
    def render():
        return HttpResponse('包含render属性')
    obj = HttpResponse('index')
    obj.render = render
    return obj
class MyMdd(MiddlewareMixin):
    def process_template_response(self,request,response):
        print('我是第一个中间件里的process_template_response方法')
        return response

...

总结:在书写中间件的时候,只要形参中有response,就需要将其返回,这个response就是给前端的响应信息

二.csrf跨站请求伪造

伪造的钓鱼网站

钓鱼网站

  通过制作一个跟正儿八经的网站一模一样的页面,骗取用户输入信息 转账交易,从而做手脚,转账交易的请求确确实实是发给了中国银行,账户的钱也是确确实实少了,唯一不一样的地方在于收款人账户不对

内部原理

  在让用户输入对方账户的那个input上面做手脚,给这个input不设置name属性,在内部隐藏一个实现写好的name和value属性的input框,这个value的值 就是钓鱼网站受益人账号

<h1>这是真的网站</h1>
<form action="" method="post">
    <p>本人:<input type="text" name="username"></p>
    <p>对方账户:<input type="text" name="target_username"></p>
    <p>金额:<input type="text" name="price"></p>
    <input type="submit">
</form>
真实页面前端页面
<h1>这是钓鱼网站</h1>
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>本人:<input type="text" name="username"></p>
    <p>对方账户:<input type="text"></p>
    <p><input type="hidden" name="target_username" value="sxc"></p>
    <p>金额:<input type="text" name="price"></p>
    <input type="submit">
</form>
钓鱼网站前端

如何防止钓鱼网站

防止钓鱼网站的思路

  真实网站通过返回给用户的form表单页面偷偷的塞一个随机字符串

  当请求到来的时候,会先比对随机字符串是否一致,如果不一致,直接返回403页面

该随机字符串有以下特点

  1.同一个浏览器每次访问都不一样

  2.不同浏览器绝对不会重复

避免csrf校验

1.form表单发送post请求的时候,需要在form表单里加上一句话

{% csrf_token %}

这时候csrf中间件需要打开才能正确的验证

 'django.middleware.csrf.CsrfViewMiddleware',

2.ajax发送post请求,避免csrf校验

一共有三种方式

  1.先在页面上写{% csrf_token %}利用标签查找,获取到该input标签的键值信息

// 第一种方式
            data:{'username':'sxc','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},

  2.直接书写'{{ csrf_token }}'

// 第二种方式
            data:{'username':'sxc','csrfmiddlewaretoken':'{{ csrf_token }}'},

  3.导入js文件,将获取随机键值对的方法写到该js文件中

首先新建一个js文件,存放以下代码,之后导入该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);
    }
  }
});

在html页面直接引入该js文件即可

{% load static %}
<script src="{% static 'setjs.js' %}"></script>

跨站请求伪造相关装饰器

当针对FBV时

1.当你网站全局都需要校验csrf的时候 有几个不需要校验该如何处理

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

# 导入模块后直接装饰在函数前
@csrf_exempt
def login(request):
    return HttpResponse('login')

2.当你网站全局不校验csrf的时候 有几个需要校验又该如何处理

# 装饰在函数前
@csrf_protect
def lll(request):
    return HttpResponse('lll')

当针对CBV时

1.当你网站全局都需要校验csrf的时候 有几个不需要校验该如何处理

from django.utils.decorators import method_decorator    
from django.views.decorators.csrf import csrf_exempt,csrf_protect
# 这两个装饰器在给CBV装饰的时候 有一定的区别
如果是csrf_protect 那么有三种方式
    # 第一种方式
    # @method_decorator(csrf_protect,name='post')  # 有效的
    class MyView(View):
        # 第三种方式
        # @method_decorator(csrf_protect)
        def dispatch(self, request, *args, **kwargs):
            res = super().dispatch(request, *args, **kwargs)
            return res

        def get(self,request):
            return HttpResponse('get')
        # 第二种方式
        # @method_decorator(csrf_protect)  # 有效的
        def post(self,request):
            return HttpResponse('post')

如果是csrf_exempt 只有两种(只能给dispatch装)   特例

@method_decorator(csrf_exempt,name='dispatch')  # 第二种可以不校验的方式
class MyView(View):
    # @method_decorator(csrf_exempt)  # 第一种可以不校验的方式
    def dispatch(self, request, *args, **kwargs):
        res = super().dispatch(request, *args, **kwargs)
        return res

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

    def post(self,request):
        return HttpResponse('post')

总结:csrf装饰器中只有crsf_exempt是特例,其他的装饰器在给CBV装饰时都可以有三种方式

三.Auth登录认证模块

1 Auth模块是什么

Auth模块是Django自带的用户认证模块:

我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。

2 auth模块常用方法

导入模块

from django.contrib import auth

auth模块的功能

查询用户
    from django.contrib import auth
    user_obj = auth.authenticate(username=username,password=password)  # 必须要用 因为数据库中的密码字段是密文的 而你获取的用户输入的是明文
记录用户状态
    auth.login(request,user_obj)  # 将用户状态记录到session中
判断用户是否登录
    print(request.user.is_authenticated)  # 判断用户是否登录  如果是你们用户会返回False
用户登录之后 获取用户对象
    print(request.user)  # 如果没有执行auth.login那么拿到的是匿名用户
校验用户是否登录
    from django.contrib.auth.decorators import  login_required
    @login_required(login_url='/xxx/')  # 局部配置
    def index(request):
        pass
    
    # 全局配置  settings文件中
    LOGIN_URL = '/xxx/'
验证密码是否正确
    request.user.check_password(old_password)
修改密码    
    request.user.set_password(new_password)
    request.user.save()  # 修改密码的时候 一定要save保存 否则无法生效
退出登陆
    auth.logout(request)  # request.session.flush()
注册用户
        # User.objects.create(username =username,password=password)  # 创建用户名的时候 千万不要再使用create 了
        # User.objects.create_user(username =username,password=password)  # 创建普通用户
        User.objects.create_superuser(username =username,password=password,email='[email protected]')  # 创建超级用户  邮箱必填

3 扩展默认的auth_user表

这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!

比如,我想要加一个存储用户手机号的字段,怎么办?

聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?

答案是当然有了。

我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。

这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。

复制代码
复制代码
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息表
    """
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    
    def __str__(self):
        return self.username
复制代码
复制代码

注意:

按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:

# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"

再次注意:

一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。

自定义auth_user表

from django.contrib.auth.models import AbstractUser
# Create your models here.
# 第一种 使用一对一关系  不考虑


# 第二种方式   使用类的继承
class Userinfo(AbstractUser):
    # 千万不要跟原来表中的字段重复 只能创新
    phone = models.BigIntegerField()
    avatar = models.CharField(max_length=32)

# 一定要在配置文件中 告诉django
# 告诉django  orm不再使用auth默认的表  而是使用你自定义的表
AUTH_USER_MODEL = 'app01.Userinfo'  # '应用名.类名'


1.执行数据库迁移命令
所有的auth模块功能 全部都基于你创建的表 
而不再使用auth_user

三.settings功能插拔式源码原理借鉴

参考Django配置文件中间件的功能模块

  一旦注释  >>>  该功能即不能用
  一旦打开  >>>  该功能即启用

我们首先写三个功能

class  Msg(object):
    def __init__(self):
        pass  # 发送短信需要的代码配置

    def send(self,content):
        print('短信通知:%s' % content)
msg.py
class Email(object):
    def __init__(self):
        pass  # 发送邮件需要的代码配置

    def send(self,content):
        print('邮件通知:%s'%content)
email.py
class QQ(object):
    def __init__(self):
        pass  # 发送qq需要的代码准备

    def send(self,content):
        print('qq通知:%s'%content)
qq.py

将这三个py文件放在一个文件夹中,这样这个文件夹就成为了一个包

我们在配置文件中配置好三个功能的路径

NOTIFY_LIST = [
    'notify.email.Email',
    'notify.msg.Msg',
    # 'notify.wechat.WeChat',
    'notify.qq.QQ',
]
settings.py

在包中的__init__文件中书写关键的代码

import settings
import importlib


def send_all(content):
    for path_str in settings.NOTIFY_LIST:  # 1.拿出一个个的字符串   'notify.email.Email'
        module_path,class_name = path_str.rsplit('.',maxsplit=1)  # 2.从右边开始 按照点切一个 ['notify.email','Email']
        module = importlib.import_module(module_path)  # from notity import msg,email,wechat
        cls = getattr(module,class_name)  # 利用反射 一切皆对象的思想 从文件中获取属性或者方法 cls = 一个个的类名
        obj = cls()  # 类实例化生成对象
        obj.send(content)  # 对象调方法

关键代码解析:

  1.从settings中循环拿出每个功能的路径

  2.解压赋值,分成路径和类名

  3.使用importlib通过字符串导入路径

  4.利用反射,通过类名字符串获取类

  5.实例化生成对象并调用对象的方法

在启动文件中传入参数就可以使用这三个功能

import notify

notify.send_all('国庆放假了 记住放八天哦')
start.py

猜你喜欢

转载自www.cnblogs.com/sxchen/p/11588538.html