衆所周知,一個網站涉及到數據的增刪改查,因此對於訪問網站的用戶必須是通過合法驗證的,這樣才能對其授予權限進行操做。同時,也能夠方便對登陸的用戶行爲進行審計。css
通常的登陸驗證過程:輸入用戶名和密碼,判斷用戶輸入信息是否正確,若是正確,則登陸成功,進入主頁;若是錯誤,則提示用戶從新輸入。html
實現過程以下:數據庫
urls.py文件:django
urlpatterns = [
url(r'^login.html$', views.login),
url(r'^index.html$', views.index),
]
views.py文件,其中index.html爲登陸成功後的頁面,login.html爲登陸頁面。flask
def login(request):
#提示信息
message = ""
if request.method == "POST":
user = request.POST.get('user')
pwd = request.POST.get('pwd')
c = models.Administrator.objects.filter(username=user, password=pwd).count()
if c:
rep = redirect('/index.html')
return rep
else:
message = "用戶名或密碼錯誤"
obj = render(request,'login.html', {'msg': message})
return obj
index.html視圖函數以下:瀏覽器
def index(request):
return render(request,'index.html')
若是驗證成功,則跳轉到歡迎頁,index.html。緩存
index.html網頁內容以下:安全
{% extends "layout.html" %}
{% block css %}
{% endblock %}
{% block content %}
<h1>歡迎學生管理中心</h1>
{% endblock %}
{% block js %}
{% endblock %}
login.html文件:提交url爲login.html,提交方法爲POST。服務器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
label{
width: 80px;
text-align: right;
display: inline-block;
}
</style>
</head>
<body>
<form action="login.html" method="post">
<div>
<label for="user">用戶名:</label>
<input id="user" type="text" name="user" />
</div>
<div>
<label for="pwd">密碼:</label>
<input id="pwd" type="password" name="pwd" />
</div>
<div>
<label> </label>
<input type="submit" value="登陸" />
<span style="color: red;">{{ msg }}</span>
</div>
</form>
</body>
</html>
體驗過程以下:cookie
雖然登陸達到了目的,也就是成功到達了歡迎頁。可是,這裏存在一個問題,不知道你是否注意到:歡迎頁的右邊用戶名爲空,也就是從login到index進行跳轉的過程當中,咱們怎麼將用戶登陸信息進行攜帶過來呢?
你可能會想到,在login的時候,把登陸的user變量設置成global,這樣不就在任何地方可使用了麼?這樣確實能夠生效,可是對於客戶端,也就是瀏覽器端無法區分當前登陸的是哪一個用戶。好比是購物網站,當一個用戶選了一些商品進入購物車,而後可能由於不當心把網址給關了,當他再次進入網站的時候,發現以前選擇的商品全沒了,此時他可能就沒心思再去選商品,由於還須要花費時間,這樣客戶就流失了。
那是否還有更好的方法解決這個問題呢?有,確定有,並且很早之前人們就想到了這樣的情景,且已經很好的解決了。答案就是session。固然cookie也同樣,由於cookie屬於session的一個變種,不一樣的是,session的內容是存在服務器端,而cookie的內容存在客戶端,用戶的瀏覽器或本地文件內。
具體cookie和session的介紹能夠看以前的文件:12、Django用戶認證之cookie和session
在實現會話的登陸驗證以前,咱們再次來回顧一下session和cookie。
一、cookie不屬於http協議範圍,因爲http協議沒法保持狀態,但實際狀況,咱們卻又須要「保持狀態」,所以cookie就是在這樣一個場景下誕生。
cookie的工做原理是:由服務器產生內容,瀏覽器收到請求後保存在本地;當瀏覽器再次訪問時,瀏覽器會自動帶上cookie,這樣服務器就能經過cookie的內容來判斷這個是「誰」了。
二、cookie雖然在必定程度上解決了「保持狀態」的需求,可是因爲cookie自己最大支持4096字節,以及cookie自己保存在客戶端,可能被攔截或竊取,所以就須要有一種新的東西,它能支持更多的字節,而且他保存在服務器,有較高的安全性。這就是session。
問題來了,基於http協議的無狀態特徵,服務器根本就不知道訪問者是「誰」。那麼上述的cookie就起到橋接的做用。
咱們能夠給每一個客戶端的cookie分配一個惟一的id,這樣用戶在訪問時,經過cookie,服務器就知道來的人是「誰」。而後咱們再根據不一樣的cookie的id,在服務器上保存一段時間的私密資料,如「帳號密碼」等等。
三、總結而言:cookie彌補了http無狀態的不足,讓服務器知道來的人是「誰」;可是cookie以文本的形式保存在本地,自身安全性較差;因此咱們就經過cookie識別不一樣的用戶,對應的在session裏保存私密的信息以及超過4096字節的文本。
四、另外,上述所說的cookie和session實際上是共通性的東西,不限於語言和框架
上面咱們已經本身寫了一個登錄頁面,在驗證了用戶名和密碼的正確性後跳轉到後臺的頁面。可是測試後也發現,若是繞過登錄頁面。直接輸入後臺的url地址也能夠直接訪問的。這個顯然是不合理的。其實咱們缺失的就是cookie和session配合的驗證。有了這個驗證過程,咱們就能夠實現和其餘網站同樣必須登陸才能進入後臺頁面了。
先說一下這種認證的機制。每當咱們使用一款瀏覽器訪問一個登錄頁面的時候,一旦咱們經過了認證。服務器端就會發送一組隨機惟一的字符串(假設是123abc)到瀏覽器端,這個被存儲在瀏覽端的東西就叫cookie。而服務器端也會本身存儲一下用戶當前的狀態,好比login=true,username=hahaha之類的用戶信息。可是這種存儲是以字典形式存儲的,字典的惟一key就是剛纔發給用戶的惟一的cookie值。那麼若是在服務器端查看session信息的話,理論上就會看到以下樣子的字典
{'123abc':{'login':true,'username:hahaha'}}
由於每一個cookie都是惟一的,因此咱們在電腦上換個瀏覽器再登錄同一個網站也須要再次驗證。那麼爲何說咱們只是理論上看到這樣子的字典呢?由於處於安全性的考慮,其實對於上面那個大字典不光key值123abc是被加密的,value值{'login':true,'username:hahaha'}在服務器端也是同樣被加密的。因此咱們服務器上就算打開session信息看到的也是相似與如下樣子的東西
{'123abc':dasdasdasd1231231da1231231}
借用一張別的大神畫的圖,能夠更直觀的看出來cookie和session的關係
視圖函數中的login:
def login(request):
message = ""
if request.method == "POST":
user = request.POST.get('user')
pwd = request.POST.get('pwd')
c = models.Administrator.objects.filter(username=user, password=pwd).count()
if c:
request.session['is_login'] = True
request.session['username'] = user
rep = redirect('/index.html')
return rep
else:
message = "用戶名或密碼錯誤"
obj = render(request,'login.html', {'msg': message})
return obj
登陸成功後,會跳轉到歡迎頁,index.html,可是這裏會有一個問題,咱們每一個頁面都須要用戶是通過驗證的,否則,沒通過驗證的用戶是能夠隨意跳轉頁面的,這確定是不安全的。由於一個網站裏面確定有不少頁面,若是不設定每一個頁面都須要登陸,那麼用戶可能會跳轉到某些管理頁面對數據進行隨意串改,因此這裏必須在每一個頁面都設定用戶必須的登陸的狀態。
用戶認證成功後,跳轉到歡迎頁。初步實現以下:
def index(request):
current_user = request.session.get('username')
is_login = request.session.get('is_login')
if is_login:
return render(request, 'index.html',{'username': current_user})
else:
return redirect('/login.html')
如前面所說,每一個頁面都須要通過驗證才能登錄,那麼咱們每一個頁面都須要上面這一段代碼。並且,更重要的是,咱們登錄成功後可能還須要添加其餘功能,不止是驗證登錄,因此每一個頁面都須要增長相同的代碼。這樣,是否是很是麻煩且繁瑣?
其實,咱們能夠有更優雅的方法解決這個問題,答案就是:裝飾器!咱們把須要的功能封裝成一個裝飾器,而後對須要驗證的函數進行裝飾,同時,你又能夠新增任何功能,須要修改的地方很是少,只是修改裝飾器!
實現以下:
def auth(func):
def inner(request, *args, **kwargs):
is_login = request.session.get('is_login')
if is_login:
return func(request, *args, **kwargs)
else:
return redirect('/login.html')
return inner
@auth
def index(request):
current_user = request.session.get('username')
return render(request, 'index.html',{'username': current_user})
以上,完美的解決了每一個頁面須要認證的問題,且你能夠隨意添加任何功能,被修飾的函數不用動任何代碼。
最後,當用戶登出時,咱們必需要session清除:
def logout(request):
request.session.clear()
return redirect('/login.html')
至此,帶會話的驗證登錄已完成。
上面功能雖然實現了,可是還有缺陷:服務端session的內容僅保存在內存中,用戶登出後,session隨即消失,而用戶再次登入時,服務器又須要從新生成。咱們是否考慮讓session持久化?保存在緩存或者數據庫中,由於session能夠設定過時時間expire_time,且Django也有特別的支持。
django有五種session存儲方式:
一、數據庫(database-backed sessions)
二、緩存(cached sessions)
三、文件系統(file-based sessions)
四、緩存+數據庫Session
五、cookie(cookie-based sessions)
一、數據庫爲緩存
setttings.py設置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默認)
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認)
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 # 是否關閉瀏覽器使得Session過時(默認)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存(默認)
程序啓動以後,你會發現Django自動生成了一張表,對session進行了保存:
服務器端:
客戶端瀏覽器:
session實現過程以下:
二、緩存Session
setttings.py設置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串
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 # 是否關閉瀏覽器使得Session過時
SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存
三、文件Session
setttings.py設置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串
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 # 是否關閉瀏覽器使得Session過時
SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存
四、緩存+數據庫Session
setttings.py設置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
五、加密cookie Session
setttings.py設置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
FBV(Function Base View)-基於函數的視圖
上面經過函數做爲視圖函數的方式就叫FBV,下面經過CBV方式實現。
CBV(Class Base View) -基於類的視圖
實現方式1,基於Django自帶的method_decorator方法:
缺點:若是類裏面的方法須要統一作某些操做,則須要本身寫一個outer裝飾器,而後類裏面的每一個方法都須要使用@method_decorator(outer)來裝飾。
views.py文件:
from django import views
from django.utils.decorators import method_decorator
def outer(func):
def inner(request, *args, **kwargs):
print(request.method)
return func(request, *args, **kwargs)
return inner
# CBV
class Login(views.View):
@method_decorator(outer)
def get(self,request, *args, **kwargs):
print('GET')
return render(request, 'login.html', {'msg': ''})
@method_decorator(outer)
def post(self, request, *args, **kwargs):
print('POST')
user = request.POST.get('user')
pwd = request.POST.get('pwd')
c = models.Administrator.objects.filter(username=user, password=pwd).count()
if c:
request.session['is_login'] = True
request.session['username'] = user
rep = redirect('/index.html')
return rep
else:
message = "用戶名或密碼錯誤"
return render(request, 'login.html', {'msg': message})
方法2:基於Django的dispatch方法,dispatch會在執行class中的每一個方法以前作一些操做。
由於dispatch方法會對類裏面的每一個方法都應用,若是類裏面的每一個方法都須要不一樣的裝飾,則單獨裝飾類裏面的每一個方法。
from django import views
class Login(views.View):
def dispatch(self, request, *args, **kwargs):
#若是咱們但願GET方法不進行裝飾,則判斷請求是GET即返回。 #若是類裏面的其餘方法須要裝飾,也能夠在這裏寫。 #好比 if request.method == 'POST':******
if request.method == 'GET':
return HttpResponse(request,'login.html')
ret = super(Login, self).dispatch(request, *args, **kwargs)
return ret
def get(self,request, *args, **kwargs):
print('GET')
return render(request, 'login.html', {'msg': ''})
def post(self, request, *args, **kwargs):
print('POST')
user = request.POST.get('user')
pwd = request.POST.get('pwd')
c = models.Administrator.objects.filter(username=user, password=pwd).count()
if c:
request.session['is_login'] = True
request.session['username'] = user
rep = redirect('/index.html')
return rep
else:
message = "用戶名或密碼錯誤"
return render(request, 'login.html', {'msg': message})
方法3:基於Django的dispatch和method_decorator聯合裝飾class
同時,咱們能夠經過method_decorator在類上面裝飾,或者針對類裏面的某個函數裝飾。
from django import views
from django.utils.decorators import method_decorator
def outer(func):
def inner(request, *args, **kwargs):
print(request.method)
return func(request, *args, **kwargs)
return inner
# CBV
#方式二,指定裝飾的方法名爲dispatch
@method_decorator(outer,name='dispatch')
class Login(views.View):
#方式一,直接在方法上進行裝飾
@method_decorator(outer)
def dispatch(self, request, *args, **kwargs):
ret = super(Login, self).dispatch(request, *args, **kwargs)
return ret
def get(self,request, *args, **kwargs):
print('GET')
return render(request, 'login.html', {'msg': ''})
def post(self, request, *args, **kwargs):
print('POST')
user = request.POST.get('user')
pwd = request.POST.get('pwd')
c = models.Administrator.objects.filter(username=user, password=pwd).count()
if c:
request.session['is_login'] = True
request.session['username'] = user
rep = redirect('/index.html')
return rep
else:
message = "用戶名或密碼錯誤"
return render(request, 'login.html', {'msg': message})
urls.py文件:
urlpatterns = [
url(r'^login.html$', views.Login.as_view()),
]
查看views源碼:
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in six.iteritems(kwargs):
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return http.HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
對於視圖函數,重點查看dispatch函數:
def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)
對於urls處理,重點查看as_view函數:
@classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
一、若是咱們須要追蹤用戶信息或者保持用戶狀態,則須要考慮使用session或者cookie。
session:數據保存在服務器端,基於cookie實現,在瀏覽器生成一個sessionid。
cookie:數據保存在用戶內存或文件中,通常是不敏感信息,如:用戶名。
二、登陸裝飾器
使用method_decorator模塊,指定方法或者類進行裝飾;
使用dispatch方法,對全部請求進行裝飾,也能夠去掉某些方法的裝飾;