高級配置文件, csrf, django settings源碼, django auth模塊, 文件配置的插拔式設計

[TOC]html

基於jiango中間件思想實現文件配置

通知功能: 郵件, 短信, 微信python

importlib模塊

import importlib

res = 'lib_test.test'

# 正常導入模塊
# from lib_test import test
# print(test)  # <module 'lib_test.test' from '...'>

# 利用字符串的形式導入模塊, 該字符串最小單位到文件名, 不能到文件裏的變量名
md = importlib.import_module(res)
print(md)  # <module 'lib_test.test' from '...'>

仿照django中間件的文件配置方式

notify(Package): init.py, email.py, msg.py, wechat.pyajax

以後新加功能或是刪除功能只須要在NOTIFY_LIST中操做對應的字符串便可數據庫

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


__init__.py:
import settings
import importlib


def send_all(content):
    for path_name in settings.NOTIFY_LIST:
        md_name, cls_name = path_name.rsplit('.', maxsplit=1)  # 從右開始以"."開始切割字符串, 只切割一次
        md = importlib.import_module(md_name)  # 以email爲例, 經過字符串導入notify.email
        cls = getattr(md, cls_name)  # 經過類名獲取notify.email中的Email
        obj = cls()
        obj.send(content)


start.py:
from notify import *

send_all('開始放假啦!')
'''

跨站請求僞造

釣魚網站

  1. 本身寫一個根中國銀行正規網站如出一轍的頁面
  2. 用戶輸入用戶名, 密碼, 轉帳帳戶, 轉帳金額並提交
  3. 請求確實是朝中國銀行的接口發送的, 可是轉帳帳戶變成了釣魚網站提早設置的帳戶
  4. 釣魚網站的form表單中讓用戶填寫轉帳帳戶的input框剔除了name屬性,
  5. 而且隱藏了一個input框, 其value值爲提早設置的釣魚帳戶而且還具備對應的name屬性

模擬該現象的產生

建立兩個django項目, 一個爲正規網站的服務器, 一個爲釣魚網站的服務器django

'''
dj fake

views.py
def transfer(request):
    return render(request, 'transfer.html')
      
transfer.html
<form action="http://127.0.0.1:8000/transfer/" method="post">  # 將用戶輸入的信息朝正規網站接口發送
    ...
    <p>
        target_account: <input type="text">
        <input type="text" name="target_account" value="jason" style="display: none">
    </p>
    ...
</form>
'''

跨站請求僞造解決方法

只處理同一個瀏覽器發出的POST請求,後端

關鍵: 判斷post請求是否爲同一瀏覽器發出的瀏覽器

解決方法:服務器

  • 服務器在返回給用戶一個form表單的時候, 會自動在該表單中隱藏一個input框, <input type="hidden" name="csrfmiddlewaretoken" value="...">
  • 該框的value是一個隨機的token加密字符串, 該token永不重複, 最重要的是會惟一標識瀏覽器信息, 而且隨每次POST請求動態變化

CsrfViewMiddleware: 校驗瀏覽器POST請求中csrfmiddlewaretoken的中間件微信

  • {% csrf_token %}, 瀏覽器在渲染HTML模板時會在對應位置作處理

ajax請求添加csrf校驗

csrf校驗機制: 在提交的post請求數據中校驗是否有 key爲csrfmiddlewaretoken, value爲token加密字符串的數據session

方式一:

先在頁面任意非form表單位置書寫{% csrf_token %}, 在發送ajax請求時, 經過標籤查找獲取token鍵值對添加到ajax的data參數中

'''
...
            $.ajax({
                url: '',
                type: 'post',
                data: {'username': 'jason', 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                success: function (data) {
                    alert(data)
                }
            })
...
</script>
'''

方式二:

  1. 通用方式, 新建setup.js文件拷貝官網提供的js代碼,
  2. 而後在HTML模板頁面任意位置: 1. 使用script標籤的src參數導入該setup.js文件, 2. 不須要在該頁面再寫任何csrf相關的代碼
  3. <script src="{% static 'setup.js' %}"></script>

csrf相關的兩個裝飾器

在FBV上裝飾的狀況

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


@csrf_exempt  # 裝飾器, 不進行csrf校驗
def index(request):
    return HttpResponse('index')


@csrf_protect  # 裝飾器, 進行csrf校驗
def login(request):
    return HttpResponse('login')

在CBV上裝飾的狀況

csrf_protect裝飾器對於cbv的三種裝飾方式都適用

csrf_exempt裝飾器只能給dispatch函數裝才能生效

from django import views
from django.utils.decorators import method_decorator


# @method_decorator(csrf_exempt, name='post')  # 不支持
class MyIndex(views.View):
    @method_decorator(csrf_exempt, name='dispatch')
    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')  # 不支持
    def post(self, request):
        return HttpResponse('OK了')

'''
urls.py: 
	url(r'^cbv/', views.MyIndex.as_view())
	
transfer.html: 
	<form action="/cbv/" method="post">
'''

django settings源碼

django有兩個配置文件:

  • 一個是內部全局的, 一個是展現給用戶能夠自定義配置的,
  • 用戶配置了就使用用戶的, 用戶沒有配置就使用內部全局的

實現原理:

  • 先加載全局配置, 給對象設置,
  • 而後加載局部配置, 再給對象設置,
  • 一旦有重複的項, 後者覆蓋前者
'''
from django.conf import settings
from real import settings

1. settings = LazySettings(), 基於模塊的單例模式, settings爲一個對象
2. os.environ是一個內部全局的大字典,
3. manage.py: os.environ.setdefault("DJANGO_SETTINGS_MODULE", "real.settings"), 給內部全局的大字典設置鍵值對
4. class LazySettings所在文件中的全局變量: ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE",
5. class LazySettings:  
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        settings_module爲展現給用戶的配置文件的路徑, '項目名.settings'
        self._wrapped = Settings(settings_module)
6. class Settings:
        for setting in dir(global_settings): 獲取django內部全局的settings中全部的變量名
            if setting.isupper():  # 變量名必須大寫
                setattr(self, setting, getattr(global_settings, setting))  
                # self: settings._wrapped對象(組合), 給settings._wrapped對象設置內部全局的settings中的鍵值對
        
        self.SETTINGS_MODULE = settings_module  # '項目名.settings'
        mod = importlib.import_module(self.SETTINGS_MODULE)  # from 項目名 import settings, mod指向展現給用戶的配置文件
        
        for setting in dir(mod):  # 獲取展現給用戶的配置文件中全部的變量名
            if setting.isupper():  # 變量名必須是大寫
                setting_value = getattr(mod, setting)  # 經過反射獲取大寫變量名所對應的值
                setattr(self, setting, setting_value)  # 給settings._wrapped對象設置展現給用戶的settings中的鍵值對
'''

django auth模塊

建立用戶

建立超級用戶(root): python manage.py createsuperuser

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.objects.create(username=username, password=password)  # 不可用, 密碼不是加密的
        User.objects.create_user(username=username, password=password)  # 建立普通用戶, 建立時密碼自動加密, 比對時密碼自動解密
        User.objects.create_superuser(username=username, password=password, email='123@qq.com')  # 建立超級用戶, 必需要有郵箱名
        return HttpResponse('註冊成功!')

    return render(request, 'register.html')

校驗用戶

from django.contrib import auth


def log_in(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # res = User.objects.filter(username=username, password=password)
        # print(res)  # <QuerySet []>, 表中密碼爲密文, 沒法直接校驗

        res = auth.authenticate(request, username=username, password=password)  # 自動加密密碼, 而後去數據庫校驗, 必須傳兩個參數
        # print(res)  # Dragon, 內部封裝好了__str__方法, res實際是一個對象
        # print(res.username)  # Dragon
        # print(res.password)  # pbk..., 密碼對應的密文

        if res:
            # 保存登陸狀態, 以前本身保存: request.session['user'] = res.username
            auth.login(request, res)  # 執行後能夠在後端任意位置經過request.user獲取當前登陸用戶對象

            return HttpResponse('登陸成功!')

    return render(request, 'log_in.html')


def get_user(request):
    print(request.user, type(request.user))
    # Dragon, <class 'django.utils.functional.SimpleLazyObject'>, 未登陸: AnonymousUser, 匿名用戶

    print(request.user.is_authenticated())  # True/False, 判斷當前用戶是否登陸
    return HttpResponse('OK!')

修改密碼

from django.contrib.auth.decorators import login_required  # 導入登陸認證裝飾器


# @login_required(login_url='/log_in/')  # 局部配置登陸認證裝飾器, login_url參數爲用戶未登陸時的跳轉頁面
@login_required  # 全局配置登陸認證裝飾器: settings.py-->LOGIN_URL = '/log_in/', 若是局部和全局都配置了以局部爲準
def change_password(request):
    user_obj = request.user  # 通過登陸認證裝飾器後, user_obj確定不爲空

    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)  # check_password: 校驗密碼
        if is_right:
            request.user.set_password(new_password)  # set_password: 設置新密碼
            request.user.save()  # 修改後須要保存

    return render(request, 'change_password.html', locals())

註銷

會刪除django_session表中的對應記錄

@login_required
def logout(request):
    # 本身刪: request.session.flush()
    auth.logout(request)
    return HttpResponse('註銷成功!')

auth模塊擴展表字段

from django.db import models
from django.contrib.auth.models import User, AbstractUser


# 方式一: 經過外鍵字段作一對一擴展
class UserDetail(models.Model):
    phone = models.BigIntegerField()
    user = models.OneToOneField(to='User')


# 方式二: 經過繼承 + settings.py-->AUTH_USER_MODEL = 'app01.UserInfo'  # 應用名.表名(類名) 擴展
class UserInfo(AbstractUser):
    phone = models.BigIntegerField()  # 擴展字段不能和原auth_user表中的原字段重複
    register_time = models.DateField(auto_now_add=True)

# 方式二擴展字段後, auth模塊全部功能仍可使用, 而且以自定義的表爲準

基於django settings源碼實現配置文件插拔式設計

'''
目錄: 
    about_django_settings(python項目)
        conf
            settings.py
        lib
            conf
                __init__.py
                global_setings.py
		start.py          
'''

# settings.py: NAME = '展現給用戶的自定義配置'

# global_settings.py: NAME = '項目默認的配置文件'

# __init__.py
import importlib
from lib.conf import global_settings
import os


class Settings(object):
    def __init__(self):
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))

        module_path = os.environ.get('SETTINGS_MODULE')
        md = importlib.import_module(module_path)  # md == settings
        for setting in dir(md):
            if setting.isupper():
                setattr(self, setting, getattr(md, setting))


settings = Settings()

# start.py
import os
import sys

BASE_DIR = os.path.dirname(__file__)

# 把當前項目的根目錄添加到環境變量, 使導入語句不會報錯, 使用pycharm會自動添加
sys.path.append(BASE_DIR)

if __name__ == '__main__':
    # os.environ.setdefault('SETTINGS_MODULE', 'conf.settings')
    os.environ['SETTINGS_MODULE'] = 'conf.settings'

    from lib.conf import settings

    print(settings.NAME)
相關文章
相關標籤/搜索