cookie与session组件
一 会话跟踪技术
什么是会话跟踪
我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向10086发出多个请求,那么这多个请求都在一个会话中。
在Web中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。
在一个会话的多个请求中共享数据,这就是会话跟踪技术。例如在一个会话中的请求如下: 请求银行主页;
- 请求登录(请求参数是用户名和密码);
- 请求转账(请求参数与转账相关的数据);
- 请求信誉卡还款(请求参数与还款相关的数据)。
在这上会话中当前用户信息必须在这个会话中共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款!这就说明我们必须在一个会话过程中有共享数据的能力。
会话路径技术是用Cookie或session完成的
我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!在Web开发中,使用session来完成会话跟踪,session底层依赖Cookie技术。
二 cookie组件
cookie的由来
大家都知道HTTP协议是无状态的。
无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
就好比用户登录商城之后购物车添加1,然后发送添加请求,服务器记不得你之前的登录状态所以需要重新提交登录请求,最后才能获得你的用户信息,所以在web应用必须在用户浏览其他商品时保持已经登录的状态,那么就需要这一系列添加商品的操作都要在一个会话中保持,多次请求共享一个数据,即在这个会话中就是该用户发送多次请求,不必每次请求都需要校验身份。因此cookie就在这样一个场景下诞生。
什么是cookie
其实Cookie是key-value结构,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器。然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!
cookie常见的登录案例
- 首先用户在客户端浏览器向服务器首次发起登录请求
- 登录成功后,服务端会把登录的用户信息存在cookie中,并将cookie返回给客户端浏览器
- 客户端浏览器接收到 cookie 请求后,会把 cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
- 以后再次访问该 web 应用时,客户端浏览器就会把本地的 cookie 带上,这样服务端就能根据 cookie 获得用户信息了,有了用户信息就能访问该用户在数据库中的数据
比如查询数据与改数据都需要登录认证,如果没有cookie情况下,登录之后发送查数据的请求,服务端返回数据给客户端,这时我需要根据查好的数据进行更改,然后发送相关请求,因为服务器是无状态,它认为你这次请求与上次请求无关,所以这次请求没有登录认证需要进行登录,然后再返回时可能是你自己设定的重定向的登录页面了。所以这两次请求就是两次会话,是数据隔离的。
如果加上cookie,登录成功之后,就会返回cookie到你的客户端中,里面有你的身份标识,然后每一次发送请求都会带着这个标识,所以可以在一个会话中发送多个需要登录认证的请求,且不需要重复的登录,直接通过cookie中的标识在服务器上得到权限,那么在查到数据后,还可以继续进行改操作,因为这几个事件都有登录状态的认证在同一个会话中。
cookie的原理
cookie的工作原理是:由服务器产生cookie,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
cookie规范
- Cookie大小上限为4KB;
- 一个服务器最多在客户端浏览器上保存20个Cookie;
- 一个浏览器最多保存300Cookie;
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
cookie的覆盖
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。
在浏览器中查看cookie
浏览器中按F12,点network---cookies就能看到
Django中操作cookie
# 添加cookie
def set_cookie(request):
# 浏览器向我这个地址发一个请求,就在浏览器写入 name = arther
obj = HttpResponse('ok')
obj.set_cookie('name', 'egon') # 写入到浏览器了,在http响应头里:cookie:name=egon
obj.set_cookie('age', '18') # 写入到浏览器了,在http响应头里:cookie:age=18
return obj
'''
Set-Cookie: name=egon; Path=/
Set-Cookie: age=18; Path=/
'''
# 得到cookie
def get_cookie(request):
# 浏览器向我这个地址发一个请求,就在浏览器写入 name = arther
print(request.COOKIES)
print(request.COOKIES.get('name'))
return HttpResponse('我拿了你传过来的cookies')
# 删除cookie
def delete_cookie(request):
obj = HttpResponse('我删除了你 name 这个cookie')
obj.delete_cookie('name')
return obj
参数:
request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
- default: 默认值
- salt: 加密盐
- max_age: 后台控制过期时间
rep = HttpResponse(...)
rep = render(request, ...)
rep.set_cookie(key,value)
rep.set_signed_cookie(key,value,salt='加密盐')
- key, 键
- value='', 值
- max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
- expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
- path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
- domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
- secure=False, 浏览器将通过HTTPS来回传cookie
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False)
# key
# value
# max_age:传个数字,以秒计,过期时间,有默认值 (6天后过期:60*60*24*5)
---了解
# expires:传时间对象,date=datetime.timedelta()
# path:默认 / 表示当前域下的所有路径 http://127.0.0.1:8000/lqz/dfd/
# domain:在那个域下有效,比如登录后在当前服务器的另一个域中没有登录状态
# secure:是否Https传输cookie
# httponly:cookie只支持http传输
Cookie版登陆校验
# 登陆的视图函数,成功则返回cookie中带有'is_login'=True(可以加盐)
# 之后的请求中cookie带有'is_login'=True,即可访问该用户
def login(request):
if request.method == 'POST':
name = request.POST.get('name')
pwd = request.POST.get('pwd')
print(name)
all_info = models.Userinfo.objects.all()
for user_obj in all_info:
if name == user_obj.name and pwd == user_obj.password:
import datetime
now = datetime.datetime.now().strftime('%Y-%m-%d %X')
print(now)
obj = JsonResponse({'code': 1, 'info': '登录成功'})
obj.set_cookie('is_login', True)
obj.set_cookie('username', name)
obj.set_cookie('login_time', now)
return obj
else:
flag = {'code': 0, 'info': '登录失败'}
return JsonResponse(flag)
# 写成装饰器
def login_auth(func):
def inner(request, *args, **kwargs):
if request.COOKIES.get('is_login'):
return func(request, *args, **kwargs)
else:
url = reverse('login')
return redirect(url)
return inner
三 Session组件
session的由来
cookie虽然在一定程度上解决了'保持状态'的需求,但是由于cookie本身最大支持4096字节,以及它本身保存在客户端,会被拦截或切取,如上述的登录装饰器,登录成功后在cookie中设置'is_login'=True来标识一个用户的身份与权限。如果有人在本地客户端窃取登录成功后cookie,然后再次发送该请求即可盗用该用户,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器(相对安全,在服务器不易被窃取),有较高的安全性。这就是Session。
问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
我们可以给每个客户端的Cookie分配一个唯一的session_id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
session组件的使用
1.存在于服务端的键值对,最终会存在session表中,有id,data,date三个字段,保存的数据会以加密的形式如用户信息拼接到字段data下的记录,然后随即生成一个字符串做为session_id来标识该用户身份,该id会放在cookie中返回到客户端的浏览器中,然后每次访问带上这个cookie就是已经登录的用户的访问
2.同一个浏览器不允许登录多个账户,不同浏览器可以登录同一个账户
3.session的使用(必须迁移数据)
-增:request.session['name']=lqz
-查:request.session['name']
-改:request.session['name']=egon
-删:del request.session['name']
-设置过期时间:request.session.set_expiry(10)
4.session的其他使用
-request.session.setdefault('k1',111) # 有该key值则不设置,没有则设置
-request.session.get('name',None)
-del request.session['k1']
# 所有 键、值、键值对
-request.session.keys()
-request.session.values()
-request.session.items()
-request.session.session_key
# 获取那个放入cookie中的随机字符串,也是session表中用来标识用户身份的字段
-request.session.exists("session_key")
# 判断这个随机字符串有没有数字
# 注意在登录过程中还没有形成session_key,所以此时为None,在登录校验完后响应浏览器返回数据 时经过中间件形成的session_key
-request.session.delete() # 在session表中删除当前这个登录者的数据,
-request.session.flush() # 所以当请求来时cookie中的session_id匹配不上session表的数 据校验失败,需要刷新将失效cookie设置为过期
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_AGE = 1209600 # Session的cookie失效日期(2周)(默认)***记住
---了解
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
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_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
四 cgb装饰器
from django.views import View
from django.utils.decorators import method_decorator
# 使用登录认证装饰器:多了一个self参数,所以调用模块method_decorator
# 用法一
# @method_decorator(login_auth,name='get')
# @method_decorator(login_auth,name='post')
class UserInfo(View):
# 用法二
@method_decorator(login_auth)
def get(self, request, *args, **kwargs):
return HttpResponse('userinfo get')
# 总结:两种用法
-加在类上:@method_decorator(login_auth,name='get')
-加载方法上:@method_decorator(login_auth)
五 cookie与session的不同
- 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
- 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
- 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
- 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
- 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。
六 练习
基于session与cbv写一个登录认证装饰器,四个视图(登录,退出,订单,用户信息)
-必须登录才能访问订单和用户信息
-没登录访问订单,会重定向到登录,登录成功后跳转到订单页面
# views.py
from django.shortcuts import render, HttpResponse, redirect, reverse
from app10.appform import MyForm
from django.utils.decorators import method_decorator
from django.forms import Form
from django.views import View
flag = None
# 全局变量,作为判断依据渲染到home.html文件中,如果为None是初始页面有登录没退出,如果登录成功改# # 为True重定向到home中,则是没有登录有退出,然后退出成功则是改回None,有退出没登录
# Create your views here.
def auth(func):
def wrapper(request, *args, **kwargs):
flag = request.COOKIES.get('sessionid')
if flag: # 如果有sessionid
if flag == request.session.session_key:
# 该sessionid与数据库中的key相等,则判定成功
# 如果是没有校验flag是否存在,那么两者有可能都是none相等
# 为了防止改动sessionid也能登录的场景
return func(request, *args, **kwargs)
else:
return redirect(reverse('login'))
else:
return redirect(reverse('login'))
return wrapper
class Login(View):
def get(self, request):
form = MyForm()
return render(request, 'login.html', {'form': form})
def post(self, request):
form = MyForm(request.POST)
global flag
if form.is_valid():
flag = True
request.session['is_login'] = True
return redirect(reverse('home'))
else:
error = form.errors.get('__all__') # 获取全局钩子的错误信息
if error is None:
return render(request, 'login.html', {'form': form})
return render(request, 'login.html', {'form': form, 'error': error})
class Home(View):
def get(self, request):
print(flag)
return render(request, 'home.html', {'flag': flag})
class Logout(View):
def get(self, request):
global flag
flag = None
request.session.delete()
request.session.flush()
return redirect(reverse('home'))
@method_decorator(auth, name='get')
class Info(View):
def get(self, request):
return HttpResponse('用户信息')
@method_decorator(auth, name='get')
class Order(View):
def get(self, request):
return HttpResponse('订单')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
<script src="{% static 'js/bootstrap.js' %}"></script>
</head>
<body>
<!-- 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 style="display: block" id="login"><a href="{% url 'login' %}">登录</a></li>
<li style="display: none" id="logout"><a href="{% url 'logout' %}">退出</a></li>
<li><a href="{% url 'info' %}">用户信息</a></li>
<li><a href="{% url 'order' %}">订单</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Dropdown <span class="caret"></span></a>
{% if flag %}
<script>
$('#login').css('display', 'none')//登录成功后flag=True,那就是登录有,退出没有
$('#logout').css('display', 'block')
</script>
{% else %}
<script>
$('#login').css('display', 'block') //最开始flag=None,相应最开始的页面也是登录有,退出没有
$('#logout').css('display', 'none')
</script>
{% endif %}
<script>
$('#logout').click(function () {
alert('退出成功!')
})
</script>
</body>
</html>