django 之csrf、auth模塊及settings源碼、插拔式設計

[TOC]javascript

csrf:Cross Site Request Forgery protectioncss

基於django中間件拷貝思想

# start.py
import notify
notify.send_all('小寶貝們!快要放假啦,大家都去哪裏玩呀?')

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


# notify文件夾下(start.py和.settings.py和notify文件夾在同一目錄下)
# __init__.py
# coding:utf8

import settings
import importlib
def send_all(content):
    for path in settings.NOTIFY_LIST:
        file_path,cls_name = path.rsplit('.',1)
        file_name = importlib.import_module(file_path)
        cls = getattr(file_name,cls_name)
        obj = cls()
        obj.send(content)
        
# email.py
# coding:utf8
class Email:
    def __init__(self):
        pass
    def send(self,content):
        print('郵件通知:%s'%content)

# msg.py
# coding:utf8
class Msg:
    def __init__(self):
        pass
    def send(self,content):
        print('短信通知:%s'%content)
 
# wechat.py
# coding:utf8
class WeChat:
    def __init__(self):
        pass
    def send(self,content):
        print('微信通知:%s'%content)

跨站請求僞造簡介

釣魚網站
			你本身寫一個跟中國銀行正規網站如出一轍的頁面
			用戶輸入用戶名 密碼 對方帳戶  轉帳金額提交
			請求確實是朝中國銀行的接口發送的 錢也扣了
			可是對方帳戶變了 變成了釣魚網站本身提早設置好的帳戶
			
			如何實現
				你在寫form表單的時候 讓用戶填寫的對方帳戶input並無name屬性
				而是你本身在內部偷偷隱藏了一個具備name屬性的input框
				而且value值是你本身的帳戶 而後將該標籤隱藏了
				
			模擬該現象的產生
				建立兩個django項目

ps:html

def transfer(request):
    if request.method =='POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print('%s給%s轉帳了%s元錢!'%(username,target_user,money))
    return render(request,'transfer.html')
<!--正經網站-->
<body>
<p>這是legal_site</p>
<form action="" method="post">
    <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>
</body>
<!--釣魚網站-->

<body>
<p>這是fake_site</p>
    <!--不指定路徑,默認往當前網站提交,因此這裏要僞形成往真正的網站站點提交,真網站後臺獲取的是釣魚網站隱藏的用戶名的input框指定的value屬性值(默認值,就是騙子帳戶),那麼以後轉帳的處理功能可定就是往這個騙子帳戶轉錢了-->
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>username:<input type="text" name="username"></p>
    <!--吧用戶轉給真正目標用戶的input框的name屬性刪除了,本身再多加一個input框添加上name屬性,並設上value(默認值)屬性(騙子的帳戶名),並把該標籤隱藏,後端獲取name屬性的username,就是騙子設置value屬性的騙子帳戶-->
    <p>target_account:<input type="text" >
        <input type="text" name="target_user"  value="zhang" style="display:none">
    </p>
    <p>money:<input type="text" name ="money"></p>
    <input type="submit">
</form>
</body>

跨站請求僞造解決思路

如何解決該問題:
	只處理本網站發送的post請求

如何識別如何判斷當前請求是不是本網張發出的

    解決:
   網站在返回給用戶一個form表單的時候 會自動在該表單隱藏一個input框
這個框的value是一個隨機字符串 可是網站可以記住給每個瀏覽器發送的隨機字符串,並與本身後臺保存的隨機字符串比較,二者一致則經過,不然就返回不存在(403),再也不對該請求作處理;

方式1:form表單發post請求解決方法

在寫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>

下圖是網站在form表單隱藏的隨機字符串,那麼屬性就是settings文件中的django自帶的七個中間件中的一個,之後用form表單發送post請求時,表單內寫上{% csrf_token %},就不須要註釋settings中的中間件django.middleware.csrf.CsrfViewMiddleware了。前端

方式2:ajax發post請求解決方法

ajax方式2:java

較爲繁瑣python

​ 先在頁面任意的位置上書寫{% csrf_token %},而後在發送ajax請求的時候 經過標籤查找獲取隨機字符串添加到data自定義對象便可。jquery

<!--ajax方式1:-->

{% csrf_token %}
<button id="d1">發送ajax</button>
<script>
    $('#d1').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'zhang','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
            success:function (data) {
                
            }
        })
    })
</script>

ajax方式2:ajax

較爲簡單數據庫

