• flask基础之session原理详解(十)


    前言

    flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活。

    flask的session机制

    session是用来干什么的呢?由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。

    实现的原理:

    用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息。

    简单使用

    from flask import Flask,session
    app = Flask(__name__)
    @app.route('/test1/')
    def test():
        session.setdefault('name', 'xiaoming')
        return 'OK'
    if __name__ == '__main__':
        app.run(host='127.0.0.1', port=80, debug=True)
    

    在flask中,如果我们想要获取session信息,直接通过flask的session获取就可以了,这是因为session是一个代理对象,代理当前请求上下文的session属性。

    session源码分析

    依据上述session的原理,来分析一下flask框架的session机制实现的过程。

    Flask对象使用open_session方法和save_session方法打开和保存会话信息,请求在创建请求上下文后会调用open_session方法获取用户的信息,在执行完处理逻辑后会调用save_session方法保存用户的信息。

    • open_session和save_session
    def open_session(self, request):
        # 调用了app的session_interface对象的方法
        return self.session_interface.open_session(self, request)
    
    def save_session(self, session, response):
        return self.session_interface.save_session(self, session, response)
    

    app对象默认的session_interface = SecureCookieSessionInterface(),SecureCookieSessionInterface重写了SessionInterface对象的open_session方法和save_session方法。

    class SecureCookieSessionInterface(SessionInterface):
        pass
    
        def open_session(self, app, request):
            # 检测是否设置了secret_key参数,返回一个签名对象
            s = self.get_signing_serializer(app)
            if s is None:
                return None
            # 去cookie中获取session信息
            val = request.cookies.get(app.session_cookie_name)
            # 如果是第一次请求,返回一个空的SecureCookieSession对象,会被交给请求上下文的session属性管理
            if not val:
                return self.session_class()
            # 获取session的失效时间
            max_age = total_seconds(app.permanent_session_lifetime)
            try:
                # 对session信息进行解码得到用户信息
                data = s.loads(val, max_age=max_age)
                # 返回有用户信息的session对象
                return self.session_class(data)
            except BadSignature:
                return self.session_class()
    
        def save_session(self, app, session, response):
            # 获取cookie设置的域
            domain = self.get_cookie_domain(app)
            # 获取cookie设置的路径
            path = self.get_cookie_path(app)
            ...
    
            # 检测SESSION_REFRESH_EACH_REQUEST参数配置
            if not self.should_set_cookie(app, session):
                return
            # 返回SESSION_COOKIE_HTTPONLY参数配置
            httponly = self.get_cookie_httponly(app)
            # 返回SESSION_COOKIE_SECURE参数配置
            secure = self.get_cookie_secure(app)
            # 返回失效的时间点
            expires = self.get_expiration_time(app, session)
            #将用户的数据加密
            val = self.get_signing_serializer(app).dumps(dict(session))
            # 设置cookie
            response.set_cookie(app.session_cookie_name, val,
                                expires=expires, httponly=httponly,
                                domain=domain, path=path, secure=secure)
    

    请求上下文RequestContext的session属性是一个SecureCookieSession对象,可以将其看做一个字典;

    相关的配置参数

    SESSION_COOKIE_NAME:设置返回给客户端的cookie的名称,默认是“session”;放置在response的头部;
    
    SESSION_COOKIE_DOMAIN:设置会话的域,默认是当前的服务器,因为Session是一个全局的变量,可能应用在多个app中;
    
    SESSION_COOKIE_PATH:设置会话的路径,即哪些路由下应该设置cookie,如果不设置,那么默认为‘/’,所有的路由都会设置cookie;这个参数和SESSION_COOKIE_DOMAIN是互斥的
    
    SERVER_NAME:设置服务器的名字,一般不用;
    
    SESSION_COOKIE_SECURE:如果 cookie 标记为“ secure ”,那么浏览器只会使用基于 HTTPS 的请求发 送 cookie,应用必须使用 HTTPS 服务来启用本变量,默认False
    
    APPLICATION_ROOT:设置应用的根路径;
    
    SESSION_REFRESH_EACH_REQUEST:是否应该为每一个请求设置cookie,默认为True,如果为False则必须显性调用set_cookie函数;
    
    SESSION_COOKIE_HTTPONLY:cookie应该和httponly标志一起设置,默认为True,这个一般采用默认。
    
    PERMANENT_SESSION_LIFETIME:设置session的有效期,即cookie的失效时间,单位是s。这个参数很重要,因为默认会话是永久性的。
    
    SESSION_COOKIE_HTTPONLY:默认为true,表示允许js脚本访问cookie;
    

    小结

    flask默认通过SecureCookieSessionInterface对象管理session,其重写了SessionInterface对象的open_session方法和save_session方法,将用户的数据加密后存储在cookie中。

    自定义session存储

    通过分析flask的session实现机制,一般认为将session信息放在cookie中不够保险,那么我们可以实现自己的session机制,思路是创建一个类继承SessionInterface,然后重写open_session方法和save_session方法,再使用我们的类替换app的session_interface属性即可。

    比如我要将session信息保存在一个session.json中。

    第一步:设置必要的配置参数
    # 配置session存放的路径
    MY_SESSION_PATH = 'session.json'
    'SESSION_TYPE' = 'file'
    # 配置默认的seesion的配置参数
    SECRET_KEY = '123'
    SESSION_USE_SIGNER = True  
    # session的有效期,单位:秒
    PERMANENT_SESSION_LIFETIME = 7200 
    
    第二步:创建自己的SessionInterface的子类
    from flask.sessions import *
    try:
        import cPickle as pickle
    except ImportError:
        import pickle
    
    import json
    from uuid import uuid4
    import time
    
    # 我们需要自定义一个Session对象用来存储用户的信息,它使用一个唯一的id标识,模仿SecureCookieSession的实现方法
    class SecureFileSession(CallbackDict, SessionMixin):
        def __init__(self, initial=None, sid=None, permanent=None):
            def on_update(self):
                self.modified = True
            CallbackDict.__init__(self, initial, on_update)
            self.sid = sid  # session的标识
            if permanent:
                self.permanent = permanent  # 失效时间
            self.modified = False
    
    # 我们使用uuid作为签名,省略校验过程
    class NewSessionInterface(SessionInterface):
    
        def _generate_sid(self):
            return str(uuid4())
    
    class JsonFileSessionInterface(NewSessionInterface):
        # 用来序列化的包
        serializer = pickle
        session_class = SecureFileSession
    
        def __init__(self, app=None):
            self.app = app
            if app is not None:
                self.init_app(app)
    
        def init_app(self, app):
            """
            替换app的session_interface属性
            :param app:
            :return:
            """
            app.session_interface = self._get_interface(app)
    
        def _get_interface(self, app):
            """
            加载配置参数返回本身,必须配置'SESSION_TYPE'和'MY_SESSION_PATH'参数,否则使用默认的session
            :param app:
            :return:
            """
            config = app.config.copy()
            if config['SESSION_TYPE'] == 'file':
                if not config['MY_SESSION_PATH']:
                    return SecureCookieSessionInterface()
                self.path = app.static_folder + config['MY_SESSION_PATH']  # session文件路径
                self.permanent = total_seconds(app.permanent_session_lifetime)  # 失效时间
                return self
            return SecureCookieSessionInterface()
    
        def open_session(self, app, request):
            """
            从文件中获取session数据
            :param app:
            :param request:
            :return:
            """
            # 获取session签名
            sid = request.cookies.get(app.session_cookie_name)
            permanent = int(time.time()) + self.permanent
            # 如果没有说明是第一次访问,返回空session对象
            if not sid:
                # 获取一个uuid
                sid = self._generate_sid()
                return self.session_class(sid=sid, permanent=permanent)
    
            with open(self.path, 'r', encoding='utf-8') as f:
                v = f.read()
                # 如果session为空,返回空session对象
                if not v:
                    return self.session_class(sid=sid, permanent=permanent)
                try:
                    val = json.loads(v)
                except ValueError as e:
                    print('配置参数错误:{}'.format(e))
                    return self.session_class(sid=sid, permanent=permanent)
                else:
                    self.val = val
                    # 通过sid获取信息
                    data = val.get(sid)
                    if not data:
                        return self.session_class(sid=sid, permanent=permanent)
                    # 判断以前的信息是否超时
                    if permanent - int(data['permanent']) > self.permanent:
                        return self.session_class(sid=sid, permanent=permanent)
                    return self.session_class(data, sid=sid)
    
        def save_session(self, app, session, response):
            """
            保存session信息
            :param app:
            :param session:
            :param response:
            :return:
            """
            # 前面借鉴flask默认的实现方式
            domain = self.get_cookie_domain(app)
            path = self.get_cookie_path(app)
            if not session:
                if session.modified:
                    response.delete_cookie(app.session_cookie_name,
                                           domain=domain, path=path)
                return
            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)
    
            # 将session信息保存在文件中
            session.update({'permanent': int(time.time()) + self.permanent})
            if hasattr(self, 'val') and isinstance(self.val, dict):
                self.val.update({session.sid: dict(session)})
            else:
                self.val = {session.sid: dict(session)}
    
            with open(self.path, 'w', encoding='utf-8') as f:
                result = json.dumps(self.val)
                f.write(result)
                response.set_cookie(app.session_cookie_name, session.sid,
                                    expires=expires, httponly=httponly,
                                    domain=domain, path=path, secure=secure)
    
    第三步:初始化替换app的session_interface
    app = Flask(__name__,template_folder='static/html')
    app.config.update({
                       'SECRET_KEY':'123',
                       'SESSION_USE_SIGNER':True,
                        'SESSION_TYPE':'file',
                        'MY_SESSION_PATH':'session.json'})
    from session_file import JsonFileSessionInterface
    se = JsonFileSessionInterface(app=app)
    if __name__ == '__main__':
        app.run(host='127.0.0.1', port=80, debug=True)
    

    小结

    经过上面的三步,我们就可以将自己实现的session对象运用到flask项目中,我们采用的是文件存储session,实际项目中有redis,memcached,mysql等都可以存储session,将它们整合起来,于是flask_session插件就应运而生了。

    flask_session扩展

    flask_session插件就是官方推荐的session实现插件,整合了redis,memcached,mysql,file,mongodb等多种第三方存储session信息,它的实现原理就是我上面自定义session所做的工作。

    安装

    pip install Flask-Session
    

    配置参数详解

    flask_session初始化后,会从app的配置中读取参数,比较重要的有:

    设置session保存的位置,可以有多种配置,
    SESSION_TYPE = ‘null’          : 采用flask默认的保存在cookie中;
    SESSION_TYPE = ‘redis’         : 保存在redis中
    SESSION_TYPE = ‘memcached’     : 保存在memcache
    SESSION_TYPE = 'filesystem'      : 保存在文件
    SESSION_TYPE = 'mongodb'        : 保存在MongoDB
    SESSION_TYPE = 'sqlalchemy'     : 保存在关系型数据库
    
    SESSION_KEY_PREFIX = 'session:' :session存储时的键的前缀
    SESSION_USE_SIGNER:是否为cookie设置签名来保护数据不被更改,默认是False;如果设置True,那么必须设置flask的secret_key参数;
    SESSION_PERMANENT:是否使用永久会话,默认True,但是如果设置了PERMANENT_SESSION_LIFETIME,则这个失效;
    
    SESSION_REDIS:
    如果SESSION_TYPE = ‘redis’,那么设置该参数连接哪个redis,其是一个连接对象;如果不设置的话,默认连接127.0.0.1:6379/0
    for example:
    SESSION_REDIS = redis.StrictRedis(host="127.0.0.1", port=6390, db=4)
    

    关于其他的保存中间人参考:https://pythonhosted.org/Flask-Session/

    一份常用的flask_session的配置

    # 指明对session数据进行保护
    SECRET_KEY = '123'
    SESSION_USE_SIGNER = True  
    # 指明保存到redis中
    SESSION_TYPE = "redis"  
    SESSION_REDIS = redis.StrictRedis(host="127.0.0.1", port=6390, db=4)
    # session的有效期,单位:秒
    PERMANENT_SESSION_LIFETIME = 7200 
    

    flask_session的使用流程

     # extensions.py
     # 创建一个session对象
     from flask_session import Session
     # 创建一个Session的实例
    session = Session()
    
    # 在app初始化时初始化session对象,即加载配置
    # __init__.py
    from flask import Flask
    app = Flask(__name__)
    session.init_app(app=app)
    
    # task.py
    from Flask import session
    
    @app.route('/test', methods=['POST'])
    def test():
        session.get('user',None)
        return ""
    

    参考:

  • 相关阅读:
    ADX3000的组网配置
    cuda编程知识普及
    最优的cuda线程配置
    JBOSS在win7环境下启动run.bat无反应
    2013年8月第2个周结
    JBOSS AS 性能调整优化
    jquery插入第一个元素? [问题点数:20分,结帖人zsw19909001]
    @Inject 注入 还是报空指针
    css3 box-shadow阴影(外阴影与外发光)讲解
    事务模板
  • 原文地址:https://www.cnblogs.com/cwp-bg/p/10084523.html
Copyright © 2020-2023  润新知