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
- 獲取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í)間
- 設(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。
<script src='/static/js/jquery.cookie.js'>
</script> $.cookie("key", value,{ path: '/' });
- 刪除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、設(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失效策略。
-
流程解析圖
image.png 示例
view:
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)擊注銷(xiāo)之后,直接重定向回登錄頁(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/">注銷(xiāo)</a>
</body>
</html>
- 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)緩存配置:
a. 配置 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)文件配置
a. 配置 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è):
- 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ò)的!!
- 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.
...
- logout(request) 注銷(xiāo)用戶
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ò)。
- 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)