身份验证和安全

您可以使用 set_cookie 方法在用户浏览器中设置 cookie。

1
2
3
4
5
6
7
class MainHandler(tornado.web.RequestHandler):
def get(self):
if not self.get_cookie("mycookie"):
self.set_cookie("mycookie", "myvalue")
self.write("Set your cookie success.")
else:
self.write("Your cookie is set.")

Cookie 并不安全,并且很容易被客户修改。如果您需要设置 cookie 来识别当前登录的用户,则需要对 cookie 进行签名以防止伪造。 Tornado 支持使用 set_secure_cookieget_secure_cookie 方法签名 cookie。要使用这些方法,您需要在创建应用程序时指定名为 cookie_secure 的安全密钥。您可以将应用程序设置作为关键字参数传递给您的应用程序:

1
2
3
4
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secure="ASDASDASDASDDSDADA")

签名 cookie 除了时间戳和 HMAC 签名之外还包含 cookie 的编码值。如果 cookie 是旧的或者签名不匹配,get_secure_cookie 将返回 None,就像未设置 cookie 一样。上例的安全版本:

1
2
3
4
5
6
7
class MainHandler(tornado.web.RequestHandler):
def get(self):
if not self.get_secure_cookie("mycookie"):
self.set_secure_cookie("mycookie", "myvalue")
self.wirte("Your cookie was not set yet.")
else:
self.write("Your cookie was set.")

Tornado 的安全 cookie 保证完整性,但不保证机密性。也就是说,cookie 不能被修改,但其内容可以被用户看到。 cookie_secret 是对称密钥,必须保证安全。

默认情况下,Tornado 的安全 cookie 会在 30 天后过期。要更改此设置,请使用 set_secure_cookieexpires_days 关键字参数和 get_secure_cookiemax_age_days 参数。对于某些敏感操作(例如更改账单信息),您在读取 ​​cookie 时使用较小的 max_age_days

用户认证

当前经过身份验证的用户在每个请求处理程序中都可以使用 self.current_user 形式,在每个模板中可以使用 current_user 形式。默认情况下,该值为“无”。

要在应用程序中实现用户身份验证,您需要重写请求处理程序中的 get_current_user() 方法,以根据 cookie 的值等确定当前用户。下面是一个示例,用户只需指定昵称即可登录应用程序,然后将昵称保存在 cookie 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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, %s" % 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.redorect("/")

application = tornado.web.Application([
(r'/', MainHandler),
(r'/login', LoginHandler),
], cookie_secret="ASDASDAdasdasadawd")

您可以要求用户使用 Python 装饰器 tornado.web.authenticated** 登录。如果结果转到带有此装饰器的方法,并且用户未登录,他们将被重定向到 login_url。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MainHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
name = tornado.escape.xhtml_escape(self.current_user)
self.write("Hello %s" % name)

settings = {
"cookie_secret":"ASDASDASDASDASDAS",
"login_url":"/login"
}

application = tornado.web.Application([
(r'/', MainHandler),
(r'/login', LoginHandler),
], **settings)

如果您使用经过身份验证的装饰器装饰 post() 方法并且用户未登录,则服务器将发送 403 响应。

第三方认证

ornado.auth 模块为网络上许多最受欢迎的站点实现身份验证和授权协议,该协议名为 OAuth2 Login。这是示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_url = "http://yourweb.com",
code = self.get_argument("code")
)
else:
await self.authorize_redirect(
redirect_url = "http://youweb.com",
client_id = self.settings["google_oauth"]["key"],
scope = ["profile", "email"],
response_type = 'code',
extra_params = {"k":"v"}
)

跨站请求伪造(CSRF/XSRF)防护

CSRF(或 XSRF)是个性化 Web 应用程序的常见问题。普遍接受的防止 CSRF 的解决方案是使用不可预测的值对每个用户进行 cookie 并在表单提交中包含该值,如果不匹配,则该请求很可能是伪造的。

Tornado 具有内置 XSRF 保护。要将其包含在您的网站中,请包含应用程序设置 xsrf_cookies

1
2
3
4
5
6
7
8
9
10
settings = {
"cookie_secret":"ADADADASDASDASDA",
"login_url":"./login",
"xsrf_cookie":True
}

application = tornado.web.Application([
(r'/', MainHandler),
(r'/login', LoginHandler)
], **settings)

如果设置了 xsrf_cookie,Tornado Web 应用程序将为所有用户设置 _xsrf cookie,并拒绝所有不包含正确 _xsrf 值的 POST、PUT 和 DELETE 请求。如果打开此设置,则需要检测通过 POST 提交的所有表单以包含此字段。您可以使用所有模板中可用的特殊 UIModule xsrf_form_html() 来执行此操作:

1
2
3
4
5
<form action="/new_message" method="post">
{% module sxrf_form_html() %}
<input type="text" name="message"/>
<input type="submit" value="post"/>
</form>

如果您使用 AJAX 提交,您还需要检测 Javascript,以便在每个请求中包含 _xsrf 值。这是我们在 FriendFeed 中用于 AJAX POST 请求的 jQuery 函数,它会自动将 _xsrf 值添加到所有请求:

1
2
3
4
5
6
7
8
9
10
11
12
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}

jQuery.postJSON = function(url, args, callback) {
args._xrsf = getCookie("_xsrf");
$.ajax({url:url, data: $.params(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 设置为副作用)。

DNS 重新绑定

通过