Skip to content

Latest commit

 

History

History
581 lines (472 loc) · 29.6 KB

44.Cookie和Session.md

File metadata and controls

581 lines (472 loc) · 29.6 KB

Cookie和Session

我們繼續來完成上一章節中的專案,實現“使用者登入”的功能,並限制只有登入的使用者才能投票。

使用者登入的準備工作

我們先為實現使用者登入做一些準備工作。

  1. 建立使用者模型。之前我們講解過如果透過Django的ORM實現從二維表到模型的轉換(反向工程),這次我們嘗試把模型變成二維表(正向工程)。

    class User(models.Model):
        """使用者"""
        no = models.AutoField(primary_key=True, verbose_name='編號')
        username = models.CharField(max_length=20, unique=True, verbose_name='使用者名稱')
        password = models.CharField(max_length=32, verbose_name='密碼')
        tel = models.CharField(max_length=20, verbose_name='手機號')
        reg_date = models.DateTimeField(auto_now_add=True, verbose_name='註冊時間')
        last_visit = models.DateTimeField(null=True, verbose_name='最後登入時間')
    
        class Meta:
            db_table = 'tb_user'
            verbose_name = '使用者'
            verbose_name_plural = '使用者'
  2. 使用下面的命令生成遷移檔案並執行遷移,將User模型直接變成關係型資料庫中的二維表tb_user

    python manage.py makemigrations polls
    python manage.py migrate polls
  3. 用下面的SQL語句直接插入兩條測試資料,通常不能將使用者的密碼直接儲存在資料庫中,因此我們將使用者密碼處理成對應的MD5摘要。MD5訊息摘要演算法是一種被廣泛使用的密碼雜湊函式(雜湊函式),可以產生出一個128位(位元)的雜湊值(雜湊值),用於確保資訊傳輸完整一致。在使用雜湊值時,通常會將雜湊值表示為16進位制字串,因此128位的MD5摘要通常表示為32個十六進位制符號。

    insert into `tb_user`
        (`username`, `password`, `tel`, `reg_date`)
    values
        ('wangdachui', '1c63129ae9db9c60c3e8aa94d3e00495', '13122334455', now()),
        ('hellokitty', 'c6f8cf68e5f68b0aa4680e089ee4742c', '13890006789', now());

    說明:上面建立的兩個使用者wangdachuihellokitty密碼分別是1qaz2wsxAbc123!!

  4. 我們在應用下增加一個名為utils.py的模組用來儲存需要使用的工具函式。Python標準庫中的hashlib模組封裝了常用的雜湊演算法,包括:MD5、SHA1、SHA256等。下面是使用hashlib中的md5類將字串處理成MD5摘要的函式如下所示。

    import hashlib
    
    
    def gen_md5_digest(content):
        return hashlib.md5(content.encode()).hexdigest()
  5. 編寫使用者登入的檢視函式和模板頁。

    新增渲染登入頁面的檢視函式:

    def login(request: HttpRequest) -> HttpResponse:
        hint = ''
        return render(request, 'login.html', {'hint': hint})

    增加login.html模板頁:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>使用者登入</title>
        <style>
            #container {
                width: 520px;
                margin: 10px auto;
            }
            .input {
                margin: 20px 0;
                width: 460px;
                height: 40px;
            }
            .input>label {
                display: inline-block;
                width: 140px;
                text-align: right;
            }
            .input>img {
                width: 150px;
                vertical-align: middle;
            }
            input[name=captcha] {
                vertical-align: middle;
            }
            form+div {
                margin-top: 20px;
            }
            form+div>a {
                text-decoration: none;
                color: darkcyan;
                font-size: 1.2em;
            }
            .button {
                width: 500px;
                text-align: center;
                margin-top: 20px;
            }
            .hint {
                color: red;
                font-size: 12px;
            }
        </style>
    </head>
    <body>
        <div id="container">
            <h1>使用者登入</h1>
            <hr>
            <p class="hint">{{ hint }}</p>
            <form action="/login/" method="post">
                {% csrf_token %}
                <fieldset>
                    <legend>使用者資訊</legend>
                    <div class="input">
                        <label>使用者名稱:</label>
                        <input type="text" name="username">
                    </div>
                    <div class="input">
                        <label>密碼:</label>
                        <input type="password" name="password">
                    </div>
                    <div class="input">
                        <label>驗證碼:</label>
                        <input type="text" name="captcha">
                        <img id="code" src="/captcha/" alt="" width="150" height="40">
                    </div>
                </fieldset>
                <div class="button">
                    <input type="submit" value="登入">
                    <input type="reset" value="重置">
                </div>
            </form>
            <div>
                <a href="/">返回首頁</a>
                <a href="/register/">註冊新使用者</a>
            </div>
        </div>
    </body>
    </html>

    注意,在上面的表單中,我們使用了模板指令{% csrf_token %}為表單新增一個隱藏域(大家可以在瀏覽器中顯示網頁原始碼就可以看到這個指令生成的type屬性為hiddeninput標籤),它的作用是在表單中生成一個隨機令牌(token)來防範跨站請求偽造(簡稱為CSRF),這也是Django在提交表單時的硬性要求。如果我們的表單中沒有這樣的令牌,那麼提交表單時,Django框架會產生一個響應狀態碼為403的響應(禁止訪問),除非我們設定了免除CSRF令牌。下圖是一個關於CSRF簡單生動的例子。

