Django對中間件的調用思想、csrf中間件詳細介紹、Django settings源碼剖析、Django的Auth模組

  • 2019 年 12 月 16 日
  • 筆記

使用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='[email protected]') # 創建超級用戶

校驗用戶名和密碼

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模組的功能都可以在你定義的表中使用。