django 認(rèn)證系統(tǒng)

COOKIE與SESSION

概念

cookie不屬于http協(xié)議范圍,由于http協(xié)議無(wú)法保持狀態(tài),但實(shí)際情況,我們卻又需要“保持狀態(tài)”,因此cookie就是在這樣一個(gè)場(chǎng)景下誕生。

cookie的工作原理是:由服務(wù)器產(chǎn)生內(nèi)容,瀏覽器收到請(qǐng)求后保存在本地;當(dāng)瀏覽器再次訪問(wèn)時(shí),瀏覽器會(huì)自動(dòng)帶上cookie,這樣服務(wù)器就能通過(guò)cookie的內(nèi)容來(lái)判斷這個(gè)是“誰(shuí)”了。

cookie雖然在一定程度上解決了“保持狀態(tài)”的需求,但是由于cookie本身最大支持4096字節(jié),以及cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節(jié),并且他保存在服務(wù)器,有較高的安全性。這就是session。
問(wèn)題來(lái)了,基于http協(xié)議的無(wú)狀態(tài)特征,服務(wù)器根本就不知道訪問(wèn)者是“誰(shuí)”。那么上述的cookie就起到橋接的作用。

我們可以給每個(gè)客戶端的cookie分配一個(gè)唯一的id,這樣用戶在訪問(wèn)時(shí),通過(guò)cookie,服務(wù)器就知道來(lái)的人是“誰(shuí)”。然后我們?cè)俑鶕?jù)不同的cookie的id,在服務(wù)器上保存一段時(shí)間的私密資料,如“賬號(hào)密碼”等等。

總結(jié)而言:cookie彌補(bǔ)了http無(wú)狀態(tài)的不足,讓服務(wù)器知道來(lái)的人是“誰(shuí)”;但是cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過(guò)cookie識(shí)別不同的用戶,對(duì)應(yīng)的在session里保存私密的信息以及超過(guò)4096字節(jié)的文本。

另外,上述所說(shuō)的cookie和session其實(shí)是共通性的東西,不限于語(yǔ)言和框架

登錄應(yīng)用

前幾節(jié)的介紹中我們已經(jīng)有能力制作一個(gè)登陸頁(yè)面,在驗(yàn)證了用戶名和密碼的正確性后跳轉(zhuǎn)到后臺(tái)的頁(yè)面。但是測(cè)試后也發(fā)現(xiàn),如果繞過(guò)登陸頁(yè)面。直接輸入后臺(tái)的url地址也可以直接訪問(wèn)的。這個(gè)顯然是不合理的。其實(shí)我們?nèi)笔У木褪莄ookie和session配合的驗(yàn)證。有了這個(gè)驗(yàn)證過(guò)程,我們就可以實(shí)現(xiàn)和其他網(wǎng)站一樣必須登錄才能進(jìn)入后臺(tái)頁(yè)面了。

先說(shuō)一下這種認(rèn)證的機(jī)制。每當(dāng)我們使用一款瀏覽器訪問(wèn)一個(gè)登陸頁(yè)面的時(shí)候,一旦我們通過(guò)了認(rèn)證。服務(wù)器端就會(huì)發(fā)送一組隨機(jī)唯一的字符串(假設(shè)是123abc)到瀏覽器端,這個(gè)被存儲(chǔ)在瀏覽端的東西就叫cookie。而服務(wù)器端也會(huì)自己存儲(chǔ)一下用戶當(dāng)前的狀態(tài),比如login=true,username=hahaha之類的用戶信息。但是這種存儲(chǔ)是以字典形式存儲(chǔ)的,字典的唯一key就是剛才發(fā)給用戶的唯一的cookie值。那么如果在服務(wù)器端查看session信息的話,理論上就會(huì)看到如下樣子的字典

{'123abc':{'login':true,'username:hahaha'}}

因?yàn)槊總€(gè)cookie都是唯一的,所以我們?cè)陔娔X上換個(gè)瀏覽器再登陸同一個(gè)網(wǎng)站也需要再次驗(yàn)證。那么為什么說(shuō)我們只是理論上看到這樣子的字典呢?因?yàn)樘幱诎踩缘目紤],其實(shí)對(duì)于上面那個(gè)大字典不光key值123abc是被加密的,value值{'login':true,'username:hahaha'}在服務(wù)器端也是一樣被加密的。所以我們服務(wù)器上就算打開(kāi)session信息看到的也是類似與以下樣子的東西

{'123abc':dasdasdasd1231231da1231231}

知道了原理,下面就來(lái)用代碼實(shí)現(xiàn)。

Django實(shí)現(xiàn)的COOKIE

1、獲取Cookie

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
    #參數(shù):
        default: 默認(rèn)值
           salt: 加密鹽
        max_age: 后臺(tái)控制過(guò)期時(shí)間

