专栏首页python小白的成长计划Django对中间件的调用思想、csrf中间件详细介绍、Django settings源码剖析、Django的Auth模块

Django对中间件的调用思想、csrf中间件详细介绍、Django settings源码剖析、Django的Auth模块

使用Django对中间件的调用思想完成自己的功能

中间件的调用只需要在配置文件中添加,如果不使用某个中间件,只需要在配置文件中将对应的字符串注释掉就可以,这种调用执行某一代码的方式是不是很方便呢?下面我们就利用Django对中间件的调用的思想,将自己的功能也实现和中间件一样的调用方式。

功能要求

假设实现的功能:信息的群发,要求我们写好的信息只需要一键发送就可以通过邮件、短信、微信三种方式一起发送出去,如果我们不需要某种通知方式只需要在配置文件中将其注释掉就可以。

importlib模块介绍

动态导入模块importlib,可以按照填入的以点隔开的字符串文件路径获的方式取到对应的文件。使用方法:

module_path = 'notify.msg'
md = importlib.import_module(module_path) #md就是notify文件夹下的msg文件

如果需要获取文件里面定义的函数或者类,可以使用反射的方法(这里将文件当做一个对象,一切皆对象)

cls = getattr(md,cls_name)#将文件名作为对象右面填类的名字就能拿到对应的类

功能的实现

1.建一个群发信息功能的包如下图,将每一张发送信息的方式写在一个独立的文件中。

2.在每一个通知文件中定义对应的通知类如:

class Msg:
    def __init__(self):
        pass

    # 发送信息前的准备

    def send(self, content):
        print(f'Msg通知:{content}')

3.将每一个文件添加到配置文件如下:

NOTIFY_LISTS = [
    'notify.email.Email',
    'notify.msg.Msg',
    'notify.qq.Qq',
    'notify.WeChat.WeChat'
]

4.在init中对类的查找和实例化进行处理

import importlib
import settings
def send_all(content):
    for path in settings.NOTIFY_LISTS:
        module_path,cls_name = path.rsplit('.',maxsplit=1)
        module = importlib.import_module(module_path)
        cls = getattr(module,cls_name)
        obj = cls()
        obj.send(content)

5.在start中调用包实现消息群发的功能

import os,sys
from notify import send_all


sys.path.append(os.path.dirname(__file__))

if __name__ == '__main__':
    send_all('现在测试通知')
    
Email通知:现在测试通知
Msg通知:现在测试通知
QQ通知:现在测试通知
WeChat通知:现在测试通知

至此功能基本实现。

csrf中间件详细介绍

跨站请求伪造

csrf全称Cross-site request forgery(跨站请求伪造), 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

跨站请求伪造最常见的应用如钓鱼网站,钓鱼网站的具体钓鱼方式:钓鱼网站伪造一个和正规网站界面一模一样的网站,然后将转账(支付)功能的的form表单进行修改,当用户登录时提供的是正规网站的登录接口,而用户支付或转账的对方账户是假的,下面隐藏的是预先设定好的账户(input框的name和value),这样用户每次给对方进行转账都会将钱转到预先设定好的账户。如何解决跨站请求伪造呢?

从服务端的角度来解决这个问题的思路就是如果每次服务端都能识别出来向我提交请求的是我自己的页面还是别人的页面,那么钓鱼网站就无法在用户访问服务器的过程中伪装成服务端网页给服务端发送转账请求了。而Django中的中间件就是通过这种思想解决跨站请求伪造的问题的。

Django csrf中间件

当用户访问有Django csrf中间件的服务端时Django csrf中间件会给用户的get请求的页面携带一个随机字符串,当用户发送post请求时会校验用户的随机字符串,如果如果校验不通过则直接报403错误,禁止用户提交post请求。

<input type="hidden" name="csrfmiddlewaretoken" value="rJ47FeK9T55wavvVJGY6UxdM1kTMHhTqotGfaXjXIK8Ahz2Uvs02yR9T8bBn5q2D">

能否提交post请求的通常是form表单和ajax请求,Djangocsrf中间件在两种post请求中的使用方式是不同的,具体使用方法如下:

form表单

我们只需在form表单中添加{% csrf_token %}。

<form action="" method="post">
    {% csrf_token %}
    <p>username:<input type="text" name="username"></p>
    <p>target_account:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>
<input type="hidden" name="csrfmiddlewaretoken" value="rJ47FeK9T55wavvVJGY6UxdM1kTMHhTqotGfaXjXIK8Ahz2Uvs02yR9T8bBn5q2D">           

ajax

ajax有三种方式添加中间件标签。

方式一

先在页面任意的位置上书写{% csrf_token %},然后在发送ajax请求的时候通过标签查找获取随机字符串添加到data自定义对象即: data:{'username':'xxx','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},

方式二

data:{'username':'xxx','csrfmiddlewaretoken':'{{ csrf_token }}'}

方式三(官方提供,建议使用此方法)

新建一个js文件,将下面的代码拷贝进去,在ajax上面导入即可。

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);
    }
  }
});

导入:

<script src="/static/setup.js"></script>

csrf相关装饰器

csrf相关的装饰器可以按照我们的需求给某个视图函数加csrf校验,或者不给某个视图函数加csrf校验。

csrf_exempt

不给某个视图函数加csrf校验

from django.views.decorators.csrf import csrf_exempt
@csrf_exempt  # 不校验 csrf
def index(request):
    return HttpResponse('index')

csrf_protect

给某个视图函数加csrf校验,这里需要在settings文件中将csrf中间件注释掉。

@csrf_protect  # 校验
def login(request):
    return HttpResponse('login')

在CBV上加csrf装饰器

csrf_exempt

只有一种加装饰器的方法,就是先导入method_decorator方法,然后在类中定义dispatch方法然后将其装饰在dispatch方法上面。@method_decorator(csrf_exempt)

# @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持该方法
@method_decorator(csrf_exempt,name='dispatch')  # csrf_exempt
class MyIndex(views.View):
    # @method_decorator(csrf_exempt)  # 可以
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request,*args,**kwargs)
    def get(self,request):
        return render(request,'transfer.html')
    # @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持该方法
    def post(self,request):
        return HttpResponse('OK')       
    # csrf_exempt这个装饰器只能给dispatch装才能生效

csrf_protect

csrf_protect装饰器用普通加装饰器的方法就可以跟普通的装饰器装饰CBV用法一样。

# @method_decorator(csrf_protect,name='post')  # 可以
class MyIndex(views.View):
    @method_decorator(csrf_protect)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request,*args,**kwargs)
    def get(self,request):
        return render(request,'transfer.html')
    # @method_decorator(csrf_protect)  # 可以
    def post(self,request):
        return HttpResponse('OK')

Django settings源码剖析及模仿使用

Django settings源码剖析

Django有两个配置文件,一个是用户可以看到的settings文件,另一个是内部的全局的配置文件,这两个配置文件的执行方式是如果用户配置了就用用户配置的settings文件,如果用户没有配置settings文件就用内部的settings文件。那么这一功能Django是如何实现的呢?一起来看看Django settings的源码。

查看内部配置文件

from django.conf import settings#配置文件实例化出的一个类
from django.conf import global_settings#配置文件

我们进入第一个settings:发现settings使用了单例模式,

进入LazySettings类里面:

进入manage.py查看项目启动时的配置:

再看LazySettings类

 def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time we need any settings at all, if the user has not
        previously configured the settings manually.
        """
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        # os.environ是一个全局的大字典,而settings_module获取了key为ENVIRONMENT_VARIABLE的值,从manage.py中可以看出settings_module获取到的就是用户配置文件路径:项目名.settings
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE))

        self._wrapped = Settings(settings_module)
        #settings路径传入了Settings,我们进Settings里面看看。
        
class Settings(object):
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):#获取全局配置文件中所有变量名
            if setting.isupper():#只获取是大写的变量名,这就是为啥配置文件中所有的变量名都是大写的
                setattr(self, setting, getattr(global_settings, setting))#setattr将获取到global_settings的变量值添加到settings对象自己的属性中

        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module#项目名.settings

        mod = importlib.import_module(self.SETTINGS_MODULE)
        #导入暴露给用户的配置文件,相当于from 用户名 import settings

        tuple_settings = (
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
        )
        
        
     self._explicit_settings = set()
        for setting in dir(mod):#获取用户配置文件中所有的变量名
            if setting.isupper():
                setting_value = getattr(mod, setting)#利用反射取出所有的变量值

                if (setting in tuple_settings and
                        not isinstance(setting_value, (list, tuple))):
                    raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                setattr(self, setting, setting_value)#将用户settings的属性和属性值写入settings对象中
                #到这里我们可以看到,实例化出的settings对象先将全局配置文件中的变量名和变量值写入,然后再将用户配置文件的变量名和变量值写入,这样如果用户配置文件配置了对应的变量名和变量值就会替换掉全局的,从而实现了如果用户配置了settings就用用户的,如果用户没有配置,就用全局的配置文件的功能。
                self._explicit_settings.add(setting)

        if not self.SECRET_KEY:
            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")

模仿使用

模仿使用其实就是模仿用户settings配置文件如果设置了就用用户的,如果没有设置就用内置的这个功能。

我们只需要在全局配置文件包的__init__中写如下代码:

import importlib
from lib.conf import global_settings
import os

class Settings(object):
    def __init__(self):
        for name in dir(global_settings):
            if name.isupper():
                setattr(self,name,getattr(global_settings,name))
        # 获取暴露给用户的配置文件字符串路径
        module_path = os.environ.get('xxx')
        md = importlib.import_module(module_path)  # md = settings
        for name in dir(md):
            if name.isupper():
                k = name
                v = getattr(md,name)
                setattr(self,k,v)


settings = Settings()

Auth模块

auth简介

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

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

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

auth模块常用方法

功能

代码

创建用户

from django.contrib.auth.models import User User.objects.create_user(username=username,password=password) # 创建普通用户,密码自动加密 User.objects.create_superuser(username=username,password=password,email='123@qq.com') # 创建超级用户

校验用户名和密码

from django.contrib import authuser_obj = auth.authenticate(request,username=username,password=password)

保存用户登录状态

auth.login(request,user_obj) 只要这句话执行了后面在任意位置 只要你能拿到request你就可以通过request.user获取到当前登录的用户对象

判断当前用户是否登录

request.user.is_authenticated()

校验原密码

request.user.check_password(old_password)返回bool值

修改密码

request.user.set_password(new_password) request.user.save() 千万不要忘了save

注销

auth.logout(request)

校验用户登录装饰器

from django.contrib.auth.decorators import login_required 局部配置 @login_required(login_url='/login/') def index(request): pass 全局配置 settings配置文件中直接配置 LOGIN_URL = '/login/' @login_required def index(request): pass 如果全局和局部都配置了以局部的为准

创建用户

create_user()

auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等,用户名和密码是必须提供的。

from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)

校验用户名和密码

提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。

如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。

authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。

user = authenticate(username='usernamer',password='password')

保存用户登录状态

该函数接受一个HttpRequest对象,以及一个经过认证的User对象。

该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据

from django.contrib.auth import authenticate, login
   
def my_view(request):
  username = request.POST['username']
  password = request.POST['password']
  user = authenticate(username=username, password=password)
  if user is not None:
    login(request, user)
    # Redirect to a success page.
    ...
  else:
    # Return an 'invalid login' error message.
    ...

判断当前用户是否登录

判断当前用户是否登录(发送的当前请求是否已经登录)

def my_view(request):
  if not request.user.is_authenticated():
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

校验原密码

auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。

密码正确返回True,否则返回False。

ok = user.check_password('密码')

修改密码

auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。

注意:设置完一定要调用用户对象的save方法!!!

user.set_password(password='')
user.save()

注销

该函数接受一个HttpRequest对象,无返回值。

当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。

from django.contrib.auth import logout
   
def logout_view(request):
  logout(request)
  # Redirect to a success page.

校验用户登录状态装饰器

局部登录认证装饰器

@login_required(login_url='/login/')判断用户是否登录如果没有则直接跳转到登录页面

from django.contrib.auth.decorators import login_required
      
@login_required
def my_view(request):
  ...

如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。

示例:

LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由

全局登录认证装饰器

在settings文件直接配置

LOGIN_URL = '/login/'#如果全局和局部都配置了以局部的为准

User对象属性(用户登录权限和管理权限)

User对象属性:username, password

is_staff : 用户是否拥有网站的管理权限.

is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

扩展auth_user表字段

方式一

思路:再建一张表,使这张表和auth_user表是一对一的关系,这样可以实现对auth_user表字段的增加。

class UserDetail(models.Model):
    phone = models.BigIntegerField()
    user = models.OneToOneField(to='User')

方式二

思路:自定义一个类和原来的auth_user继承同一个基类,然后自定义类中的字段,这里需要说明的是在自定义类之前不能执行数据库迁移命令,定义好才能执行数据库迁移命令。另外,定义好类之后需要在配置文件中添加下面的配置。

#自定义类
from django.contrib.auth.models import AbstractUser
class Userinfo(AbstractUser):
    phone = models.BigIntegerField()
    register_time = models.DateField(auto_now_add=True)
    
#配置文件中添加
AUTH_USER_MODEL = 'app01.Userinfo'  # 应用名.表名

上面的步骤完成之后,auth模块的功能都可以在你定义的表中使用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • AJAX-前后端交互的艺术

    当我们通过提交表单向服务器提交内容,或者进行一些其他操作,均涉及到了与浏览器之间的交互,传统的方式与AJAX方式的处理方法是不同的

    BWH_Steven
  • 浅析YSlow-23条规则

    起初想要去了解如何提高网页加载性能,发现Yahoo发布的一款基于FireFox的插件YSlow。

    IMWeb前端团队
  • Flask前后端分离实践:Todo App(3)

    如果你们是看了Miguel的狗书,或是李辉大大的狼书,一定知道我们在提交表单时,常常会附带上一个隐藏的csrf值,用来防止CSRF攻击。关于CSRF是什么这里就...

    岂不美哉Frost
  • How is call from CXF delegated to our application code?

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明...

    Jerry Wang
  • Promise接口实现之jQuery 的deferred对象

    我们知道JavaScript是单线程,如果遇到某些耗时很长的javascript操作,那么其他的操作就必须等待。,通常的解决方法是将那些排在后面的操作,写成“回...

    IMWeb前端团队
  • Spring MVC 的跨域解决方案

    一句话:同一个ip、同一个网络协议、同一个端口,三者都满足就是同一个域,否则就是跨域。

    一个优秀的废人
  • JavaScript强化教程——jQuery AJAX 实例

    本文为 H5EDU 机构官方 HTML5培训 教程,主要介绍:JavaScript强化教程 —— jQuery AJAX实例

    IMWeb前端团队
  • Mock.js前端开发cgi数据模拟工具

      Mockjs是个能够拦截页面ajax请求并模拟返回数据的小工具,借助Mockjs,前端开发中在后台测试接口还没有给的时候就可以自己拦截请求模拟数据进行愉快的...

    IMWeb前端团队
  • Flask 实现远程日志实时监控

    在自动化运维系统中,常常需要监控日志,这些日志是不断更新的。本文提供了一种实时日志监控的 Python 实现。主要实现以下功能:

    岂不美哉Frost
  • js中几种实用的跨域方法原理详解

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据。...

    似水的流年

扫码关注云+社区

领取腾讯云代金券