在ajax方式1的基礎上,將ajax內部參數data改爲以下方式,這樣就不須要在任意位置寫{{% csrf_token %}}了, 由於data中的模板語法{{csrf_token}}就至關於一個變量,幫咱們生成一個隨機字符串。django

<!--ajax方式2:-->
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}

ajax方式3:

最通用的一種方式

官網提供的文件,直接興建js文件拷貝代碼 導入便可 你不須要作任何的csrf相關的代碼書寫

第一步:先在settings文件中配置static路徑:

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static')
]

第二部:創建static文件夾,自內部建一個js文件,將以下代碼拷進去;

// setup.js文件

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

第三步:在html頁面上經過導入該文件便可自動幫咱們解決ajax提交post數據時校驗csrf_token的問題,(導入該配置文件以前,須要先導入jQuery,由於這個配置文件內的內容是基於jQuery來實現的)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    <button id="d1">發送ajax</button>
    
<!--導入該js文件-->
<script src="/static/setup.js"></script>

<script>
    $('#d1').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'zhang'},
            success:function (data) {
                alert(data)
            }
        })
    })
</script>
</body>
</html>

scrf詳見===>Djagno官方文檔中關於CSRF的內容](https://docs.djangoproject.com/en/1.11/ref/csrf/)

csrf相關的兩個裝飾器

將settings中csrf中間件註釋掉,表明全部的視圖函數在瀏覽器的請求都不校驗;

註釋掉csrf中間件的狀況下,若是想對某個post請求的視圖函數校驗,能夠加裝飾器@csrf_protect

沒註釋掉csrf中間件的狀況下,若是朝某個視圖函數發送post請求,排除校驗,能夠加裝飾器@csrf_exempt

這兩個裝飾器跟settings中的csrf中間配置沒有關係,它內部源碼自動調用csrf。

from django.shortcuts import render,HttpResponse
    from django.views.decorators.csrf import csrf_exempt,csrf_protect
    # @csrf_exempt  # 不校驗 csrf
    def index(request):
        return HttpResponse('index')

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

csrf裝飾器在CBV上的特例

csrf_exempt和csrf_protect裝飾器在CBV上的不一樣:

​ csrf_exempt:這個裝飾器只能給dispatch裝才能生效

​ csrf_protect:全均可以,跟普通的裝飾器裝飾CBV一致

ps:csrf_exempt

# @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持該方法
@method_decorator(csrf_exempt,name='dispatch')  # 意思是給其類內部名爲dispatch的函數裝裝飾器,支持該方法。
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')

ps:csrf_protect

# @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有兩個配置文件 一個是暴露給用戶能夠配置的

​ 一個是內部全局的(用戶配置了就用用戶的 用戶沒有配就用本身的)

from django.conf import global_settings
from django.conf import settings

obj.name = 'zhang'  # 全局
obj.name = 'li'  # 局部
		
先加載全局配置 給對象設置
而後在加載局部配置  再給對象設置
一旦有重複的項  後者覆蓋前者
class Settings(object):
    def __init__(self, settings_module):  # 項目名:settings
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):  # 獲取django全局配置文件中全部的變量名
            if setting.isupper():  # 只有大寫的才能經過
                setattr(self, setting, getattr(global_settings, setting))
                # self就是settings
        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module

        mod = importlib.import_module(self.SETTINGS_MODULE)
        # from 項目名 import settings 導入暴露給用戶的配置文件
        # mod就是模塊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)  # 
                self._explicit_settings.add(setting)

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

        if hasattr(time, 'tzset') and self.TIME_ZONE:
            # When we can, attempt to validate the timezone. If we can't find
            # this file, no check happens and it's harmless.
            zoneinfo_root = '/usr/share/zoneinfo'
            if (os.path.exists(zoneinfo_root) and not
                    os.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))):
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # Move the time zone info into os.environ. See ticket #2315 for why
            # we don't do this unconditionally (breaks Windows).
            os.environ['TZ'] = self.TIME_ZONE
            time.tzset()

    def is_overridden(self, setting):
        return setting in self._explicit_settings

    def __repr__(self):
        return '<%(cls)s "%(settings_module)s">' % {
            'cls': self.__class__.__name__,
            'settings_module': self.SETTINGS_MODULE,
        }

auth模塊簡介