2、設(shè)置Cookie

rep = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect()
 
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密鹽',...) 

參數(shù):

def set_cookie(self, key,                 鍵
             value='',            值
             max_age=None,        超長(zhǎng)時(shí)間
             expires=None,        超長(zhǎng)時(shí)間
             path='/',           Cookie生效的路徑,
                                         瀏覽器只會(huì)把cookie回傳給帶有該路徑的頁(yè)面,這樣可以避免將
                                         cookie傳給站點(diǎn)中的其他的應(yīng)用。
                                         / 表示根路徑,特殊的:根路徑的cookie可以被任何url的頁(yè)面訪問(wèn)
             
                     domain=None,         Cookie生效的域名
                                        
                                          你可用這個(gè)參數(shù)來(lái)構(gòu)造一個(gè)跨站cookie。
                                          如, domain=".example.com"
                                          所構(gòu)造的cookie對(duì)下面這些站點(diǎn)都是可讀的:
                                          www.example.com 、 www2.example.com 
                         和an.other.sub.domain.example.com 。
                                          如果該參數(shù)設(shè)置為 None ,cookie只能由設(shè)置它的站點(diǎn)讀取。

             secure=False,        如果設(shè)置為 True ,瀏覽器將通過(guò)HTTPS來(lái)回傳cookie。
             httponly=False       只能http協(xié)議傳輸,無(wú)法被JavaScript獲取
                                         (不是絕對(duì),底層抓包可以獲取到也可以被覆蓋)
          ): pass

由于cookie保存在客戶端的電腦上,所以,JavaScript和jquery也可以操作cookie。

$.cookie("key", value,{ path: '/' });

3、刪除cookie

response.delete_cookie("cookie_key",path="/",domain=name)

cookie存儲(chǔ)到客戶端
優(yōu)點(diǎn):
數(shù)據(jù)存在在客戶端,減輕服務(wù)器端的壓力,提高網(wǎng)站的性能。
缺點(diǎn):
安全性不高:在客戶端機(jī)很容易被查看或破解用戶會(huì)話信息

Django實(shí)現(xiàn)的SESSION

1、 基本操作

1、設(shè)置Sessions值
          request.session['session_name'] ="admin"
2、獲取Sessions值
          session_name = request.session["session_name"]
3、刪除Sessions值
          del request.session["session_name"]
4、檢測(cè)是否操作session值
          if "session_name" is request.session :
5、get(key, default=None)
 
fav_color = request.session.get('fav_color', 'red')
 
6、pop(key)
 
fav_color = request.session.pop('fav_color')
 
7、keys()
 
8、items()
 
9、setdefault()
 
10、flush() 刪除當(dāng)前的會(huì)話數(shù)據(jù)并刪除會(huì)話的Cookie。
            這用于確保前面的會(huì)話數(shù)據(jù)不可以再次被用戶的瀏覽器訪問(wèn)
            例如,django.contrib.auth.logout() 函數(shù)中就會(huì)調(diào)用它。
 
 
11 用戶session的隨機(jī)字符串
        request.session.session_key
  
        # 將所有Session失效日期小于當(dāng)前日期的數(shù)據(jù)刪除
        request.session.clear_expired()
  
        # 檢查 用戶session的隨機(jī)字符串 在數(shù)據(jù)庫(kù)中是否
        request.session.exists("session_key")
  
        # 刪除當(dāng)前用戶的所有Session數(shù)據(jù)
        request.session.delete("session_key")
  
        request.session.set_expiry(value)
            * 如果value是個(gè)整數(shù),session會(huì)在些秒數(shù)后失效。
            * 如果value是個(gè)datatime或timedelta,session就會(huì)在這個(gè)時(shí)間后失效。
            * 如果value是0,用戶關(guān)閉瀏覽器session就會(huì)失效。
            * 如果value是None,session會(huì)依賴全局session失效策略。

2、 流程解析

image.png

3、 示例

views:

def log_in(request):
    if request.method=="POST":
        username=request.POST['user']
        password=request.POST['pwd']
        user=UserInfo.objects.filter(username=username,password=password)

        if user:
            #設(shè)置session內(nèi)部的字典內(nèi)容
            request.session['is_login']='true'
            request.session['username']=username

            #登錄成功就將url重定向到后臺(tái)的url
            return redirect('/backend/')

    #登錄不成功或第一次訪問(wèn)就停留在登錄頁(yè)面
    return render(request,'login.html')


