導航
用戶指南
認證與安全
cookies與安全cookies
你可以使用set_cookie
方法在用戶的瀏覽器中設置cookie:
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支持使用set_secure_cookie
和get_secure_cookie
方法簽名的cookie。 要使用這些方法,您需要在創建應用程序時指定名爲cookie_secret
的密鑰。您可以將設置作爲關鍵字參數傳遞給應用程序:
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
除了時間戳和HMAC簽名之外,簽名cookie還包含cookie的編碼值。如果cookie是舊的或簽名不匹配,get_secure_cookie
將返回None,就像沒有設置cookie一樣。 以上示例的安全版本:
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雖然不能被修改,但客戶端可以看到裏面的內容。 cookie_secret是一個對稱密鑰,必須保密——任何獲得此密鑰值的人都可以生成自己的簽名cookie。
默認情況下,Tornado的安全cookie將在30天后過期。要更改此設置,請使用set_secure_cookie
的expires_days
關鍵字參數和get_secure_cookie
的max_age_days
參數。 這兩個值是分開傳遞的,這樣設計的原因是,比如你可以對於大多數用途,設置有一個有效期爲30天的cookie,但對於某些敏感操作(例如更改帳單信息),在讀取cookie時使用較小的max_age_days
。
Tornado還支持多個簽名密鑰以啓用簽名密鑰輪換。cookie_secret
必須是一個以一個整數類型的版本號作爲鍵值,並以相應的密鑰作爲值的字典。需要注意的是,只能使用應用中通過key_version
設置的那個版本的密鑰進行簽名,但可以使用字典中所有其他密鑰進行cookie簽名的驗證。 要實現cookie更新,可以通過get_secure_cookie_key_version
查詢當前的簽名密鑰版本。
用戶認證
當前經過身份驗證的用戶可以在每個請求處理程序中的self.current_user
獲取到,在每個模板中則從current_user
中獲取。 默認情況下,current_user
爲None
。
要在應用程序中實現用戶身份驗證,你需要重寫請求處理程序中的get_current_user()
方法,以根據cookie的值確定當前用戶。這是一個只需要驗證cookie中保存的用戶暱稱就可以登錄應用的示例:
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裝飾器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
裝飾器只是簡寫,如果不是self.current_user:self.redirect()
,可能不適合非基於瀏覽器的登錄方案。
查看Tornado Blog示例應用程序,獲取使用身份驗證的完整示例(並將用戶數據存儲在MySQL數據庫中)。
第三方認證
tornado.auth
模塊爲網絡上許多最受歡迎的網站實施身份驗證和授權協議,包括Google / Gmail,Facebook,Twitter和FriendFeed。該模塊包括通過這些站點記錄用戶的方法,以及在適用的情況下授權訪問服務的方法,以便你下載用戶的地址簿或代表他們發佈Twitter消息。
以下是使用Google進行身份驗證的示例處理程序,將Google憑據保存在Cookie中以供日後訪問:
class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
tornado.auth.GoogleOAuth2Mixin):
async def get(self):
if self.get_argument('code', False):
user = await 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:
await 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
模塊文檔。
跨站點請求僞造(CSRF)保護
跨站點請求僞造(XSRF)是個人Web應用程序的常見問題。有關XSRF如何工作的更多信息,請參閱Wikipedia文章。
普遍接受的防止XSRF的解決方案是爲每個用戶提供不可預測的值,並將該值作爲附加參數包含在網站上的每個表單提交中。 如果cookie和表單提交中的值不匹配,則該請求可能是僞造的。
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提交的所有表單都要包含對應字段。你可以使用所有模板中提供的特殊UIModulexsrf_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
請求(以及不使用表單編碼參數的POST
請求),XSRF令牌也可以通過名爲X-XSRFToken
的HTTP頭傳遞。 XSRF cookie通常在使用xsrf_form_html
時設置,但在不使用任何常規表單的純Javascript應用程序中,你可能需要手動獲取self.xsrf_token
(只需讀取屬性就足以將cookie設置爲函數副作用)。
如果需要基於每個處理程序自定義XSRF行爲,則可以重寫RequestHandler.check_xsrf_cookie()
。 例如,如果你的API的身份驗證不使用cookie,你可能希望通過使用check_xsrf_cookie()
不執行任何操作來禁用XSRF保護。但是,如果同時支持cookie和非基於cookie的身份驗證,則必須在使用cookie對當前請求進行身份驗證時使用XSRF保護。
DNS重新綁定攻擊(DNS Rebinding)
DNS重新綁定是一種可以繞過同源策略並允許外部站點訪問專用網絡上的資源的攻擊。 此攻擊包含一個TTL值特別小的DNS名稱,該名稱在返回由攻擊者控制的IP地址和受害者控制的IP地址之間交替(通常是可猜測的私有IP地址,例如127.0.0.1或192.168.1.1)
使用TLS的應用程序不容易受到此攻擊(因爲瀏覽器將顯示警告並阻止自動訪問,因爲被DNS被修改後訪問的站點與真實目標站點的證書不匹配)。
無法使用TLS並依賴網絡級訪問控制的應用程序(例如,假設127.0.0.1上的服務器只能由本地計算機訪問)應通過驗證Host HTTP標頭來防止DNS重新綁定。這意味着將限制主機名模式傳遞給HostMatches路由器或Application.add_handlers
的第一個參數:
# BAD: uses a default host pattern of r'.*'
app = Application([('/foo', FooHandler)])
# GOOD: only matches localhost or its ip address.
app = Application()
app.add_handlers(r'(localhost|127\.0\.0\.1)',
[('/foo', FooHandler)])
# GOOD: same as previous example using tornado.routing.
app = Application([
(HostMatches(r'(localhost|127\.0\.0\.1)'),
[('/foo', FooHandler)]),
])
此外,Application
和DefaultHostMatches
路由器的default_host
參數不得在可能易受DNS重新綁定攻擊的應用程序中使用,因爲它與通配符主機模式具有類似的效果。
上一篇:模板與UI
下一篇:運行與部署