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