zoukankan      html  css  js  c++  java
  • Django 1.10 中文文档------3.3.8 会话sessions

    欢迎大家访问我的个人网站《刘江的博客和教程》:www.liujiangblog.com

    主要分享Python 及Django教程以及相关的博客


    django支持匿名会话。它将数据存放在服务器端,并抽象cookies的发送和接收过程。cookie包含一个会话ID而不是数据本身(除非你使用的是基于后端的cookie)。

    3.3.8.1 启用会话

    Django通过一个中间件来实现会话功能。要启用会话就要先启用该中间件。
    编辑MIDDLEWARE设置,确保存在django.contrib.sessions.middleware.SessionMiddleware这一行。默认情况在新建的项目中它是存在的。

    如果你不想使用会话功能,那么在settings文件中,将SessionMiddleware从MIDDLEWARE中删除,将django.contrib.sessions从 INSTALLED_APPS中删除就OK了。

    3.3.8.2 配置会话引擎

    默认情况下,django将会话数据保存在数据库内(通过使用django.contrib.sessions.models.Session模型)。当然,你也可以将数据保存在文件系统或缓存内。

    1. 基于数据库的会话

    先在INSTALLED_APPS设置中,确保django.contrib.sessions的存在,然后运行manage.py migrate命令在数据库内创建sessions表。

    2. 基于缓存的会话

    从性能角度考虑,也许你想使用基于缓存的会话。

    首先,你得先配置好你的缓存,请参考3.11节查看详细。

    警告:
    本地缓存不是多进程安全的,因此对于生产环境不是一个好的选择。
    

    如果你定义有多个缓存,django将使用默认的那个。如果你想用其它的,请将SESSION_CACHE_ALIAS参数设置为那个缓存的名字。

    配置好缓存后,你可以选择两种保存数据的方法:

    • 一是将SESSION_ENGINE设置为"django.contrib.sessions.backends.cache",简单的对会话进行保存。但是这种方法不是很可靠,因为当缓存数据存满时将清除部分数据,或者遇到缓存服务器重启。
    • 为了数据安全保障,你可以将SESSION_ENGINE设置为"django.contrib.sessions.backends.cached_db"。这种方式在每次缓存的时候会同时将数据在数据库内写一份。当缓存不可用时,会话会从数据库内读取数据。

    两种方法都很迅速,但是第一种简单的缓存更快一些,因为它忽略了数据的持久性。如果你使用缓存+数据库的方式,你同样需要按上面小节所述对数据库进行配置。

    3. 基于文件的会话

    将SESSION_ENGINE设置为"django.contrib.sessions.backends.file"。同时,你必须查看
    SESSION_FILE_PATH配置(默认根据tempfile.gettempdir()生产,就像/tmp目录),确保你的文件存储目录,以及Web服务器对该目录具有读写权限。

    4. 基于cookie的会话

    将SESSION_ENGINE设置为"django.contrib.sessions.backends.signed_cookies"。Django将使用加密签名工具和安全秘钥设置保存会话的数据。

    注意:
    建议将SESSION_COOKIE_HTTPONLY设置为True,阻止javascript对会话数据的访问。
    

    3.3.8.3 在视图中使用会话

    当会话中间件启用后,传递给视图request参数的HttpRequest对象将包含一个session属性,就像一个字典对象一样。

    你可以在视图的任何地方读写request.session属性,或者多次编辑使用它。

    class backends.base.SessionBase
        # 这是所有会话对象的基类,包含标准的字典方法:
        __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,根据用户会话是否是浏览器关闭后就结束。
            
        clear_expired()
            # 删除已经失效的会话数据。
        cycle_key()
            # 创建一个新的会话秘钥用于保持当前的会话数据。django.contrib.auth.login() 会调用这个方法。
    
    1. 会话序列化

    Django默认使用JSON序列化会话数据。你可以在SESSION_SERIALIZER设置中自定义序列化格式,甚至写入警告说明。但是我们强烈建议你还是使用JSON,尤其是以cookie的方式进行会话时。

    举个例子,这里有一个使用pickle序列化会话数据的攻击场景。如果你使用的是已签名的Cookie会话并且SECRET_KEY被攻击者知道了(通过其它手段),攻击者就可以在会话中插入一个字符串,在pickle反序列化时,可以在服务器上执行危险的代码。在因特网上这个攻击技术很简单并很容易使用。尽管Cookie会话会对数据进行签名以防止篡改,但是SECRET_KEY的泄漏却使得一切前功尽弃。

    绑定的序列化方法

    class serializers.JSONSerializer

    对 django.core.signing中JSON序列化方法的一个包装。只可以序列化基本的数据类型。另外,JSON只支持以字符串作为键值,使用其它的类型会导致异常

    >>> # initial assignment
    >>> request.session[0] = 'bar'
    >>> # subsequent requests following serialization & deserialization
    >>> # of session data
    >>> request.session[0] # KeyError
    >>> request.session['0']
    'bar'
    

    同样,无法被JSON编码的,例如非UTF8格式的字节’xd9’一样是无法被保存的,它会导致UnicodeDecodeError异常。

    class serializers.PickleSerializer

    支持任意类型的python对象,但是就像前面说的,可能导致远端执行代码的漏洞,如果攻击者知道了SECRET_KEY。

    编写你自己的序列化方法
    你的序列化类必须分别实现dumps(self, obj)和loads(self, data)方法,用来实现序列化和反序列化会话数据字典。

    2. 会话对象使用建议
    • 使用普通的python字符串作为request.session字典的键值。这不是一条硬性规则而是为方便起见。
    • 以一个下划线开始的会话字典的键被Django保留作为内部使用。
    • 不要用新对象覆盖request.session,不要访问或设置它的属性。像一个python字典一样的使用它。
    3. 范例

    这个简单的视图设置一个has_commented变量为True在用户发表评论后。它不允许用户重复发表评论。

    def post_comment(request, new_comment):
        if request.session.get('has_commented', False):
            return HttpResponse("You've already commented.")
        c = comments.Comment(comment=new_comment)
        c.save()
        request.session['has_commented'] = True
        return HttpResponse('Thanks for your comment!')
    

    下面是一个简单的用户登录视图:

    def login(request):
        m = Member.objects.get(username=request.POST['username'])
        if m.password == request.POST['password']:
            request.session['member_id'] = m.id
            return HttpResponse("You're logged in.")
        else:
            return HttpResponse("Your username and password didn't match.")
    

    下面则是一个退出登录的视图,与上面的相关:

    def logout(request):
        try:
            del request.session['member_id']
        except KeyError:
            pass
        return HttpResponse("You're logged out.")
    

    标准的django.contrib.auth.logout()函数实际上所做的内容比这个要更严谨,以防止意外的数据泄露,它会调用request.session的flush()方法。我们使用这个例子只是演示如何利用会话对象来工作,而不是一个完整的logout()实现。

    3.3.8.4 设置测试cookie

    为了方便,Django 提供一个简单的方法来测试用户的浏览器是否接受Cookie。只需在一个视图中调用request.session的set_test_cookie()方法,并在随后的视图中调用test_cookie_worked()获取测试结果(True或False)。注意,不能在同一个视图中调用这两个方法。

    造成这种分割调用的原因是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')
    

    3.8.3.5 在视图外使用session

    注意:
    在下面的例子中,我们直接从django.contrib.sessions.backends.db中导入了SessionStore对象。在你的实际代码中,你应该采用下面的导入方法,根据SESSION_ENGINE的设置进行导入,如下所示:
    >>> from importlib import import_module
    >>> from django.conf import settings
    >>> SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
    

    在视图外有一个API可以操作会话数据:

    >>> 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()用于创建一个新的会话。save()方法用于保存一个已经存在的会话。create方法会调用save方法并循环直到生成一个未使用的session_key。直接调用save方法也可以创建一个新的会话,但在生成session_key的时候有可能和已经存在的发生冲突。

    如果你使用的是django.contrib.sessions.backends.db模式,那么每一个会话其实就是一个普通的Django模型,你可以使用普通的Django数据库API访问它。会话模型的定义在django/contrib/sessions/models.py文件里。例如:

    >>> from django.contrib.sessions.models import Session
    >>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
    >>> s.expire_date
    datetime.datetime(2005, 8, 20, 13, 35, 12)
    

    注意,你需要调用get_decoded()方法才能获得会话字典,因为字典是采用编码格式保存的。如下:

    >>> s.session_data
    'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
    >>> s.get_decoded()
    {'user_id': 42}
    

    3.3.8.6 何时保存会话

    默认情况下,只有当会话字典的任何值被指定或删除的时候,Django才会将会话内容保存到会话数据库内。

    # 会话被修改
    request.session['foo'] = 'bar'
    # 会话被修改
    del request.session['foo']
    # 会话被修改
    request.session['foo'] = {}
    # 会话没有被修改,只是修改了request.session['foo']
    request.session['foo']['bar'] = 'baz'
    

    要理解上面最后一种情况有点费劲。我们可以通过设置会话对象的modified属性值,显式地告诉会话对象它已经被修改过:request.session.modified = True

    要改变上面的默认行为,将SESSION_SAVE_EVERY_REQUEST设置为True,那么每一次单独的请求过来,Django都会保存会话到数据库。

    注意,会话的Cookie只有在一个会话被创建或修改后才会再次发送。如果SESSION_SAVE_EVERY_REQUEST为True,每个请求都会发送cookie。

    类似地,会话Cookie的失效部分在每次发送会话Cookie时都会更新。

    如果响应的状态码为500,则会话不会被保存。

    3.3.8.7 浏览器生存期间会话 VS 持久会话

    默认情况下,SESSION_EXPIRE_AT_BROWSER_CLOSE设置为False,也就是说cookie保存在用户的浏览器内,直到失效日期,这样用户就不必每次打开浏览器后都要再登录一次。

    相反的SESSION_EXPIRE_AT_BROWSER_CLOSE设置为True,则意味着浏览器一关闭,cookie就失效,每次重新打开浏览器,你就得重新登录。

    这个设置是一个全局的默认值,可以通过显式地调request.session的set_expiry()方法来覆盖,前面我们已经描述过了。

    注意:有些浏览器(比如Chrome)具有在关闭后重新打开浏览器,会话依然保持的功能。这会与Django的SESSION_EXPIRE_AT_BROWSER_CLOSE设置发生冲突。请一定要小心。
    

    3.3.8.8 清除已保存的会话

    随着用户的访问,会话数据会越来越庞大。如果你使用的是数据库保存模式,那么django_session表的内容会逐渐增长。如果你使用的是文件模式,那么你的临时目录内的文件数量会不断增加。

    造成这个问题的原因是,如果用户手动退出登录,Django将自动删除会话数据,但是如果用户不退出登录,那么对应的会话数据不会被删除。

    Django没有提供自动清除失效会话的机制。因此,你必须自己完成这项工作。Django提供了一个命令clearsessions用于清除会话数据,建议你基于这个命令设置一个周期性的自动清除机制。

    不同的是,使用缓存模式的会话不需要你清理数据,因为缓存系统自己有清理过期数据的机制。使用cookie模式的会话也不需要,因为数据都存在用户的浏览器内,不用你帮忙。

    3.3.8.9 设置

    这里有一些Django的设置,用于帮助你控制会话的行为:

    • SESSION_CACHE_ALIAS
    • SESSION_COOKIE_AGE
    • SESSION_COOKIE_DOMAIN
    • SESSION_COOKIE_HTTPONLY
    • SESSION_COOKIE_NAME
    • SESSION_COOKIE_PATH
    • SESSION_COOKIE_SECURE
    • SESSION_ENGINE
    • SESSION_EXPIRE_AT_BROWSER_CLOSE
    • SESSION_FILE_PATH
    • SESSION_SAVE_EVERY_REQUEST
    • SESSION_SERIALIZER

    3.3.8.10 会话安全

    一个站点下的子域名能够在为整个域名的客户设置Cookie。如果子域名被不受信任的用户控制,那么可能发生会话安全问题。

    例如,一个攻击者可以登录good.example.com并为他的账号获取一个合法的会话。如果该攻击者控制了bad.example.com域名,那么他就可以使用这个域名来发送他的会话秘钥给你,因为子域名允许在*.example.com上设置Cookie。当你访问good.example.com时,你有可能以攻击者的身份登录,然后无意中泄露了你的个人敏感信息(例如信用卡信息)到攻击者的账号中。攻击者自然就获得了这些信息。

    另外一个可能的攻击是,如果good.example.com设置它的SESSION_COOKIE_DOMAIN为".example.com" ,这可能导致来自该站点的会话Cookie被发送到bad.example.com。

    3.3.8.11 技术细节

    • 会话字典接收任意的json序列化值,或者任何可通过pickle序列化的python对象
    • 会话数据被保存在一张名为django_session的表内
    • Django 只发送它需要的Cookie。如果你没有设置任何会话数据,它不会发送任何Cookie

    SessionStore对象

    在会话内部,Django使用一个与会话引擎对应的会话保存对象。根据管理,这个会话保存对象命名为SessionStore,位于SESSION_ENGINE设置指定的模块内。

    所有Django支持的SessionStore类都继承SessionBase类,并实现了下面的数据操作方法:

    • exists()
    • create()
    • save()
    • delete()
    • load()
    • clear_expored()

    为了创建一个自定义会话引擎或修改一个现成的引擎,你也许需要创建一个新的类,它继承SessionBase类或任何其他已经存在的SessionStore类。

    3.3.8.12 扩展基于数据库的会话引擎

    Django 1.9版本以后才有的功能。

    要创建一个自定义的基于数据库的会话引擎,需要继承AbstractBaseSession类或者SessionStore类。

    AbstractBaseSession和BaseSessionManager可以从django.contrib.sessions.base_session内导入,因此不一定非要在INSTALLED_APPS中包含django.contrib.sessions。

    class base_session.AbstractBaseSession  # 抽象会话基类
        session_key
            主键。最多40个字符。目前是一个32为随机数字或字母组合字符串。
        session_data
            一个包含了编码过的的或序列化过的会话字典的字符串
        expire_date
            失效日期
        get_session_store_class()
            这是一个类方法。返回一个会话保存类。
        get_decoded()
            返回解码后的会话数据。通过会话保存类进行解码。
    

    你也可以自定义模型管理器,通过编写一个BaseSessionManager的子类。

    class base_session.BaseSessionManager
        encode(session_dict)
            通过会话保存类,将会话字典序列化或编码成一个字符串
        save(session_key, session_dict, expire_date)
            根据一个提供的session秘钥保存会话数据,或者删除一个空的会话
    

    通过重写下面这些SessionStore类的方法和属性,可以进行自定制:

    class backends.db.SessionStore
        实现基于数据库的会话保存
        get_model_class()
            这是一个类方法。如果有需要,重写这个方法并返回一个自定义的会话模型。
        create_model_instance(data)
            返回一个会话模型对象的新实例,它代表当前会话的状态。重写这个方法,你将获得在它被保存之前,修改会话模型数据的能力。
    
    class backends.cached_db.SessionStore
        实现基于缓存和数据库的会话保存
        cache_key_prefix
            在会话秘钥前添加一个前缀,用于构造缓存键值字符串。
    

    范例

    下面的例子展示一个自定义的基于数据库的会话引擎,包括一个额外的数据列用于储存用户的ID。

    from django.contrib.sessions.backends.db import SessionStore as DBStore
    from django.contrib.sessions.base_session import AbstractBaseSession
    from django.db import models
    
    class CustomSession(AbstractBaseSession):
        account_id = models.IntegerField(null=True, db_index=True)
    
        @classmethod
        def get_session_store_class(cls):
            return SessionStore
    
    class SessionStore(DBStore):
        @classmethod
        def get_model_class(cls):
            return CustomSession
    
        def create_model_instance(self, data):
            obj = super(SessionStore, self).create_model_instance(data)
            try:
                account_id = int(data.get('_auth_user_id'))
            except (ValueError, TypeError):
                account_id = None
            obj.account_id = account_id
            return obj
    

    如果你是通过从Django内置的cached_db会话保存迁移到自定义的cached_db,你应该重写缓存键值的前缀,以防止命名空间的冲突,如下所示:

    class SessionStore(CachedDBStore):
        cache_key_prefix = 'mysessions.custom_cached_db_backend'
        # ...
    

    3.3.8.13 URLs中的会话IDs

    Django的会话框架完全地、唯一地基于Cookie。它不像PHP一样,把会话的ID放在URL中。它不仅使得URL变得丑陋,还使得你的网站易于受到通过"Referer"头部进行窃取会话ID的攻击。

  • 相关阅读:
    jQuery Ajax同步参数导致浏览器假死怎么办
    自顶而下系统构架分析
    IEnumerable,IQueryable之前世今生
    C#执行存储过程
    JQuery iframe
    跨服务器插入查询数据
    使用游标、存储过程、pivot 三种方法导入数据
    分库分表的面试题3
    分库分表的面试题2
    分库分表的面试题1
  • 原文地址:https://www.cnblogs.com/feixuelove1009/p/5974521.html
Copyright © 2011-2022 走看看