tornado系列:用cookie進行使用者驗證
在本節中,我們將建立一個應用,詢問訪客的名字,然後將其儲存在安全cookie中,以便之後取出。後續的請求將認出回客,並展示給她一個定製的頁面。你將學到login_url引數和tornado.web.authenticated裝飾器的相關知識,這將消除在類似應用中經常會涉及到的一些頭疼的問題。
1 示例:歡迎回來¶
在這個例子中,我們將只通過儲存在安全cookie裡的使用者名稱標識一個人。當某人首次在某個瀏覽器(或cookie過期後)訪問我們的頁面時,我們展示一個登入表單頁面。表單作為到LoginHandler路由的POST請求被提交。post方法的主體呼叫set_secure_cookie()
程式碼清單6-2中的Tornado應用展示了我們本節要討論的驗證函式。LoginHandler類渲染登入表單並設定cookie,而LogoutHandler類刪除cookie。
程式碼清單6-2 驗證訪客:cookies.pyimport 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<html> <head> <title>Please Log In</title> </head> <body> <form action="/login" method="POST"> {% raw xsrf_form_html() %} Username: <input type="text" name="username" /> <input type="submit" value="Log In" /> </form> </body> </html>
<html> <head> <title>Welcome Back!</title> </head> <body> <h1>Welcome back, {{ user }}</h1> </body> </html>
2 authenticated裝飾器¶
為了使用Tornado的認證功能,我們需要對登入使用者標記具體的處理函式。我們可以使用@tornado.web.authenticated裝飾器完成它。當我們使用這個裝飾器包裹一個處理方法時,Tornado將確保這個方法的主體只有在合法的使用者被發現時才會呼叫。讓我們看看例子中的WelcomeHandler吧,這個類只對已登入使用者渲染index.html模板。
class WelcomeHandler(BaseHandler): @tornado.web.authenticated def get(self): self.render('index.html', user=self.current_user)
在get方法被呼叫之前,authenticated裝飾器確保current_usr屬性有值。(我們將簡短的討論這個屬性。)如果current_user值為假(None、False、0、""),任何GET或HEAD請求都將把訪客重定向到應用設定中login_url指定的URL。此外,非法使用者的POST請求將返回一個帶有403(Forbidden)狀態的HTTP響應。
如果發現了一個合法的使用者,Tornado將如期呼叫處理方法。為了實現完整功能,authenticated裝飾器依賴於current_user屬性和login_url設定,我們將在下面看到具體講解。
2.1 current_user屬性¶
請求處理類有一個current_user屬性(同樣也在處理程式渲染的任何模板中可用)可以用來儲存為當前請求進行使用者驗證的標識。其預設值為None。為了authenticated裝飾器能夠成功標識一個已認證使用者,你必須覆寫請求處理程式中預設的get_current_user()方法來返回當前使用者。
實際的實現由你決定,不過在這個例子中,我們只是從安全cookie中取出訪客的姓名。很明顯,你希望使用一個更加魯棒的技術,但是出於演示的目的,我們將使用下面的方法:
class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("username")
儘管這裡討論的例子並沒有在儲存和取出使用者密碼或其他憑證上有所深入,但本章中討論的技術可以以最小的額外努力來擴充套件到查詢資料庫中的認證。
2.2 login_url設定¶
讓我們簡單看看應用的建構函式。記住這裡我們傳遞了一個新的設定給應用:login_url是應用登入表單的地址。如果get_current_user方法返回了一個假值,帶有authenticated裝飾器的處理程式將重定向瀏覽器的URL以便登入。
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', '/'))這樣的行來重定向登入後用戶回到的頁面。