Tornado 4.3
於2015年11月6日發佈,該版本正式支持Python3.5
的async
/await
關鍵字,而且用舊版本CPython編譯Tornado一樣可使用這兩個關鍵字,這無疑是一種進步。其次,這是最後一個支持Python2.6
和Python3.2
的版本了,在後續的版本了會移除對它們的兼容。如今網絡上尚未Tornado4.3
的中文文檔,因此爲了讓更多的朋友能接觸並學習到它,我開始了這個翻譯項目,但願感興趣的小夥伴能夠一塊兒參與翻譯,項目地址是tornado-zh on Github,翻譯好的文檔在Read the Docs上直接能夠看到。歡迎Issues or PR。javascript
你能夠在用戶瀏覽器中經過set_cookie
方法設置 cookie:html
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_cookie("mycookie"): self.set_cookie("mycookie", "myvalue") self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!")
普通的cookie並不安全, 能夠經過客戶端修改. 若是你須要經過設置cookie,例如來識別當前登陸的用戶, 就須要給你的cookie簽名防止僞造. Tornado支持經過 RequestHandler.set_secure_cookie
和RequestHandler.get_secure_cookie
方法對cookie簽名. 想要使用這些方法, 你須要在你建立應用的時候, 指定一個名爲cookie_secret
的密鑰. 你能夠在應用的設置中以關鍵字參數的形式傳遞給應用程序:java
application = tornado.web.Application([ (r"/", MainHandler), ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
簽名後的cookie除了時間戳和一個HMAC
簽名還包含編碼後的cookie值. 若是cookie過時或者簽名不匹配,get_secure_cookie
將返回None
就像沒有設置cookie同樣. 上面例子的安全版本:python
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_secure_cookie("mycookie"): self.set_secure_cookie("mycookie", "myvalue") self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!")
Tornado的安全cookie保證完整性可是不保證機密性. 也就是說, cookie不能被修改可是它的內容對用戶是可見的. 密鑰cookie_secret
是一個對稱的key, 並且必須保密--任何得到這個key的人均可以僞造出本身簽名的cookie.jquery
默認狀況下, Tornado的安全cookie過時時間是30天. 能夠給set_secure_cookie
使用expires_days
關鍵字參數 同時get_secure_cookie
設置max_age_days
參數也能夠達到效果. 這兩個值分別經過這樣(設置)你就能夠達到以下的效果, 例如大多數狀況下有30天有效期的cookie, 可是對某些敏感操做(例如修改帳單信息)你可使用一個較小的max_age_days
.git
Tornado也支持多簽名密鑰, 使簽名密鑰輪換.cookie_secret
而後必須是一個以整數key版本做爲key, 以相對應的密鑰做爲值的字典. 當前使用的簽名鍵必須是 應用設置中key_version
的集合. 不過字典中的其餘key都容許作cookie簽名驗證, 若是當前key版本在cookie集合中.爲了實現cookie更新, 能夠經過RequestHandler.get_secure_cookie_key_version
查詢當前key版本.github
當前已經經過認證的用戶在每一個請求處理函數中均可以經過self.current_user
獲得, 在每一個模板中可使用current_user
得到. 默認狀況下,current_user
是None
.web
爲了在你的應用程序中實現用戶認證, 你須要在你的請求處理函數中複寫get_current_user()
方法來判斷當前用戶, 好比能夠基於cookie的值.這裏有一個例子, 這個例子容許用戶簡單的經過一個保存在cookie中的特殊暱稱登陸到應用程序中:ajax
class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("user") class MainHandler(BaseHandler): def get(self): if not self.current_user: self.redirect("/login") return name = tornado.escape.xhtml_escape(self.current_user) self.write("Hello, " + name) class LoginHandler(BaseHandler): def get(self): self.write('<html><body><form action="/login" method="post">' 'Name: <input type="text" name="name">' '<input type="submit" value="Sign in">' '</form></body></html>') def post(self): self.set_secure_cookie("user", self.get_argument("name")) self.redirect("/") application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
你可使用 Python裝飾器(decorator)
tornado.web.authenticated
要求用戶登陸. 若是請求方法帶有這個裝飾器而且用戶沒有登陸, 用戶將會被重定向到login_url
(另外一個應用設置).上面的例子能夠被重寫:數據庫
class MainHandler(BaseHandler): @tornado.web.authenticated def get(self): name = tornado.escape.xhtml_escape(self.current_user) self.write("Hello, " + name) settings = { "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", "login_url": "/login", } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
若是你使用authenticated
裝飾post()
方法而且用戶沒有登陸,服務將返回一個403
響應.@authenticated
裝飾器是if not self.current_user: self.redirect()
的簡寫. 可能不適合非基於瀏覽器的登陸方案.
經過 Tornado Blog example application
能夠看到一個使用用戶驗證(而且在MySQL數據庫中存儲用戶數據)的完整例子.
tornado.auth
模塊實現了對一些網絡上最流行的網站的身份認證和受權協議,包括Google/Gmail, Facebook, Twitter,和FriendFeed. 該模塊包括經過這些網站登陸用戶的方法, 並在適用狀況下容許訪問該網站服務的方法, 例如, 下載一個用戶的地址簿或者在他們支持下發布一條Twitter信息.
這是個使用Google身份認證, 在cookie中保存Google的認證信息以供以後訪問的示例處理程序:
class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleOAuth2Mixin): @tornado.gen.coroutine def get(self): if self.get_argument('code', False): user = yield self.get_authenticated_user( redirect_uri='http://your.site.com/auth/google', code=self.get_argument('code')) # Save the user with e.g. set_secure_cookie else: yield self.authorize_redirect( redirect_uri='http://your.site.com/auth/google', client_id=self.settings['google_oauth']['key'], scope=['profile', 'email'], response_type='code', extra_params={'approval_prompt': 'auto'})
查看 tornado.auth
模塊的文檔以瞭解更多細節.
跨站請求僞造(Cross-site request forgery),或XSRF, 是全部web應用程序面臨的一個主要問題. 能夠經過Wikipedia 文章來了解更多關於XSRF的細節.
廣泛接受的預防XSRF攻擊的方案是讓每一個用戶的cookie都是不肯定的值, 而且把那個cookie值在你站點的每一個form提交中做爲額外的參數包含進來. 若是cookie和form提交中的值不匹配, 則請求多是僞造的.
Tornado內置XSRF保護. 你須要在你的應用設置中使用xsrf_cookies
即可以在你的網站上使用:
settings = { "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", "login_url": "/login", "xsrf_cookies": True, } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
若是設置了xsrf_cookies
, Tornado web應用程序將會給全部用戶設置_xsrf
cookie而且拒絕全部不包含一個正確的_xsrf
值的POST
,PUT
, 或DELETE
請求. 若是你打開這個設置, 你必須給全部經過POST
請求的form提交添加這個字段. 你可使用一個特性的UIModule
`xsrf_form_html()
`來作這件事情, 這個方法在全部模板中都是可用的:
<form action="/new_message" method="post"> {% module xsrf_form_html() %} <input type="text" name="message"/> <input type="submit" value="Post"/> </form>
若是你提交一個AJAX的POST
請求, 你也須要在每一個請求中給你的JavaScript添加_xsrf
值. 這是咱們在FriendFeed爲了AJAX的POST
請求使用的一個 jQuery函數,能夠自動的給全部請求添加_xsrf
值:
function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } jQuery.postJSON = function(url, args, callback) { args._xsrf = getCookie("_xsrf"); $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", success: function(response) { callback(eval("(" + response + ")")); }}); };
對於PUT
和DELETE
請求(除了不使用form編碼(form-encoded) 參數的POST
請求, XSRF token也會經過一個X-XSRFToken
的HTTP頭傳遞.XSRF cookie 一般在使用xsrf_form_html
會設置, 可是在不使用正規form的純Javascript應用中, 你可能須要訪問self.xsrf_token
手動設置(只讀這個屬性足夠設置cookie了).
若是你須要自定義每個處理程序基礎的XSRF行爲, 你能夠複寫RequestHandler.check_xsrf_cookie()
. 例如, 若是你有一個沒有使用cookie驗證的API, 你可能想禁用XSRF保護, 能夠經過使check_xsrf_cookie()
不作任何處理. 然而, 若是你支持基於cookie和非基於cookie的認證, 重要的是,當前帶有cookie認證的請求究竟何時使用XSRF保護.