目录

昨日补充:将自己写的 login_auth 装饰装在 CBV 上

三种方式 (别忘了导模块)

from django.utils.decorators import method_decorator

# @method_decorator(login_auth, name='get')  # 第一种, name 参数必须指定
class MyHome(View):
# @method_decorator(login_auth) # 第二种, get 和 post 都会被装饰(登录验证)(直接把 dispatch 拿过来,加个装饰器)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs) @method_decorator(login_auth) # 第三种,直接装饰在单个方法上
def get(self, request):
return HttpResponse('get') def post(self, request):
return HttpResponse('post')

django 中间件

django 中间件 就类似于是 django 的门户,请求来的时候需要先经过 中间件 才能到达 django 后端(urls),响应走的时候也需要经过 中间件 才能到达 web服务网关接口(wsgif 模块)

django 中间件可以用来做什么

  • 做网站全局的身份校验,限制访问频率,权限校验(反爬)... 只要是涉及到全局的校验几乎都可以在中间件中完成,第一时间该想到的也是中间件

django 的中间件是设计比较完善的,逻辑最清晰,最简单(flask的中间件不如它)

讲完这个中间件就知道为什么我们前面每次提交 post 请求都会写上先去 settings.py 里把 csrf 这个中间件暂时注释掉了

django 请求生命周期 *****

经过 中间件 之后才能进入 urls.py(再 views.py ... 一层一层递进)

Django-中间件-csrf扩展请求伪造拦截中间件-Django Auth模块使用-效仿 django 中间件配置实现功能插拔式效果-09-LMLPHP

默认中间件及其大概方法组成

# 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',
]

点进去观察 中间件源码

'''
django.middleware.security.SecurityMiddleware
--> 本质是动态导入(可以看最后面的那个模仿案例)
# from django.middleware.security import SecurityMiddleware django.middleware.csrf.CsrfViewMiddleware
--> from django.middleware.csrf import CsrfViewMiddleware
'''

发现 **django 中间件 **中有五个用户可以自定义的方法

# django.middleware.csrf.CsrfViewMiddleware  --> from django.middleware.csrf import CsrfViewMiddleware
class CsrfViewMiddleware(MiddlewareMixin):
def _accept(self, request):
def _reject(self, request, reason):
def _get_token(self, request):
def _set_token(self, request, response):
def process_request(self, request):
def process_view(self, request, callback, callback_args, callback_kwargs):
def process_response(self, request, response): # django.middleware.security.SecurityMiddleware --> django.middleware.security.SecurityMiddleware
class SecurityMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
def process_request(self, request):
def process_response(self, request, response): # django.contrib.sessions.middleware.SessionMiddleware
class SessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
def process_request(self, request):
def process_response(self, request, response):
  • 需要我们掌握的方法有
  1. process_ request() 方法
  2. process_ response ()方法
  • 需要了解的方法
  1. process_ view()
  2. process exception ()
  3. process_ template_ response ()

中间件的执行顺序

大体同 django 请求生命周期 那张图,可能会受以下情况的影响

自定义中间件探究不同操作对中间件执行顺序的影响

测试思路:

  • 在 settings.py 里注册不同中间件,探究默认的执行顺序
  • 在不同中间件的 process_request 和 process_response 等方法中 return HttpResponse 对象会对执行顺序造成什么影响
  • 了解五种方法的触发时机

自定义中间件

  1. 新建一个文件夹(放在全局或 app 内)
  2. 写一个类继承 MiddlewareMiXin 类
  3. 里面书写需要的(五个方法中的某些)方法
  4. 一定要在 settings.py 里配置中间件

代码

