翻譯: introduce to tornado - Writing Secure Appli...

編寫加密的應用

不少時候,加密應用是一件很是繁瑣的事情(也很讓開發人員頭疼).tornado 的web服務端設計之初已經考慮到了這些事情,內置了不少加密模塊,讓咱們能夠輕鬆地對容易出現問題的地方進行處理.一個可靠的 cookies 能夠避免用戶的狀態被瀏覽器的惡意代碼進行修改.此外瀏覽器的 cookies 還能夠配合 http 請求的變量來避免一些僞造的跨站惡意攻擊.在這一章節,咱們將會了解到 tornado 的這些功能是如何防止攻擊的,同時咱們還會看到一個用戶認證的例子和相關的功能. javascript

危險的cookie

許多網頁會使用瀏覽器的cookies來存儲用戶的身份標識與sessions信息.經過瀏覽器的sessions狀態來肯定用戶的狀態是很是簡單且經常使用的手段,具備很是普遍的應用.不幸的是,瀏覽器的cookies很是容易被跨站攻擊.這裏會有一小段內容展現tornado是如何防止惡意腳本篡改你應用存儲的cookies的. html

僞造cookies

有許多途徑能夠截取到瀏覽器中的cookies信息,網頁中的javascript和flash都有讀寫這個域中的cookies信息的權限.瀏覽器插件一樣也能夠經過程序去獲取這些cookies信息.這些經過網頁完成的腳本攻擊,能夠直接篡改用戶瀏覽器中的cookies數據. java

加密cookies

tornado使用加密的簽名驗證cookies的值,以保證這些cookies的數值不可以被服務器之外的第三方修改.惡意的攻擊腳本並不知道加密的key,因此它們將沒法修改應用中的任何cookies數據. python

使用加密的cookies

tornado的 set_secure_cookies() 和 get_secure_cookes() 功能用於發送和接受瀏覽器cookies數據,防止這些數據在瀏覽器中被篡改.要想使用這些功能,你必須在應用的構造函數中聲明 cookies_secret 變量.讓咱們來看一個簡單的例子. web

例子6-1中的應用將會返回一個瀏覽器中累計重載網頁的次數.若是cookies沒有設置過(或cookies被修改過),這個應用將會設置一個新的cookies值 1. 不然應用將會獲取cookies的值並進行自增操做(對原數值加一). 例子6-1 cookie_counter.py ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import tornado . httpserver
import tornado . ioloop
import tornado . web
import tornado . options
 
from tornado . options import define , options
define ( "port" , default = 8000 , help = "run on the given port" , type = int )
 
class MainHandler ( tornado . web . RequestHandler ) :
     def get ( self ) :
         cookie = self . get_secure_cookie ( "count" )
         count = int ( cookie ) + 1 if cookie else 1
 
         countString = "1 time" if count == 1 else "%d times" % count
         self . set_secure_cookie ( "count" , str ( count ) )
         self . write (
             ''
             '<h1>
  You’ve viewed this page %s times.
  
</h1>' % countString
             ''
     )
 
if __name__ == "__main__" :
     tornado . options . parse_command_line ( )
     settings = {
         "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E="
     }
     application = tornado . web . Application ( [
         ( r '/' , MainHandler )
     ] , * * settings )
     http_server = tornado . httpserver . HTTPServer ( application )
     http_server . listen ( options . port )
     tornado . ioloop . IOLoop . instance ( ) . start ( )

若是你去檢查瀏覽器中的cookies,你將會注意到通過計算以後存儲的數值是MQ==|1310335926|8ef174ecc489ea963c5cdc26ab6d41b49502f2e2. tornado 使用 base-64 編碼存儲cookies目錄中的數據,包括每一次追加 timestamp 和 HMAC 簽名.若是你cookies中的 timestamp 太舊(或者超前了).或者簽名沒有符合指望值.get_secure_cookie() 函數會假設這個cookies已經被修改並返回一個 None 的空值.當作cookie 沒有設置過. shell

這個 cookie_secret 值將會作爲一個惟一的隨機字符串傳送給應用的構造函數.在 python shell 中執行下面的代碼,將會生成一個字符串給你. 數據庫

1
2
3
>>> import base64 , uuid
>>> base64 . b64encode ( uuid . uuid4 ( ) . bytes + uuid . uuid4 ( ) . bytes )
'bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E='

