57. django之Cookie和Session

1.  Cookie与Session介绍

1.1 cookie

客户端与服务端交互时,存储在客户端上与用户信息相关的数据。

Cookie是存储在用户计算机上的小型文本文件,用于跟踪用户在网站上的活动。
初始版本的Cookie只能存储很少的数据,并且没有强制加密机制,容易被篡改或窃取。

1.2 Session简介

Session是在服务端存储用户会话数据的一种技术。
每当用户访问网站时,服务器会为其创建一个唯一的Session标识符(Session ID),并将会话数据存储在服务器上。

Session ID一般通过Cookie或URL参数传递给客户端,用于识别用户的会话状态。

Session ID通过Set-Cookie发送给浏览器(Cookie 名通常为JSESSIONID或PHPSESSID)。
浏览器存储 Session ID:浏览器将包含Session ID的 Cookie 保存为会话 Cookie

1.3 Cookie和Session的关联

在实际应用中,Cookie和Session通常结合使用。

当用户首次访问网站时,服务器会为其分配一个唯一的Session ID,通过Set-Cookie发送给浏览器,。
随后,客户端在每次请求中都会携带该Cookie,服务器通过解析Cookie中的Session ID,读取对应的会话数据,实现用户状态的跟踪和管理。

1.4 token

Token 身份验证是无状态的分布式身份认证方案,核心是:服务器不存储用户会话状态,所有身份信息都加密编码在客户端持有的 Token 字符串中,每次请求由服务器验证 Token 的合法性来完成身份校验。

客户端                          服务器
   |                              |
1. 提交用户名/密码登录请求 -----> |
   |                              |
   |                          2. 验证用户凭证
   |                              |
   | <----- 3. 生成并返回签名Token |
   |                              |
4. 本地存储Token(localStorage/
   sessionStorage/HttpOnly Cookie)
   |                              |
5. 后续请求携带Token(Authorization: Bearer <token>| ----->                       |
   |                              |
   |                          6. 验证Token签名+有效期
   |                              |
   | <----- 7. 返回受保护资源/401未授权 |

token最主流实现:JWT(JSON Web Token)

JWT 是目前 Web 领域最通用的 Token 标准(RFC 7519),本质是三段式 Base64 编码字符串,用.分隔,格式:Header.Payload.Signature
部分作用内容示例关键说明
Header(头部) 声明 Token 类型和签名算法 {"alg":"HS256","typ":"JWT"} 固定 Base64 编码,任何人可解码
Payload(载荷) 存储身份信息和声明 {"sub":"123456","name":"张三","exp":1714000000} 仅 Base64 编码,绝对不能放密码、手机号等敏感信息
Signature(签名) 防篡改核心 HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload), secret) 用服务器私钥 / 密钥生成,只有服务器能验证

签名防篡改原理:
  服务器用只有自己知道的密钥,对 Header+Payload 的拼接字符串进行哈希签名
  客户端修改任何一段内容,重新计算的签名都会和原签名不一致
  服务器验证时,会用相同的密钥重新计算签名,对比是否匹配,不匹配直接拒绝

Token 与 Session-Cookie 核心区别

维度Token(JWT)Session-Cookie
存储位置 客户端(前端可控) 服务器(Session)+ 客户端(Cookie 存 SessionID)
状态 无状态(服务器不存会话) 有状态(服务器需维护 Session 表)
扩展性 极佳(分布式 / 微服务直接共享) 差(需 Session 共享 / 粘性会话)
跨域支持 天然支持(放在请求头) 受同源策略限制
CSRF 风险 低(不依赖 Cookie) 高(Cookie 自动携带)
XSS 风险 高(存在 localStorage 易被窃取) 低(HttpOnly Cookie 无法被 JS 读取)
主动注销 困难(需黑名单机制) 简单(直接删除服务器 Session)

2. Django操作Cookie

2.1 前言

虽然cookie是服务端发送给客户端浏览器需要的保存内容
但是客户端浏览器可以选择拒绝保存
如果禁止自动保存cookie
那么只要是需要登录的网站都没办法正常登录了

2.2 视图函数返回值

正常情况下返回的是对象

return HttpResponse()
return render()
return redirect()

不直接返回对象,先用变量名指代,返回变量名

obj1 = HttpResponse()
return obj1

obj2 = render()
return obj2

obj3 = redirect()
return obj3
想要操作cookie,必须进行以上变形才可以

2.3 操作cookie

[1] 设置cookie

obj = HttpResponse()
obj.set_cookie(key,value)
return obj

参数:

  key, 键
  value='', 值
  max_age=None, 超时时间,以秒为单位
  expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  domain=None, Cookie生效的域名
  secure=False, https传输
  httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

