flask源码解读06: Session(会话)

Flask中Session的实现,关于session和cookie的原理读者可查阅其他资料,我的理解如下:

服务器会为每个新到来的连接创建一个sessionID,以此识别各个连接,另外服务器可以根据sessionID来找到保存在服务器端的这个连接之前保存的数据。这个功能有两个好处:将数据保存在服务器端更安全,相比客户端而言更不容易被攻击;二是由于http协议是无状态协议,即后一个连接不会带有前一个连接的任何信息。但如果有session,后面的连接可以在被处理之前,找到存放在服务器中的数据。那么每个连接的sessionID如何保存,它被保存在客户端的cookie中。cookie中存放的相当于一把钥匙,这把钥匙可以打开存放在服务器的东西。

在flask中,请求环境入栈的时候,有下面的代码

        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)            

如果请求环境的session为空,会根据请求和app中的信息创建session对象,如果创建失败会创建一个空的session

class SecureCookieSessionInterface(SessionInterface):
    """The default session interface that stores sessions in signed cookies
    through the :mod:`itsdangerous` module.
    """
    #: the salt that should be applied on top of the secret key for the
    #: signing of cookie based sessions.
    salt = 'cookie-session'
    #: the hash function to use for the signature.  The default is sha1
    digest_method = staticmethod(hashlib.sha1)
    #: the name of the itsdangerous supported key derivation.  The default
    #: is hmac.
    key_derivation = 'hmac'
    #: A python serializer for the payload.  The default is a compact
    #: JSON derived serializer with support for some extra Python types
    #: such as datetime objects or tuples.
    serializer = session_json_serializer
    session_class = SecureCookieSession

    def get_signing_serializer(self, app):
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation,
            digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(app.secret_key, salt=self.salt,
                                      serializer=self.serializer,
                                      signer_kwargs=signer_kwargs)

    def open_session(self, app, request):
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        if not val:
            return self.session_class()
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)

        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )

            return

        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')

        if not self.should_set_cookie(app, session):
            return

        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(
            app.session_cookie_name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure
        )

看上面代码中open_session方法,先根据app创建一个序列化对象s,然后查看request.cookies.get(app.session_cookie_name)是否存在,这个值就类似我们前面说的sessionID,如果值存在且通过s.loads方法反序列化成功,则return self.session_class(data),返回用data初始化的session实例。如果值不存在或者反序列化失败,都是return self.session_class()返回没有初始化的session实例。

注意flask此处储存用户session数据的方法简化了,前面说的服务器会储存用户的session数据,一般是用数据库存储。但在flask中,数据序列化后直接存储在用户端的cookie中,不过序列化数据能提供一定的保密性。

self.session_class(data)用到的session类是SecureCookieSession

class SecureCookieSession(CallbackDict, SessionMixin):
    """Base class for sessions based on signed cookies."""

    def __init__(self, initial=None):
        def on_update(self):
            self.modified = True
            self.accessed = True

        super(SecureCookieSession, self).__init__(initial, on_update)
        self.modified = False
        self.accessed = False

    def __getitem__(self, key):
        self.accessed = True
        return super(SecureCookieSession, self).__getitem__(key)

    def get(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).get(key, default)

    def setdefault(self, key, default=None):
        self.accessed = True
        return super(SecureCookieSession, self).setdefault(key, default)

SecureCookieSession继承自werkzeug.datastructures模块的CallbackDict,CallbackDict实现了每次字典发生修改时,都会调用之前传入的函数的功能。这个功能的实现来自CallbackDict的父类UpdateDictMixin

class UpdateDictMixin(object):

    """Makes dicts call `self.on_update` on modifications.

    .. versionadded:: 0.5

    :private:
    """

    on_update = None

    def calls_update(name):
        def oncall(self, *args, **kw):
            rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)
            if self.on_update is not None:
                self.on_update(self)
            return rv
        oncall.__name__ = name
        return oncall

    def setdefault(self, key, default=None):
        modified = key not in self
        rv = super(UpdateDictMixin, self).setdefault(key, default)
        if modified and self.on_update is not None:
            self.on_update(self)
        return rv

    def pop(self, key, default=_missing):
        modified = key in self
        if default is _missing:
            rv = super(UpdateDictMixin, self).pop(key)
        else:
            rv = super(UpdateDictMixin, self).pop(key, default)
        if modified and self.on_update is not None:
            self.on_update(self)
        return rv

    __setitem__ = calls_update('__setitem__')
    __delitem__ = calls_update('__delitem__')
    clear = calls_update('clear')
    popitem = calls_update('popitem')
    update = calls_update('update')
    del calls_update

可以看到,UpdateDictMixin类中所有可以修改数据的方法,都进行改写。首先定义了calls_update装饰器,这样像__setitem__实际是经过装饰后的方法,在装饰器中会找到同名的原方法调用,并且在self.on_update不为空的情况下,调用self.on_update。

回到flask中用到的SecureCookieSession,他定义了on_update方位为修改modified和accessed属性为真。这两个属相在后面保存session的时候会用到。

猜你喜欢

转载自www.cnblogs.com/lovelaker007/p/8576880.html