百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

05.Tornado Cookies(tornado怎么读)

ccwgpt 2024-10-14 08:42 26 浏览 0 评论

05.Tornado Cookies

Cookie 是很多网站为了辨别用户的身份而储存在用户本地终端(Client Side)的数据,定义于 RFC2109。

5.1RequestHandler.cookies

self.request.cookies 的别名。

5.2RequestHandler.get_cookie(name: str, default: Optional[str] = None) -> Optional[str]

返回具有给定名称的请求cookie的值。

如果指定的cookie不存在,则返回默认值。

此方法仅返回请求中存在的Cookie。它看不到此处理程序中set_cookie设置的传出cookie。

5.3RequestHandler.set_cookie(name: str, value: Union[str, bytes], domain: Optional[str] = None, expires: Optional[Union[float, Tuple, datetime]] = None, path: str = '/', expires_days: Optional[float] = None, *, max_age: Optional[int] = None, httponly: bool = False, secure: bool = False, samesite: Optional[str] = None, **kwargs: Any) -> None

使用给定的选项设置传出cookie名称/值。

新设置的cookie不会立即通过get_cookie显示;他们直到下一次请求才出现。

大多数参数都直接传递给http.cookies。

expires可以是time.time返回的数字时间戳、time.gmtime返回的时间元组或datetime.datetime对象。expires_days是为了方便设置从今天开始的到期时间(如果都设置了,则使用expires)。

自6.3版本起弃用:关键字参数目前不区分大小写。在Tornado 7.0中,这将被更改为只接受小写参数。

5.4RequestHandler.clear_cookie(name: str, **kwargs: Any) -> None

删除具有给定名称的cookie。

此方法接受与set_cookie相同的参数,除了expires和max_age。清除cookie需要与设置时相同的域和路径参数。在某些情况下,还需要匹配相同的站点和安全参数。

与set_cookie类似,只有在发出以下请求后才能看到此方法的效果。

5.5RequestHandler.clear_all_cookies(**kwargs: Any) -> None

尝试删除用户随此请求发送的所有Cookie。

有关关键字参数的更多信息,请参阅clear_cookie。由于cookie协议的限制,无法在服务器端确定哪些值是域、路径、samesite或安全参数所必需的,只有在设置cookie时始终对这些参数使用相同的值,这种方法才能成功。

与set_cookie类似,只有在发出以下请求后才能看到此方法的效果。

在版本3.2中更改:添加了路径和域参数。

在版本6.3中更改:现在接受set_cookie所做的所有关键字参数。

自6.3版本以来已弃用:管理Cookie的规则越来越复杂,使得clear_all_cookies方法无法可靠工作,因为我们对Cookie的所有了解都是它们的名称。应用程序通常应该一次使用一个clear_cookie。

5.6RequestHandler.get_signed_cookie(name: str, value: Optional[str] = None, max_age_days: float = 31, min_version: Optional[int] = None) -> Optional[bytes]

如果给定的签名cookie有效,则返回该cookie,否则返回None。

解码后的cookie值以字节字符串的形式返回(与get_cookie不同)。

与get_cookie类似,此方法仅返回请求中存在的cookie。它看不到此处理程序中由set_signed_cookie设置的传出cookie。

在版本3.2.1中更改:

添加了min_version参数。推出cookie版本2;默认情况下接受版本1和2。

在版本6.3中更改:从get_secure_cookie重命名为get_signed_cookie,以避免与cookie属性和前缀中“安全”的其他用法混淆。旧名字仍然是别名。

5.7RequestHandler.set_signed_cookie(name: str, value: Union[str, bytes], expires_days: Optional[float] = 30, version: Optional[int] = None, **kwargs: Any) -> None

对cookie进行签名和时间戳,使其无法伪造。

您必须在应用程序中指定cookie_secret设置才能使用此方法。它应该是一个长的随机字节序列,用作签名的HMAC密钥。

要使用此方法读取cookie集,请使用get_signed_cookie()。

请注意,expires_days参数设置了浏览器中cookie的生存期,但与get_signed_cookie的max_age_days参数无关。值“无”将生存期限制为当前浏览器会话。

安全Cookie可能包含任意字节值,而不仅仅是unicode字符串(与常规Cookie不同)

与set_cookie类似,只有在发出以下请求后才能看到此方法的效果。

在版本3.2.1中更改:添加了版本参数。引入cookie版本2并将其设置为默认值。

在版本6.3中更改:从set_secure_cookie重命名为set_signed_cookie,以避免与cookie属性和前缀中“安全”的其他用法混淆。旧名字仍然是别名。

5.8 Cookies 安全机制

在实际应用中,Cookie 经常用于保存 Session 信息。如:

import tornado.web 
session_id = 1 
class MainHandler(tornado.web.RequestHandler): 
    def get(self): 
        global session_id
        if not self.get_cookie("session"): 
            self.set_cookie("session",str( session_id)) 
            session_id = session_id + 1 
            self.write("Your session got a new session!") 
        else: 
            self.write("Your session was set!")

本例中用 get_cookie()函数判断 Cookie 名“session”是否存在,如果不存在则为其赋予新的session_id。

因为 Cookie 总是被保存在客户端,所以如何保证其不被篡改是服务器端程序必须解决的问题。Tornado 提供了为 Cookie 信息加密的机制,使得客户端无法随意解析和修改 Cookie 的键值。如:

import tornado.web
import asyncio

session_id = 1

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        global session_id
        if not self.get_signed_cookie("session"):
            self.set_signed_cookie("session",str( session_id))
            session_id = session_id + 1
            self.write("Your session got a new session!")
        else:
            self.write("Your session was set!")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ], cookie_secret="SECRET_DONT_LEAK")

async def main():
    app = make_app()
    app.listen(8888)
    shutdown_event = asyncio.Event()
    await shutdown_event.wait()

if __name__ == "__main__":
    asyncio.run(main())

本例网站只提供了一个根目录页面,解析其关键点如下。

  1. 在 tornado.web.Application 对象初始化时赋予 cookie_secret 参数,该参数值是一个字符串,用于保存本网站 Cookie 加密时的密钥。
  2. 在需要读取 Cookie 的地方用 RequestHandler.get_signed_cookie 替换原来的 RequestHandler.get_cookie 调用。
  3. 在需要写入 Cookie 的地方用 RequestHandler.set_signed_cookie 替换原来的 RequestHandler.set_cookie 调用。

这样,开发者就无须担心 Cookie 的伪造问题了。

5.9用户身份认证

在 Tornado 的 RequestHandler 类中有一个 current_user 属性用于保存当前请求的用户名。RequestHandler.current_user 的默认值是 None,在 get()、post()等处理函数中可以随时读取该属性以获得当前的用户名。RequestHandler.current_user 是一个只读属性,所以开发者需要重载RequestHandler.get_current_user()函数以设置该属性值。

如:通过使用 RequestHandler.current_user 属性及 RequestHandler.get_current_user() 方法来实现的用户身份控制。

import tornado.web
import tornado.ioloop
import uuid # UUID 生成库

dict_sessions = {} # 保存所有登录的 Session

class BaseHandler(tornado.web.RequestHandler): # 公共基类
    def get_current_user(self): # 写入 current_user 的函数
        if self.get_secure_cookie("session_id") is None:
            return None
        session_id = self.get_secure_cookie("session_id").decode("utf-8")
        return dict_sessions.get(session_id)

class MainHandler(BaseHandler):
    @tornado.web.authenticated # 需要身份认证才能访问的处理器
    def get(self):
        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):  # 验证是否允许登录
        if len(self.get_argument("name")) < 3:
            self.redirect("/login")
            return
        session_id = str(uuid.uuid1())
        dict_sessions[session_id] = self.get_argument("name")
        self.set_secure_cookie("session_id", session_id)
        self.redirect("/")

application = tornado.web.Application(
    [  # URL 映射定义
         (r"/", MainHandler),
         (r"/login", LoginHandler),
    ],
    cookie_secret="SECRET_DONT_LEAK",  # Cookie 加密密钥
    login_url="/login")  # 定义登录页面

def main():
    application.listen(8888)
    tornado.ioloop.IOLoop.current().start()  # 挂起监听

if __name__ == "__main__":
    main()

上面的例子是一个完整的身份认证编程框架,对其代码解析如下:

  1. 用全局字典 dict_sessions 保存已经登录的用户信息,为简单起见,本例只用其保存“会话 ID:用户名”的键/值对。
  2. 定义公共基类 BaseHandler,该类继承自 tornado.web.RequestHandler,用于定义本网站所有处理器的公共属性和行为。重载它的 get_current_user()函数,其在开发者访问 RequestHandler.current_user 属性时自动被 Tornado 调用。该函数首先用get_secure_cookie()获得本次访问的会话 ID,然后用会话 ID 从 dict_sessions 中获得用户名并返回。
  3. MainHander 类是一个要求用户经过身份认证才能访问的处理器实例。该处理器中的处理函数 get()使用了装饰器 tornado.web.authenticated,具有该装饰器的处理函数在执行之前根据 current_user 是否已经被赋值来判断用户的身份认证情况。如果已经被赋值则可以进行正常逻辑,否则自动重定向到网站的登录页面。
  4. LoginHandler 类是登录页面处理器,其 get()函数用于渲染登录页面,post()函数用于验证是否允许用户登录。本例中只要用户输入的用户名大于等于 3 个字节即允许用户登录。
  5. 在 tornado.web.Application 的初始化函数中通过 login_url 参数给出网站的登录页面地址。该地址被用于 tornado.web.authenticated 装饰器在发现用户尚未验证时重定向到一个 URL。
  6. Tornado 使用 bytes 类型保存 cookie 值,因此在用 get_secure_cookie()读取 cookie 后需要用 decode() 函数将其转换为 string 类型再使用。

