Tornado 4.3文档翻译: 用户指南-认证和安全

译者说

Tornado 4.3于2015年11月6日发布,该版本正式支持Python3.5async/await关键字,而且用旧版本CPython编译Tornado一样可使用这两个关键字,这无疑是一种进步。其次,这是最后一个支持Python2.6Python3.2的版本了,在后续的版本了会移除对它们的兼容。如今网络上尚未Tornado4.3的中文文档,因此为了让更多的朋友能接触并学习到它,我开始了这个翻译项目,但愿感兴趣的小伙伴能够一块儿参与翻译,项目地址是tornado-zh on Github,翻译好的文档在Read the Docs上直接能够看到。欢迎Issues or PR。javascript

认证和安全

Cookies 和 secure cookies

你能够在用户浏览器中经过set_cookie方法设置 cookie:html

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!")

普通的cookie并不安全, 能够经过客户端修改. 若是你须要经过设置cookie,例如来识别当前登陆的用户, 就须要给你的cookie签名防止伪造. Tornado支持经过 RequestHandler.set_secure_cookieRequestHandler.get_secure_cookie 方法对cookie签名. 想要使用这些方法, 你须要在你建立应用的时候, 指定一个名为cookie_secret的密钥. 你能够在应用的设置中以关键字参数的形式传递给应用程序:java

application = tornado.web.Application([
        (r"/", MainHandler),
    ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

签名后的cookie除了时间戳和一个HMAC 签名还包含编码后的cookie值. 若是cookie过时或者签名不匹配,get_secure_cookie将返回None就像没有设置cookie同样. 上面例子的安全版本:python

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是一个对称的key, 并且必须保密--任何得到这个key的人均可以伪造出本身签名的cookie.jquery

默认状况下, Tornado的安全cookie过时时间是30天. 能够给set_secure_cookie使用expires_days关键字参数 同时get_secure_cookie设置max_age_days参数也能够达到效果. 这两个值分别经过这样(设置)你就能够达到以下的效果, 例如大多数状况下有30天有效期的cookie, 可是对某些敏感操做(例如修改帐单信息)你可使用一个较小的max_age_days.git

Tornado也支持多签名密钥, 使签名密钥轮换.cookie_secret而后必须是一个以整数key版本做为key, 以相对应的密钥做为值的字典. 当前使用的签名键必须是 应用设置中key_version的集合. 不过字典中的其余key都容许作cookie签名验证, 若是当前key版本在cookie集合中.为了实现cookie更新, 能够经过RequestHandler.get_secure_cookie_key_version 查询当前key版本.github

用户认证

当前已经经过认证的用户在每一个请求处理函数中均可以经过self.current_user 获得, 在每一个模板中可使用current_user得到. 默认状况下,current_userNone.web

为了在你的应用程序中实现用户认证, 你须要在你的请求处理函数中复写get_current_user()方法来判断当前用户, 好比能够基于cookie的值.这里有一个例子, 这个例子容许用户简单的经过一个保存在cookie中的特殊昵称登陆到应用程序中:ajax

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)

若是你使用authenticated装饰post()方法而且用户没有登陆,服务将返回一个403响应.@authenticated装饰器是if not self.current_user: self.redirect()的简写. 可能不适合非基于浏览器的登陆方案.

经过 Tornado Blog example application能够看到一个使用用户验证(而且在MySQL数据库中存储用户数据)的完整例子.

第三方用户验证

tornado.auth 模块实现了对一些网络上最流行的网站的身份认证和受权协议,包括Google/Gmail, Facebook, Twitter,和FriendFeed. 该模块包括经过这些网站登陆用户的方法, 并在适用状况下容许访问该网站服务的方法, 例如, 下载一个用户的地址簿或者在他们支持下发布一条Twitter信息.

这是个使用Google身份认证, 在cookie中保存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'})

查看 tornado.auth 模块的文档以了解更多细节.

跨站请求伪造(防御)

跨站请求伪造(Cross-site request forgery),或XSRF, 是全部web应用程序面临的一个主要问题. 能够经过Wikipedia 文章来了解更多关于XSRF的细节.

广泛接受的预防XSRF攻击的方案是让每一个用户的cookie都是不肯定的值, 而且把那个cookie值在你站点的每一个form提交中做为额外的参数包含进来. 若是cookie和form提交中的值不匹配, 则请求多是伪造的.

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应用程序将会给全部用户设置_xsrfcookie而且拒绝全部不包含一个正确的_xsrf值的POST,PUT, 或DELETE请求. 若是你打开这个设置, 你必须给全部经过POST请求的form提交添加这个字段. 你可使用一个特性的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 + ")"));
        }});
    };

对于PUTDELETE请求(除了不使用form编码(form-encoded) 参数的POST请求, XSRF token也会经过一个X-XSRFToken的HTTP头传递.XSRF cookie 一般在使用xsrf_form_html会设置, 可是在不使用正规form的纯Javascript应用中, 你可能须要访问self.xsrf_token手动设置(只读这个属性足够设置cookie了).

若是你须要自定义每个处理程序基础的XSRF行为, 你能够复写RequestHandler.check_xsrf_cookie(). 例如, 若是你有一个没有使用cookie验证的API, 你可能想禁用XSRF保护, 能够经过使check_xsrf_cookie()不作任何处理. 然而, 若是你支持基于cookie和非基于cookie的认证, 重要的是,当前带有cookie认证的请求究竟何时使用XSRF保护.

相关文章
相关标签/搜索