學生管理之登陸實現

1、普通登陸驗證

 衆所周知,一個網站涉及到數據的增刪改查,因此對於訪問網站的用戶必須是通過合法驗證的,這樣才能對其授予權限進行操做。同時,也能夠方便對登陸的用戶行爲進行審計。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

 

2、帶會話的登陸驗證

 在實現會話的登陸驗證以前,咱們再次來回顧一下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'   # 引擎

 

3、FBV和CBV

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
 

  

4、總結

一、若是咱們須要追蹤用戶信息或者保持用戶狀態,則須要考慮使用session或者cookie。

  session:數據保存在服務器端,基於cookie實現,在瀏覽器生成一個sessionid。

  cookie:數據保存在用戶內存或文件中,通常是不敏感信息,如:用戶名。

二、登陸裝飾器

  使用method_decorator模塊,指定方法或者類進行裝飾;

  使用dispatch方法,對全部請求進行裝飾,也能夠去掉某些方法的裝飾;

相關文章
相關標籤/搜索