Django-中間件-csrf擴展請求僞造攔截中間件-Django Auth模塊使用-效仿 django 中間件配置實現功能插拔式效果-09

[TOC]html

昨日補充:將本身寫的 login_auth 裝飾裝在 CBV 上

類裏面的方法通常都是類綁定方法 或者 對象綁定方法,第一個參數是類 或者 對象自己,那麼前面寫的裝飾器就要改參數才能用了,不過這裏咱們能夠用 django 給咱們寫好的裝飾器,從不須要更改寫好的裝飾器前端

三種方式 (別忘了導模塊)python

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 模塊)nginx

django 中間件能夠用來作什麼web

  • 作網站全局的身份校驗,限制訪問頻率,權限校驗(反爬)... 只要是涉及到全局的校驗幾乎均可以在中間件中完成,第一時間該想到的也是中間件

django 的中間件是設計比較完善的,邏輯最清晰,最簡單(flask的中間件不如它)ajax

講完這個中間件就知道爲何咱們前面每次提交 post 請求都會寫上先去 settings.py 裏把 csrf 這個中間件暫時註釋掉了數據庫

django 請求生命週期 *****

通過 中間件 以後才能進入 urls.py(再 views.py ... 一層一層遞進)django

科普:flask

  1. wsgiref 不可以承受高併發,因此上線以後會換成 uwsgi 模塊(前面再加一個 nginx 作反向代理)
  • WSGI 與 wsgi 以及 uwsgi 分別什麼意思

​ WSGI是一個協議標準,wsgiref 和 uwsgi 都是實現了 WSGI 協議的功能模塊後端

  1. 請求在進入第一層中間件時會去緩存數據庫中判斷有沒有數據
  • 若是有的話會直接拿到數據並返回請求(這樣能夠節約資源,下降服務器以及數據庫的壓力)
  • 若是沒有的話會接着一層一層地走中間件,而後路由配置、views.py ...,等請求再次來到最後一層中間件時,在返回數據的同時,會保存一份在緩存數據庫中。(下次就能夠直接在緩存數據庫中拿到數據了)

具體原理等後期涉及到展開來說, 先知道這個概念就行

默認中間件及其大概方法組成

django 默認有七個中間件

django 支持用戶自定義本身的中間件,而且暴露給用戶,還暴露給用戶五個能夠自定義中間件的方法

# 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

請求來的時候會依次執行 settings.py 配置文件中註冊了的中間件裏的該方法

  • 若是沒有該方法則直接跳過,走下一個中間件
  • 若是該方法裏返回了 HttpResponse 對象,那麼會直接從當前中間件的 process_response 方法 從下往上依次執行返回,不會再接着往下執行
  • 執行順序:從上往下
  • 該方法能夠實現對用戶身份的校驗,訪問頻率的限制,用戶權限的校驗...

基於該特色就能夠作訪問頻率限制

process_response

響應走的時候會依次執行 settings.py 配置文件中註冊了的中間件裏的該方法(必須將 response 形參返回,由於這個 response 指代的就是返回給前端的數據)

  • 若是沒有該方法則直接跳過,走下一個中間件
  • 執行順序:從下往上
  • 該方法能夠幫你實現緩存機制(減緩服務器、數據庫的壓力)

須要瞭解的方法

process_view

路由匹配成功 執行視圖函數以前 自動觸發(從上往下依次執行)

process_exception

視圖函數報錯了,自動觸發(從下往上依次執行)

process_template_response

視圖函數返回的 HttpResponse 對象中包含了 render 屬性時會觸發,或者是代表一個對象時 TemplateResponse 對象或等價方法 的時候也會觸發(從下往上依次執行)

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

當你的網站全局不校驗 csrf 的時候(註釋掉 csrf 中間件時),有幾個須要校驗該如何處理 ?@csrf_protect

未註釋掉 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 模塊

科普提示小點:

  • 一個方法的放回結果打印出來是 「字符串」 最好 type 確認一下,多是對象重寫了 __str__() 方法
  • django 後臺管理只有超級用戶才能進入
  • 用了 Auth 模塊的方法 ,就最好都用 Auth 模塊的方法
  • 修改密碼必須調用 .save() 保存,不然無效

使用 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

這樣之後,全部的 auth 模塊功能,所有都基於你建立的表,而再也不使用 auth_user(不會再自動創那些表了)

效仿 django中間件配置 實現 功能插拔式效果

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 使用技巧

想知道當前跳進來的代碼的位置,能夠點擊圖中的圖標,快速定位,可讓你知道目錄結構(看看推測對不對)

原文出處:https://www.cnblogs.com/suwanbin/p/11591887.html

相關文章
相關標籤/搜索