• Tornado-Secure cookie and Session


    这一节涉及的内容有:

    1.客户端和服务端操作cookie的方法

    2.secure cookie的机制

    3.基本/自定义session

    文件结构

    三个启动文件由下往上对应的分别是三种服务端:使用secure cookie,使用基本的session,使用自定义session。

    另外,这一节中的index.html中并任何实质的内容,我只是在里面练习写了一个服务端生成自定义时间的键值对cookie,同样附在下面

    Python代码

    start.py

    from tornado import web
    import tornado.ioloop
    
    
    class IndexHandler(web.RequestHandler):
        def get(self):
            if self.get_argument('user', None) in ['yeff', 'mike']:
                self.set_secure_cookie('n', self.get_argument('user'))
                self.write('欢迎')
            else:
                self.write('请登陆')
    
    
    class ManagerHandler(web.RequestHandler):
        def get(self):
            # 注意这里取得的cookie是bytes格式的,不是字符串格式
            if self.get_secure_cookie('n', None) in [b'yeff', b'mike']:
                self.write('欢迎登陆: ' + str(self.get_secure_cookie('n'),encoding="utf-8"))
            else:
                self.redirect('/index')
    
    
    settings = {
        "template_path": "views",
        "static_path": "static",
        "cookie_secret": "salt",
    }
    
    application = web.Application([
        (r"/index", IndexHandler),
        (r"/manager", ManagerHandler),
    ], **settings)
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    使用secure cookie的服务端

    session-start.py

    还有很大的缺陷,如登陆后再装到index还是会提示未登陆。这一点在自定义session中有修正。

    from tornado import web
    import tornado.ioloop
    import time
    import hashlib
    
    sessions = {}
    
    # 可以注意到在用了session后,每次重启客户端后,用户拿着cookie也是没有办法登陆的
    # 因为客户端这边的sessions中的内容被清空了,好比用户手里虽然还有钥匙(cookie键值对)
    # 但服务端的箱子已经没有了
    # 当然sessions可以放在数据库/文件/缓存中,而不是存储在内存中
    # cookie是否使用完全看服务端的需求:只要将对应user的islogin信息换成False即可
    # 通过在服务端和cookie间多添加一层抽象,既便于存储大量信息,也提升了安全性
    
    
    class IndexHandler(web.RequestHandler):
        def get(self):
            if self.get_argument('usn', None) in ['yeff', 'mike']:
                _en = hashlib.md5()
                _en.update(bytes(str(time.time()), encoding="utf-8"))
                user_key = _en.hexdigest()
                sessions[user_key] = {}
                sessions[user_key]['name'] = 'Yifei Xu'
                sessions[user_key]['age'] = '23'
                sessions[user_key]['accountInfo'] = 'xyfst'
                sessions[user_key]['isLogin'] = True
                self.set_cookie(name='steam', value=user_key)
                self.write('登陆成功了哦@_@')
            else:
                self.write('请登陆-_-')
    
    
    class ManagerHandler(web.RequestHandler):
        def get(self):
            user_key = self.get_cookie(name='steam')
            user_info = sessions.get(user_key)
            if not user_info:
                self.redirect("/index")
            else:
                if user_info.get('isLogin'):
                    display_str = "Name:%s	Age:%s	Account:%s" % (user_info.get('name'), user_info.get('age'), user_info.get('accountInfo'))
                    self.write(display_str)
                else:
                    self.write("登陆信息已失效,得重新登陆啦*_*")
    
    
    settings = {
        "template_path": "views",
        "static_path": "static",
    }
    
    application = web.Application([
        (r"/index", IndexHandler),
        (r"/manager", ManagerHandler),
    ], **settings)
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    使用基本session的服务端

     class-session-start.py

    from tornado import web
    import tornado.ioloop
    
    SESSIONS = {}
    
    
    class Sessions(object):
        def __init__(self, handler, cookie_key):
            self.handler = handler
            self.user_key = None
            self.cookie_key = cookie_key
    
        @staticmethod
        def generate_random_str():
            import hashlib, time
            _en = hashlib.md5()
            _en.update(bytes(str(time.time()), encoding="utf-8"))
            return _en.hexdigest()
    
        def __setitem__(self, k, v):
            # 创建随机字符串
            # 创建自己的箱子
            # 在箱子中放入信息对
            # 在客户端中放入箱子的钥匙
    
            # 如果服务端还没有钥匙,先制作一个钥匙,并创建一个对应的箱子
            if not SESSIONS.get(self.user_key):
                self.user_key = Sessions.generate_random_str()
                SESSIONS[self.user_key] = {}
            # 起初/manager页面无论如何都无法进入,想了很久
            # 这个点费了我不少时间才找到:这边对SESSIONS中是否存在钥匙没有判断,使得每次set方法清空钥匙对应的箱子
            SESSIONS[self.user_key][k] = v
            self.handler.set_cookie(self.cookie_key, self.user_key)
    
        def __getitem__(self, item):
            # 获取客户端递来的钥匙
            # 看看房间里有对应钥匙吗
            # 用钥匙打开箱子,获得信息对,取出值
            # 没有钥匙返回None
            value = None
            _user_key = self.handler.get_cookie(self.cookie_key)
            if SESSIONS.get(_user_key):
                self.user_key = _user_key
                value = SESSIONS[self.user_key][item]
            return value
    
    
    # IndexHandler和ManagerHandler都继承这个类
    # tornado为我们留的钩子,使用继承RequestHandler的类并初始化时(执行__init__方法),会在最后执行 self.initialize(**kwargs)
    # 我们可以自己定义initialize的函数内容,以实现不同的效果
    # 这里我们是初始化了服务端的Sessions类,继承之后,两个类就不用自己再初始化了,更简洁一些
    class BaseHandler(web.RequestHandler):
        def initialize(self):
            self.session = Sessions(self, 'steam')
    
    
    # tornado内部通过反射调用get和post方法
    # obj = IndexxHandler()
    # func = getattr(obj, "get")
    # func()
    class IndexHandler(BaseHandler):
        def get(self):
            # 如果客户端有对应的钥匙则转到manager页面尝试匹配
            if SESSIONS.get(self.get_cookie('steam')):
                self.redirect('/manager')
            # 注意这里的多层嵌套
            # 第一次我将两个if写在了同级,但在执行了redirect函数后,还是会接着执行下面判断中的else块
            # 虽不至于中止程序,但会报错,所以这里写成了两层嵌套
            else:
                # 判断用户登陆信息是否正确(这里作了简化,只判断了用户名)
                if self.get_argument('usn', None) in ['yeff', 'mike']:
                    # session = Sessions(self, 'steam')
                    self.session['isLogin'] = True
                    self.session['name'] = 'Yifei Xu'
                    self.session['age'] = '23'
                    self.session['accountInfo'] = 'xyfst'
                    self.write('登陆成功了哦@_@')
                else:
                    self.write('请先登陆呦')
    
    
    class ManagerHandler(BaseHandler):
        def get(self):
            # session = Sessions(self, 'steam')
    
            # 从客户端拿来钥匙
            # 看看服务端的房间里有钥匙吗
            # 有则取出对应的信息对
            _site_user_key = self.get_cookie(self.session.cookie_key)
            if not SESSIONS.get(_site_user_key):
                self.write('登陆信息已失效,请先登陆*。*')
            else:
                display_str = "Name:%s	Age:%s	Account:%s" % (self.session['name'], self.session['age'], self.session['accountInfo'])
                self.write(display_str)
    
    
    settings = {
        "template_path": "views",
        "static_path": "static",
    }
    
    application = web.Application([
        (r"/index", IndexHandler),
        (r"/manager", ManagerHandler),
    ], **settings)
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    使用自定义session的服务端

     HTML代码

    index.html

    并没有实质的实际内容,内部只有一个我自己写的生成自定义cookie的方法

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>Cookie</h1>
    <script>
        <!--根据用户输入设置cookie,时间单位可以是yMdhms,格式是数字+对应字母-->
        <!--也可以针对不同单位的时间分开写方法,我这里整合为了一种方法,顺便复习了js的字符串操作方法以及switch语法-->
        function setCookie(name,value,expires){
            var current_date = new Date();
            if(typeof(expires) == 'string'){
                var unit = expires.charAt(expires.length-1);
                var str_num = expires.slice(0,expires.length-1);
                var num = parseInt(str_num);
                if('yMdhms'.includes(unit) && num){
                    switch(unit){
                        case 'y':
                            current_date.setDate(current_date.getDate() + num * 365);
                        case 'M':
                            current_date.setDate(current_date.getDate() + num * 30);
                        case 'd':
                            current_date.setDate(current_date.getDate() + num);
                        case 'h':
                            current_date.setSeconds(current_date.getSeconds() + num * 60 * 60);
                        case 'm':
                            current_date.setSeconds(current_date.getSeconds() + num * 60);
                        case 's':
                            current_date.setSeconds(current_date.getSeconds() + num);
                    }
                }else{
                    return false;
                }
            }else{
                return false;
            }
            document.cookie = name + "=" + value + ";expires=" + current_date.toUTCString();
            return true;
        }
    
    </script>
    </body>
    </html>
    浏览器端生成自定义cokie

    额外内容

    这一节也涉及到了python类的特殊方法以及钩子函数,在注释里都有讲到,下面也附带一些解释。

    class Foo:
        def __call__(self, *args, **kwargs):
            pass
        
        def __init__(self):
            pass
        
        def __class__(self):
            pass
        
        def __setitem__(self, key, value):
            pass
        
        def __getitem__(self, item):
            pass
        
        def __delitem__(self, key):
            pass
        
        
    obj = Foo()       # __call__, __init__
    obj['k1'] = 'v1'  # __setitem__
    obj['k1']         # __getitem__
    del obj['k1']     # __delitem__    

    钩子函数: http://blog.csdn.net/sunstars2009918/article/details/39340449(这位作者讲的很清晰,虽然例子是C。*-*)

  • 相关阅读:
    Linux 如何在镜像文件里面创建分区
    PostgreSQL主从配置
    nginx跨域配置2022
    小程序 canvas 拼接图片 下载图片 海报 新版 旧版 版本库2.9
    Invalid Host header nginx vue
    springboot使用@Async注解时异步方法不生效原因分析及解决方案
    Java stream().map()将对象转换为其他对象
    java Synchonized 锁的特性
    Springboot tomcat 多线程 模型 web容器 模型 并发模型 Java
    mysql 8.0 绿色版 在windows上的使用
  • 原文地址:https://www.cnblogs.com/yifeixu/p/8028908.html
Copyright © 2020-2023  润新知