Auth模塊使Django自帶的用戶認證模塊,它內置了強大的用戶認證系統--auth,默認使用auth-user表來存儲用戶數據;這樣開發人員就不用手寫django-ORM部分的登陸、註冊,用戶認證、註銷、修改密碼等功能,以及建立user表的麻煩。

首先,執行數據庫遷移命令,找到auth-user這張表,內部有:password、last_login、is_superuser first_name last_name email is_staff is_active date_joined username 字段。

而後建立按超級用戶:

​ python3 manage.py createsuperuser # cmd執行該命令

​ 須要輸入建立超級用戶的用戶名和密碼以及再次建立密碼

auth建立用戶

1.建立用戶

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')
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='111@qq.com')
        return HttpResponse('註冊成功!!')

    return render(request,'register.html')

2.校驗用戶名和密碼是否正確

from django.contrib import auth
# 必須傳用戶名和密碼兩個參數缺一不能
user_obj = auth.authenticate(request,username=username,password=password)

3.保存用戶登陸狀態

auth.login(request,user_obj)
# 只要這句話執行了 後面在任意位置 只要你能拿到request你就能夠經過request.user獲取到當前登陸的用戶對象
def login_dunc(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 校驗當前用戶名和密碼是否正確
        # models.User.objects.filter(username=username,password=password)
        # res = User.objects.filter(username=username,password=password)  # 密碼沒法校驗,前端傳的是明文,後端存的是密文
        # print(res)
        res = auth.authenticate(request,username=username,password=password)  # 自動加密密碼 而後去數據庫校驗
        # print(res)  # 數據對象
        # print(res.username)
        # print(res.password)  # 密碼是密文
        if res:
            # 保存用戶登陸狀態
            # request.session['user'] = 'zhang'
            auth.login(request,res)
            """
            只要執行了這一句話 以後你能夠在任意位置
            經過request.user獲取到當前登陸用戶對象
            """
            return HttpResponse('ok')
    return render(request,'login_dunc.html')

4.判斷當前用戶是否登陸

request.user.is_authenticated()
def get_user(request):
    print(request.user)  # AnonymousUser  匿名用戶
    print(type(request.user))  # <class'django.utils.functional'>
    print(request.user.is_authenticated())  # 判斷當前用戶是否登陸,返回布爾值
    return HttpResponse('ok')

5.校驗原密碼是否正確

request.user.check_password(old_password)

6.修改密碼

request.user.set_password(new_password)
request.user.save() # 修改密碼必定要保存
# @login_required(login_url='/lgg/')  # 局部配置,如沒有登陸,讓用戶直接跳到登陸頁面
@login_required
def check_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:
            request.user.set_password(new_password)
            request.user.save()  # 修改密碼必定要保存
    user_obj = request.user
    return render(request,'change_password.html',locals())

7.註銷

auth.logout(request)
@login_required
def logout(request):
    # request.session.flush()
    auth.logout(request)
    return HttpResponse('註銷了')

8.校驗用戶是否登陸裝飾器

局部配置

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
# 若是全局配置了 局部也配置  以局部的爲準

auth擴展表

如何擴展auth_user表字段?

方式1 利用一對一外鍵字段關係

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

方式2:

​ 利用繼承關係

首先先去配置文件settings中配置
AUTH_USER_MODEL = 'app01.Userinfo'  # 應用名.表名
以後全部的auth模塊功能全都以你寫的表爲準,因爲繼承了AbstractUser,會將auth_user那張表與本身建的這張表整合到一塊兒,auth_user自己所擁有的功能,userinfo也能照常使用,使表的操做性更強了。
# 表字段寫好以後,記得數據庫遷移
from django.contrib.auth.models import AbstractUser
class Userinfo(AbstractUser):
    phone = models.Bi  gIntegerField()
    register_time = models.DateField(auto_now_add=True)

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

conf文件夾、lib文件夾、start.py處於同級

# conf文件夾
# settings.py
NAME = '暴露給用戶自定義配置'
#lib>conf文件夾


# __init__.py文件
import importlib
from lib.conf import global_settings
import os
class Settings(object):
    def __init__(self):
        # print(111111)
        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()

# global_settings.py文件
NAME = '項目默認的配置文件'
# start.py文件
import os
import sys

BASE_DIR = os.path.dirname(__file__)
sys.path.append(BASE_DIR)
if __name__ == '__main__':
    os.environ.setdefault('xxx','conf.settings')
    from lib.conf import settings
    print(settings.NAME)  # settings就是類Settings的對象(單例)
相關文章
相關標籤/搜索