tornado 能夠保證cookies不被窺探.然而,攻擊者仍然能夠經過瀏覽器腳本或插件去截取 cookies 信息.或者直接竊聽未加密的網絡數據,記住 cookie 變量必須使用加密的簽字.即便如此,惡意的程序仍然能夠獲取到存儲的 cookie 數據,而且發送這些數據到其它服務器上或者修改的 cookie 僞造請求發送到應用服務器上.所以,必須避免存儲敏感的用戶數據到瀏覽器的 cookie 中. 咱們還應該注意到,用戶能夠修改本身的 cookies.這可能會帶來一次提權攻擊.例如,咱們存儲一些用戶已經購買且能夠查看的文章數量到 cookie 中.咱們須要防止用戶試圖經過修改這個數值獲取免費內容的行爲. httponly 和 secure 的 cookie 屬性能夠幫助咱們防止這種類型的攻擊. json

cookies 的 httponly 和 SSL

tornado 的 cookie 功能是基於 python 的 cookie 模塊構建的.因此咱們可使用一些加密功能提供的高級特性, 能夠只加密 http cookie 中的部分信息.而且它經過運行的腳本去告訴瀏覽器能夠暴露哪些 cookie 數據給,經過什麼方式鏈接服務器.例如,咱們能夠只經過 SSL 鏈接去傳送 cookie的數據,減少網絡請求被截取的機率.咱們還能夠經過 javascript 要求瀏覽器隱藏 cookie 的數據. 設置 secure 屬性告訴瀏覽器只能經過 SSL 鏈接去傳送加密的 cookie.(這可能有些使人困惑, tornado 加密的 cookies 與其它的不太同樣,準確的說,這是一個 signed cookies).從 python2.6 的版本開始, cookie 對象還支持 httponly 屬性.包括告訴瀏覽器如何讓 cookie 更不容易被 javascript 調用.這能夠防止跨站攻擊腳本去獲取 cookie 的值. 要使用這些功能,你必須傳送一個 keyword 參數給 set_cookie 和 set_secure_cookie 方法.例如,一個加密, http-only 的 cookie (沒有使用 tornado 的簽署功能)能夠經過 self.set_cookie(‘foo’, ‘bar’, httponly=Ture, secure=True) 進行發送. 如今咱們來探討如何保護 cookie 中存儲的持久化數據的策略.咱們能夠看到經過另外一個公共的攻擊變量,」脆弱的請求」在96頁將會看到如何防止惡意網頁僞造的請求攻擊你的應用. 瀏覽器

脆弱的請求

對於任何 web 應用一個主要的安全漏洞是跨站請求的攻擊.一般縮寫爲 CSRF 或 XSRF, 它的發音是 「sea surf」. 這個是經過瀏覽器的一個安全漏洞.將惡意代碼注入到受害者的網站,僞造非法的請求危害登陸網站的用戶利益.讓咱們來看一個例子.

剖析一個僞造的跨站請求

咱們假設有一個正常的 burt 書店的用戶 alice.當她使用她的賬號登陸到在線商店後.網站將會標識她的瀏覽器 cookie.如今假設她是一個不當心的用戶, melvin 想要增長他店中書籍的銷售量,在 alice 常常訪問的一個網絡社區, melvin 已經發表了一篇帶有 HTML 圖片標記的文章,這個 HTML 圖片標記的代碼中包含一個在線書店中的購買連接,例如: <img src="http://store.burts-books.com/purchase?title=Melvins+Web+Sploitz" /> alice 的瀏覽器在試圖獲取這個圖片資源的時候,將會把合法的 cookie 添加到請求中.並不知道這個kitten的圖片已經被替換掉了, 這條 URL 將會對在線商店發起一次購買請求.

防止僞造的請求

有許多預防這類攻擊的方法.首先在你開發的時候,必需要考慮的是隻能接受來自同一個域的請求.這會對全部 HTTP 請求帶來反作用,相似點擊按鈕去提交購買訂單,編輯帳戶設置,修改密碼,或者刪除的操做,都應該使用 HTTP 的 POST 方法.這是一個很好的 RESTful 實踐,同時它還有有個額外的優勢,避免一些相似於剛纔看到的惡意圖片帶來的 XSRF 攻擊,固然這是遠遠不夠的,一些惡意的網站仍然能夠經過好比 HTML 表單或 XMLHTTPRequest 的 API 僞造 POST 請求去攻擊你的應用.咱們須要經過其它策略去保護 POST 請求. 一個防止僞造 POST 請求的策略是,咱們在每個請求中包含一個變量 token,這個變量將會與發起請求的cookie相匹配,咱們的應用將會爲每個頁面提供不一樣的 token , 服務經過 cookie 頭和網頁中隱藏的 HTML 元素給用戶的瀏覽器提供 token. 若是兩個都匹配,咱們的應用就會認爲這個請求是合法的.

使用tornado的 XSRF 防禦

你還能夠在應用的構造函數中包含 xsrf_cookies 變量去啓用 XSRF 保護.

