驗證與安全性¶
使用者驗證¶
目前已驗證的使用者可在每個請求處理器中以 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_signed_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_signed_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
裝飾器只是 if not self.current_user: self.redirect()
的簡寫,可能不適用於非基於瀏覽器的登入方案。
請查看 Tornado 部落格範例應用程式,以取得使用驗證(並將使用者資料儲存在 PostgreSQL 資料庫中)的完整範例。
第三方驗證¶
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_signed_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
模組文件,以了解更多詳細資訊。
跨網站請求偽造保護¶
跨網站請求偽造 (XSRF) 是個人化網頁應用程式的常見問題。
防止 XSRF 的普遍接受的解決方案是為每個使用者設定一個不可預測的值的 cookie,並將該值作為額外引數包含在您網站上的每個表單提交中。如果 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 網頁應用程式將為所有使用者設定 _xsrf
cookie,並拒絕所有不包含正確 _xsrf
值的 POST
、PUT
和 DELETE
請求。如果您開啟此設定,您需要對所有透過 POST
提交的表單進行檢測,使其包含此欄位。您可以使用所有樣板中可用的特殊 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
請求(以及不使用表單編碼引數的 POST
請求),XSRF 權杖也可以透過名為 X-XSRFToken
的 HTTP 標頭傳遞。XSRF cookie 通常在使用 xsrf_form_html
時設定,但在不使用任何常規表單的純 JavaScript 應用程式中,您可能需要手動存取 self.xsrf_token
(僅讀取屬性就足以設定 cookie 作為副作用)。
如果您需要在每個處理器的基礎上自訂 XSRF 行為,您可以覆寫 RequestHandler.check_xsrf_cookie()
。例如,如果您有一個不使用 cookie 進行驗證的 API,您可能想要停用 XSRF 保護,方法是讓 check_xsrf_cookie()
不執行任何操作。但是,如果您同時支援基於 cookie 和非基於 cookie 的驗證,則在使用 cookie 驗證目前請求時,務必使用 XSRF 保護。
DNS 重新繫結¶
DNS 重新繫結是一種可以繞過同源策略並允許外部網站存取私人網路資源的攻擊。此攻擊涉及一個 DNS 名稱(具有短的 TTL),它會在返回由攻擊者控制的 IP 位址和由受害者控制的 IP 位址(通常是可猜測的私人 IP 位址,例如 127.0.0.1
或 192.168.1.1
)之間交替。
使用 TLS 的應用程式不會受到此攻擊的影響(因為瀏覽器會顯示憑證不符警告,阻止自動存取目標網站)。
無法使用 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)]),
])
此外,可能容易受到 DNS 重綁定攻擊的應用程式不應使用 Application
的 default_host
參數和 DefaultHostMatches
路由器,因為它與使用萬用字元主機模式的效果類似。