身份验证和安全性¶
用户身份验证¶
当前经过身份验证的用户在每个请求处理程序中都可用作 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 decorator 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)
如果你装饰 post()
方法 authenticated
如果用户没有登录,服务器将发送一个 403
反应。这个 @authenticated
装饰师只是 if not self.current_user: self.redirect()
并且可能不适用于非基于浏览器的登录方案。
退房 Tornado Blog example application 对于使用身份验证(并将用户数据存储在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_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
模块文档了解更多详细信息。
跨站点请求伪造保护¶
Cross-site request forgery
_,或XSRF,是个性化Web应用程序的常见问题。
防止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 Web应用程序将设置 _xsrf
所有用户的cookie并拒绝所有用户 POST
, PUT
和 DELETE
不包含正确的 _xsrf
价值。如果启用此设置,则需要检测通过 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
每个请求的值。这就是 jQuery 我们在FriendFeed for Ajax中使用的函数 POST
自动添加 _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
不使用表单编码参数的请求),也可以通过名为 X-XSRFToken
. XSRF cookie通常在以下情况下设置: xsrf_form_html
,但在不使用任何需要访问的常规表单的纯JavaScript应用程序中 self.xsrf_token
手动(仅读取属性就足以将cookie设置为副作用)。
如果需要基于每个处理程序自定义XSRF行为,可以重写 RequestHandler.check_xsrf_cookie()
. 例如,如果您有一个认证不使用cookie的API,您可能希望通过 check_xsrf_cookie()
什么也不做。但是,如果您同时支持基于cookie和非cookie的身份验证,那么在当前请求通过cookie进行身份验证时使用xsrf保护是很重要的。
DNS再绑定¶
DNS rebinding 是一种可以绕过同一源策略并允许外部站点访问专用网络上的资源的攻击。这种攻击涉及一个DNS名称(有一个短的TTL),它交替返回由攻击者控制的IP地址和由受害者控制的IP地址(通常是可猜测的专用IP地址,如 127.0.0.1
或 192.168.1.1
)
使用TLS的应用程序是 not 易受攻击(因为浏览器将显示阻止自动访问目标站点的证书不匹配警告)。
无法使用TLS并依赖网络级访问控制的应用程序(例如,假设服务器 127.0.0.1
只能由本地计算机访问)应通过验证 Host
HTTP报头。这意味着将限制性主机名模式传递给 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)]),
])
此外, default_host
参数 Application
以及 DefaultHostMatches
路由器不能用于可能易受DNS重新绑定攻击的应用程序,因为它具有与通配符主机模式类似的效果。