zoukankan      html  css  js  c++  java
  • Flask-Cookies和Session

    cookies

    在Flask的框架中,自己已经封装了 cookie的respons,request 有存储就有读取及删除,那么就拿购物车来举例

      在我们登陆的时候会有之前在购物车存放的物品。也就是说在一个地方为我们保存了这些数据。前提有一个是要你登陆之后才能看到自己的购物车

      cookie对应的是client session对应的是server。 也就是说,要在服务器上登录你对应的账户,才能看到你自己在购物车添加的物品。但是

      物品那么多,不能都存在服务器上吧,所以一般cookie都存在自己的计算机上,只是找不到而已,这里就不说了。一些简单的原理实现一下。

    首先来看cookie的简单存储,读数据,删除数据怎么实现

    # cookie相关的操作,依赖于make_response库,调用cookie依赖request
    from flask import Flask, make_response, request
    
    
    app = Flask(__name__)
    
    app.config.from_pyfile('config.ini')
    
    # cookie存在client里。存在自己电脑的个人当资料里
    # 存cookie的方法
    @app.route('/setcookie')
    def set_cookie():
        resp = make_response('存储cookie')
        # 使用set方法,来存储 key-values 形式的数据
        resp.set_cookie('productname','卫生纸')
        return resp
    
    
    # 获取,取到数据,调用cookie的方法
    @app.route('/getcookie')
    def get_cookie():
        # 通过request模块的cookies属性的方法指定Key 来调用value
        resp = request.cookies.get('productname')
        return resp
    
    # 删除cookie的方法
    @app.route('/delcookie')
    def del_cookie():
        # 通过make_response对象内置的delete_cookie方法来指定key来删除vaule
        resp = make_response('删除cookie')
        resp.delete_cookie('productname')
        return resp
    
    
    
    if __name__ == "__main__":
    	app.run()
    

    session

    • session:存放在客户端的键值对
    • token:存放在客户端,通过算法来校验

    在使用session之前必须现在设置一下密钥

    app.secret_key="asdas" #值随便
    

    除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。 (app.session_interface对象)

    设置:session['username'] = 'xxx'
    # 在django中发什么三件事,
    #  	 1 生成一个随机的字符串 
    #    2 往数据库存 
    #    3 写入cookie返回浏览器
    
    # 在flask中他没有数据库,但session是怎样实现的?
    # 生成一个密钥写入这个cookie,然后下次请求的时候,通过这个cookie解密,然后赋值给session
    #我们通过app.session_interface来查看
      
    删除:session.pop('username', None)
    

    save_session的参数

    save_sessionapp.session_interface中的函数,他的参数如下:

    参数 作用
    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.comwww2.example.com an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
    secure=False 浏览器将通过HTTPS来回传cookie
    httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

    session源码执行流程

    请求第一次过来时

    在flask请求上下文和应用上下文中已经知道session是一个LocalProxy()对象:

    current_app = LocalProxy(_find_app)
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    g = LocalProxy(partial(_lookup_app_object, 'g'))
    

    客户端的请求进来时,会调用app.wsgi_app():

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                # 寻找视图函数,并执行
                # 获取返回值 response
                response = self.full_dispatch_request()
    

    此时,会生成一个ctx,其本质是一个RequestContext对象:

    class RequestContext(object):
        def __init__(self, app, environ, request=None, session=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = None
            try:
                self.url_adapter = app.create_url_adapter(self.request)
            except HTTPException as e:
                self.request.routing_exception = e
            self.flashes = None
            self.session = session
    

    RequestContext 对象中定义了session,且初值为None。

    接着继续看wsgi_app函数中,ctx.push()函数:

    def push(self):
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
    
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
    
        # 把ctx对象加入到Local()对象中
        _request_ctx_stack.push(self)
    
        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是否为空,我在RequestContext 中看到session初值为空.

    在 Flask 中,所有和 session 有关的调用,都是转发到 self.session_interface 的方法调用上(这样用户就能用自定义的session_interface来控制 session 的使用)。而默认的 session_inerface 有默认值:

    session_interface = SecureCookieSessionInterface()
    

    执行SecureCookieSessionInterface.open_session()来生成默认session对象:

    def open_session(self, app, request):
        # 获取session签名的算法
        s = self.get_signing_serializer(app)
        # 如果为空 直接返回None
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        # 如果val为空,即request.cookies为空
        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()
    

    请求第一次来时,request.cookies为空,即返回self.session_class():

    session_class = SecureCookieSession
    

    SecureCookieSession

    class SecureCookieSession(CallbackDict, SessionMixin):
        modified = False
        accessed = False
        
        def __init__(self, initial=None):
            def on_update(self):
                self.modified = True
                self.accessed = True
    
            super(SecureCookieSession, self).__init__(initial, on_update)
    
        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)
    

    看其继承关系,其实就是一个特殊的字典。到此我们知道了session就是一个特殊的字典,调用SecureCookieSessionInterface类的open_session()创建,并保存在ctx中,即RequestContext对象中。但最终由session = LocalProxy(..., 'session')对象代为管理,到此,在视图函数中就可以导入session并使用了。

    请求第二次进来

    当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回空字典

    SecureCookieSession

    默认的 session 对象是 SecureCookieSession,这个类就是一个基本的字典,外加一些特殊的属性,比如 permanent(flask 插件会用到这个变量)、modified(表明实例是否被更新过,如果更新过就要重新计算并设置 cookie,因为计算过程比较贵,所以如果对象没有被修改,就直接跳过)。

    怎么知道实例的数据被更新过呢?SecureCookieSession是基于 werkzeug/datastructures:CallbackDict 实现的,这个类可以指定一个函数作为 on_update 参数,每次有字典操作的时候(__setitem__、__delitem__、clear、popitem、update、pop、setdefault)会调用这个函数。

    SecureCookieSession:

    class SecureCookieSession(CallbackDict, SessionMixin):
     
        modified = False
        accessed = False
     
        def __init__(self, initial=None):
            def on_update(self):  
                self.modified = True
                self.accessed = True
            #将on_update()传递给CallbackDict
            super(SecureCookieSession, self).__init__(initial, on_update)
     
        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)
    

    继承的 CallbackDict:

    class CallbackDict(UpdateDictMixin, dict):
     
        def __init__(self, initial=None, on_update=None):
            dict.__init__(self, initial or ())
            self.on_update = on_update
     
        def __repr__(self):
            return '<%s %s>' % (
                self.__class__.__name__,
                dict.__repr__(self)
            )
    

    CallbackDict又继承UpdateDictMixin:

    class UpdateDictMixin(object):
        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()可知,对session进行改动会调用pop, __setitem__等方法,同时就会调用on_update()方法,从而修改modify,security的值

    签名算法

    都获取 cookie 数据的过程中,最核心的几句话是:

    s = self.get_signing_serializer(app)
    val = request.cookies.get(app.session_cookie_name)
    data = s.loads(val, max_age=max_age)
    
    return self.session_class(data)
    

    其中两句都和 s 有关,signing_serializer 保证了 cookie 和 session 的转换过程中的安全问题。如果 flask 发现请求的 cookie 被篡改了,它会直接放弃使用。

    我们继续看 get_signing_serializer 方法:

    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)
    

    我们看到这里需要用到很多参数:

    • secret_key:密钥。这个是必须的,如果没有配置 secret_key 就直接使用 session 会报错

    • salt:为了增强安全性而设置一个 salt 字符串(可以自行搜索“安全加盐”了解对应的原理)

    • serializer:序列算法

    • signer_kwargs:其他参数,包括摘要/hash算法(默认是 sha1)和 签名算法(默认是 hmac)

    URLSafeTimedSerializeritsdangerous库的类,主要用来进行数据验证,增加网络中数据的安全性。itsdangerours提供了多种 Serializer,可以方便地进行类似 json 处理的数据序列化和反序列的操作。

    session的生命周期

    前面的几个问题实际上都发生在wsgi_app()前两句函数中,主要就是ctx.push()函数中,下面看看wsgi_app()后面干了嘛:

    def wsgi_app(self, environ, start_response):
      
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # ctx.push函数是前半部分最重要的一个函数
                # 生成request和session并将二者保存到RequestContext()对象ctxz中
                # 最后将ctx,push到LocalStack()对象_request_ctx_stack中
                ctx.push()
                # 寻找视图函数,并执行
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            # 最后, 将自己请求在local中的数据清除
            ctx.auto_pop(error)
    

    full_dispatch_request

    def full_dispatch_request(self):
        #执行before_first_request
        self.try_trigger_before_first_request_functions()
        try:
            # 触发request_started 信号
            request_started.send(self)
            # 调用before_request
            rv = self.preprocess_request()
            if rv is None:
                #执行视图函数
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
    

    前半部分就在执行flask钩子,before_first_request, before_request以及信号,接着执行视图函数生成rv,

    我们主要看finalize_request(rv)

    def finalize_request(self, rv, from_error_handler=False):
            response = self.make_response(rv)
            try:
                response = self.process_response(response)
                request_finished.send(self, response=response)
            except Exception:
                if not from_error_handler:
                    raise
                self.logger.exception('Request finalizing failed with an '
                                      'error while handling an error')
            return response
    

    首先根据rv生成response。再执行process_response:

    def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
            
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
            for handler in funcs:
                response = handler(response)
                
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
            
        return response
    

    前半部分主要执行flask的钩子,看后面,判断,session是否为空,如果不为空,则执行save_session():

    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)
        samesite = self.get_cookie_samesite(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,
            samesite=samesite
        )
    

    save_session()比较简单,且有注释,便不再讲解,主要就是将session写入response.set_cookie中。这样便完成session的写入response工作,并由response返回至客户端。

    再请求结束时会执行wsgi_app()finally:ctx.auto_pop(error)函数,将与对应请求相关的request,session清除,session生命周期便结束。

    总结

    至此,flask内置session的机制便讲解完毕,session的实现是依赖与flask的上下文管理,因此先弄清楚flask上下文,再来看session就比较容易理解。其主要的就是SecureCookieSessionInterface对象的open_session()save_session() open_session在请求刚进来时执行,完成session对象的创建(就是一特殊字典),在视图函数中完成对session的赋值操作,save_session()在视图函数执行完后,生成response后执行,将session写入response的cookie中。

  • 相关阅读:
    C#中正则表达式的使用
    Asp.Net MVC 身份验证-Forms
    ASP.NET MVC:窗体身份验证及角色权限管理示例
    asp.net mvc forms身份认证
    ASP.NET MVC Form验证
    C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解
    参考例子,学习Func<T, TResult>委托
    Razor 中的@helper 与 @function 用法
    @Helper辅助方法和@functions自定义函数
    ASP.NET MVC传递参数(model), 如何保持TempData的持久性
  • 原文地址:https://www.cnblogs.com/Hades123/p/11773417.html
Copyright © 2011-2022 走看看