Tornado 部署公域网络安全与防护(上)

摘要:很多开发者会将 Tornado 服务直接部署到公网,尤其是量化交易相关业务,如果缺少安全防护,外部攻击者可以随意访问接口,窃取交易数据、篡改业务逻辑。本文讲解 Tornado 中身份验证相关安全机制:签名 Cookie、用户登录认证、第三方 OAuth 登录的实现与避坑要点。

前言

有些小伙伴想把 Tornado 放到公域网络上,但有一个问题需要注意 ------网络安全。 如果没有做访问阻挡,其他人就有可能访问到你的 Tornado 服务,获取量化交易等核心业务内容。本篇就来讲解 Tornado 如何搭建基础安全壁垒,第一部分聚焦身份验证相关安全实践。

一、身份验证与安全

我们可以使用set_cookie方法直接在浏览器设置 Cookie。但普通 Cookie 本身并不安全,客户端可以随意修改 Cookie 内容。

如果业务场景需要依靠 Cookie 识别登录用户,必须使用签名 Cookie ,防止 Cookie 被伪造篡改。 Tornado 提供set_signed_cookieget_signed_cookie两个接口实现签名 Cookie。使用该功能,创建Application实例时必须配置密钥cookie_secret

签名 Cookie 内部包含编码后的值、时间戳以及 HMAC 签名。当 Cookie 过期或者签名校验失败时,get_signed_cookie会返回None,等同于 Cookie 不存在。

⚠️ 重要注意点

  1. Tornado 签名 Cookie只保证完整性,不保证保密性:Cookie 不能被篡改,但是浏览器端可以直接查看 Cookie 存储的原始内容,不要存放密码这类敏感明文。

  2. cookie_secret属于对称密钥,必须严格保密;密钥一旦泄露,攻击者就可以伪造合法签名 Cookie。

  3. 默认签名 Cookie 有效期为 30 天。

    • set_signed_cookie通过expires_days设置 Cookie 过期时间

    • get_signed_cookie通过max_age_days控制读取校验的最大存活时间

    两个参数相互独立。例如一般业务有效期 30 天;修改账单等高危接口读取 Cookie 时,设置更小的max_age_days,缩短校验有效期提升安全。

  4. 密钥轮换cookie_secret支持传入字典实现多套密钥。key 为密钥版本整数,value 为密钥字符串;配置key_version标记当前生效密钥,旧版本密钥仍然可以用于校验历史 Cookie。使用get_signed_cookie_key_version可以获取 Cookie 对应的密钥版本。

2. 用户身份验证

在 Handler 中通过self.current_user获取当前登录用户,模板中直接使用current_user,未登录默认返回None

实现登录认证需要重写get_current_user()方法,一般结合签名 Cookie 完成登录态判断。

示例代码:把登录昵称保存在签名 Cookie,未登录跳转登录页

bash 复制代码
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_signed_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_signed_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 提供@tornado.web.authenticated装饰器,简化登录判断逻辑。

当 GET 接口被该装饰器修饰,用户未登录时会自动重定向到login_url配置地址; 如果装饰post方法,未登录请求直接返回403状态码。
💡提示:authenticated只是语法糖,底层等价if not self.current_user的判断逻辑,不适合非浏览器接口登录场景

改写后代码:

bash 复制代码
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)

3. 第三方身份验证

tornado.auth内置主流第三方授权协议,支持 Google、Facebook 等账号登录,也可以获取第三方账号授权(读取通讯录、代发动态等)。

摘要:很多开发者会将 Tornado 服务直接部署到公网,尤其是量化交易相关业务,如果缺少安全防护,外部攻击者可以随意访问接口,窃取交易数据、篡改业务逻辑。本文讲解 Tornado 中身份验证相关安全机制:签名 Cookie、用户登录认证、第三方 OAuth 登录的实现与避坑要点。

前言

有些小伙伴想把 Tornado 放到公域网络上,但有一个问题需要注意 ------网络安全。 如果没有做访问阻挡,其他人就有可能访问到你的 Tornado 服务,获取量化交易等核心业务内容。本篇就来讲解 Tornado 如何搭建基础安全壁垒,第一部分聚焦身份验证相关安全实践。

一、身份验证与安全

我们可以使用set_cookie方法直接在浏览器设置 Cookie。但普通 Cookie 本身并不安全,客户端可以随意修改 Cookie 内容。

如果业务场景需要依靠 Cookie 识别登录用户,必须使用签名 Cookie ,防止 Cookie 被伪造篡改。 Tornado 提供set_signed_cookieget_signed_cookie两个接口实现签名 Cookie。使用该功能,创建Application实例时必须配置密钥cookie_secret

签名 Cookie 内部包含编码后的值、时间戳以及 HMAC 签名。当 Cookie 过期或者签名校验失败时,get_signed_cookie会返回None,等同于 Cookie 不存在。

⚠️ 重要注意点

  1. Tornado 签名 Cookie只保证完整性,不保证保密性:Cookie 不能被篡改,但是浏览器端可以直接查看 Cookie 存储的原始内容,不要存放密码这类敏感明文。

  2. cookie_secret属于对称密钥,必须严格保密;密钥一旦泄露,攻击者就可以伪造合法签名 Cookie。

  3. 默认签名 Cookie 有效期为 30 天。

    • set_signed_cookie通过expires_days设置 Cookie 过期时间

    • get_signed_cookie通过max_age_days控制读取校验的最大存活时间

    两个参数相互独立。例如一般业务有效期 30 天;修改账单等高危接口读取 Cookie 时,设置更小的max_age_days,缩短校验有效期提升安全。

  4. 密钥轮换cookie_secret支持传入字典实现多套密钥。key 为密钥版本整数,value 为密钥字符串;配置key_version标记当前生效密钥,旧版本密钥仍然可以用于校验历史 Cookie。使用get_signed_cookie_key_version可以获取 Cookie 对应的密钥版本。

2. 用户身份验证

在 Handler 中通过self.current_user获取当前登录用户,模板中直接使用current_user,未登录默认返回None

实现登录认证需要重写get_current_user()方法,一般结合签名 Cookie 完成登录态判断。

示例代码:把登录昵称保存在签名 Cookie,未登录跳转登录页

bash 复制代码
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_signed_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_signed_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 提供@tornado.web.authenticated装饰器,简化登录判断逻辑。

当 GET 接口被该装饰器修饰,用户未登录时会自动重定向到login_url配置地址; 如果装饰post方法,未登录请求直接返回403状态码。
💡提示:authenticated只是语法糖,底层等价if not self.current_user的判断逻辑,不适合非浏览器接口登录场景

改写后代码:

bash 复制代码
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)

3. 第三方身份验证

tornado.auth内置主流第三方授权协议,支持 Google、Facebook 等账号登录,也可以获取第三方账号授权(读取通讯录、代发动态等)。

下面是 Google OAuth2 登录示例,登录成功后将授权信息存入签名 Cookie:

bash 复制代码
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_uri='http://your.site.com/auth/google',
                code=self.get_argument('code'))
            # Save the user with e.g. set_signed_cookie
        else:
            await 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'})

注意:使用第三方登录时,client_idclient_secret等密钥需要存放在配置文件,禁止硬编码提交到代码仓库。

小结

本篇主要介绍公网部署 Tornado 的身份认证基础:签名 Cookie 机制、自定义登录态、authenticated装饰器、第三方 OAuth 登录。

  • 普通 Cookie 极易篡改,登录会话务必使用set_signed_cookie
  • 签名 Cookie 防篡改但不加密,不能存储敏感明文
  • cookie_secret密钥做好保管,支持密钥轮换平滑更新密钥
  • authenticated适合浏览器 web 页面,API 接口不要直接依赖该装饰器

下篇将会继续讲解 XSS 防护、CSRF 防御、请求限制、服务端口与反向代理等 Tornado 公网防护内容。

下面是 Google OAuth2 登录示例,登录成功后将授权信息存入签名 Cookie:

bash 复制代码
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_uri='http://your.site.com/auth/google',
                code=self.get_argument('code'))
            # Save the user with e.g. set_signed_cookie
        else:
            await 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'})

注意:使用第三方登录时,client_idclient_secret等密钥需要存放在配置文件,禁止硬编码提交到代码仓库。

小结

本篇主要介绍公网部署 Tornado 的身份认证基础:签名 Cookie 机制、自定义登录态、authenticated装饰器、第三方 OAuth 登录。

  • 普通 Cookie 极易篡改,登录会话务必使用set_signed_cookie
  • 签名 Cookie 防篡改但不加密,不能存储敏感明文
  • cookie_secret密钥做好保管,支持密钥轮换平滑更新密钥
  • authenticated适合浏览器 web 页面,API 接口不要直接依赖该装饰器

下篇将会继续讲解 XSS 防护、CSRF 防御、请求限制、服务端口与反向代理等 Tornado 公网防护内容。

投资有风险,入市需谨慎。

相关推荐
风跟我说过她1 小时前
SQL 一键转经典 Chen 风格 ER 图:开源 CLI + 在线工具 + Agent Skill
数据库·python·sql·开源·开源软件
Ulyanov2 小时前
AudioVision Pro:基于 PySide6 + sounddevice 的实时音频可视化播放器设计
python·算法·音视频
ID34610744202 小时前
【课程设计】基于Spring Boot+Vue的校园共享无人机服务系统设计与实现-计算机毕设 附源码44219
javascript·vue.js·spring boot·python·node.js·php·课程设计
星空2 小时前
pycharm复习
ide·python·pycharm
Maiko Star2 小时前
Python 包与项目管理工具——uv
python·uv
weixin199701080162 小时前
[特殊字符]《京东POP二手品类对接:B2C订单模型 vs 二手C2C属性的字段转换难题》(附Python源码)
前端·python·算法
承渊政道2 小时前
Python IDLE鸿蒙PC适配全记录:从Tkinter桌面程序到ArkUI原生开发闭环
开发语言·python·harmonyos·鸿蒙系统·桌面程序
人工干智能2 小时前
爱因斯坦求和约定(Einstein Summation Convention)及einsum() 函数
python
YMWM_2 小时前
查看realsense相机序列号脚本
开发语言·python·数码相机