摘要:很多开发者会将 Tornado 服务直接部署到公网,尤其是量化交易相关业务,如果缺少安全防护,外部攻击者可以随意访问接口,窃取交易数据、篡改业务逻辑。本文讲解 Tornado 中身份验证相关安全机制:签名 Cookie、用户登录认证、第三方 OAuth 登录的实现与避坑要点。
前言
有些小伙伴想把 Tornado 放到公域网络上,但有一个问题需要注意 ------网络安全。 如果没有做访问阻挡,其他人就有可能访问到你的 Tornado 服务,获取量化交易等核心业务内容。本篇就来讲解 Tornado 如何搭建基础安全壁垒,第一部分聚焦身份验证相关安全实践。
一、身份验证与安全
1. Cookie 与签名 Cookie
我们可以使用set_cookie方法直接在浏览器设置 Cookie。但普通 Cookie 本身并不安全,客户端可以随意修改 Cookie 内容。
如果业务场景需要依靠 Cookie 识别登录用户,必须使用签名 Cookie ,防止 Cookie 被伪造篡改。 Tornado 提供set_signed_cookie、get_signed_cookie两个接口实现签名 Cookie。使用该功能,创建Application实例时必须配置密钥cookie_secret。
签名 Cookie 内部包含编码后的值、时间戳以及 HMAC 签名。当 Cookie 过期或者签名校验失败时,get_signed_cookie会返回None,等同于 Cookie 不存在。
⚠️ 重要注意点
-
Tornado 签名 Cookie只保证完整性,不保证保密性:Cookie 不能被篡改,但是浏览器端可以直接查看 Cookie 存储的原始内容,不要存放密码这类敏感明文。
-
cookie_secret属于对称密钥,必须严格保密;密钥一旦泄露,攻击者就可以伪造合法签名 Cookie。 -
默认签名 Cookie 有效期为 30 天。
-
set_signed_cookie通过expires_days设置 Cookie 过期时间 -
get_signed_cookie通过max_age_days控制读取校验的最大存活时间
两个参数相互独立。例如一般业务有效期 30 天;修改账单等高危接口读取 Cookie 时,设置更小的
max_age_days,缩短校验有效期提升安全。 -
-
密钥轮换 :
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 如何搭建基础安全壁垒,第一部分聚焦身份验证相关安全实践。
一、身份验证与安全
1. Cookie 与签名 Cookie
我们可以使用set_cookie方法直接在浏览器设置 Cookie。但普通 Cookie 本身并不安全,客户端可以随意修改 Cookie 内容。
如果业务场景需要依靠 Cookie 识别登录用户,必须使用签名 Cookie ,防止 Cookie 被伪造篡改。 Tornado 提供set_signed_cookie、get_signed_cookie两个接口实现签名 Cookie。使用该功能,创建Application实例时必须配置密钥cookie_secret。
签名 Cookie 内部包含编码后的值、时间戳以及 HMAC 签名。当 Cookie 过期或者签名校验失败时,get_signed_cookie会返回None,等同于 Cookie 不存在。
⚠️ 重要注意点
-
Tornado 签名 Cookie只保证完整性,不保证保密性:Cookie 不能被篡改,但是浏览器端可以直接查看 Cookie 存储的原始内容,不要存放密码这类敏感明文。
-
cookie_secret属于对称密钥,必须严格保密;密钥一旦泄露,攻击者就可以伪造合法签名 Cookie。 -
默认签名 Cookie 有效期为 30 天。
-
set_signed_cookie通过expires_days设置 Cookie 过期时间 -
get_signed_cookie通过max_age_days控制读取校验的最大存活时间
两个参数相互独立。例如一般业务有效期 30 天;修改账单等高危接口读取 Cookie 时,设置更小的
max_age_days,缩短校验有效期提升安全。 -
-
密钥轮换 :
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_id、client_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_id、client_secret等密钥需要存放在配置文件,禁止硬编码提交到代码仓库。
小结
本篇主要介绍公网部署 Tornado 的身份认证基础:签名 Cookie 机制、自定义登录态、authenticated装饰器、第三方 OAuth 登录。
- 普通 Cookie 极易篡改,登录会话务必使用
set_signed_cookie - 签名 Cookie 防篡改但不加密,不能存储敏感明文
cookie_secret密钥做好保管,支持密钥轮换平滑更新密钥authenticated适合浏览器 web 页面,API 接口不要直接依赖该装饰器
下篇将会继续讲解 XSS 防护、CSRF 防御、请求限制、服务端口与反向代理等 Tornado 公网防护内容。
投资有风险,入市需谨慎。