Tornado入门(七)【认证和安全】
Cookies和安全Cookies
通过set_cookie
来设置浏览器中的cookies信息。
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!")
Cookies信息通常不安全,很容易被篡改。如果需要通过cookies来区分不同的登录用户,则需要对cookies进行签名,以防伪造。Tornado通过set_secure_cookie
和get_secure_cookie
方法支持签名Cookies。为了使用这两个方法,需要在应用中配置cookie_secret
。
application = tornado.web.Application([
(r"/", MainHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
签过名的cookies包含了编码之后的cookie值,时间戳,和一个HMAC签名。如果cookie过期或者签名不匹配,则get_secure_cookie
将返回None
。
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机制并不能保证绝对的安全,一是因为cookie值可以被用户看到,二是因为cookie_secret
是一个对称密钥,所以必须确保它的安全。
默认情况下,Tornado cookies的生存期为30天,如果需要修改,可以将参数expire_days
传递给set_secure_cookie
和 max_age_days
参数传递给get_secure_cookie
。这个两个参数是分别传递的。
用户认证
认证过的用户可以通过self.current_user
访问,在模板中可以通过current_user
访问,默认情况下current_user
为None
。
为了实现用户认证,需要重写处理器中的get_current_user
方法,以确定当前的用户。下面是一个简单的示例。
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__")
通过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)
第三方认证
tornado.auth
模块实现了第三方的认证协议,包括Google和Facebook等。
下面是使用Google的认证:
class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
tornado.auth.GoogleOAuth2Mixin):
@tornado.gen.coroutine
def get(self):
if self.get_argument('code', False):
user = yield 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:
yield 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的方法是传递一个不可预测的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应用会为所有的用户设置_xsrf
cookie,并拒绝所有没有携带正确的_xsrf
字段的POST
,PUT
或者DELETE
请求。注意需要确保所有提交的表单都包含了这个字段。可以使用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提交表单,同样需要确保提供了_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
, xsrf_form_html
可以通过头部字段X-XSRFToken
设置。
如果需要自定义XSRF
的行为,可以重写RequestHandler.check_xsrf_cookie
方法。