mymiddleware/mdd.py 自定义中间件

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 def process_view(self, request, view_func, view_args, view_kwargs):
print(view_func)
print(view_args)
print(view_kwargs)
print('我是第一个中间件里面的process_view方法') def process_exception(self, request, exception):
print('我是第一个中间件里面的process_exception') def process_template_response(self, request, response):
print('我是第一个中间件里面的process_template_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 def process_view(self, request, view_func, view_args, view_kwargs):
print(view_func)
print(view_args)
print(view_kwargs)
print('我是第二个中间件里面的process_view方法') def process_exception(self, request, exception):
print('我是第二个中间件里面的process_exception') def process_template_response(self, request, response):
print('我是第二个中间件里面的process_template_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 def process_view(self, request, view_func, view_args, view_kwargs):
print(view_func)
print(view_args)
print(view_kwargs)
print('我是第三个中间件里面的process_view方法') def process_exception(self, request, exception):
print('我是第三个中间件里面的process_exception') def process_template_response(self, request, response):
print('我是第三个中间件里面的process_template_response')
return response

在 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',
'mymiddleware.mdd.MyMdd', # 配置上
'mymiddleware.mdd.MyMdd1', # 配置上
'mymiddleware.mdd.MyMdd2', # 配置上
]

需要掌握的方法

process_request

基于该特点就可以做访问频率限制

process_response

需要了解的方法

process_view

process_exception

process_template_response

def index(request):
print("我是 index 视图函数")
def render():
return HttpRespone('用户最终能够看到的结果') # ******
obj = HttpResponse('index')
obj.render = render # 返回的 HttpResponse 对象中必须包含 render 属性,才能触发中间件里定义的 process_template_response 方法
return obj

强调:

在写中间件的时候,只要形参中有 response ,就要记得将其返回,这个Response 是要给前端的信息

csrf 中间件 跨站请求伪造

钓鱼网站

原理:写了一个一模一样的网站,一个隐藏框,发送往隐藏当做收钱方

问题:如何区分当前用户朝我们网站发送的请求页面是不是我们本网站给的

防止思路

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

请求到来的时候,会先比对随机字符串是否一致,如果不一致,直接拒绝(403 FORBIDDEN)

解决方案

在页面上放一个 隐藏的 input 框,value 里面放的是一个字符串,每次刷新都会更新里面的 value,这样别人的网站就不知道;,这个 value 就无法伪造了

django 的实现 {% csrf_token %}

该随机字符串有以下特点:

  • 同一个浏览器每一次访问都不一样
  • 不同浏览器绝对不一样

post请求提交数据通过 csrf 校验

form 表单

form 表单发送 post 请求的时候,需要你做的是写一段代码 {% csrf_token %} 即可,不需要注释 csrf 中间件了

ajax 发送

三种方式(第三种可以用在前后端分离时)

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

  2. ajax data 值 那里直接写 {{ csrf_token }}data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},

  3. 参考官方文档推荐,自定义 js 文件,在要用到的页面载入这个 js 脚本,自动获取并传递 csrf 校验 *****

    • 你可以将下面的 js 代码 放到一个 js 文件中
    // js 代码(一般放在 static 文件夹下)
    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 'setjs.js' %}"></script> 导入就行
    • 这样就不需要 在 html 中写 {% csrf_token %} 或在 ajax 中写 {{ csrf_token }}

csrf 装饰器相关

两个问题

未注释掉 csrf 中间件时 单功能取消 csrf 校验:csrf_exempt

FBV

from django.views.decorators.csrf import csrf_exempt

# 全局开启时,局部禁用
@csrf_exempt
def index(request):
pass

CBV

有两种方式,不能针对单个方法,是针对全局的

# CBV比较特殊,不能单独加在某个方法上
# 只能加在类上或dispatch方法上
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt # @method_decorator(csrf_exempt,name='dispatch') # 第一种
class Csrf_Token(View):
@method_decorator(csrf_exempt) # 第二种
def dispatch(self,request,*args,**kwargs):
res = super().dispatch(request,*args,**kwargs)
return res
# @method_decorator(csrf_exempt) # 这里这么写不行!!!
def get(self,request):
pass
def post(self,request):
pass

注释掉 csrf 中间件时 单功能开启 csrf 校验:csrf_protect

FBV

from django.views.decorators.csrf import csrf_protect

@csrf_protect
def lll(request):
return HttpResponse('lll')

CBV 还要改改

有三种方式,既可以针对全局的,也可以针对单个的

from django.views.decorators.csrf import csrf_protect

from django.views import View
from django.utils.decorators import method_decorator # 第一种方式
# @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 装饰器中只有 csrf_exempt 是特例,其他的装饰器在给CBV 装饰的时候 都可以有三种方式

Auth 模块

使用 django 自带的 auth 表做登录功能

涉及到的 auth 相关方法

python3 manage.py createsuperuser  # 命令行下创建超级用户(可以拥有登录 django admin 后台管理的权限)

# 查询用户是否存在
user_obj = auth.authenticate(username=username, password=password) # 数据库中的密码是密文的(该方法不能只传用户名一个参数),返回值要么是对象,要么是 None # 记录用户状态
auth.login(request, user_obj) # 登录,会自动存 session
# 优点:只要执行了这一句话,你就可以在后端任意位置通过 request.user 拿到当前登录的用户对象(未登录会报错,AnonymousUser 匿名用户) # 获取用户对象
request.user # 用户登录了直接获取用户对象,用户没登录获取到 AnonymousUser 匿名用户 # 判断当前用户是否登录,未登录(AnonymousUser)会返回 False,其他情况下返回 True
request.user.is_authenticated # 验证用户密码是否正确
is_right = request.user.check_password(old_password) # 将获取的用户密码,自动加密,然后去数据库中对比(返回布尔值) # 修改密码
request.user.set_password(new_password) # 修改密码
request.user.save() # 需要保存才能生效 # 注销用户
auth.logout(request) # 等价于 request.session.flush() (删除了 session
表中记录,浏览器 cookie) # 登录验证装饰器
from django.contrib.auth.decorators import login_required # @login_required # 自动校验当前用户是否登录,如果没有登录,(未传参数的情况下)默认跳转到 django 自带的登录页面(还是 404 ?)
# ------ 局部配置
@login_required(login_url='/login/')
def set_password(request):
pass # ------ 全局配置(不用在里面写配置了)
# 在 settings.py 中写
LOGIN_URL = '/login/' # 注册用户
from django.contrib.auth.models import User # 这就是那张 auth 表
# 创建普通用户
User.objects.create_user(username=username, password=password)
# 创建超级用户
User.objects.create_superuser(username=username, password=password, email='12323132@qq.com') # 创建超级用户必须传邮箱
# 不能用 User.objects.create(username=username, password=password) (这样密码没有加密)