注:加入身份认证的所有页面处理器需要继承自 BaseHandler 类,而不是直接继承原来的 tornado.web.RequestHandler 类。

商用的用户身份认证还要完善更多的内容,比如加入密码验证机制、管理登录超时、将用户信息保存到数据库等,这些内容留给读者自己实践。

5.10防止跨站攻击

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种对网站的恶意利用。通过 CSRF,攻击者可以冒用用户的身份,在用户不知情的情况下执行恶意操作。

5.10.1CSRF 攻击原理

上图展示了 CSRF 的基本原理,其中 Site1 是存在 CSRF 漏洞的网站,而 Site2 是存在攻击行为的恶意网站。

  1. 用户首先访问了存在 CSRF 漏洞的网站 Sites1,成功登录并获取到了 Cookie。此后,所有该用户对 Site1 的访问均会携带 Site1 的 Cookie,因此被 Site1 认为是有效操作。
  2. 此时用户又访问了带有攻击行为的站点 Site2,而 Site2 的返回页面中带有一个访问 Site1进行恶意操作的连接,但被伪装成了合法内容。比如超链接看上去是一个抽奖信息,实际上却是向 Site1 站点提交提款请求的信息。
  3. 用户一旦点击恶意链接,就在不知情的情况下向 Site1 站点发送了请求。因为之前用户在 Site1 进行过登录且尚未退出,所以 Site1 在收到用户的请求和附带的 Cookie 时将认为该请求是用户发出的正常请求。此时,恶意站点的目的已经达到。

5.10.2用 Tornado 防范 CSRF 攻击

为了防范 CSRF 攻击,要求每个请求包括一个参数值作为令牌来匹配存储在 Cookie 中的对应值。

Tornado 应用可以通过一个 Cookie 头和一个隐藏的 HTML 表单元素向页面提供令牌。这样,当一个合法页面的表单被提交时,它将包括表单值和已存储的 Cookie。如果两者匹配,则 Tornado 应用认定请求有效。

开启 Tornado 的 CSRF 防范功能需要两个步骤:

  1. 在实例化 tornado.web.Application 时传入 xsrf_cookies=True 参数,即:
  2. application = tornado.web.Application(
    [
    (
    r'/', MainHandler),
    (
    r'/purchase', PurchaseHandler),
    ],
    cookie_secret = "DONT_LEAK_SECRET",
    xsrf_cookies = True)
  3. 或者
  4. settings = {
    "cookie_secret": "DONT_LEAK_SECRET",
    "xsrf_cookies": True
    }

    application = tornado.web.Application(
    [
    (
    r'/', MainHandler),
    (
    r'/login', LoginHandler),
    ],
    **settings)
  5. 当 tornado.web.Application 需要初始化的参数过多时,可以像本例一样通过 setting 字典的形式传入命名参数。
  6. 在每个具有 HTML 表单的模板文件中,为所有表单添加 xsrf_form_html()函数标签。
  7. <form action="/login" method="post">
    {% module xsrf_form_html() %}
    <input type="text" name="message"/>
    <input type="submit" value="Post"/>
    </form>
  8. 这里的{% module xsrf_form_html() %}起到了为表单添加隐藏元素以防止跨站请求的作用。Tornado 的安全 Cookies 支持和 XSRF 防范框架减轻了应用开发者的很多负担。没有它们,开发者需要思考很多防范的细节措施,因此 Tornado 内建的安全功能也非常有用。

相关推荐

RACI矩阵:项目管理中的角色与责任分配利器

作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...

在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」

慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...

Scrum模式:每日站会(Daily Scrum)

定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...

大家都在谈论的敏捷开发&amp;Scrum,到底是什么?

敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...

敏捷与Scrum是什么?(scrum敏捷开发是什么)

敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...

敏捷项目管理与敏捷:Scrum流程图一览

敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...

一张图掌握项目生命周期模型及Scrum框架

Mockito 的最佳实践(mock方法)

记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...

EffectiveJava-5-枚举和注解(java枚举的作用与好处)

用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...

Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!

Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...

Java单元测试框架PowerMock学习(java单元测试是什么意思)

前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...

Spring框架基础知识-第六节内容(Spring高级话题)

Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...

Java单元测试浅析(JUnit+Mockito)

作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...

揭秘Java代码背后的质检双侠:JUnit与Mockito!

你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...

单元测试框架哪家强?Junit来帮忙!

大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...

取消回复欢迎 发表评论: