三、Tornado的Cookie与安全

文章目录

    • Cookie
        • 设置cookie
        • 通过set_header设置cookie
        • 获取cookie
        • 清除cookie
    • 安全Cookie
        • 设置与获取安全cookie
    • XSRF保护
        • Tornado开启XSRF保护
        • 使用模板实现xsrf
        • 非模板实现xsrf
            • 请求体携带_xsrf参数
            • HTTP头X-XSRFToken


Cookie

设置cookie

RequestHandler提供了操作cookie的方法
set_cookie(name, value, domain=None, expires=None, path='/', expires_days=None)

参数名 说明
name cookie名
value cookie值
domain 提交cookie时匹配的域名
path 提交cookie时匹配的路径
expires cookie的有效期,可以是时间戳整数、时间元组或者datetime类型,为UTC时间
expires_days cookie的有效期,天数,优先级低于expires
import tornado.web
import tornado.ioloop
import time


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_cookie("cookie1", "version1")
        self.set_cookie("cookie2", "version2", path="/cookie2",
                        expires=time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S"))
        self.set_cookie("cookie3", "version3", expires_days=20)
        # 利用time.mktime将本地时间转换为UTC标准时间
        self.set_cookie("cookie4", "version4",
                        expires=time.mktime(time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S")))
        self.write("OK")


if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/', MainHandler),  # 生成服务实例,通常包含路由信息
    ], debug=True)
    app.listen(8000)  # 监听端口
    tornado.ioloop.IOLoop.current().start()  # 开启服务

三、Tornado的Cookie与安全_第1张图片
三、Tornado的Cookie与安全_第2张图片

通过set_header设置cookie

设置cookie实际是通过设置header的Set-Cookie来实现

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_cookie("cookie1", "version1")
        self.set_cookie("cookie2", "version2", path="/cookie2",
                        expires=time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S"))
        self.set_cookie("cookie3", "version3", expires_days=20)
        # 利用time.mktime将本地时间转换为UTC标准时间
        self.set_cookie("cookie4", "version4",
                        expires=time.mktime(time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S")))
        # 通过set_header来设置cookie
        self.set_header("Set-Cookie", "cookie5=version5; expires=Fri, 11 Aug 2019 15:59:59 GMT; Path=/")
        self.write("OK")

获取cookie

get_cookie(name, default=None)

import tornado.web
import tornado.ioloop
import tornado.httpserver


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_cookie("cookie1", "version1")  # 设置cookie
        cookie = self.get_cookie("cookie1")  # 获取cookie
        self.write(cookie)  # 将cookie打印出来


if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/', MainHandler)
    ], debug=True)
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8000)
    tornado.ioloop.IOLoop.current().start()

清除cookie

clear_cookie(name, path='/', domain=None)
删除名为name,并同时匹配domain和path的cookie

clear_all_cookies(path='/', domain=None)
删除同时匹配domain和path的所有cookie

import tornado.web
import tornado.ioloop
import time


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_cookie("cookie1", "version1")
        self.set_cookie("cookie2", "version2", path="/cookie2",
                        expires=time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S"))
        self.set_cookie("cookie3", "version3", expires_days=20)
        # 利用time.mktime将本地时间转换为UTC标准时间
        self.set_cookie("cookie4", "version4",
                        expires=time.mktime(time.strptime("2019-08-11 23:59:59", "%Y-%m-%d %H:%M:%S")))
        self.write("OK")


class ClearOneCookieHandler(tornado.web.RequestHandler):
    def get(self):
        self.clear_cookie("cookie3")  # 清除cookie3对应的cookie值
        self.write("OK")


class ClearAllCookieHandler(tornado.web.RequestHandler):
    def get(self):
        self.clear_all_cookies()  # 清除所有cookie
        self.write("OK")


class CookieHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("OK")


if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/', MainHandler),  # 生成服务实例,通常包含路由信息
        (r'/clear_one', ClearOneCookieHandler),
        (r'/clear_all', ClearAllCookieHandler),
        (r'/cookie2', CookieHandler)
    ], debug=True)
    app.listen(8000)  # 监听端口
    tornado.ioloop.IOLoop.current().start()  # 开启服务

执行清除cookie操作后,并不是立即删除了浏览器中的cookie,而是给cookie值置空,并改变其有效期使其失效。真正的删除cookie是由浏览器去清理的
三、Tornado的Cookie与安全_第3张图片


安全Cookie

Tornado提供了一种对Cookie进行简易加密签名的方法来防止Cookie被恶意篡改
安全Cookie需要为应用配置一个用来给Cookie进行签名的秘钥cookie_secret,将其传递给Application的构造函数

uuid模块的uuid4()函数可以随机产生一个uuid码,bytes属性将此uuid码作为16字节字符串
Base64是一种基于64个可打印字符来表示二进制数据的表示方法
两者结合使用可以生成Cookie签名需要的secret key

生成secret_key

import base64
import uuid

print(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes))
# hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk=
app = tornado.web.Application(
    [(r"/", MainHandler),],
    cookie_secret = "hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk="
)

设置与获取安全cookie

set_secure_cookie(name, value, expires_days=30)
设置一个带签名和时间戳的cookie,防止cookie被伪造

get_secure_cookie(name, value=None, max_age_days=31)
如果cookie存在且验证通过,返回cookie的值,否则返回None
expires_days是设置浏览器中cookie的有效期,而max_age_day是过滤安全cookie的时间戳

import tornado.web
import tornado.ioloop


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        cookie = self.get_secure_cookie("count")
        count = int(cookie) + 1 if cookie else 1
        self.set_secure_cookie("count", str(count))
        self.write(
                'Cookie Counter'
                '

访问量:%d次。

'
% count + '' ) if __name__ == '__main__': app = tornado.web.Application([ (r'/', MainHandler), ], debug=True, cookie_secret="hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk=" ) app.listen(8000) tornado.ioloop.IOLoop.current().start()

签名后的cookie

"2|1:0|10:1563683178|5:count|4:MTU=|bceba900821617648a262cde89b0961257eae5afdbafa19b9fea250b6cb62d55"

字段说明:

  1. 安全cookie的版本,默认使用版本2,不带长度说明前缀
  2. 默认为0
  3. 时间戳
  4. cookie名
  5. base64编码的cookie值
  6. 签名值,不带长度说明前缀

Tornado的安全cookie只是一定程度的安全,仅仅是增加了恶意修改的难度
Tornado的安全cookies仍然容易被窃听,而cookie值是签名不是加密,攻击者能够读取已存储的cookie值,并且可以传输他们的数据到任意服务器,或者通过发送没有修改的数据给应用伪造请求
避免在浏览器cookie中存储敏感的用户数据仍然非常重要


XSRF保护

浏览器有一个很重要的概念——同源策略(Same-Origin Policy)。 所谓同源是指,域名,协议,端口相同。 不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源

由于第三方站点没有访问cookie数据的权限(同源策略),所以我们可以要求每个请求包括一个特定的参数值作为令牌来匹配存储在cookie中的对应值,如果两者匹配,我们的应用认定请求有效

Tornado开启XSRF保护

import tornado.web
import tornado.ioloop


class MainHandler(tornado.web.RequestHandler):
    def get(self):  # 向当前url发送post请求
        self.write(
                """
"""
) def post(self): self.write("hello world") if __name__ == '__main__': app = tornado.web.Application([ (r'/', MainHandler), ], debug=True, cookie_secret="hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk=", xsrf_cookies=True # 打开xsrf验证需求并生成cookie ) app.listen(8000) tornado.ioloop.IOLoop.current().start()

浏览器会提示我们没有权限访问
三、Tornado的Cookie与安全_第4张图片

使用模板实现xsrf

我们知道实现xsrf就是在发送post请求的时候将xsrf_cookie值一同发送给服务器,让服务器对两个cookie进行比较来判断请求是否合法
因此可以为模板的表单中添加了一个隐藏的输入名为xsrf,其值为xsrfCookie
当通过此html页面发送post请求时,浏览器会将模板中隐藏的cookie发送到服务器

import tornado.web
import tornado.ioloop


class MainHandler(tornado.web.RequestHandler):
    def get(self):  # 向当前url发送post请求
        self.render("main.html")

    def post(self):
        self.write("hello world")


if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/', MainHandler),
    ],
            debug=True,
            cookie_secret="hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk=",
            xsrf_cookies=True  # 打开xsrf验证需求
    )
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()

main.html


<html>
<head>
    <title>XSRFtitle>
head>
<body>
    <form method="post">
      {% module xsrf_form_html() %}
      <input type="text" name="message"/>
      <input type="submit" value="Post"/>
    form>
body>
html>

非模板实现xsrf

在任意的Handler中通过获取self.xsrf_token的值来生成_xsrf并设置Cookie

import tornado.web
import tornado.ioloop


class XSRFTokenHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("main2.html")  # 加载模板

    def post(self):
        self.write("OK")


if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/', XSRFTokenHandler),
    ],
            debug=True,
            cookie_secret="hhLgDUloTO2hKpawAGathnZEwNDbDEAOrNZQLj1DAzk=",
            xsrf_cookies=True  # 打开xsrf验证需求
    )
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()
请求体携带_xsrf参数

main2.html


<html>
<head>
    <meta charset="utf-8">
    <title>XSRFtitle>
head>
<body>
<a href="javascript:;" onclick="xsrfPost()">发送POST请求-main2a>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js">script>
<script type="text/javascript">
    //获取指定Cookie的函数
    function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }
    //AJAX发送post请求,表单格式数据
    function xsrfPost() {
        var xsrf = getCookie("_xsrf");
        $.post("/", "_xsrf=" + xsrf, function (data) {
            alert("OK");
        });
    }
script>
body>
html>
HTTP头X-XSRFToken

请求体是其他格式的(如json或xml等),可以通过设置HTTP头X-XSRFToken来传递_xsrf值


<html>
<head>
    <meta charset="utf-8">
    <title>测试XSRFtitle>
head>
<body>
<a href="javascript:;" onclick="xsrfPost()">发送POST请求-main3a>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js">script>
<script type="text/javascript">
    //获取指定Cookie的函数
    function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }
    //AJAX发送post请求,json格式数据
    function xsrfPost() {
        var xsrf = getCookie("_xsrf");
        var data = {};  //请求参数,这里没有设置
        var json_data = JSON.stringify(data);
        $.ajax({
            url: "/",  //请求地址
            method: "POST",  //请求方式
            headers: {
                "X-XSRFToken": xsrf,  //请求头中设置xsrf信息
            },
            data: json_data,
            success: function (data) {
                alert("OK");
            }
        })
    }
script>
body>
html>

你可能感兴趣的:(Tornado)