def backend(request):
    print(request.session,"------cookie")
    print(request.COOKIES,'-------session')
    """
    這里必須用讀取字典的get()方法把is_login的value缺省設(shè)置為False,
    當(dāng)用戶訪問(wèn)backend這個(gè)url先嘗試獲取這個(gè)瀏覽器對(duì)應(yīng)的session中的
    is_login的值。如果對(duì)方登錄成功的話,在login里就已經(jīng)把is_login
    的值修改為了True,反之這個(gè)值就是False的
    """
    is_login=request.session.get('is_login',False)
    #如果為真,就說(shuō)明用戶是正常登陸的
    if is_login:
        #獲取字典的內(nèi)容并傳入頁(yè)面文件
        cookie_content=request.COOKIES
        session_content=request.session

        username=request.session['username']
        return render(request,'backend.html',locals())
    else:
        """
        如果訪問(wèn)的時(shí)候沒(méi)有攜帶正確的session,
        就直接被重定向url回login頁(yè)面
        """
        return redirect('/login/')

def log_out(request):
    """
    直接通過(guò)request.session['is_login']回去返回的時(shí)候,
    如果is_login對(duì)應(yīng)的value值不存在會(huì)導(dǎo)致程序異常。所以
    需要做異常處理
    """
    try:
        #刪除is_login對(duì)應(yīng)的value值
        del request.session['is_login']
        # OR---->request.session.flush() # 刪除django-session表中的對(duì)應(yīng)一行記錄
    except KeyError:
        pass
    #點(diǎn)擊注銷之后,直接重定向回登錄頁(yè)面
    return redirect('/login/')

template:

===================================login.html==================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/login/" method="post">
    <p>用戶名: <input type="text" name="user"></p>
    <p>密碼: <input type="password" name="pwd"></p>
    <p><input type="submit"></p>
</form>


</body>
</html>


===================================backend.html==================

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>hello {{ username }}</h3>
<a href="/logout/">注銷</a>

</body>
</html>

4、session存儲(chǔ)的相關(guān)配置

(1)數(shù)據(jù)庫(kù)配置(默認(rèn)):

Django默認(rèn)支持Session,并且默認(rèn)是將Session數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)中,即:django_session 表中。
  
a. 配置 settings.py
  
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默認(rèn))
      
    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在瀏覽器上時(shí)的key,即:sessionid=隨機(jī)字符串(默認(rèn))
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路徑(默認(rèn))
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默認(rèn))
    SESSION_COOKIE_SECURE = False                            # 是否Https傳輸cookie(默認(rèn))
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http傳輸(默認(rèn))
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默認(rèn))
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否關(guān)閉瀏覽器使得Session過(guò)期(默認(rèn))
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次請(qǐng)求都保存Session,默認(rèn)修改之后才保存(默認(rèn))

(2)緩存配置

配置 settings.py
  
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    SESSION_CACHE_ALIAS = 'default'                            # 使用的緩存別名(默認(rèn)內(nèi)存緩存,也可以是memcache),此處別名依賴緩存的設(shè)置
  
    SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在瀏覽器上時(shí)的key,即:sessionid=隨機(jī)字符串
    SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路徑
    SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                             # 是否Https傳輸cookie
    SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http傳輸
    SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否關(guān)閉瀏覽器使得Session過(guò)期
    SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次請(qǐng)求都保存Session,默認(rèn)修改之后才保存

(3)文件配置

配置 settings.py
  
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    SESSION_FILE_PATH = None                                    # 緩存文件路徑,如果為None,則使用tempfile模塊獲取一個(gè)臨時(shí)地址tempfile.gettempdir()        
    SESSION_COOKIE_NAME = "sessionid"                          # Session的cookie保存在瀏覽器上時(shí)的key,即:sessionid=隨機(jī)字符串
    SESSION_COOKIE_PATH = "/"                                  # Session的cookie保存的路徑
    SESSION_COOKIE_DOMAIN = None                                # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                               # 是否Https傳輸cookie
    SESSION_COOKIE_HTTPONLY = True                              # 是否Session的cookie只支持http傳輸
    SESSION_COOKIE_AGE = 1209600                                # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                     # 是否關(guān)閉瀏覽器使得Session過(guò)期
    SESSION_SAVE_EVERY_REQUEST = False                          # 是否每次請(qǐng)求都保存Session,默認(rèn)修改之后才保存

用戶認(rèn)證

auth模塊

from django.contrib import auth

django.contrib.auth中提供了許多方法,這里主要介紹其中的三個(gè):

1 、authenticate()

提供了用戶認(rèn)證,即驗(yàn)證用戶名以及密碼是否正確,一般需要username password兩個(gè)關(guān)鍵字參數(shù)

如果認(rèn)證信息有效,會(huì)返回一個(gè) User 對(duì)象。authenticate()會(huì)在User 對(duì)象上設(shè)置一個(gè)屬性標(biāo)識(shí)哪種認(rèn)證后端認(rèn)證了該用戶,且該信息在后面的登錄過(guò)程中是需要的。當(dāng)我們?cè)噲D登陸一個(gè)從數(shù)據(jù)庫(kù)中直接取出來(lái)不經(jīng)過(guò)authenticate()的User對(duì)象會(huì)報(bào)錯(cuò)的!!