接下來,我們可以編寫提供驗證碼和實現使用者登入的檢視函式,在此之前,我們先說說一個Web應用實現使用者跟蹤的方式以及Django框架對實現使用者跟蹤所提供的支援。對一個Web應用來說,使用者登入成功後必然要讓伺服器能夠記住該使用者已經登入,這樣伺服器才能為這個使用者提供更好的服務,而且上面說到的CSRF也是透過釣魚網站來套取使用者登入資訊進行惡意操作的攻擊手段,這些都是以使用者跟蹤技術為基礎的。在理解了這些背景知識後,我們就清楚使用者登入時到底需要執行哪些操作。

實現使用者跟蹤

如今,一個網站如果不透過某種方式記住你是誰以及你之前在網站的活動情況,失去的就是網站的可用性和便利性,繼而很有可能導致網站使用者的流式,所以記住一個使用者(更專業的說法叫使用者跟蹤)對絕大多數Web應用來說都是必需的功能。

在伺服器端,我們想記住一個使用者最簡單的辦法就是建立一個物件,透過這個物件就可以把使用者相關的資訊都儲存起來,這個物件就是我們常說的session(使用者會話物件)。那麼問題來了,HTTP本身是一個無連線(每次請求和響應的過程中,伺服器一旦完成對客戶端請求的響應之後就斷開連線)、無狀態(客戶端再次發起對伺服器的請求時,伺服器無法得知這個客戶端之前的任何資訊)的協議,即便伺服器透過session物件保留了使用者資料,還得透過某種方式來確定當前的請求與之前儲存過的哪一個session是有關聯的。相信很多人都能想到,我們可以給每個session物件分配一個全域性唯一的識別符號來識別session物件,我們姑且稱之為sessionid,每次客戶端發起請求時,只要攜帶上這個sessionid,就有辦法找到與之對應的session物件,從而實現在兩次請求之間記住該使用者的資訊,也就是我們之前說的使用者跟蹤。

要讓客戶端記住並在每次請求時帶上sessionid又有以下幾種做法:

  1. URL重寫。所謂URL重寫就是在URL中攜帶sessionid,例如:http://www.example.com/index.html?sessionid=123456,伺服器透過獲取sessionid引數的值來取到與之對應的session物件。

  2. 隱藏域(隱式表單域)。在提交表單的時候,可以透過在表單中設定隱藏域向伺服器傳送額外的資料。例如:<input type="hidden" name="sessionid" value="123456">

  3. 本地儲存。現在的瀏覽器都支援多種本地儲存方案,包括:cookie、localStorage、sessionStorage、IndexedDB等。在這些方案中,cookie是歷史最為悠久也是被詬病得最多的一種方案,也是我們接下來首先為大家講解的一種方案。簡單的說,cookie是一種以鍵值對方式儲存在瀏覽器臨時檔案中的資料,每次請求時,請求頭中會攜帶本站點的cookie到伺服器,那麼只要將sessionid寫入cookie,下次請求時伺服器只要讀取請求頭中的cookie就能夠獲得這個sessionid,如下圖所示。

    在HTML5時代要,除了cookie,還可以使用新的本地儲存API來儲存資料,就是剛才提到的localStorage、sessionStorage、IndexedDB等技術,如下圖所示。

總結一下,要實現使用者跟蹤,伺服器端可以為每個使用者會話建立一個session物件並將session物件的ID寫入到瀏覽器的cookie中;使用者下次請求伺服器時,瀏覽器會在HTTP請求頭中攜帶該網站儲存的cookie資訊,這樣伺服器就可以從cookie中找到session物件的ID並根據此ID獲取到之前建立的session物件;由於session物件可以用鍵值對的方式儲存使用者資料,這樣之前儲存在session物件中的資訊可以悉數取出,伺服器也可以根據這些資訊判定使用者身份和了解使用者偏好,為使用者提供更好的個性化服務。

Django框架對session的支援

在建立Django專案時,預設的配置檔案settings.py檔案中已經激活了一個名為SessionMiddleware的中介軟體(關於中介軟體的知識我們在後面的章節做詳細講解,這裡只需要知道它的存在即可),因為這個中介軟體的存在,我們可以直接透過請求物件的session屬性來操作會話物件。前面我們說過,session屬性是一個像字典一樣可以讀寫資料的容器物件,因此我們可以使用“鍵值對”的方式來保留使用者資料。與此同時,SessionMiddleware中介軟體還封裝了對cookie的操作,在cookie中儲存了sessionid,這一點我們在上面已經提到過了。

在預設情況下,Django將session的資料序列化後儲存在關係型資料庫中,在Django 1.6以後的版本中,預設的序列化資料的方式是JSON序列化,而在此之前一直使用Pickle序列化。JSON序列化和Pickle序列化的差別在於前者將物件序列化為字串(字元形式),而後者將物件序列化為位元組串(二進位制形式),因為安全方面的原因,JSON序列化成為了目前Django框架預設序列化資料的方式,這就要求在我們儲存在session中的資料必須是能夠JSON序列化的,否則就會引發異常。還有一點需要說明的是,使用關係型資料庫儲存session中的資料在大多數時候並不是最好的選擇,因為資料庫可能會承受巨大的壓力而成為系統性能的瓶頸,在後面的章節中我們會告訴大家如何將session儲存到快取服務中以提升系統的效能。

實現使用者登入驗證

首先,我們在剛才的polls/utils.py檔案中編寫生成隨機驗證碼的函式gen_random_code,內容如下所示。

import random

ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'


def gen_random_code(length=4):
    return ''.join(random.choices(ALL_CHARS, k=length))

編寫生成驗證碼圖片的類Captcha

"""
圖片驗證碼
"""
import os
import random
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    """貝塞爾曲線"""

    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def make_bezier(self, n):
        """繪製貝塞爾曲線"""
        try:
            return self.beziers[n]
        except KeyError:
            combinations = pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha:
    """驗證碼"""

    def __init__(self, width, height, fonts=None, color=None):
        self._image = None
        self._fonts = fonts if fonts else \
            [os.path.join(os.path.dirname(__file__), 'fonts', font)
             for font in ['Arial.ttf', 'Georgia.ttf', 'Action.ttf']]
        self._color = color if color else random_color(0, 200, random.randint(220, 255))
        self._width, self._height = width, height

    @classmethod
    def instance(cls, width=200, height=75):
        """用於獲取Captcha物件的類方法"""
        prop_name = f'_instance_{width}_{height}'
        if not hasattr(cls, prop_name):
            setattr(cls, prop_name, cls(width, height))
        return getattr(cls, prop_name)

    def _background(self):
        """繪製背景"""
        Draw(self._image).rectangle([(0, 0), self._image.size],
                                    fill=random_color(230, 255))

    def _smooth(self):
        """平滑影象"""
        return self._image.filter(ImageFilter.SMOOTH)

    def _curve(self, width=4, number=6, color=None):
        """繪製曲線"""
        dx, height = self._image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = Bezier().make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(self._image).line(points, fill=color if color else self._color, width=width)

    def _noise(self, number=50, level=2, color=None):
        """繪製擾碼"""
        width, height = self._image.size
        dx, dy = width / 10, height / 10
        width, height = width - dx, height - dy
        draw = Draw(self._image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)),
                      fill=color if color else self._color, width=level)

    def _text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        """繪製文字"""
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(self._image)
        char_images = []
        for c in captcha_text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = self._image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            self._image.paste(char_image,
                              (offset, int((height - c_height) / 2)),
                              mask)
            offset += int(c_width * squeeze_factor)

    @staticmethod
    def _warp(image, dx_factor=0.3, dy_factor=0.3):
        """影象扭曲"""
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        warp_image = Image.new(
            'RGB',
            (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
        warp_image.paste(image, (abs(x1), abs(y1)))
        width2, height2 = warp_image.size
        return warp_image.transform(
            (width, height),
            Image.QUAD,
            (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))

    @staticmethod
    def _offset(image, dx_factor=0.1, dy_factor=0.2):
        """影象偏移"""
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        offset_image = Image.new('RGB', (width + dx, height + dy))
        offset_image.paste(image, (dx, dy))
        return offset_image

    @staticmethod
    def _rotate(image, angle=25):
        """影象旋轉"""
        return image.rotate(random.uniform(-angle, angle),
                            Image.BILINEAR, expand=1)

    def generate(self, captcha_text='', fmt='PNG'):
        """生成驗證碼(文字和圖片)
        :param captcha_text: 驗證碼文字
        :param fmt: 生成的驗證碼圖片格式
        :return: 驗證碼圖片的二進位制資料
        """
        self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
        self._background()
        self._text(captcha_text, self._fonts,
                   drawings=['_warp', '_rotate', '_offset'])
        self._curve()
        self._noise()
        self._smooth()
        image_bytes = BytesIO()
        self._image.save(image_bytes, format=fmt)
        return image_bytes.getvalue()


def pascal_row(n=0):
    """生成畢達哥拉斯三角形(楊輝三角)"""
    result = [1]
    x, numerator = 1, n
    for denominator in range(1, n // 2 + 1):
        x *= numerator
        x /= denominator
        result.append(x)
        numerator -= 1
    if n & 1 == 0:
        result.extend(reversed(result[:-1]))
    else:
        result.extend(reversed(result))
    return result


def random_color(start=0, end=255, opacity=255):
    """獲得隨機顏色"""
    red = random.randint(start, end)
    green = random.randint(start, end)
    blue = random.randint(start, end)
    if opacity is None:
        return red, green, blue
    return red, green, blue, opacity

說明:上面的程式碼中用到了三個字型檔案,字型檔案位於polls/fonts目錄下,大家可以自行新增字型檔案,但是需要注意字型檔案的檔名跟上面程式碼的第45行保持一致。

接下來,我們先完成提供驗證碼的檢視函式。

def get_captcha(request: HttpRequest) -> HttpResponse:
    """驗證碼"""
    captcha_text = gen_random_code()
    request.session['captcha'] = captcha_text
    image_data = Captcha.instance().generate(captcha_text)
    return HttpResponse(image_data, content_type='image/png')

注意上面程式碼中的第4行,我們將隨機生成的驗證碼字串儲存到session中,稍後使用者登入時,我們要將儲存在session中的驗證碼字串和使用者輸入的驗證碼字串進行比對,如果使用者輸入了正確的驗證碼才能夠執行後續的登入流程,程式碼如下所示。

def login(request: HttpRequest) -> HttpResponse:
    hint = ''
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username and password:
            password = gen_md5_digest(password)
            user = User.objects.filter(username=username, password=password).first()
            if user:
                request.session['userid'] = user.no
                request.session['username'] = user.username
                return redirect('/')
            else:
                hint = '使用者名稱或密碼錯誤'
        else:
            hint = '請輸入有效的使用者名稱和密碼'
    return render(request, 'login.html', {'hint': hint})

說明:上面的程式碼沒有對使用者名稱和密碼沒有進行驗證,實際專案中建議使用正則表示式驗證使用者輸入資訊,否則有可能將無效的資料交給資料庫進行處理或者造成其他安全方面的隱患。

上面的程式碼中,我們設定了登入成功後會在session中儲存使用者的編號(userid)和使用者名稱(username),頁面會重定向到首頁。接下來我們可以稍微對首頁的程式碼進行調整,在頁面的右上角顯示出登入使用者的使用者名稱。我們將這段程式碼單獨寫成了一個名為header.html的HTML檔案,首頁中可以透過在<body>標籤中新增{% include 'header.html' %}來包含這個頁面,程式碼如下所示。

<div class="user">
    {% if request.session.userid %}
    <span>{{ request.session.username }}</span>
    <a href="/logout">登出</a>
    {% else %}
    <a href="/login">登入</a>&nbsp;&nbsp;
    {% endif %}
    <a href="/register">註冊</a>
</div>

如果使用者沒有登入,頁面會顯示登入和註冊的超連結;而使用者登入成功後,頁面上會顯示使用者名稱和登出的連結,登出連結對應的檢視函式如下所示,URL的對映與之前講過的類似,不再贅述。

def logout(request):
    """登出"""
    request.session.flush()
    return redirect('/')

上面的程式碼透過session物件flush方法來銷燬session,一方面清除了伺服器上session物件儲存的使用者資料,一方面將儲存在瀏覽器cookie中的sessionid刪除掉,稍後我們會對如何讀寫cookie的操作加以說明。

我們可以透過專案使用的資料庫中名為django_session 的表來找到所有的session,該表的結構如下所示:

session_key session_data expire_date
c9g2gt5cxo0k2evykgpejhic5ae7bfpl MmI4YzViYjJhOGMyMDJkY2M5Yzg3... 2019-05-25 23:16:13.898522

其中,第1列就是瀏覽器cookie中儲存的sessionid;第2列是經過BASE64編碼後的session中的資料,如果使用Python的base64對其進行解碼,解碼的過程和結果如下所示。

import base64

base64.b64decode('MmI4YzViYjJhOGMyMDJkY2M5Yzg3ZWIyZGViZmUzYmYxNzdlNDdmZjp7ImNhcHRjaGEiOiJzS3d0Iiwibm8iOjEsInVzZXJuYW1lIjoiamFja2ZydWVkIn0=')

第3列是session的過期時間,session過期後瀏覽器儲存的cookie中的sessionid就會失效,但是資料庫中的這條對應的記錄仍然會存在,如果想清除過期的資料,可以使用下面的命令。

python manage.py clearsessions

Django框架預設的session過期時間為兩週(1209600秒),如果想修改這個時間,可以在專案的配置檔案中新增如下所示的程式碼。

# 配置會話的超時時間為1天(86400秒)
SESSION_COOKIE_AGE = 86400

有很多對安全性要求較高的應用都必須在關閉瀏覽器視窗時讓會話過期,不再保留使用者的任何資訊,如果希望在關閉瀏覽器視窗時就讓會話過期(cookie中的sessionid失效),可以加入如下所示的配置。

# 設定為True在關閉瀏覽器視窗時session就過期
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

如果不希望將session的資料儲存在資料庫中,可以將其放入快取中,對應的配置如下所示,快取的配置和使用我們在後面講解。

# 配置將會話物件放到快取中儲存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 配置使用哪一組快取來儲存會話
SESSION_CACHE_ALIAS = 'default'

如果要修改session資料預設的序列化方式,可以將預設的JSONSerializer修改為PickleSerializer

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

接下來,我們就可以限制只有登入使用者才能為老師投票,修改後的praise_or_criticize函式如下所示,我們透過從request.session中獲取userid來判定使用者是否登入。

def praise_or_criticize(request: HttpRequest) -> HttpResponse:
    if request.session.get('userid'):
        try:
            tno = int(request.GET.get('tno'))
            teacher = Teacher.objects.get(no=tno)
            if request.path.startswith('/praise/'):
                teacher.good_count += 1
                count = teacher.good_count
            else:
                teacher.bad_count += 1
                count = teacher.bad_count
            teacher.save()
            data = {'code': 20000, 'mesg': '投票成功', 'count': count}
        except (ValueError, Teacher.DoesNotExist):
            data = {'code': 20001, 'mesg': '投票失敗'}
    else:
        data = {'code': 20002, 'mesg': '請先登入'}
    return JsonResponse(data)

當然,在修改了檢視函式後,teachers.html也需要進行調整,使用者如果沒有登入,就將使用者引導至登入頁,登入成功再返回到投票頁,此處不再贅述。

在檢視函式中讀寫cookie

下面我們對如何使用cookie做一個更為細緻的說明以便幫助大家在Web專案中更好的使用這項技術。Django封裝的HttpRequestHttpResponse物件分別提供了讀寫cookie的操作。

HttpRequest封裝的屬性和方法:

  1. COOKIES屬性 - 該屬性包含了HTTP請求攜帶的所有cookie。
  2. get_signed_cookie方法 - 獲取帶簽名的cookie,如果簽名驗證失敗,會產生BadSignature異常。

HttpResponse封裝的方法:

  1. set_cookie方法 - 該方法可以設定一組鍵值對並將其最終將寫入瀏覽器。
  2. set_signed_cookie方法 - 跟上面的方法作用相似,但是會對cookie進行簽名來達到防篡改的作用。因為如果篡改了cookie中的資料,在不知道金鑰的情況下是無法生成有效的簽名,這樣伺服器在讀取cookie時會發現資料與簽名不一致從而產生BadSignature異常。需要說明的是,這裡所說的金鑰就是我們在Django專案配置檔案中指定的SECRET_KEY,而鹽是程式中設定的一個字串,你願意設定為什麼都可以,只要是一個有效的字串。

上面提到的方法,如果不清楚它們的具體用法,可以自己查閱一下Django的官方文件,沒有什麼資料比官方文件能夠更清楚的告訴你這些方法到底如何使用。

剛才我們說過了,啟用SessionMiddleware之後,每個HttpRequest物件都會繫結一個session屬性,它是一個類似字典的物件,除了儲存使用者資料之外還提供了檢測瀏覽器是否支援cookie的方法,包括:

  1. set_test_cookie方法 - 設定用於測試的cookie。
  2. test_cookie_worked方法 - 檢測測試cookie是否工作。
  3. delete_test_cookie方法 - 刪除用於測試的cookie。
  4. set_expiry方法 - 設定會話的過期時間。
  5. get_expire_age/get_expire_date方法 - 獲取會話的過期時間。
  6. clear_expired方法 - 清理過期的會話。

下面是在執行登入之前檢查瀏覽器是否支援cookie的程式碼。通常情況下,瀏覽器預設開啟了對cookie的支援,但是可能因為某種原因,使用者禁用了瀏覽器的cookie功能,遇到這種情況我們可以在檢視函式中提供一個檢查功能,如果檢查到使用者瀏覽器不支援cookie,可以給出相應的提示。

def login(request):
    if request.method == 'POST':
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
            # Add your code to perform login process here
        else:
            return HttpResponse("Please enable cookies and try again.")
    request.session.set_test_cookie()
    return render_to_response('login.html')

Cookie的替代品

之前我們說過了,cookie的名聲一直都不怎麼好,當然我們在實際開發中是不會在cookie中儲存使用者的敏感資訊(如使用者的密碼、信用卡的賬號等)的,而且儲存在cookie中的資料一般也會做好編碼和簽名的工作。對於支援HTML5的瀏覽器來說,可以使用localStorage和sessionStorage做為cookie的替代方案,相信從名字上你就能聽出二者的差別,儲存在localStorage的資料可以長期保留;而儲存在sessionStorage的資料會在瀏覽器關閉時會被清除 。關於這些cookie替代品的用法,建議大家查閱MDN來進行了解。