[2] 获取cookie

request.COOKIES.get(key)

[3] 删除cookie

    obj = redirect('/app01/login/')
    obj.delete_cookie('sign')
    return obj

2.4 设置与获取cookie代码案例

[1] 登录功能--服务端向客户端发送cookie

项目的urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/login/', views.login)
]

app01的views.py

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            obj = HttpResponse('登录成功')
            obj.set_cookie('name1', 'avril1')
            return obj
    return render(request, 'login.html')

app01的login.html

<body>
<form action="" method="post">
    <p>username: <input type="text" name="username"></p>
    <input type="submit">
</form>
</body>

首次打开浏览器时cookie为空

image

输入用户名avril点击提交之后

image

[2] 只有登录才能查看home页面

项目的urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/home/', views.home),
    path('app01/login/', views.login),
]

app01的views.py

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            return redirect('/app01/home/')
    return render(request, 'login.html')

def home(request):
    return HttpResponse('只有登录的用户才可以查看home页面')

app01的login.html

<body>
<form action="" method="post">
    <p>username: <input type="text" name="username"></p>
    <input type="submit">
</form>
</body>

打开登录页面,输入用户名点击提交,跳转到home页面

image

image

直接访问home页面(没有登录直接访问)

image

[3] 解决不登录直接访问的问题

app01的views.py

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            obj = redirect('/app01/home/')
            # 设置cookie
            obj.set_cookie('sign', 'abc123')
            return obj
    return render(request, 'login.html')

def home(request):
    # cookie正确则登录成功
    if request.COOKIES.get('sign') == 'abc123':
        return HttpResponse('用户已登录,当前是home页面')
    # cookie错误则跳转到登录页面
    return redirect('/app01/login/')

 直接访问/app01/home/时,跳转到/app01/login/

访问登录页面,输入用户名点击提交

image

image

[4] 多个视图函数需要添加登录认证

当多个视图函数都需要用到登录认证时,需要用到装饰器

项目的urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/home/', views.home),
    path('app01/login/', views.login),
    path('app01/index/', views.index),
    path('app01/order/', views.order),
]

app01的views.py

from django.shortcuts import render, HttpResponse, redirect

# Create your views here.

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            obj = redirect('/app01/home/')
            # 设置cookie
            obj.set_cookie('sign', 'abc123')
            return obj
    return render(request, 'login.html')


def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('sign') == 'abc123':
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect('/app01/login/')

    return inner

@login_auth
def home(request):
    return HttpResponse('用户已登录,当前是home页面')

@login_auth
def index(request):
    return HttpResponse('用户已登录,当前是index页面')

@login_auth
def order(request):
    return HttpResponse('用户已登录,当前是order页面')

当直接访问app01/home/、app01/index/、app01/order/时,跳转到login页面

输入用户名avril点击提交之后,首先直接跳转到home页面,此时有了cookie

image

image

然后可以访问app01/index/、app01/order/

image

image

当直接访问app01/home/、app01/index/、app01/order/时,跳转到login页面,输入用户名登录之后统一跳转到home页面,无法跳转到index、order

[5] 解决跳转到login之后无法跳转到目的URL的问题

(1)前提知识

URL举例:http://localhost:8000/app01/login/?next={target_url}

  ? 是 HTTP URL 标准中查询字符串(Query String)的唯一分隔符,它将 URL 拆分为两部分:
  前面:http://localhost:8000/app01/login/ → 服务器要处理的资源路径(对应 Django 的 URLconf 路由)
  后面:next={target_url} → 传给该资源的GET 请求参数(键值对形式)
1、通用 HTTP 层面的作用
  传递额外的动态参数:多个参数用&连接,格式为?key1=value1&key2=value2
  影响服务器响应:服务器解析这些参数后,可返回差异化内容(比如分页?page=2、筛选?status=active)
  GET 请求专属:参数明文出现在 URL 中,长度有限制(浏览器 / 服务器通常限制 2KB 左右),不适合传敏感数据

2、Django 登录场景的作用
  next={target_url} 是 Django 内置登录系统的标准约定参数,也是最常用的查询参数用法
  功能:用户登录成功后,自动重定向到next指定的目标页面

? 之后的内容 不是 URL 的实际路径,仅作为查询参数(Query String)存在,用于向服务器传递额外的键值对数据。
服务器在进行路由匹配时只会解析 ? 之前的部分,? 之后的内容仅用于向该路径传递额外数据。

(2) 代码

app01的views.py

from django.shortcuts import render, HttpResponse, redirect

