一、什么是Session和Cookie?
这里有必要先了解一下Session和Cookie的概念。
我们知道,HTTP是无状态、无连接的协议,但是只要结合实际场景的话,你显然会对这个说法感到疑惑,因为有很多实际应用中的例子,似乎都表明了HTTP是’有状态’的。比方说,你登录一个网站,并没有输入账号密码就会自动登录,你在购物车中购买的商品,并没有标注你的身份就可以被服务器正确识别,这又是怎么一回事呢?其实做到这些的,不是HTTP,而是另外两个技术,Cookie和Session。
我们登录一个网站输入网址,就相当于为浏览器与服务器开启了一次临时会话,在这次会话中,不可避免的要产生很多数据,但HTTP是无状态的,为了记住这些数据,从而出现了Cookie和Session。其中,Cookie是客户端技术,而Session是服务端技术。因为Cookie保存在客户端,因此Cookie中的信息并不是很安全,我们通常将一些不是很重要的信息保存在Cookie中,而将一些用户的关键性信息保存在Session中。
当浏览器向服务器发送一个请求时,浏览器会自动生成一个Session和一个Session ID,Session ID用来唯一标注这个Session,然后将该ID发送给浏览器,之后浏览器如果发生第二次请求,会将该Session ID一同发送给服务器,服务器会根据ID找到相应的Session,这样就基本保证了有状态。
不说太多了,有关Cookie和Session可以参考如下博客:
http协议无状态中的【状态】到底指的是什么?!
以及知乎上有关Cookie和Session的区别的一个很赞的回答:
session 是一个抽象概念,开发者为了实现中断和继续等操作,将 user agent 和 server 之间一对一的交互,抽象为“会话”,进而衍生出“会话状态”,也就是 session 的概念。
而 cookie 是一个实际存在的东西,http 协议中定义在 header 中的字段。可以认为是 session 的一种后端无状态实现。
而我们今天常说的 “session”,是为了绕开 cookie 的各种限制,通常借助 cookie 本身和后端存储实现的,一种更高级的会话状态实现。
所以 cookie 和 session,你可以认为是同一层次的概念,也可以认为是不同层次的概念。具体到实现,session 因为 session id 的存在,通常要借助 cookie 实现,但这并非必要,只能说是通用性较好的一种实现方案。
链接:https://www.zhihu.com/question/19786827
二、在Django中开启Session功能
首先需要知道的是,Django的sessions framewor是完全基于cookie的,它不像PHP那样,把会话的ID放在URL中,那样不仅使得URL变得丑陋,还使得你的网站易于受到通过"Referer"头部进行窃取会话ID的攻击。
要想启用会话功能,需要在配置文件中的 MIDDLEWARE
参数里设置django.contrib.sessions.middleware.SessionMiddleware
中间件,这个设置是默认的。
如果你不想使用会话,请将该中间件以及INSTALLED APPS
中注册的django.contrib.sessions
一并删除。
session data的存储位置并没有一定规范,你可以将它存储在数据库中,或者缓存中,或者文件中,这都可以,而默认情况下,Django将session data存储在数据库中,使用的是django.contrib.sessions.models.Session
。
而决定将session data存储在哪里的是SESSION_ENGINE
配置参数。
SESSION_ENGINE
默认: 'django.contrib.sessions.backends.db'
可选engine:
'django.contrib.sessions.backends.db'
'django.contrib.sessions.backends.file'
'django.contrib.sessions.backends.cache'
'django.contrib.sessions.backends.cached_db'
'django.contrib.sessions.backends.signed_cookies'
(1)使用基于数据库的sessions
这是Django默认的配置,SESSION_ENGINE
已经配置好了因此无需修改,除此之外,你需要在INSTALLED APPS
中注册django.contrib.sessions
,然后migrate以生产数据表。
(2)使用基于缓存的sessions
要想使用Django's cache system
来保存session data,需要先确保你正确配置了cache,关于这点可参阅官方文档。
(3)使用基于文件的sessions
SESSION_ENGINE
需要使用django.contrib.sessions.backends.file
。
同时需要设置SESSION_FILE_PATH
参数,用以给出session data的文件目录。它的默认值为None,如果设置为None,那么Django将使用Python的标准临时文件模块去存储(默认是tempfile.gettempdir())。
【注】关于Python的tempfile临时文件模块:
Python模块学习:tempfile 临时文件(夹)操作
(4)使用基于cookie的sessions
SESSION_ENGINE
需要使用django.contrib.sessions.backends.signed_cookies
。
session data将使用Django的加密签名( cryptographic signing)
工具配合SECECT KEY
进行保存,即将数据进行加密。
相关文档请参阅:cryptographic signing
建议将SESSION_COOKIE_HTTPONLY
设置为True以防止通过JavaScript访问存储的数据。
SESSION_COOKIE_HTTPONLY (settings参数)
默认:True
作用:是否在会话cookie上使用HttpOnly标志。如果设置为True,客户端的JavaScript脚本将无法访问会话cookie。
HttpOnly是一个包含在set-Cookie HTTP响应头中的标志,通过使用它可以降低客户端脚本获取受保护的cookie数据的风险。
使用cookie保存session data的几个注意点:
- 如果你的SECRET_KEY并没有被完全保密,并且你使用的是 PickleSerializer(之后会介绍到),这将可能导致外部代码被执行。
- session data已签名(sign),但并未被加密。因此使用cookie时,客户端是可以读取session data的。使用
MAC(MessageAuthenticationCode)
可以保护数据不受客户端更改的影响,从而使session data在被篡改时失效。如果存储cookie的客户端(例如,用户的浏览器)不能存储所有session cookie并因此删除了data,也会发生同样的失效。 - No freshness guarantee。这个的意思就是说,虽然MAC可以保证cookie session的有效性和完整性,但它不能保证该cookie session就是服务器发送回来的最后一份session。不像其它类型的session,一旦用户注销session就会失效,基于cookie的session即便用户注销了依然有效。因此,如果攻击者窃取了用户的cookie,即使用户注销,他们也可以使用该cookie作为该用户登录,只有当你持有比攻击者的cookie的更新的cookie时,服务器才会认为攻击者的cookie是陈旧的,从而拒绝他的请求。
- cookie的大小可能会对你的网站的性能造成一定影响。
建议:没有特殊需求的话,还是不要将session data保存在cookie中。
二、在视图中使用sessions
当SessionMiddleware启动时,每一个HttpRequest对象就会自动携带一个session属性,它是一个类似于字典的对象。
backends.base.SessionBase
:所有session object的基类。
methods:
__getitem__(key)
Example: fav_color = request.session['fav_color']
__setitem__(key, value)
Example: request.session['fav_color'] = 'blue'
__delitem__(key)
Example: del request.session['fav_color'].
__contains__(key)
Example: 'fav_color' in request.session
get(key, default=None)
Example: fav_color = request.session.get('fav_color', 'red')
pop(key, default=__not_given)
Example: fav_color = request.session.pop('fav_color', 'blue')
keys()
items()
setdefault()
clear()
# 它还有下面的方法:
flush()
# 删除当前的会话数据和会话cookie。经常用在用户退出后,删除会话
set_test_cookie()
# 设置一个测试cookie,用于探测用户浏览器是否支持cookies。由于cookie的工作机制,你只有在下次用户请求的时候才可以测试
test_cookie_worked()
# 返回True或者False,取决于用户的浏览器是否接受测试cookie。你必须在之前先调用set_test_cookie()方法
delete_test_cookie()
# 删除测试cookie
set_expiry(value)
# 设置cookie的有效期。可以传递不同类型的参数值:
#如果值是一个整数,session将在对应的秒数后失效。例如request.session.set_expiry(300) 将在300秒后失效.
#如果值是一个datetime或者timedelta对象, 会话将在指定的日期失效
#如果为0,在用户关闭浏览器后失效
#如果为None,则将使用全局会话失效策略
#失效时间从上一次会话被修改的时间起开始计时。
get_expiry_age()
# 返回返回此会话失效前的秒数。对于没有自定义失效时间的会话,这等同于SESSION_COOKIE_AGE.
# 该方法接受2个可选的关键字参数
#modification:会话的最后修改时间(datetime对象)。默认是当前时间。
#expiry: 会话失效信息,可以是datetime对象,也可以是int或None
get_expiry_date()
# 返回此会话将失效的日期
get_expire_at_browser_close()
# 返回True或False,取决于用户的Web浏览器关闭时用户的会话cookie是否过期
clear_expired()
# 删除已经失效的会话数据
cycle_key()
# 创建一个新的会话秘钥用于保持当前的会话数据。django.contrib.auth.login()也会调用该方法
三、会话序列化
Django默认使用JSON序列化session data。你可以在SESSION_SERIALIZER
设置中自定义序列化格式。但建议你还是使用JSON,尤其是以cookie的方式进行会话时。
SESSION_SERIALIZER
Default: 'django.contrib.sessions.serializers.JSONSerializer'
Included serializers are:
'django.contrib.sessions.serializers.PickleSerializer'
'django.contrib.sessions.serializers.JSONSerializer'
例如一个使用pickle序列化session data的攻击场景。如果你使用的是已签名的cookie session并且SECRET_KEY
被恶意攻击者知道了,攻击者就可以在会话中插入一个字符串,在pickle反序列化时就可以在服务器上执行危险的代码。这种攻击手段简单并且易于使用,虽然cookie session会对数据进行签名以防止篡改,但是SECRET_KEY的泄漏仍将产生这个漏洞。
JSON序列化器
class serializers.JSONSerializer
该类是对 django.core.signing
的一个包装,只能序列化基本数据类型。
此外,JSON只支持字典键为字符串类型,另外非UTF8格式的字符也无法被正确编码。
class serializers.PickleSerializer
支持任意Python对象,但如上所述,如果SECRET_KEY
被攻击者发现了会造成安全隐患。
自定义序列化器(serializer)
以上两个serializer要么支持基本数据类型,要么支持Python 对象,如果你想使用一个可以支持所有数据类型的serializer,请自定义它。需要注意的是,自定义的serializer必须实现dumps(self, obj)
和loads(self, data)
方法。
四、设置测试cookie
为了方便起见,Django提供了一种测试用户浏览器是否接受cookie的方法。
在一个视图中设置set_test_cookie()
方法,然后在另一个视图中调用 test_cookie_worked()
,切记不要把这两个方法用在同一个视图。
这种使用方法对于cookie的工作机制来说是必须的,当您设置cookie时,在浏览器的下一个请求之前,您实际上无法判断浏览器是否接受了它。
另外,在测试之后,使用delete_test_cookie()
收拾残局。
下面是一个例子:
from django.http import HttpResponse
from django.shortcuts import render
def login(request):
if request.method == 'POST':
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponse("You're logged in.")
else:
return HttpResponse("Please enable cookies and try again.")
request.session.set_test_cookie()
return render(request, 'foo/login_form.html')
五、在视图之外使用sessions
在视图外使用sessions需要获得一个SessionStore
对象,这个SessionStore
对象与会话引擎相对应,该对象存储着session data。获得它的方法如下:
>>> from importlib import import_module
>>> from django.conf import settings
>>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
获得之后,处理方式可参考如下:
>>> from django.contrib.sessions.backends.db import SessionStore
>>> s = SessionStore()
>>> # stored as seconds since epoch since datetimes are not serializable in JSON.
>>> s['last_login'] = 1376587691
>>> s.create()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login']
1376587691
SessionStore.create()
用于创建一个新的session。
save()
方法用于保存一个已经存在的session。
create方法会调用save方法并循环直到生成一个未被用过的session_key。直接调用save方法也可以创建一个新的session,但在生成session_key的时候有可能和已经存在的发生冲突。
除此之外,其余的方法还有:
- exists()
- delete()
- load()
- clear_expired()
如果您使用的是django.contrib.sessions.backends.db
,每个session都是一个正常的Django模型。这个session模型定义为django/contrib/sessions/models.py。因为它是一个正常的模型,所以可以使用普通的Django数据库API访问它:
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)
六、保存sessions
默认情况下,只有当session的某个值被重新设置或删除的时候,Django才会将数据保存到数据库内。
这可能有以下几种情况:
# Session is modified.
request.session['foo'] = 'bar'
# Session is modified.
del request.session['foo']
# Session is modified.
request.session['foo'] = {}
若你不想采取这种默认行为,可以将SESSION_SAVE_EVERY_REQUEST
设置为True,以便每一次请求都会触发保存数据库的操作。
需要注意的是,session cookie只有在一个会话被创建或修改后才会再次发送。如果SESSION_SAVE_EVERY_REQUEST
为True,那么每次请求都会发送cookie。
如果响应的状态代码为500,则不保存会话。
七、持久会话
默认情况下,SESSION_EXPIRE_AT_BROWSER_CLOSE
设置为False,这意味着会话cookie将存储在用户的浏览器中,它的寿命由SESSION_COOKIE_AGE
设置。如果您不希望人们每次打开浏览器时都必须登录,请使用此默认配置。
相反的,如果将SESSION_EXPIRE_AT_BROWSER_CLOSE
设置为True,则意味着浏览器一关闭,cookie就会失效,每次重新打开浏览器,你就得重新登录。
这个设置是一个全局的默认值,可以通过显式地调用set_expiry()
方法来覆盖。
【注意】有些浏览器(比如Chrome)具有在关闭后重新打开浏览器,会话依然保持的功能。这会与Django的SESSION_EXPIRE_AT_BROWSER_CLOSE
设置发生冲突。
八、清除会话
当用户在您的网站上创建新的会话时,会话数据会在您的会话存储中不断积累。如果使用数据库后端,则django_session数据库表将不断增长。如果使用文件后端,则临时目录将包含越来越多的文件。
造成这种问题是因为,每有一个用户登录,Django都会在django_session的数据库中添加一个新的行,只有在用户手动注销登录的时候,该行才会被删除。很多用户在浏览完网站后可能都只是简单的关闭了浏览器,这种情况下session data并没有被删除。
并且,Django没有提供自动清除失效会话的机制。因此,这是你的工作。但是Django为此提供了一个clearsessions
命令用于清除会话数据,建议定期调用此命令去清楚会话数据。
【注意】使用缓存模式的会话不需要你清理数据,因为缓存系统自己有清理过期数据的机制。使用cookie模式的会话也不需要,因为数据都存在用户的浏览器内。