tornado 05登录和安全cookie - nuanxin1111/react GitHub Wiki

我们已经实现了登录的功能, 但是由于 Web 应用程序是使用 HTTP 协议传输数据的。 HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户 A 购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户 A 的会话还是用户 B 的会话了。要跟踪该会话,必须引入一种机制。

5.1 防止请求伪造

任何 Web 应用所面临的一个主要安全漏洞是跨站请求伪造, 通常被简写为 CSRF 或 XSRF, 这个漏洞利用了浏览器的一个允许恶意攻击者在受害者网站注入脚本使未授权请求代表一个已登录用户的安全漏洞。

settings = {
"xsrf_cookies": True
}

开启 XSRF 保护。 于此同时,我们的 ajax 请求也就无法通过验证到达我们的后台, 我们需要在 html 中,引入 {% module xsrf_form_html() %} 这个模块会自动在浏览器中去设置 key 为_csrf 的 cookie,我们需要用 js 去提取,然后随着ajax 请求发送到后台。这些东西我们在上节已经简单的提到过,现在再重复一下我们编写一个模块的大致流程,首先我们去urls编写路由,

urls = [ 

    (r"/register", index.RegisterHandler),
    (r"/login", index.LoginHandler),
]

贴上登录页面部分的代码:

var _xsrf = getCookie('_xsrf');
$.ajax({
type: "POST",
url:"/login",
data: {
username:username,
passwd:passwd,
_xsrf:_xsrf,
},

接着index.py(LoginHandler部分)

class LoginHandler(BaseHandler):
    def get(self):
        self.render("login.html")
        
    def post(self):
    '''处理登录请求
    '''
        username = self.get_argument('username')
        passwd = self.get_argument('passwd')
        db = self.application.mydb
        a = db.users.find_one({'name':username,                                'passwd':passwd})
        if a is not None:
            del a['_id']
            self.set_cookie('username', a['name'])
            self.redirect(“/”)
        else:
            context = {'status':'error'}
            self.write(json.dumps(context))

我们把用户名存储在 cookie 中,然后在”/”对应的 indexhandler 中,

import tornado.web
import sys
from base import BaseHandler

class IndexHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        self.render("index.html")

我们继承了一个 baseHandler,然后用了一个 web.authenticated 装饰器,为了使用 Tornado的认证功能,我们需要对登录用户标记具体的处理函数。我们可以使用@tornado.web.authenticated 装饰器完成它。当我们使用这个装饰器包裹一个处理方法时, Tornado 将确保这个方法的主体只有在合法的用户被发现时才会调用。 在 get 方法被调用之前,authenticated 装饰器确保 current_user 属性有值。(我们将简短的讨论这个属性。)如果 current_user 值为假(None、False、0、""),任何 GET或 HEAD请求都将把访客重定向到应用设置中 login_url 指定的 URL。此外,非法用户的POST请求将返回一个带有 403(Forbidden)状态的 HTTP 响应。如果发现了一个合法的用户,Tornado 将如期调用处理方法。为了实现完整功能,authenticated 装饰器依赖于 current_user 属性和 login_url 设置

5.2 current_user

请求处理类有一个 current_user属性(同样也在处理程序渲染的任何模板中可用)可以用来存储为当前请求进行用户验证的标识。其默认值为 None。为了 authenticated 装饰器能够成功标识一个已认证用户,你必须覆写请求处理程序中默认的get_current_user()方法来返回当前用户。

实际的实现由你决定,不过这个例子中

Handler/base.py
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_cookie("username")

然后我们用需要验证才能使用的 handler 继承这个 BaseHandler. 然后我们去设置 settings.记住这里我们传递了一个新的设置给应用:login_url是应用登录表单的地址。如果 get_current_user 方法返回了一个假值,带有authenticated装饰器的处理程序将重定向浏览器的 URL 以便登录。

settings = {
"login_url": "/login"
}

当 Tornado 构建重定向 URL 时,它还会给查询字符串添加一个 next 参数,其中包含了发起重定向到登录页面的 URL 资源地址。你可以使用像self.redirect(self.get_argument('next', '/'))这样的行来重定向登录后用户回到的页面。

5.3 更加安全

到目前为止我们使用的都是 cookie,接下来我们使用 tornado 自带的安全 cookie. Tornado 的安全 cookies 使用加密签名来验证 cookies 的值没有被服务器软件以外的任何人修改过。因为一个恶意脚本并不知道安全密钥,所以它不能在应用不知情时修改 cookies。 使用安全 cookie: Application.py

settings = {
#用于 secure_cookie
"cookie_secret":"y6KHYgM1SbCtH8axxEgt1omSARJwmkaMoBlezBg0EEE=",
"template_path":os.path.join(os.path.dirname(__file__), "templates"),
"static_path":os.path.join(os.path.dirname(__file__), "static"),
"login_url":"/login",
"xsrf_cookies":True,
"debug":True,
}

传递给 Application 构造函数的 cookie_secret 值应该 是唯一的随机字符串。在 Python shell 下执行下面的代码 片段将产生一个你自己的值:

>>> import base64, uuid
>>> base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
'bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E='

在 handler 里面把 set_cookie 和 get_cookie 改成 set_secure_cookie 和 get_secure_cookie 就行了,其他方面不需要变动。我们可以在之前的 js 中打印 cookie,观察两者的变化.