核心代码

app01/views.py

from django.shortcuts import render, HttpResponse
from django.contrib import auth def xxx(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 取数据库查询当前用户数据
# models.User.objects.filter(username=username,password=password).first()
user_obj = auth.authenticate(username=username, password=password) # 必须要用 因为数据库中的密码字段是密文的 而你获取的用户输入的是明文
print(user_obj)
# print(user_obj)
# print(user_obj.username)
# print(user_obj.password)
# 保存用户状态
# request.session['user'] = user_obj
auth.login(request, user_obj) # 将用户状态记录到session中
"""只要执行了这一句话 你就可以在后端任意位置通过request.user获取到当前用户对象"""
return render(request, 'xxx.html') def yyy(request):
print(request.user) # 如果没有执行auth.login那么拿到的是匿名用户
print(request.user.is_authenticated) # 判断用户是否登录 如果是你们用户会返回False
# print(request.user.username)
# print(request.user.password)
return HttpResponse('yyy') from django.contrib.auth.decorators import login_required # 修改用户密码
@login_required # 自动校验当前用户是否登录 如果没有登录 默认跳转到 一个莫名其妙的登陆页面
def set_password(request):
if request.method == 'POST':
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
# 先判断原密码是否正确
is_right = request.user.check_password(old_password) # 将获取的用户密码 自动加密 然后去数据库中对比当前用户的密码是否一致
if is_right:
print(is_right)
# 修改密码
request.user.set_password(new_password)
request.user.save() # 修改密码的时候 一定要save保存 否则无法生效
return render(request, 'set_password.html') @login_required
def logout(request):
# request.session.flush()
auth.logout(request)
return HttpResponse("logout") from django.contrib.auth.models import User def register(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = User.objects.filter(username=username)
if not user_obj:
# 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='123@qq.com') # 创建超级用户
return render(request, 'register.html')

自定义扩展 autor 表字段

前提:

settings.py 添加额外配置

# ... 其他配置
# 告诉 django 不再使用 auth 默认的表 而是使用你自定义的表
AUTH_USER_MODEL = 'app01.Userinfo' # '应用名.模型表类名'
# ... 其他配置

两种方式

app01/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser # Create your models here.
# 第一种 使用一对一关系 不考虑 # 第二种方式 使用类的继承
class Userinfo(AbstractUser):
# 千万不要跟原来表(AbstractUser)中的字段有冲突
phone = models.BigIntegerField()
avatar = models.CharField(max_length=32)
# 别忘了去 settings.py 里配置

后续操作

执行数据库迁移命令(python3 manage.py makemigrations、python3 manage.py migrate

效仿 django中间件配置 实现 功能插拔式效果

我们效仿中间件(后面要学的 restframework 的设计思想也是这样的),做一个通知功能, 可以发微信通知、短信通知、右键通知

代码实现

重点配置部分(设计思想好好学学)

start.py 入口文件

import notify

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

notify/__init__.py 关键代码(结合了 importlib 动态导入、反射 等知识点)

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) # 对象调方法

settings.py 配置(可以在这里开启或关闭功能)

NOTIFY_LIST = [
'notify.email.Email',
'notify.msg.Msg',
# 'notify.wechat.WeChat', # 注释掉了,这个功能就不执行了
'notify.qq.QQ',
]

功能扩展部分

然后是各个功能的文件(拆分成了各个文件,搭配 settings.py 起到可插拔式效果),要想新增这种功能直接加个文件实现这几个代码即可

notify/email.py

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

notify/msg.py

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

notify/qq.py

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

notify/wechat.py

class WeChat(object):
def __init__(self):
pass # 发送微信需要的代码配置 def send(self, content):
print('微信通知:%s' % content)
补充:pycharm 使用技巧

想知道当前跳进来的代码的位置,可以点击图中的图标,快速定位,可以让你知道目录结构(看看推测对不对)

Django-中间件-csrf扩展请求伪造拦截中间件-Django Auth模块使用-效仿 django 中间件配置实现功能插拔式效果-09-LMLPHP

04-03 17:16