user = authenticate(username='someone',password='somepassword')

2 、login(HttpRequest, user)

該函數(shù)接受一個(gè)HttpRequest對(duì)象,以及一個(gè)認(rèn)證了的User對(duì)象
此函數(shù)使用django的session框架給某個(gè)已認(rèn)證的用戶附加上session id等信息。

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.
    ...

3 、logout(request) 注銷用戶

from django.contrib.auth import logout
   
def logout_view(request):
  logout(request)
  # Redirect to a success page.

該函數(shù)接受一個(gè)HttpRequest對(duì)象,無(wú)返回值。當(dāng)調(diào)用該函數(shù)時(shí),當(dāng)前請(qǐng)求的session信息會(huì)全部清除。該用戶即使沒(méi)有登錄,使用該函數(shù)也不會(huì)報(bào)錯(cuò)。

4 、user對(duì)象的 is_authenticated()

要求:

1 用戶登陸后才能訪問(wèn)某些頁(yè)面,

2 如果用戶沒(méi)有登錄就訪問(wèn)該頁(yè)面的話直接跳到登錄頁(yè)面

3 用戶在跳轉(zhuǎn)的登陸界面中完成登陸后,自動(dòng)訪問(wèn)跳轉(zhuǎn)到之前訪問(wèn)的地址
方法1:

def my_view(request):
  if not request.user.is_authenticated():
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

方法2:
django已經(jīng)為我們?cè)O(shè)計(jì)好了一個(gè)用于此種情況的裝飾器:login_requierd()

from django.contrib.auth.decorators import login_required
      
@login_required
def my_view(request):
  ...

若用戶沒(méi)有登錄,則會(huì)跳轉(zhuǎn)到django默認(rèn)的 登錄URL '/accounts/login/ ' (這個(gè)值可以在settings文件中通過(guò)LOGIN_URL進(jìn)行修改)。并傳遞 當(dāng)前訪問(wèn)url的絕對(duì)路徑 (登陸成功后,會(huì)重定向到該路徑)。

User對(duì)象

User 對(duì)象屬性:username, password(必填項(xiàng))password用哈希算法保存到數(shù)據(jù)庫(kù)

is_staff : 用戶是否擁有網(wǎng)站的管理權(quán)限.

is_active : 是否允許用戶登錄, 設(shè)置為False,可以不用刪除用戶來(lái)禁止 用戶登錄

2.1 、is_authenticated()

如果是真正的 User 對(duì)象,返回值恒為 True 。 用于檢查用戶是否已經(jīng)通過(guò)了認(rèn)證。
通過(guò)認(rèn)證并不意味著用戶擁有任何權(quán)限,甚至也不檢查該用戶是否處于激活狀態(tài),這只是表明用戶成功的通過(guò)了認(rèn)證。 這個(gè)方法很重要, 在后臺(tái)用request.user.is_authenticated()判斷用戶是否已經(jīng)登錄,如果true則可以向前臺(tái)展示request.user.name

2.2 、創(chuàng)建用戶

使用 create_user 輔助函數(shù)創(chuàng)建用戶:

from django.contrib.auth.models import User
user = User.objects.create_user(username='',password='',email='')

2.3 、check_password(passwd)

用戶需要修改密碼的時(shí)候 首先要讓他輸入原來(lái)的密碼 ,如果給定的字符串通過(guò)了密碼檢查,返回 True

2.4 、修改密碼

使用 set_password() 來(lái)修改密碼

user = User.objects.get(username='')
user.set_password(password='')
user.save 

2.5 、簡(jiǎn)單示例

注冊(cè):

def sign_up(request):
    state = None
    if request.method == 'POST':
        password = request.POST.get('password', '')
        repeat_password = request.POST.get('repeat_password', '')
        email=request.POST.get('email', '')
        username = request.POST.get('username', '')
        if User.objects.filter(username=username):
                state = 'user_exist'
        else:
                new_user = User.objects.create_user(username=username, password=password,email=email)
                new_user.save()
                return redirect('/book/')
    content = {
        'state': state,
        'user': None,
    }
    return render(request, 'sign_up.html', content)  

修改密碼:

@login_required
def set_password(request):
    user = request.user
    state = None
    if request.method == 'POST':
        old_password = request.POST.get('old_password', '')
        new_password = request.POST.get('new_password', '')
        repeat_password = request.POST.get('repeat_password', '')
        if user.check_password(old_password):
            if not new_password:
                state = 'empty'
            elif new_password != repeat_password:
                state = 'repeat_error'
            else:
                user.set_password(new_password)
                user.save()
                return redirect("/log_in/")
        else:
            state = 'password_error'
    content = {
        'user': user,
        'state': state,
    }
    return render(request, 'set_password.html', content)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容