您可以使用?set_cookie
?方法在用戶瀏覽器中設(shè)置 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 不安全,很容易被修改。 如果您需要設(shè)置 cookie,例如,識別當(dāng)前登錄的用戶,您需要簽署您的 cookie 以防止偽造。 Tornado 支持使用 set_secure_cookie 和 get_secure_cookie 方法簽名的 cookie。 要使用這些方法,您需要在創(chuàng)建應(yīng)用程序時指定一個名為 ?cookie_secret
? 的密鑰。 您可以將應(yīng)用程序設(shè)置作為關(guān)鍵字參數(shù)傳遞給您的應(yīng)用程序:
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
除了時間戳和 HMAC 簽名之外,簽名的 cookie 還包含 cookie 的編碼值。 如果 cookie 是舊的或者簽名不匹配,?get_secure_cookie
? 將返回 ?None
? ,就好像 cookie 沒有設(shè)置一樣。 上面示例的安全版本:
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 保證完整性,但不保證機(jī)密性。 也就是說,cookie 無法修改,但用戶可以看到其內(nèi)容。 ?cookie_secret
? 是一個對稱密鑰,必須保密——任何獲得此密鑰值的人都可以生成自己的簽名 cookie。
默認(rèn)情況下,Tornado 的安全 cookie 會在 30 天后過期。 要更改此設(shè)置,請使用 ?set_secure_cookie
? 的 ?expires_days
? 關(guān)鍵字參數(shù)和 ?get_secure_cookie
? 的 ?max_age_days
? 參數(shù)。 這兩個值分別傳遞,以便您可以例如 對于大多數(shù)用途,cookie 的有效期為 30 天,但對于某些敏感操作(例如更改帳單信息),您在讀取 cookie 時使用較小的 ?max_age_days
?。
Tornado 還支持多個簽名密鑰以啟用簽名密鑰輪換。 那么 ?cookie_secret
? 必須是一個字典,其中整數(shù)密鑰版本作為鍵,相應(yīng)的秘密作為值。 然后必須將當(dāng)前使用的簽名密鑰設(shè)置為 ?key_version
? 應(yīng)用程序設(shè)置,但如果在 cookie 中設(shè)置了正確的密鑰版本,則允許 dict 中的所有其他密鑰進(jìn)行 cookie 簽名驗證。 要實現(xiàn) cookie 更新,可以通過 get_secure_cookie_key_version 查詢當(dāng)前的簽名密鑰版本。
當(dāng)前經(jīng)過身份驗證的用戶在每個請求處理程序中作為 self.current_user 可用,在每個模板中作為 ?current_user
? 可用。 默認(rèn)情況下,?current_user
? 為?None
?。
要在您的應(yīng)用程序中實現(xiàn)用戶身份驗證,您需要覆蓋請求處理程序中的 ?get_current_user()
方法,以根據(jù)例如 cookie 的值來確定當(dāng)前用戶。 下面是一個示例,用戶只需指定昵稱即可登錄應(yīng)用程序,然后將昵稱保存在 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 decorator tornado.web.authenticated 登錄。 如果一個請求使用這個裝飾器發(fā)送到一個方法,并且用戶沒有登錄,他們將被重定向到 ?login_url
?(另一個應(yīng)用程序設(shè)置)。 上面的例子可以重寫:
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()
? 方法,并且用戶沒有登錄,服務(wù)器將發(fā)送一個 ?403
? 響應(yīng)。 ?@authenticated
? 裝飾器只是 ?if not self.current_user: self.redirect()
? 的簡寫,可能不適用于非基于瀏覽器的登錄方案。
tornado.auth 模塊為網(wǎng)絡(luò)上許多最流行的站點實現(xiàn)身份驗證和授權(quán)協(xié)議,包括 Google/Gmail、Facebook、Twitter 和 FriendFeed。 該模塊包括通過這些站點登錄用戶的方法,以及在適用的情況下授權(quán)訪問服務(wù)的方法,以便您可以下載用戶的通訊錄或代表他們發(fā)布 Twitter 消息。
這是一個使用 Google 進(jìn)行身份驗證的示例處理程序,將 Google 憑據(jù)保存在 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'})
防止 XSRF 的普遍接受的解決方案是使用不可預(yù)測的值對每個用戶進(jìn)行 cookie,并將該值作為附加參數(shù)包含在您網(wǎng)站上的每個表單提交中。 如果 cookie 和表單提交中的值不匹配,那么請求很可能是偽造的。
Tornado 帶有內(nèi)置的 XSRF 保護(hù)。 要將其包含在您的站點中,請包含應(yīng)用程序設(shè)置 ?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)
如果設(shè)置了 ?xsrf_cookies
?,Tornado Web 應(yīng)用程序?qū)樗杏脩粼O(shè)置 ?_xsrf
? cookie,并拒絕所有不包含正確 ?_xsrf
? 值的 ?POST
?、?PUT
? 和 ?DELETE
? 請求。 如果打開此設(shè)置,則需要檢測通過 ?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 函數(shù),它會自動將 ?_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
?請求(以及不使用表單編碼參數(shù)的 ?POST
?請求),XSRF 令牌也可以通過名為 ?X-XSRFToken
? 的 HTTP 標(biāo)頭傳遞。 XSRF cookie 通常在使用 ?xsrf_form_html
? 時設(shè)置,但在不使用任何常規(guī)表單的純 JavaScript 應(yīng)用程序中,您可能需要手動訪問 ?self.xsrf_token
?(只需讀取屬性就足以將 cookie 設(shè)置為副作用) .
如果您需要基于每個處理程序自定義 XSRF 行為,您可以覆蓋 RequestHandler.check_xsrf_cookie()。 例如,如果您的 API 的身份驗證不使用 cookie,您可能希望通過使 ?check_xsrf_cookie()
? 什么都不做來禁用 XSRF 保護(hù)。 但是,如果您同時支持 cookie 和非基于 cookie 的身份驗證,那么無論何時使用 cookie 對當(dāng)前請求進(jìn)行身份驗證,都必須使用 XSRF 保護(hù)。
DNS 重新綁定是一種可以繞過同源策略并允許外部站點訪問專用網(wǎng)絡(luò)上的資源的攻擊。 這種攻擊涉及一個 DNS 名稱(具有短 TTL),該名稱在返回由攻擊者控制的 IP 地址和由受害者控制的 IP 地址(通常是可猜測的私有 IP 地址,例如 ?127.0.0.1
? 或 ?192.168.1.1
?)之間交替。
使用 TLS 的應(yīng)用程序不容易受到這種攻擊(因為瀏覽器會顯示阻止自動訪問目標(biāo)站點的證書不匹配警告)。
不能使用 TLS 并依賴網(wǎng)絡(luò)級訪問控制的應(yīng)用程序(例如,假設(shè) ?127.0.0.1
? 上的服務(wù)器只能由本地計算機(jī)訪問)應(yīng)通過驗證 ?Host
? HTTP 表頭來防止 DNS 重新綁定。 這意味著將限制性主機(jī)名模式傳遞給 HostMatches 路由器或 Application.add_handlers 的第一個參數(shù):
# 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
?參數(shù)不得用于可能易受 DNS 重新綁定攻擊的應(yīng)用程序中,因為它與通配符主機(jī)模式具有類似的效果。
更多建議: