Tornado 文档学习:认证与安全

13 篇文章 0 订阅

转自:

Cookies 和安全 Cookies

你可以使用 set_cookie 方法设置用户浏览器的 cookies:

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

Cookies 不太安全,很容易被客户端修改。如果你需要将 cookie 设置为例如识别当前登录用户的信息的话,你需要给你的 cookies 签名防止伪造。Tornado 支持签名 cookies,你可以使用 set_secure_cookie 和get_secure_cookie 方法。为了使用这些方法,你需要在创建应用时指定 cookie_secret 密钥。你可以用关键字参数的方式进行设置:

 
 
  1. app = tornado.web.Application([
  2. (r"/", MainHandler),
  3. ], cookie_secret="__TODO:__GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

签名 cookie 除了包含编码过的 cookie 值,还有一个时间戳和一个 HMAC 签名。如果 cookie 过期或者签名匹配失败,get_secure_cookie 会直接返回 None 表示 cookie 并没有设定。安全的版本如下:

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

Tornado 的安全 cookie 可以保证完整性,但不能保护机密。也就是说,cookie 本身是不能被篡改的,但是会被用户看到。cookie_secret 是一个对称密钥,所以需要保护好——任何拥有这个密钥的人都可以生成符合签名的 cookies。

默认情况下,Tornado 安全 cookies 30 天后过期。可以在调用 set_secure_cookie 时传递 expire_days 参数或者在调用 get_secure_cookie 时传递 max_age_days 参数修改。采用这两种分开的设置方式,在大多数情况下,更长的时间符合需求,但对于某些特殊场景(如更改账户信息),你可以使用一个较短的 max_age_days来读取 cookies。

Tornado 也支持多个签名密钥,从而实现密钥轮转。cookie_secret 就必须是一个字典,其键是一个表示版本的整数,值则是相应的密钥。当前使用的签名密钥必须要设定为 key_version,而其它在字典中的密钥都可以用来签名验证,前提是在 cookie 中设定了正确的密钥版本。为了实现 cookies 更新,可以使用get_secure_cookie_key_version 获取当前签名密钥版本。

用户认证

当前认证用户可以在请求 handler 中使用 self.current_user 访问,在模板中用 current_user 访问。默认情况下current_user 是 None

为了实现用户认证,你需要在请求 handler 中重写 get_current_handler() 方法,从而基于 cookie 值验证当前用户身份。下面是用户使用指定的用户名登录,然后记录到 cookie 中的示例:

 
 
  1. class BaseHandler(tornado.web.RequestHandler):
  2. def get_current_user(self):
  3. return self.get_secure_cookie("user")
  4. class MainHandler(BaseHandler):
  5. def get(self):
  6. if not self.current_user:
  7. return self.redirect("/login")
  8. return
  9. name = tornado.escape.xhtml_escape(self.current_user)
  10. self.write("Hello, " + name)
  11. class LoginHandler(BaseHandler):
  12. def get(self):
  13. self.write('<html><body><form action="/login" method="post">'
  14. 'Name: <input type="text" name="name">'
  15. '<input type="submit" value="Sign in">'
  16. '</form></body></html>')
  17. def post(self):
  18. self.set_secure_cookie("user", self.get_argument("username"))
  19. self.redirect("/")
  20. application = tornado.web.Application([
  21. (r"/", MainHandler),
  22. (r"/login", LoginHandler),
  23. ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

你可以使用 tornado.web.authenticated 装饰器要求用户登录。如果一个请求对应的方法使用了该装饰器,当用户没有登录时,会自动定向到 login_url(另外一个用户设置)。示例如下:

 
 
  1. class MainHandler(BaseHandler):
  2. @tornado.web.authenticated
  3. def get(self):
  4. name = tornado.escape.xhtml_escape(self.current_user)
  5. self.write("Hello, " + name)
  6. settings = {
  7. "cookie_secret": "_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
  8. "login_url": "/login"
  9. }
  10. application = tornado.web.Application([
  11. (r"/", MainHandler),
  12. (r"/login", LoginHandler)
  13. ], **settings)

如果在 post() 方法上使用了 authenticated 装饰器,并且用户并未登录,则服务器会发送一个 403 响应。@authenticated 装饰器只是 if not self.current_user: self.redirect() 简写,并且对不是基于浏览器登录的方案可能不适合。

详细查看 Tornado 博客应用,该项目展示了用户认证(并将用户数据存储在 MySQL 数据库)。

第三方认证

tornado.auth 模块实现了一些列非常流行的网站的认证和授权协议,包括:Google/Gmail, Facebook, Twitter 和 FriendFeed。该模块包含了可以通过这些网站登录用户的方法,以及可以授权用户访问一些特权服务,如下载用户地址簿或者发布 Twitter 消息。

以下是一个使用 Google 认证的示例 handler,它将 Google 身份信息存储在 cookie 中用于后期访问:

 
 
  1. class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleOAuth2Mixin):
  2. @tornado.gen.coroutine
  3. def get(self):
  4. if self.get_argument("code", False):
  5. user = yield self.get_authenticated_user(redirect_uri="http://your.site.com/auth/google",
  6. code=self.get_argument("code"))
  7. # Save the user with `set_secure_cookie`
  8. else:
  9. yield self.authorize_redirect(
  10. redirect_uri="http://your.site.com/auth/google",
  11. client_id=self.settings['google_oauth']['key'],
  12. scope=['profile', 'email'],
  13. response_type='code',
  14. extra_params={'approval_prompt': 'auto'}
  15. )

详情参见 tornado.auth 模块文档。

CSRF 保护

跨站请求伪造(Cross-site request forgery, CSRF)对于个性化网站是一个常见的问题。详情查看Wikipedia 的介绍。

通常采用的预防措施是在用户 cookie 中存放一个不可预测的值,并且在每次提交时都将那个值作为额外的参数传递。如果 cookie 和表单中的值不一致,则请求有可能是伪造的。

Tornado 内建了 XSRF 保护。只需要在设置中启用 xsrf_cookies 即可:

 
 
  1. settings = {
  2. "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
  3. "login_url": "/login",
  4. "xsrf_cookies": True,
  5. }
  6. application = tornado.web.Application([
  7. (r"/", MainHandler),
  8. (r"/login", LoginHandler),
  9. ], **settings)

如果启用了 xsrf_cookies,Tornado web 应用会给所有用户设置 _xsrf cookie,并会拒绝不含正确 _xsrf 值的POSTPUT 和 DELETE 请求。此外,需要给所有通过 POST 提交的表单 HTML 中添加 XSRF 相关元素。你可以使用 UIModule 的 xsrf_form_html() 模块,它在所有的模板中都可以使用:

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

如果你使用 AJAX 提交,也需要 JavaScript 在每次请求中包含 _xsrf 值。下面是在 FriendFeed 使用 AJAXPOST 请求时自动添加 _xsrf 值的示例:

 
 
  1. function getCookie(name) {
  2. var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
  3. return r ? r[1] : undefined
  4. }
  5. jQuery.postJSON = function(url, args, callback) {
  6. args._xsrf = getCookie("_xsrf");
  7. $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", success: function(response) {
  8. callback(eval("(" + response + ")"));
  9. }});
  10. };

对于 PUT 和 DELETE 请求(或者不使用编码参数的表单的 POST 请求),可以通过 HTTP 头部 X-XSRFToken传递 XSRF token。通常当 xsrf_form_html 使用时,XSRF cookie 会被设置,但是在纯 JavaScript 应用中,你可能需要手动访问 self.xsrf_token(只需要读取该属性就可以设置 cookie 了)。

如果需要自定义 XSRF 行为,你可以重写 RequestHandler.check_xsrf_cookie()。例如,如果你的 API 不使用 cookies 进行认证,你可能会让 check_xsrf_cookie() 什么都不做从而关闭 XSRF 保护。然而,如果你需要同时支持 cookie 和非 cookie 认证方式,非常重要的一点是无论当前请求是否含有 cookie,都应当启用 XSRF 保护。

参考

版权声明

  1. 本文由 Christopher L 发表,采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。请确保你已了解许可协议,并在 转载 时声明。
  2. 本文固定链接:http://blog.chriscabin.com/?p=1595.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值