1
2
3
4
5
6
7
8
settings = {
     "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" ,
     "xsrf_cookies" : True
}
application = tornado . web . Application ( [
     ( r '/' , MainHandler ) ,
     ( r '/purchase' , PurchaseHandler ) ,
] , * * settings )

設置了這個標記,tornado將會拒絕並刪除沒有包含正確 _xsrf 值的變量的 POST PUT請求.tornado 還會處理這個狀況下的 _xsrf cookies.固然你的網頁表單必須包含 XSRF token 的合法纔會被當成受權請求處理.要實現它,只須要在你的 template 中簡單地調用一個函數 xsrf_form_html就能夠了:

1
 

XSRF tokens 和 AJAX 請求

AJAX 請求一樣也要聲明 _xsrf 變量,並且要在返回的頁面中顯式聲明 _xsrf 的值.這個腳本能夠查詢客戶端瀏覽器中的 cookie 值. 經過兩個功能能夠清楚得看到, AJAX POST 的請求中添加了 token 的值.第一個函數經過用戶名獲取 cookie ,這樣第二個函數就能夠很方便地添加 _xsrf 變量到存儲數據的對象中,並傳送給 postJSON 函數.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getCookie ( name ) {
     var c = document . cookie . match ( "\\b" + name + "=([^;]*)\\b" ) ;
     return c ? c [ 1 ] : undefined ;
}
jQuery . postJSON = function ( url , data , callback ) {
     data . _xsrf = getCookie ( "_xsrf" ) ;
     jQuery . ajax ( {
         url : url ,
         data : jQuery . param ( data ) ,
         dataType : "json" ,
         type : "POST" ,
         success : callback
     } ) ;
}

這裏有許多必需要預先考慮的事情, tornado 的 secure cookie 支持 XSRF 防禦能夠減小應用開發者的負擔.它內建的加密功能很是有用.儘管如此,你仍然要特別留心應用的安全性,互聯網上有很是多在線的 web 應用加密資料能夠參考,其中實際應用最普遍的是 mozilla 的 secure coding guidelines

用戶驗證

如今咱們來了解一下 XSRF 攻擊的原理,而後學習如何設置和恢復安全的 cookies,咱們以一個簡單的用戶驗證系統作爲示例,在這一段中,咱們將會構建一個應用,詢問訪問者的用戶名並將其存儲到加密的 cookie 中以及如何恢復 cookie 數據.接下來的請求將會記錄回訪者的信息並顯式一個特定的用戶頁面給回訪者.你須要學習一些 login_url 的變量和 tornado.web.authenticated 修飾符的知識,這樣能夠去掉應用中一些經常使用的 headaches .

例子:歡迎回來

在這個例子中,咱們將經過加密的 cookie 中存儲的數據直接識別用戶信息.當某人經過特定的瀏覽器第一次訪問咱們的網站時(或者她的 cookie 已通過期),應用將會展示一個帶有登陸表單的頁面,這個表單將會以 POST 請求的方式向 LoginHandler 地址提交信息.表單中的 POST 方法會調用 set_secure_cookie() 去存儲請求中提交的用戶名等參數. 這個有用戶驗證功能的 tornado 應用示例代碼能夠查看例6-2.咱們將會在這一段落深刻討論其中的細節. LoginHandler 類會返回登陸表單並設置 cookie 直到 LogoutHandler 類將其刪除掉. 例子6-2:cookies.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import tornado . httpserver
import tornado . ioloop
import tornado . web
import tornado . options
import os.path
 
from tornado . options import define , options
define ( "port" , default = 8000 , help = "run on the given port" , type = int )
 
class BaseHandler ( tornado . web . RequestHandler ) :
     def get_current_user ( self ) :
         return self . get_secure_cookie ( "username" )
class LoginHandler ( BaseHandler ) :
     def get ( self ) :
         self . render ( 'login.html' )
     def post ( self ) :
         self . set_secure_cookie ( "username" , self . get_argument ( "username" ) )
         self . redirect ( "/" )
class WelcomeHandler ( BaseHandler ) :
     @ tornado . web . authenticated
     def get ( self ) :
         self . render ( 'index.html' , user = self . current_user )
class LogoutHandler ( BaseHandler ) :
     def get ( self ) :
         if ( self . get_argument ( "logout" , None ) ) :
         self . clear_cookie ( "username" )
         self . redirect ( "/" )
if __name__ == "__main__" :
     tornado . options . parse_command_line ( )
     settings = {
         "template_path" : os.path . join ( os.path . dirname ( __file__ ) , "templates" ) ,
         "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" ,
         "xsrf_cookies" : True ,
         "login_url" : "/login"
     }
     application = tornado . web . Application ( [
         ( r '/' , WelcomeHandler ) ,
         ( r '/login' , LoginHandler ) ,
         ( r '/logout' , LogoutHandler )
     ] , * * settings )
     http_server = tornado . httpserver . HTTPServer ( application )
     http_server . listen ( options . port )
     tornado . ioloop . IOLoop . instance ( ) . start ( )

