3. Cookie 和 Session 会话
因为 HTTP 协议是无状态的,每次浏览器请求 request
都是无状态的,后台服务器无法识别当前请求与上一次请求及之后请求是否为同一用户。
对于静态网站来说无所谓(所有用户看到的都是一样的),但是动态网站(如:淘宝,登录或非登录状态看到的东西是不一样的),无法识别用户是很致命的。
为了保证被连接的状态,服务器会通过用户浏览器在用户被限定硬盘位置写入一些数据,等下次请求时,需要带上这些数据,服务器根据这些就能识别,这就是 Cookie。
3.1 简介
虽然 Cookie 解决了 HTTP 协议无状态的问题,但是因为它是保存在用户本地,也容易被利用对服务器进行恶意攻击,这时出现了 Session。
Session 依赖于 Cookie,但是数据保存在服务端,用户浏览器的 Cookie 只会保存一段非明文的识别信息(好比一把钥匙)。每次请求时,都会带上这段信息,然后与数据库中保存的 Session ID 验证是否一致。
Django 内置了通用 session 框架 'django.contrib.sessions'
,在项目中任何地方都可以调用,同时它也有多种数据保存方式:
- 数据库
- 缓存 catch
- 文件
- cookie
但是通常我们都保存在数据库中,类似于以下:
3.2 Session 常用方法
session 封装在 HttpRequest
对象中,以键值对的形式存储,通过 request.session
即可读写。
常用方法:
# 类似字典数据类型的内置方法
keys()
items()
setdefault()
clear()
# 它还有下面的方法:
1. flush()
request.session.flush() # 将 session 数据清除,并且 cookie 失效
# 删除当前的会话数据和会话cookie。经常用在用户退出后,删除会话。
2. set_test_cookie()
# 设置一个测试cookie,用于探测用户浏览器是否支持cookies。由于cookie的工作机制,你只有在下次用户请求的时候才可以测试。
3. test_cookie_worked()
# 返回True或者False,取决于用户的浏览器是否接受测试cookie。你必须在之前先调用set_test_cookie()方法。
4. delete_test_cookie()
# 删除测试cookie。
5. set_expiry(value)
# 设置cookie的有效期。可以传递不同类型的参数值:
• 如果值是一个整数,session将在对应的秒数后失效。例如request.session.set_expiry(300) 将在300秒后失效.
• 如果值是一个datetime或者timedelta对象, 会话将在指定的日期失效
• 如果为0,在用户关闭浏览器后失效
• 如果为None,则将使用全局会话失效策略
失效时间从上一次会话被修改的时刻开始计时。
6. get_expiry_age()
# 返回多少秒后失效的秒数。对于没有自定义失效时间的会话,这等同于SESSION_COOKIE_AGE.
# 这个方法接受2个可选的关键字参数
• modification:会话的最后修改时间(datetime对象)。默认是当前时间。
•expiry: 会话失效信息,可以是datetime对象,也可以是int或None
7. get_expiry_date()
# 和上面的方法类似,只是返回的是日期
8. get_expire_at_browser_close()
# 返回True或False,根据用户会话是否是浏览器关闭后就结束。
9. clear_expired()
# 删除已经失效的会话数据。
10. cycle_key()
# 创建一个新的会话秘钥用于保持当前的会话数据。django.contrib.auth.login() 会调用这个方法。
3.3 示例一
使用 Cookie 和 Session 来验证登录用户与非登录用户
- 视图函数
views.py
from django.shortcuts import render, redirect
from app.models import User
from functools import wraps
def check_code(f):
"""
验证登录,验证成功,进入首页,失败则重定向到登录页面
:param f:
:return:
"""
@wraps(f)
def inner(request, *args, **kwargs):
if request.session.get('is_login'):
return f(request, *args, **kwargs)
else:
return redirect('login')
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = User.objects.filter(username=username, password=password)[0]
# 登录成功
# 1. 生成特殊的字符串
# 2. 特殊字符串当成key,在数据库的session表中对应一个session value
# 3. 在响应中向浏览器写了一个Cookie Cookie的值就是 特殊的字符串
if user:
request.session['is_login'] = True
request.session['user_id'] = user.id
return redirect('home') # 重定向到个人主页
else:
return render(request, 'login.html')
@check_code # 相当于 home = check_code(home)
def home(request):
"""个人主页"""
user_id = request.session.get('user_id')
# 根据 user_id 去数据库中去查数据
user_obj = User.objects.filter(id=user_id)[0]
if user_obj:
return render(request, 'home.html', {'user_obj': user_obj})
else:
return render(request, 'home.html', {'user_obj': '匿名用户'})
用户登录,验证成功,写入 session到数据库中。个人主页根据来查相关个人信息。
登出
def logout(request):
"""登出"""
# 如果没有登录,即意味着没有登出一说
if not request.session.get('is_login', None):
return redirect('login')
# 清除session
request.session.flush()
# 或者使用下面的方法
# del request.session['is_login']
# del request.session['user_id']
# del request.session['user_name']
return redirect('login')
- 模板
<!--home.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人主页</title>
</head>
<body>
<h1>个人主页</h1>
<p>用户名:{{ user_obj.username }}</p>
<p>密码:{{ user_obj.password }}</p>
<button><a href="{% url 'logout' %}">登出</a> </button>
</body>
</html>
------------------------------------------------------------------------------------------------------------------------
<!--login.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录页面</h1>
<form action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
<p>
<label>用户名</label>
<input type="text" name="username">
</p>
<p>
<label>密码</label>
<input type="password" name="password">
</p>
<input type="submit" value="登录">
</form>
</body>
</html>
- 路由
from django.contrib import admin
from django.urls import path
from app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home, name='home'),
path('login/', views.login, name='login'),
path('logout/', views.logout, name='logout'),
]
3.4 示例二
如果登录则显示个人信息(我的账户、修改密码、登出等),没有登录则显示注册和登录。
- 视图函数
def home(request):
"""首页"""
return render(request, 'home.html')
def login(request):
"""登录"""
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = User.objects.get(username=username)
if user.password == password:
request.session['is_login'] = True
request.session['user_name'] = user.username
request.session['user_id'] = user.id
return redirect('home')
return render(request, 'login.html')
def logout(request):
"""登出"""
if not request.session.get('is_login', None):
return redirect('login')
request.session.flush()
return redirect('home')
- 首页
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
padding-top: 70px;
}
</style>
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Hubery_Jun</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">首页 <span class="sr-only">(current)</span></a></li>
<li><a href="#">热点</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!--如果登录则显示个人信息-->
{% if request.session.is_login %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.session.user_name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">我的账户</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="{% url 'logout' %}">登出</a></li>
</ul>
</li>
<!--没登录显示注册登录-->
{% else %}
<li><a href="#">注册</a></li>
<li><a href="{% url 'login' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1>首页</h1>
</div>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
</html>
- 登录
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录页面</h1>
<form action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
<p>
<label>用户名</label>
<input type="text" name="username">
</p>
<p>
<label>密码</label>
<input type="password" name="password">
</p>
<input type="submit" value="登录">
</form>
</body>
</html>
- 路由
from django.contrib import admin
from django.urls import path
from app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home, name='home'),
path('login/', views.login, name='login'),
path('logout/', views.logout, name='logout'),
]