身份验证和安全¶
用户身份验证¶
当前认证的用户在每个请求处理程序中都可用,表示为 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 模块实现了 Web 上一些最受欢迎网站的身份验证和授权协议,包括 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)是个性化 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,并拒绝所有不包含正确 _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()。例如,如果您有一个 API 的身份验证不使用 Cookie,您可能希望通过使 check_xsrf_cookie() 不执行任何操作来禁用 XSRF 保护。但是,如果您同时支持基于 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)]),
    ])
此外,Application 的 default_host 参数和 DefaultHostMatches 路由器不得在可能容易受到 DNS 重绑定攻击的应用程序中使用,因为它们的作用类似于通配符主机模式。