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()
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\tAge:%s\tAccount:%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()
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\tAge:%s\tAccount:%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()
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>
额外内容
这一节也涉及到了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。*-*)