Tornado之初學者(五)
Tronado的web應用安全(cookie和CSRF/XSRF)
安全cookies是web應用的安全防範之一,瀏覽器中的cookies存儲了用戶的個人信息,當然包括了某些重要的敏感的信息,如果一些惡意的腳本得到甚至修改了用戶的cookies的信息,用戶的信息就得不到安全的保障,所以應該對用戶的cookies進行保護。Tornado的安全cookies可以對cookies簽名進行安全加密,已檢查cookies是否被修改過,因為惡意腳本不知道安全密鑰,所以無法修改(但是惡意腳本仍然可以截獲cookies來“冒充”用戶,只是不能修改cookies而已,這也是另外一個安全隱患,本文並不討論這點)。
Tornado的get_secure_cookie()和set_secure_cookie()可以安全的獲取和發送瀏覽器的cookies,可以防止瀏覽器中的惡意修改,但是為了使用這個功能,必須在tornado.web.Application的settings中設置cookie_secret,其值為一個唯一的隨機字符串(比如:base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)可以產生一個唯一的隨機字符串)。
CSRF或XSRF,即跨站請求偽造,是web應用都涉及到的一個安全漏洞,它利用了瀏覽器的一個安全漏洞:瀏覽器允許惡意攻擊者在受害者網站註入腳本使未授權請求代表一個已登錄用戶。即別人可以“冒充”你做一些事情,而服務器也會認為這些操作是你做的。
為了防止XSRF攻擊,應該註意:一是開發者考慮某些重要的請求時需要使用POST方法,二是Tornado的一個防範偽造POST的功能(這個功能是一種策略,tornado也實現了這種策略),就是在每個請求中包含一個參數值(隱藏的HTML表單元素值)和存儲的cookie值,若兩者(稱之為令牌)匹配上了,則證明請求有效,當某個不可信的站點沒有訪問cookie數據的權限時,它就不能再請求中包含這個令牌cookie值,自然就無法發送有效的請求了。tornado中使用這個功能需要在tornado.web.Application的settings中設置xsrf_cookies,值為True,同時必須在HTML的表單中包含xsrf_form_html()函數,以此形成cookie令牌。
tornado的用戶驗證可以使用裝飾器@tornado.web.authenticated,使用這個裝飾器時需註意:①必須重寫get_current_user()方法,這個返回的值將賦給self.current_user,②被裝飾的方法被執行前會檢查self.current_user的bool值是否為False(默認是None),若為False則會重定向到tornado.web.Application的settings中login_url指定的URL,③需要在tornado.web.Application的settings中login_url的URL,以便用戶驗證self.current_user之前可以重定向到這個URL。④當Tornado構建重定向URL時,它還會給查詢字符串添加一個next參數,其值為重定向之前的URL,可以使用如self.redirect(self.get_argument(‘next‘, ‘/‘))這樣的語句在用戶驗證成功後回到原來的頁面。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import tornado.httpserver 4 import tornado.ioloop 5 import tornado.web 6 import tornado.options 7 import os.path 8 import base64 9 import uuid 10 11 from tornado.options import define, options 12 13 define(‘port‘, default=8000, help=‘run on the given port‘, type=int) 14 15 16 class BaseHandler(tornado.web.RequestHandler): 17 def get_current_user(self): 18 """重寫此方法,返回的值將賦給self.current_user""" 19 return self.get_secure_cookie(‘username‘) # 獲取cookies中username的值 20 21 22 class LoginHandler(BaseHandler): 23 def get(self): 24 self.render(‘login.html‘) 25 26 def post(self): 27 self.set_secure_cookie(‘username‘, self.get_argument(‘username‘)) # 將請求中的username值賦給cookie中的username 28 self.redirect(‘/‘) 29 30 31 class HomeHandler(BaseHandler): 32 @tornado.web.authenticated # 使用此裝飾器必須重寫方法get_current_user 33 def get(self): 34 """被@tornado.web.authenticated裝飾的方法被執行前會檢查self.current_user的bool值是否為False,不為False時才會執行此方法""" 35 self.render(‘index.html‘, user=self.current_user) 36 37 38 class LogoutHandler(BaseHandler): 39 def get(self): 40 if self.get_argument(‘logout‘, None): 41 self.clear_cookie(‘username‘) # 清楚cookies中名為username的cookie 42 self.redirect(‘/‘) 43 44 45 if __name__ == ‘__main__‘: 46 tornado.options.parse_command_line() 47 48 settings = { 49 ‘template_path‘: os.path.join(os.path.dirname(__file__), ‘templates‘), 50 ‘cookie_secret‘: base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), # 設置cookies安全,值為一個唯一的隨機字符串 51 ‘xsrf_cookies‘: True, # 設置xsrf安全,設置了此項後必須在HTML表單中包含xsrf_form_html() 52 ‘login_url‘: ‘/login‘ # 當被@tornado.web.authenticated裝飾器包裝的方法檢查到self.current_user的bool值為False時,會重定向到這個URL 53 } 54 55 application = tornado.web.Application([ 56 (r‘/‘, HomeHandler), 57 (r‘/login‘, LoginHandler), 58 (r‘/logout‘, LogoutHandler) 59 ], **settings) 60 61 http_server = tornado.httpserver.HTTPServer(application) 62 http_server.listen(options.port) 63 tornado.ioloop.IOLoop.instance().start()
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Welcom Back!</title> 6 </head> 7 <body> 8 <h1>Welcom back, {{ user }}</h1> 9 </body> 10 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Please Log In</title> 6 </head> 7 <body> 8 <form action="/login" method="POST"> 9 {% raw xsrf_form_html() %}<!-- 這其實是一個隱藏的<input>元素,定義了“_xsrf”的值,會檢查POST請求以防止跨站點請求偽造 --> 10 Username: <input type="text" name="username" /> 11 <input type="submit" value="Log In" /> 12 </form> 13 </body> 14 </html>
Tornado之初學者(五)