而例子6-3和例子6-4對應的文件應該放到應用所屬目錄的 templates 目錄下. 例子6-3:login.html

1
 

例子6-4:index.html

1
2
3
4
< h1 >
   Welcome back , { { user } }
  
< / h1 >

authenticated 修飾符

要想使用 tornado 的驗證功能,咱們必需要標記一個專用的 handlers 處理咱們的用戶登陸請求.使用 @tornado.web.authenticated 修飾符可讓咱們更方便地實現這個需求.只要在編寫處理方法的時候附加上這個修飾符,tornado 將會在校驗用戶信息正確以後纔會調用這個方法.讓咱們來看看例子中的 welcomehandler.它只有確認用戶登陸以後纔會返回 index.html 的頁面.

1
2
3
4
class WelcomeHandler ( BaseHandler ) :
     @ tornado . web . authenticated
     def get ( self ) :
         self . render ( 'index.html' , user = self . current_user )

在 get 方法被調用以前, authenticated 修飾符會確保 current_user 屬性存在對應的值.(後面會對這個屬性作簡單的討論).若是 current_user 的值是 「falsy」 (None, False, 0, 「」)任何 get 或 head 請求將會被重定向到 login_url 應用設定的 URL. 請注意, 沒有經過用戶校驗的 POST 請求將會返回 http 403 狀態的響應信息. 若是用戶校驗經過, tornado 將會調用指望的 handler 方法.這個 authenticated 修飾符信賴 current_user 屬性和 login_url 設置的所有功能,如今咱們來看看下一步作什麼.

current_user 屬性

這個請求處理的類有 current_user 屬性(它也能夠用在其它 template 的上)能夠用於存儲正常請求中的用戶身份驗證信息.在默認狀況下,這個值是None,若是要讓 authenticated 修飾符成功識別用戶信息,你必須重載正經常使用戶請求處理的 get_current_user() 方法中的默認值. 在這個例子中,啓用這個接口以後,咱們能夠從訪問者的 cookie 中快速地恢復他的用戶信息.很顯然,你們都但願使用健壯性更強的技術,爲了達到這個目的,咱們必須使用下面幾個方法:

1
2
3
class BaseHandler ( tornado . web . RequestHandler ) :
     def get_current_user ( self ) :
         return self . get_secure_cookie ( "username" )

在這裏討論的例子沒有涉及存儲和檢索用戶密碼或其它證書,可是進行極小的改動以後本章所描述的技術能夠擴展成查詢數據庫憑據.

login_url 設置

請留意這個應用中構造方法的內容.記住咱們傳給應用的新設置: Login_url 在應用中的地址爲 login 形式.若是 get_current_user 方法返回了一個 falsy 值, authenticated 修飾符將會把請求處理重定向瀏覽器到登陸頁面的地址上.

1
2
3
4
5
6
7
8
9
10
11
settings = {
         "template_path" : os.path . join ( os.path . dirname ( __file__ ) , "templates" ) ,
         "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" ,
         "xsrf_cookies" : True ,
         "login_url" : "/login"
     }
     application = tornado . web . Application ( [
         ( r '/' , WelcomeHandler ) ,
         ( r '/login' , LoginHandler ) ,
         ( r '/logout' , LogoutHandler )
     ] , * * settings )

當 tornado 構建這條重定向 URL 時,還能夠附加上一個 next 的請求字符串變量,這個變量會包含在重定向的到登陸頁面的 URL 中,你可使用這樣的語句 self.redirect(self.get_argument('next','/'))讓用戶完成登陸以後返回登陸前的頁面.

總結

目前咱們只看到使用兩種技術來幫助咱們保護 tornado應用.如何使用 @tornado.web.authenticated 修飾符去實現用戶的身份驗證.在第七章中,咱們將會着眼於如何擴展咱們已經討論過的技術,將其應用到外部的 web 服務中.經過外部的web server例如 facebook 或 twitter 完成用戶身份驗證.


博主渣基礎,對於web應用還不夠了解,未必能將做者的意圖都翻譯過來,若是其中有不夠完善的地方,請幫忙糾正. 原創翻譯:發佈於http://blog.xihuan.de/tech/web/tornado/tornadowritingsecure_applications.html

上一篇: 翻譯:introduce to tornado-Asynchronous Web Services

下一篇: 翻譯: introduce to tornado - Authenticating with External Services

相關文章
相關標籤/搜索