# Create your views here.

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            # 在登录认证已经获取目的URL的基础上,再获取目的URL
            # next内容是GET请求参数里的键值对
            next_path = request.GET.get('next')
            # 跳转到目的URL next_path,而非之前的/app01/home/
            obj = redirect(next_path)
            # 设置cookie
            obj.set_cookie('sign', 'abc123')
            return obj
    return render(request, 'login.html')

def login_auth(func):
    def inner(request, *args, **kwargs):
        print(request.get_full_path())  # 获取用户登录之前的目的URL
        target_url = request.get_full_path()
        if request.COOKIES.get('sign') == 'abc123':
            res = func(request, *args, **kwargs)
            return res
        else:
            # 将目的URL当作参数传给登录函数
            return redirect(f'/app01/login/?next={target_url}')

    return inner

@login_auth
def home(request):
    return HttpResponse('用户已登录,当前是home页面')

@login_auth
def index(request):
    return HttpResponse('用户已登录,当前是index页面')

@login_auth
def order(request):
    return HttpResponse('用户已登录,当前是order页面')

访问http://localhost:8000/app01/index/,仍然访问/app01/login/,但是增加了查询参数next

image

输入用户名avril点击提交,跳转到index页面而非之前的home页面

image

当直接访问login页面,输入用户名并点击提交时,报错

image

image

login函数加上if next_path

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        if username == 'avril':
            # 在登录认证已经获取目的URL的基础上,再获取目的URL
            # next内容是GET请求参数里的键值对
            next_path = request.GET.get('next')
            # 用户有可能直接访问登录页面
            if next_path:
                obj = redirect(next_path)  # 跳转到目的URL next_path,而非之前的/app01/home/
            else:
                obj = redirect('/home/')  # 直接访问登录页面之后跳转到home页面
            # 设置cookie
            obj.set_cookie('sign', 'abc123')
            return obj
    return render(request, 'login.html')

直接访问login页面,输入用户名点击提交,跳转到指定的home页面

image

 image

2.5 删除cookie代码案例

views.py中添加logout函数

@login_auth
def logout(request):
    obj = redirect('/app01/login/')
    obj.delete_cookie('sign')
    return obj

输入用户名avril,点击提交之后跳转到home页面,生成cookie

image

image

以上登录基础上访问logout,跳转到login页面,cookie被删除

image

3. django操作session

3.1 前言

session数据是保存在服务端的,给客户端返回的是一个加密后的字符串(sessionid)

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
 
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
 
# 会话session的key
request.session.session_key
 
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
 
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
 
# 删除当前会话的所有Session数据  只删客户端
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie。 服务端、客户端都删
request.session.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。
 
# 设置会话Session和Cookie的超时时间  默认过期时间是14天
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

必须先进行orm迁移,在数据库中自动生成django_session表

否则直接访问设置session的视图函数会报错

image

3.1 设置Session

[1] 语法

request.session['key'] = value

[2] 代码

项目的urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/tool/', views.tool),
]

app01的views.py

def tool(request):
    request.session['sign1'] = 'abc666'
    return HttpResponse('设置session操作')

访问设置session的路由时,django_session数据库生成session_key、session_data,session_key以sessionid形式发送给客户端浏览器

image

image

3.2 获取Session

[1] 语法 

request.session.get('key')

[2] 原理

从请求 Cookie 中读取 sessionid
通过 session_key 查找未过期的数据库记录
数据解码,返回 Python 字典

[2] 代码

项目的urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/tool/', views.tool),
    path('app01/get_sign/', views.get_sign)
]

app01的views.py

def tool(request):
    request.session['sign1'] = 'abc666'
    return HttpResponse('设置session操作')

def get_sign(request):
    res = request.session.get('sign1')
    print(res)
    return HttpResponse('获取session操作')

image

获取session时,后端打印的是实际的session值,而非加密后的字符串

image

3.3 设置、获取多个session

app01的views.py

def tool(request):
    request.session['sign1'] = 'abc666'
    request.session['sign2'] = 'abc999'
    request.session['sign3'] = 'abc123'
    return HttpResponse('设置session操作')

def get_sign(request):
    res = request.session.get('sign1')
    res2 = request.session.get('sign2')
    res3 = request.session.get('sign3')
    print(res, res2, res3)
    return HttpResponse('获取session操作')

先访问设置页面

image

image

再访问获取页面

image

image

结论:

给session设置多个值的时候,存在数据库中的数据仍是一条
但是在取session的时候,可以通过request.session对象获取到设置的多组键值对

3.4 session配置

django提供了5种类型的session

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
 
2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 
 
4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
 
5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
 
其他公用设置项:
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,默认修改之后才保存(默认)

 

posted @ 2026-06-07 21:33  pythondjango  阅读(15)  评论(0)    收藏  举报