Cookie 與 Session¶
開始之前¶
任務目標
在這個章節中,我們會完成:
- 了解什麼是 Cookie
- 學習如何在 Django 中操作 Cookie
- 認識 Cookie 的優缺點與使用場景
- 了解什麼是 Session
- 學習如何在 Django 中操作 Session
- 認識 Session 的優缺點與使用場景
- 比較 Cookie 與 Session 的差異
什麼是 Cookie?¶
Cookie 是一種存在於瀏覽器端的小型文字資料,由伺服器傳送給瀏覽器,瀏覽器會將它儲存起來,並在後續的請求中自動帶上。
Cookie 的運作原理¶
sequenceDiagram
participant Browser as 瀏覽器
participant Server as 伺服器
Browser->>Server: 第一次訪問網站
Server->>Browser: Set-Cookie: username=Django
Note over Browser: 儲存 Cookie
Browser->>Server: 第二次訪問網站 (帶 Cookie)
Note over Browser: Cookie: username=Django
Server->>Browser: 回應(知道你是 Django)
Cookie 的特性
- 儲存在瀏覽器:資料存在使用者的電腦上
- 自動傳送:每次請求都會自動帶上
- 有大小限制:單個 Cookie 最大約 4KB
- 有期限:可以設定過期時間
- 有數量限制:每個網域約 50-150 個 Cookie
Cookie 的用途¶
Cookie 常用於:
- 記住使用者偏好:語言、主題、字體大小
- 購物車:暫存購物車內容
- 記住登入狀態:「記住我」功能
- 追蹤分析:Google Analytics、廣告追蹤
在 Django 中操作 Cookie¶
讓我們透過實作來學習如何操作 Cookie。
範例 1:記錄訪問次數¶
我們將建立一個頁面,記錄使用者訪問了幾次。
建立 View¶
編輯 practices/views.py,加入以下程式碼:
from django.http import HttpResponse
from django.shortcuts import render
def hello_world(request):
return render(request, "practices/hello.html")
def greeting(request):
name = "Django"
return render(request, "practices/greeting.html", {"name": name})
def search(request):
keyword = request.GET.get("q", "")
return render(request, "practices/search.html", {"keyword": keyword})
def product_list(request):
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"分類: {category}, 排序: {sort}, 頁數: {page}")
def filter_products(request):
colors = request.GET.getlist("color")
return HttpResponse(f"選擇的顏色: {', '.join(colors)}")
def hello_name(request, name):
return HttpResponse(f"Hello, {name}!")
def article_detail(request, year, month, slug):
return HttpResponse(f"文章: {year} 年 {month} 月 - {slug}")
def user_articles(request, username):
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"{username} 的文章, 排序: {sort}, 頁數: {page}")
def advanced_search(request):
keyword = request.GET.get("q", "")
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
return render(
request,
"practices/advanced_search.html",
{
"keyword": keyword,
"category": category,
"sort": sort,
},
)
def color_filter(request):
colors = request.GET.getlist("color")
return render(
request,
"practices/color_filter.html",
{"colors": colors},
)
def contact(request):
context = {}
if request.method == "POST":
name = request.POST.get("name", "")
email = request.POST.get("email", "")
message = request.POST.get("message", "")
context = {
"success": True,
"name": name,
"email": email,
"message": message,
}
return render(request, "practices/contact.html", context)
def cookie_counter(request): # (1)!
# 從 Cookie 讀取訪問次數
visit_count = request.COOKIES.get("visit_count", "0") # (2)!
visit_count = int(visit_count) + 1 # (3)!
# 建立回應
response = HttpResponse(f"你已經訪問了 {visit_count} 次")
# 設定 Cookie
response.set_cookie("visit_count", str(visit_count)) # (4)!
return response
- 建立一個新的 view 來記錄訪問次數
- 從
request.COOKIES讀取 Cookie,預設值為 "0" - 將字串轉為整數並加 1
- 使用
response.set_cookie()設定 Cookie
程式碼重點
- 讀取 Cookie:
request.COOKIES.get("key", "default") - 設定 Cookie:
response.set_cookie("key", value) - Cookie 值都是字串:需要手動轉換型別
設定 URL¶
編輯 practices/urls.py,加入 URL 路由:
from django.urls import path
from practices import views
app_name = "practices"
urlpatterns = [
path("hello/", views.hello_world, name="hello_world"),
path("greeting/", views.greeting, name="greeting"),
path("search/", views.search, name="search"),
path("products/", views.product_list, name="product_list"),
path("products/filter/", views.filter_products, name="product_filter"),
path("hello/<str:name>/", views.hello_name, name="hello_name"),
path(
"articles/<int:year>/<int:month>/<slug:slug>/",
views.article_detail,
name="article_detail",
),
path(
"users/<str:username>/articles/",
views.user_articles,
name="user_articles",
),
path("advanced-search/", views.advanced_search, name="advanced_search"),
path("color-filter/", views.color_filter, name="color_filter"),
path("contact/", views.contact, name="contact"),
path("cookie-counter/", views.cookie_counter, name="cookie_counter"),
]
測試¶
啟動開發伺服器:
訪問 http://127.0.0.1:8000/practices/cookie-counter/:
- 第一次訪問:「你已經訪問了 1 次」
- 第二次訪問:「你已經訪問了 2 次」
- 第三次訪問:「你已經訪問了 3 次」
開發者工具查看 Cookie
在瀏覽器中按 F12 開啟開發者工具:
- 切換到「Application」或「儲存空間」頁籤
- 左側選擇「Cookies」
- 選擇網站網址
- 可以看到
visit_countCookie
範例 2:記住使用者偏好¶
讓我們建立一個更實用的範例:記住使用者選擇的主題顏色。
建立 View¶
編輯 practices/views.py,加入以下程式碼:
from django.http import HttpResponse
from django.shortcuts import render
def hello_world(request):
return render(request, "practices/hello.html")
def greeting(request):
name = "Django"
return render(request, "practices/greeting.html", {"name": name})
def search(request):
keyword = request.GET.get("q", "")
return render(request, "practices/search.html", {"keyword": keyword})
def product_list(request):
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"分類: {category}, 排序: {sort}, 頁數: {page}")
def filter_products(request):
colors = request.GET.getlist("color")
return HttpResponse(f"選擇的顏色: {', '.join(colors)}")
def hello_name(request, name):
return HttpResponse(f"Hello, {name}!")
def article_detail(request, year, month, slug):
return HttpResponse(f"文章: {year} 年 {month} 月 - {slug}")
def user_articles(request, username):
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"{username} 的文章, 排序: {sort}, 頁數: {page}")
def advanced_search(request):
keyword = request.GET.get("q", "")
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
return render(
request,
"practices/advanced_search.html",
{
"keyword": keyword,
"category": category,
"sort": sort,
},
)
def color_filter(request):
colors = request.GET.getlist("color")
return render(
request,
"practices/color_filter.html",
{"colors": colors},
)
def contact(request):
context = {}
if request.method == "POST":
name = request.POST.get("name", "")
email = request.POST.get("email", "")
message = request.POST.get("message", "")
context = {
"success": True,
"name": name,
"email": email,
"message": message,
}
return render(request, "practices/contact.html", context)
def cookie_counter(request):
# 從 Cookie 讀取訪問次數
visit_count = request.COOKIES.get("visit_count", "0")
visit_count = int(visit_count) + 1
# 建立回應
response = HttpResponse(f"你已經訪問了 {visit_count} 次")
# 設定 Cookie
response.set_cookie("visit_count", str(visit_count))
return response
def theme_preference(request): # (1)!
# 從 GET 參數讀取主題設定
theme = request.GET.get("theme") # (2)!
# 從 Cookie 讀取目前的主題
current_theme = request.COOKIES.get("theme", "light") # (3)!
# 如果有新的主題設定就更新
if theme: # (4)!
current_theme = theme
# 建立回應
response = render(request, "practices/theme.html", {"theme": current_theme})
# 儲存主題設定到 Cookie
if theme: # (5)!
response.set_cookie("theme", current_theme, max_age=365 * 24 * 60 * 60) # (6)!
return response
- 建立主題偏好設定的 view
- 從 GET 參數讀取使用者選擇的主題
- 從 Cookie 讀取目前的主題,預設為 "light"
- 如果有新的主題設定,就更新
- 只有在使用者選擇新主題時才更新 Cookie
- 設定 Cookie 有效期為 1 年(365 天)
set_cookie 的參數
set_cookie() 常用參數:
key:Cookie 的名稱value:Cookie 的值max_age:有效期限(秒),例如:365 * 24 * 60 * 60代表 1 年expires:過期時間(datetime 物件)path:Cookie 的有效路徑,預設為/domain:Cookie 的有效網域secure:是否只在 HTTPS 連線中傳送httponly:是否禁止 JavaScript 存取
建立 Template¶
建立 practices/templates/practices/theme.html:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="This is my first Django Template" />
<meta name="keywords" content="Django, Template, HTML" />
<title>主題設定</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
transition: all 0.3s;
}
/* 淺色主題 */
body.light {
background-color: #ffffff;
color: #333333;
}
/* 深色主題 */
body.dark {
background-color: #1a1a1a;
color: #f0f0f0;
}
.container {
max-width: 600px;
margin: 0 auto;
}
h1 {
margin-bottom: 30px;
}
.theme-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
border: 2px solid currentColor;
border-radius: 5px;
background-color: transparent;
color: inherit;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: currentColor;
color: white;
}
.current-theme {
padding: 15px;
border-left: 4px solid currentColor;
background-color: rgba(128, 128, 128, 0.1);
}
</style>
</head>
<body class="{{ theme }}">
<div class="container"> <!-- (1)! -->
<h1>
主題設定
</h1>
<div class="theme-buttons">
<a href="?theme=light"> <!-- (2)! -->
<button>
淺色主題
</button>
</a>
<a href="?theme=dark"> <!-- (3)! -->
<button>
深色主題
</button>
</a>
</div>
<div class="current-theme">
<p>
目前使用:<strong>{{ theme }}</strong> 主題
</p>
<p> <!-- (4)! -->
你的偏好已儲存在 Cookie 中,下次訪問時會自動套用。
</p>
</div>
</div>
</body>
</html>
- 根據主題設定不同的 class
- 點擊後會傳送
?theme=light參數 - 點擊後會傳送
?theme=dark參數 - 顯示目前的主題
設定 URL¶
編輯 practices/urls.py:
from django.urls import path
from practices import views
app_name = "practices"
urlpatterns = [
path("hello/", views.hello_world, name="hello_world"),
path("greeting/", views.greeting, name="greeting"),
path("search/", views.search, name="search"),
path("products/", views.product_list, name="product_list"),
path("products/filter/", views.filter_products, name="product_filter"),
path("hello/<str:name>/", views.hello_name, name="hello_name"),
path(
"articles/<int:year>/<int:month>/<slug:slug>/",
views.article_detail,
name="article_detail",
),
path(
"users/<str:username>/articles/",
views.user_articles,
name="user_articles",
),
path("advanced-search/", views.advanced_search, name="advanced_search"),
path("color-filter/", views.color_filter, name="color_filter"),
path("contact/", views.contact, name="contact"),
path("cookie-counter/", views.cookie_counter, name="cookie_counter"),
path("theme/", views.theme_preference, name="theme_preference"),
]
測試¶
訪問 http://127.0.0.1:8000/practices/theme/:
- 點擊「深色主題」按鈕
- 頁面變成深色背景
- 關閉瀏覽器並重新開啟
- 再次訪問 http://127.0.0.1:8000/practices/theme/
- 頁面自動顯示深色主題(Cookie 記住了你的選擇)
Cookie 成功運作
現在你已經學會:
- ✅ 讀取 Cookie:
request.COOKIES.get("key") - ✅ 設定 Cookie:
response.set_cookie("key", value) - ✅ 設定過期時間:
max_age=365 * 24 * 60 * 60
Cookie 的優缺點與使用場景¶
優點¶
Cookie 的優勢
- 實作簡單:瀏覽器自動處理儲存和傳送
- 適合小資料:適合儲存簡單的偏好設定
- 減輕伺服器負擔:資料存在使用者端
- 可設定期限:彈性控制資料有效期
缺點¶
Cookie 的限制
- 容量限制:單個 Cookie 約 4KB
- 安全性問題:
- 可以被使用者修改
- 可能被 XSS 攻擊竊取(如果沒有設定
httponly) - 在 HTTP 連線中會以明文傳送(除非使用 HTTPS)
- 隱私問題:追蹤 Cookie 引發隱私爭議
- 效能影響:每次請求都會傳送所有 Cookie
適合使用 Cookie 的場景¶
適合的場景
- ✅ 使用者偏好設定:語言、主題、字體大小
- ✅ 追蹤和分析:Google Analytics
- ✅ 廣告投放:追蹤使用者興趣
- ✅ 簡單的狀態記錄:訪問次數、上次訪問時間
不適合使用 Cookie 的場景¶
不適合的場景
- ❌ 敏感資料:密碼、信用卡號碼(容易被竊取)
- ❌ 大量資料:超過 4KB 的資料
- ❌ 需要安全性的資料:使用者可以隨意修改 Cookie
- ❌ 跨裝置同步:Cookie 只存在單一瀏覽器
什麼是 Session?¶
Session 是一種存在於伺服器端的資料儲存機制,用來追蹤使用者的狀態。與 Cookie 不同,Session 的資料存在伺服器上,瀏覽器只會收到一個 Session ID。
Session 的運作原理¶
sequenceDiagram
participant Browser as 瀏覽器
participant Server as 伺服器
participant DB as Session 儲存
Browser->>Server: 第一次訪問
Server->>DB: 建立 Session
Note over DB: session_id: abc123<br/>data: {}
Server->>Browser: Set-Cookie: sessionid=abc123
Note over Browser: 儲存 Session ID
Browser->>Server: 第二次訪問 (帶 Session ID)
Note over Browser: Cookie: sessionid=abc123
Server->>DB: 根據 Session ID 讀取資料
DB-->>Server: 回傳 Session 資料
Server->>Browser: 回應
Session 的特性
- 資料存在伺服器:更安全,使用者無法直接修改
- 容量更大:沒有 4KB 限制
- 使用 Session ID:瀏覽器只儲存一個 ID(透過 Cookie)
- 有過期機制:可設定閒置多久後失效
- 需要儲存空間:使用資料庫或快取來儲存
Session 的儲存方式¶
Django 支援多種 Session 儲存方式:
| 儲存方式 | 說明 | 優點 | 缺點 |
|---|---|---|---|
| 資料庫 | 預設方式,存在 django_session 表 |
可靠、持久化 | 較慢 |
| 快取 | 存在記憶體快取(如 Redis) | 快速 | 伺服器重啟後消失 |
| 檔案 | 存在檔案系統 | 簡單 | I/O 負擔 |
| Cookie | 資料加密後存在 Cookie | 不需要伺服器儲存 | 有容量限制 |
Django 預設使用資料庫
Django 預設使用資料庫儲存 Session,資料會存在 django_session 表中。這是最可靠的方式,適合大多數情況。
在 Django 中操作 Session¶
範例 3:購物車功能¶
讓我們使用 Session 來實作一個簡單的購物車。
建立 View¶
編輯 practices/views.py:
from django.http import HttpResponse
from django.shortcuts import render
def hello_world(request):
return render(request, "practices/hello.html")
def greeting(request):
name = "Django"
return render(request, "practices/greeting.html", {"name": name})
def search(request):
keyword = request.GET.get("q", "")
return render(request, "practices/search.html", {"keyword": keyword})
def product_list(request):
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"分類: {category}, 排序: {sort}, 頁數: {page}")
def filter_products(request):
colors = request.GET.getlist("color")
return HttpResponse(f"選擇的顏色: {', '.join(colors)}")
def hello_name(request, name):
return HttpResponse(f"Hello, {name}!")
def article_detail(request, year, month, slug):
return HttpResponse(f"文章: {year} 年 {month} 月 - {slug}")
def user_articles(request, username):
sort = request.GET.get("sort", "newest")
page = request.GET.get("page", "1")
return HttpResponse(f"{username} 的文章, 排序: {sort}, 頁數: {page}")
def advanced_search(request):
keyword = request.GET.get("q", "")
category = request.GET.get("category", "all")
sort = request.GET.get("sort", "newest")
return render(
request,
"practices/advanced_search.html",
{
"keyword": keyword,
"category": category,
"sort": sort,
},
)
def color_filter(request):
colors = request.GET.getlist("color")
return render(
request,
"practices/color_filter.html",
{"colors": colors},
)
def contact(request):
context = {}
if request.method == "POST":
name = request.POST.get("name", "")
email = request.POST.get("email", "")
message = request.POST.get("message", "")
context = {
"success": True,
"name": name,
"email": email,
"message": message,
}
return render(request, "practices/contact.html", context)
def cookie_counter(request):
# 從 Cookie 讀取訪問次數
visit_count = request.COOKIES.get("visit_count", "0")
visit_count = int(visit_count) + 1
# 建立回應
response = HttpResponse(f"你已經訪問了 {visit_count} 次")
# 設定 Cookie
response.set_cookie("visit_count", str(visit_count))
return response
def theme_preference(request):
# 從 GET 參數讀取主題設定
theme = request.GET.get("theme")
# 從 Cookie 讀取目前的主題
current_theme = request.COOKIES.get("theme", "light")
# 如果有新的主題設定就更新
if theme:
current_theme = theme
# 建立回應
response = render(request, "practices/theme.html", {"theme": current_theme})
# 儲存主題設定到 Cookie
if theme:
response.set_cookie("theme", current_theme, max_age=365 * 24 * 60 * 60)
return response
def shopping_cart(request): # (1)!
# 取得操作類型
action = request.GET.get("action") # (2)!
product = request.GET.get("product")
# 從 Session 取得購物車, 如果沒有就建立空字典
cart = request.session.get("cart", {}) # (3)!
# 處理加入購物車
if action == "add" and product: # (4)!
cart[product] = cart.get(product, 0) + 1 # (5)!
request.session["cart"] = cart # (6)!
# 處理移除商品
elif action == "remove" and product: # (7)!
if product in cart:
del cart[product]
request.session["cart"] = cart
# 處理清空購物車
elif action == "clear": # (8)!
request.session["cart"] = {}
# 重新取得購物車, 可能已更新
cart = request.session.get("cart", {})
return render(request, "practices/cart.html", {"cart": cart})
- 建立購物車 view
- 從 GET 參數取得操作類型(add/remove/clear)
- 從 Session 取得購物車資料,預設為空字典
- 處理「加入購物車」操作
- 如果商品已存在,數量加 1;否則設為 1
- 更新 Session 中的購物車
- 處理「移除商品」操作
- 處理「清空購物車」操作
Session 操作重點
- 讀取 Session:
request.session.get("key", default) - 寫入 Session:
request.session["key"] = value - 刪除 Session:
del request.session["key"] - 自動儲存:Django 會自動處理 Session 的儲存
建立 Template¶
建立 practices/templates/practices/cart.html:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="This is my first Django Template" />
<meta name="keywords" content="Django, Template, HTML" />
<title>購物車</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
}
.products {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin-bottom: 30px;
}
.product-btn {
padding: 15px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 5px;
text-align: center;
display: block;
}
.product-btn:hover {
background-color: #0056b3;
}
.cart {
background-color: #f8f9fa;
padding: 20px;
border-radius: 5px;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.remove-btn {
background-color: #dc3545;
color: white;
padding: 5px 10px;
text-decoration: none;
border-radius: 3px;
font-size: 14px;
}
.remove-btn:hover {
background-color: #c82333;
}
.clear-btn {
background-color: #6c757d;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
display: inline-block;
margin-top: 10px;
}
.clear-btn:hover {
background-color: #5a6268;
}
.empty-cart {
color: #999;
text-align: center;
padding: 20px;
}
</style>
</head>
<body>
<h1>
購物車範例
</h1>
<h2>
商品列表
</h2>
<div class="products">
<a href="?action=add&product=蘋果" class="product-btn">加入 蘋果</a>
<a href="?action=add&product=香蕉" class="product-btn">加入 香蕉</a>
<a href="?action=add&product=橘子" class="product-btn">加入 橘子</a>
<a href="?action=add&product=葡萄" class="product-btn">加入 葡萄</a>
</div>
<h2>
購物車內容
</h2>
<div class="cart">
{% if cart %}
{% for product, quantity in cart.items %}
<div class="cart-item">
<span>{{ product }} x {{ quantity }}</span>
<a href="?action=remove&product={{ product }}" class="remove-btn">移除</a>
</div>
{% endfor %}
<a href="?action=clear" class="clear-btn">清空購物車</a>
{% else %}
<div class="empty-cart">
購物車是空的
</div>
{% endif %}
</div>
</body>
</html>
設定 URL¶
編輯 practices/urls.py:
from django.urls import path
from practices import views
app_name = "practices"
urlpatterns = [
path("hello/", views.hello_world, name="hello_world"),
path("greeting/", views.greeting, name="greeting"),
path("search/", views.search, name="search"),
path("products/", views.product_list, name="product_list"),
path("products/filter/", views.filter_products, name="product_filter"),
path("hello/<str:name>/", views.hello_name, name="hello_name"),
path(
"articles/<int:year>/<int:month>/<slug:slug>/",
views.article_detail,
name="article_detail",
),
path(
"users/<str:username>/articles/",
views.user_articles,
name="user_articles",
),
path("advanced-search/", views.advanced_search, name="advanced_search"),
path("color-filter/", views.color_filter, name="color_filter"),
path("contact/", views.contact, name="contact"),
path("cookie-counter/", views.cookie_counter, name="cookie_counter"),
path("theme/", views.theme_preference, name="theme_preference"),
path("cart/", views.shopping_cart, name="shopping_cart"),
]
測試¶
訪問 http://127.0.0.1:8000/practices/cart/:
- 點擊「加入 蘋果」按鈕數次
- 看到購物車顯示「蘋果 x 3」
- 加入其他商品
- 點擊「移除」可以刪除特定商品
- 點擊「清空購物車」可以清空所有商品
- 關閉瀏覽器並重新開啟
- 再次訪問購物車,資料依然存在(Session 記住了)
Session 成功運作
Session 的資料:
- ✅ 存在伺服器端,使用者無法修改
- ✅ 可以儲存複雜的資料結構(字典、列表)
- ✅ 重新開啟瀏覽器後依然存在
- ✅ 可以設定過期時間
查看 Session 資料¶
在資料庫中查看¶
Django 的 Session 預設存在資料庫的 django_session 表中。
執行以下命令:
在 Python shell 中:
from django.contrib.sessions.models import Session # (1)!
# 取得所有 Session
sessions = Session.objects.all() # (2)!
# 查看第一個 Session
s = sessions.first() # (3)!
print(s.session_key) # Session ID
print(s.get_decoded()) # Session 資料(解碼後)
- 匯入 Session 模型
- 取得所有 Session
- 查看第一個 Session 的資料
你會看到類似這樣的輸出:
Session 的結構
每個 Session 包含:
session_key:Session ID(存在 Cookie 中)session_data:Session 資料(加密後的字串)expire_date:過期時間
Session 的優缺點與使用場景¶
優點¶
Session 的優勢
- 安全性高:資料存在伺服器,使用者無法修改
- 容量更大:沒有 Cookie 的 4KB 限制
- 支援複雜資料:可以儲存字典、列表等
- 自動管理:Django 自動處理建立、讀取、更新、刪除
缺點¶
Session 的限制
- 需要儲存空間:使用資料庫或快取
- 效能開銷:每次請求都要讀取資料庫
- 擴展性問題:多伺服器時需要共享 Session 儲存
- 依賴 Cookie:Session ID 還是透過 Cookie 傳送
適合使用 Session 的場景¶
適合的場景
- ✅ 使用者登入狀態:儲存使用者 ID、權限
- ✅ 購物車:暫存購物車內容
- ✅ 多步驟表單:暫存表單資料
- ✅ 臨時資料:需要跨頁面的暫存資料
不適合使用 Session 的場景¶
不適合的場景
- ❌ 長期儲存:應該存在資料庫
- ❌ 大量資料:影響效能
- ❌ 跨裝置同步:Session 綁定瀏覽器
- ❌ 公開資料:不需要隱藏的資料可以用 GET 參數
Cookie 與 Session 的比較¶
比較表¶
| 項目 | Cookie | Session |
|---|---|---|
| 儲存位置 | 瀏覽器端 | 伺服器端 |
| 容量限制 | 約 4KB | 取決於伺服器 |
| 安全性 | 低(可被修改) | 高(存在伺服器) |
| 效能 | 好(不佔伺服器資源) | 較差(需要讀取資料庫) |
| 生命週期 | 可設定(天、月、年) | 通常較短(分鐘、小時) |
| 跨網域 | 可設定 domain | 不支援 |
| 適用資料 | 簡單、非敏感 | 複雜、敏感 |
視覺化比較¶
graph TD
A[需要儲存資料] --> B{資料是否敏感?}
B -->|是| C[使用 Session]
B -->|否| D{資料大小?}
D -->|小於 4KB| E{需要長期保存?}
D -->|大於 4KB| C
E -->|是| F[使用 Cookie]
E -->|否| G{需要跨頁面?}
G -->|是| C
G -->|否| H[使用 GET/POST 參數]
style C fill:#90EE90
style F fill:#87CEEB
style H fill:#FFD700
實務建議¶
選擇指南
使用 Cookie:
- 使用者偏好設定(語言、主題)
- 追蹤和分析
- 簡單的狀態記錄
使用 Session:
- 使用者登入狀態
- 購物車
- 多步驟表單
- 任何敏感或複雜的資料
組合使用:
- Session 依賴 Cookie 來傳送 Session ID
- 可以同時使用兩者來達成不同目的
安全性建議¶
重要的安全設定
在生產環境中,務必設定:
# Cookie 安全設定
SESSION_COOKIE_SECURE = True # 只在 HTTPS 傳送
SESSION_COOKIE_HTTPONLY = True # 禁止 JavaScript 存取
SESSION_COOKIE_SAMESITE = "Lax" # 防止 CSRF 攻擊
# Session 設定
SESSION_COOKIE_AGE = 1209600 # 2 週(秒)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 關閉瀏覽器後是否刪除
SESSION_SAVE_EVERY_REQUEST = False # 每次請求都更新過期時間
這些設定可以大幅提升安全性!
進階:Session 的儲存方式¶
切換到快取儲存¶
如果你的網站流量很大,可以使用快取(如 Redis)來儲存 Session,以提升效能。
Django 5.1+ 內建 Redis 支援
從 Django 5.1 開始,Django 內建了 Redis cache backend,不需要額外安裝第三方套件!
設定¶
編輯 settings.py:
# Cache 設定
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache", # (1)!
"LOCATION": "redis://127.0.0.1:6379/1", # (2)!
}
}
# 使用快取作為 Session 儲存
SESSION_ENGINE = "django.contrib.sessions.backends.cache" # (3)!
SESSION_CACHE_ALIAS = "default" # (4)!
- 使用 Django 內建的 Redis cache backend
- Redis 伺服器位置(預設為本機 6379 埠,資料庫 1)
- 設定 Session 使用快取儲存
- 指定使用哪個快取設定
儲存方式比較
| 方式 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 資料庫 | 可靠、持久化 | 較慢 | 一般網站 |
| 快取 | 快速 | 伺服器重啟後消失 | 高流量網站 |
| 快取+資料庫 | 快速且可靠 | 需要額外設定 | 大型網站 |
任務結束¶
完成!
恭喜你完成了這個章節!現在你已經:
- 了解什麼是 Cookie
- 學習如何在 Django 中操作 Cookie
- 認識 Cookie 的優缺點與使用場景
- 了解什麼是 Session
- 學習如何在 Django 中操作 Session
- 認識 Session 的優缺點與使用場景
- 比較 Cookie 與 Session 的差異
你學會了:
-
Cookie 操作:
- 讀取:
request.COOKIES.get("key") - 設定:
response.set_cookie("key", value, max_age=...) - 實際應用:訪問計數器、主題偏好
- 讀取:
-
Session 操作:
- 讀取:
request.session.get("key") - 設定:
request.session["key"] = value - 刪除:
del request.session["key"] - 實際應用:購物車功能
- 讀取:
-
選擇原則:
- 簡單、非敏感資料 → Cookie
- 複雜、敏感資料 → Session
- 根據需求靈活運用