这一节我们介绍应用安全与认证,其实中间省略了一个数据库。对于tornado来说,读取数据库的数据,性能的瓶颈还是在数据库上面。关于数据库,我在<<web框架--flask>>中介绍了sqlalchemy,这是一个工业级的orm,可以看看,这里就不介绍了。直接进入今天的主题内容。

1.cookie

ookie是储存在客户端的键值对,保存了用户的信息。我们都知道http协议时无状态的,只知道有人链接就行进行交互,但是却不知道是谁。于是这个时候cookie就出现了,当我们第一次访问的时候,服务端就会创建一个cookie然后返回给我们,当我们下次再访问的时候就带着之前的cookie过去就行了,所以我们明明没有输入用户名和密码,却自动登录了,就是因为cookie。当我们换一个浏览器,或者清理垃圾,把cookie删除了,那么就又需要重新登录了。既然提到cookie就要说到session,session是我们人工引入的一个抽象概念,session的实现需要依赖于cookie。当我们的身份信息比较重要时,那么将信息存储在cookie中会不安全,所以session就出现了,服务端把不把敏感信息返回了,而是返回一个随机密串,这个随机密串是由当前的session经过序列化再加密得到的,也就是session id,将这个session id作为cookie值返回给客户端。当客户端下次再访问的时候,只需要带着session id过来就行,将session id解密反序列化,判断用户是否登录。所以session是我们人工引入的一个抽象概念,session的实现依赖于cookie。
那么在tornado中如何设置cookie呢?首先在tornado中,可以设置两种cookie,一个是普通cookie,另一种是加密cookie
设置普通cookie

设置普通cookie

self.set_cookie(name, value, domain=None, expires=None, path="/", expires_days=None, **kwargs)
参数:
name:cookies名
value:cookie值
domain:提交cookie时匹配的域名
path:提交cookie时匹配的路径
expires:cookie的有效期,可以是时间戳整数,时间元素,datetime类型。为UTC时间
expires_days:cookie的有效期天数,优先级低于expires
import tornado.web

class SatoriHandler(tornado.web.RequestHandler):

    def get(self, *args, **kwargs):

        self.set_cookie("satori", "love")
# 实际上set_cookie本质上是通过set_header来实现的
self.set_header("Set-Cookie", "koishi=love")
self.write("6666"

获取cookie

import tornado.web

class SatoriHandler(tornado.web.RequestHandler):

    def get(self, *args, **kwargs):

        self.set_cookie("satori", "love")
cookie = self.get_cookie("satori")
self.write(cookie)

清除cookie

self.clear_cookie(name, path="/", domain=None)  # 删除名为name,同时匹配path和domain的cookie
self.clear_all_cookie(path="/", domain=None) # 删除同时匹配path和domain的所有cookie

注意:执行清除cookie的操作时,并不是tornado去删除浏览器上的cookie,而是将cookie的值设为空,并将有效时间改为失效。真正删除cookie是由浏览器进行操作的

此外再介绍一下HTTP-only和SSL-Cookies

Tornado的cookie功能依附于Python内建的Cookie模块。因此,我们可以利用它所提供的一些安全功能。这些安全属性是HTTP cookie规范的一部分,并在它可能是如何暴露其值给它连接的服务器和它运行的脚本方面给予浏览器指导。比如,我们可以通过只允许SSL连接的方式减少cookie值在网络中被截获的可能性。我们也可以让浏览器对JavaScript隐藏cookie值。
为cookie设置secure属性来指示浏览器只通过SSL连接传递cookie。(这可能会产生一些困扰,但这不是Tornado的安全cookies,更精确的说那种方法应该被称为签名cookies。)从Python 2.6版本开始,Cookie对象还提供了一个httponly属性。包括这个属性指示浏览器对于JavaScript不可访问cookie,这可以防范来自读取cookie值的跨站脚本攻击。
为了开启这些功能,你可以向set_cookie和set_secure_cookie方法传递关键字参数。比如,一个安全的HTTP-only cookie(不是Tornado的签名cookie)可以调用self.set_cookie('foo', 'bar', httponly=True, secure=True)发送。

设置安全cookie

首先需要设置一个随机密串用来给cookie进行混淆加密,然后写在配置文件的settings里,"cookie_secret": "xxxx"

import os
import uuid
import base64 cookie_secret = str(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), encoding="utf-8")
'''
cRi40+RmRy6iVoLoIeK03KMsjfu1T0hzonLWSEtDb2o=
''' options = {"port": 7777} BASE_DIR = os.path.dirname(os.path.abspath(__file__))
settings = {"static_path": os.path.join(BASE_DIR, "static"),
"template_path": os.path.join(BASE_DIR, "templates"),
"static_url_prefix": "/static/",
"compiled_static_cache": True,
"compiled_template_cache": True,
"server_traceback": True,
"cookie_secret": cookie_secret # 要在settings中注册cookie_secret
}

然后使用self.set_secure_cookie(设置安全cookie)进行设置,可以防止cookie被伪造,参数和设置普通cookie是相同的。

import tornado.web

class SatoriHandler(tornado.web.RequestHandler):

    def get(self, *args, **kwargs):

        self.set_secure_cookie("satori", "love")
cookie = self.get_cookie("satori")
self.write(cookie)

可以看到浏览器中显示的不再是satori = love,而是"2|1:0|10:1535867275|6:satori|8:bG92ZQ==|e8ec0f270440d95b2d004e54633cf7730d9ab4ca3ea8ed5a6fffdbc7ea61b952"; expires=Tue, 02 Oct 2018 05:47:55 GMT; Path=/
说明:安全cookie使用的版本,默认使用版本2;默认为0;时间戳;cookie名;base64编码的cookie值;签名值,不带长度说明

获取安全cookie

self.get_secure_cookies(name, value=None, max_age_days=31, min_version=None), 当然self.get_cookie也是可以的。
name:cookie名称
value:如果获取不到返回None
max_age_days:不同于expires_days,expires_days表示设置浏览器中cookie的有效时间。而max_age_days是过滤安全cookie的时间戳。
但是安全cookie也不是绝对安全的,只是在一定程度增加了破解的难度,还是不要在cookie中存储敏感信息为好

2.xsrf

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
举个栗子:古明地恋这个时候正在浏览银行账户,古明地觉在古明地恋的站点上编写了一个取款的form表单提交的链接,并将此链接作为图片src,如果银行保存的古明地恋的cookie还没有过期,那么在点击图片的时候就会带上cookie将form表单提交,这样在古明地恋不知情的情况下便完成了取款

那么如何防范请求伪造呢?

有很多预防措施可以防止这种类型的攻击。首先你在开发应用时需要深谋远虑。任何会产生副作用的HTTP请求,比如点击购买按钮、编辑账户设置、改变密码或删除文档,都应该使用HTTP POST方法。无论如何,这是良好的RESTful做法,但它也有额外的优势用于防范像我们刚才看到的恶意图像那样琐碎的XSRF攻击。但是,这并不足够:一个恶意站点可能会通过其他手段,如HTML表单或XMLHTTPRequest API来向你的应用发送POST请求。保护POST请求需要额外的策略。
为了防范伪造POST请求,我们会要求每个请求包括一个参数值作为令牌来匹配存储在cookie中的对应值。我们的应用将通过一个cookie头和一个隐藏的HTML表单元素向页面提供令牌。当一个合法页面的表单被提交时,它将包括表单值和已存储的cookie。如果两者匹配,我们的应用认定请求有效。
由于第三方站点没有访问cookie数据的权限,他们将不能在请求中包含令牌cookie。这有效地防止了不可信网站发送未授权的请求。正如我们看到的,Tornado同样会让这个实现变得简单。

xsrf保护:同源策源

开启xsrf保护,可以在配置中添加

模板中应用

可以form表单中添加{%module xsrf_form_html()%},当然也要在配置文件settings中注册"xsrf_cookies": True

作用:
1.为浏览器设置了名为_xsrf的cookie,这个cookie在关闭浏览器之后会失效。
2.为模板表单添加了一个隐藏的域,名为_xsrf,值为_xsrf的cookie值

非模板中应用

手动创建一个input,并将name的属性值设置为_xsrf,value值为_xsrf的cookie值

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form>
<input type="hidden" id="hi" name="_xsrf" value="">
name:<input type="text" name="username">
passwd:<input type="password" name="passwd">
<input type="submit" value="submit">
</form>
<script>
function getCookie(name) {
var cook=document.cookie.match("\\b"+name+"=([^;]*)\\b");
return cook?cook[1]:undefined
}
document.getElementById("hi").value = getCookie("_xsrf")
</script>
</body>
</html>

发起ajax请求

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form>
name:<input type="text" name="username">
passwd:<input type="password" name="passwd">
<button onclick="login()">login</button>
</form>
<script>
function getCookie(name) {
var cook=document.cookie.match("\\b"+name+"=([^;]*)\\b");
return cook?cook[1]:undefined
}
function login() {
$.post("提交的地址",
"_xsrf="+getCookie("_xsrf")+"&username="+"用户名"+"&password="+"密码",
function(data){
alert("ok")
}
)
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form>
name:<input type="text" name="username">
passwd:<input type="password" name="passwd">
<button onclick="login()">login</button>
</form>
<script>
function getCookie(name) {
var cook=document.cookie.match("\\b"+name+"=([^;]*)\\b");
return cook?cook[1]:undefined
}
function login() {
data = {
"username": "xxxx",
"password": "xxxx",
};
var datastr = json.stringify(data);
$.ajax({
url: "提交的地址",
method: "POST",
data: datastr,
success: function(data){
alert("ok")
},
headers: {
"X-XSRFToken": getCookie("_xsrf")
}
})
}
</script>
</body>
</html>

需要手动添加_xsrf的cookie,需要在进入主页时就自动设置_xsrf的cookie,因此可以使用tornado.web下的StaticFileHandler。但是我们无法直接在StaticFileHandler中添加逻辑,因此我们想到可以自定义一个类,然后继承StaticFileHandler。

import tornado.web

class StaticFileHandler(tornado.web.StaticFileHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.xsrf_token

3.用户认证

作用:指在受到用户请求后,进行预先判断的用户状态(是否登录),若验证通过则正常处理,否则返回到登录页面
方法:tornado.web.authenticated装饰器,tornado将确保所装饰函数的主题只有合法的用户才能使用
get_current_user(),验证用户的逻辑应该写在该方法中,如果返回结果为True,验证成功,否则验证失败。当验证失败会将用户返回到指定的路由。这个指定的路由需要再配置文件的settings中进行设置,"login_url": "your_url"
import os
import uuid
import base64 cookie_secret = str(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), encoding="utf-8")
'''
cRi40+RmRy6iVoLoIeK03KMsjfu1T0hzonLWSEtDb2o=
''' options = {"port": 7777} BASE_DIR = os.path.dirname(os.path.abspath(__file__))
settings = {"static_path": os.path.join(BASE_DIR, "static"),
"template_path": os.path.join(BASE_DIR, "templates"),
"static_url_prefix": "/static/",
"compiled_static_cache": True,
"compiled_template_cache": True,
"server_traceback": True,
"cookie_secret": cookie_secret, # 要在settings中注册cookie_secret
"xsrf_cookies": True,
"login_url": "/satori"
}

application.py

import tornado.web
from views import view
import config
import os class Application(tornado.web.Application):
def __init__(self): handlers = [
(r"/satori", view.SatoriHandler),
(r"/koishi", view.KoishiHandler),
(r"/mashiro", view.MashiroHandler),
(r"/(.*)$", view.StaticFileHandler, {"path": os.path.join(config.BASE_DIR,"static/html"),
"default_filename": "index.html"})
] super(Application, self).__init__(handlers=handlers, **config.settings)

view.py

import tornado.web

class StaticFileHandler(tornado.web.StaticFileHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.xsrf_token class SatoriHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs):
self.write("<h1>satori</h1>") class KoishiHandler(tornado.web.RequestHandler):
def get_current_user(self):
return None @tornado.web.authenticated
def get(self, *args, **kwargs):
self.write("<h1>koishi</h1>") class MashiroHandler(tornado.web.RequestHandler):
def get_current_user(self):
return True @tornado.web.authenticated
def get(self):
self.write("<h1>mashiro</h1>")
我访问/koishi和/mashiro,会触发get请求,但是get请求被认证装饰器装饰,所以会执行get_current_user(),如果返回为True,那么执行get,如果返回为False,那么会重定向到配置文件中settings的"login_url"所对应的url

访问localhost:7777/koishi

当我访问/koishi, 由于验证不通过,所以跳转到了satori。而且/satori后面还跟着?next=%2Fkoishi,也说明是由/koishi跳转过来的

但我访问/mashiro,由于验证通过,所以执行对应的get请求,显示mashiro

关于应用安全与认证就介绍到这里,下一节将介绍tornado高性能的第二个杀手锏(第一个是基于epoll的IO多路复用),异步

4.(基础)tornado应用安全与认证的更多相关文章

  1. Tornado web.authenticated 用户认证浅析

    在Web服务中会有用户登录后的一系列操作, 如果一个客户端的http请求要求是用户登录后才能做得操作, 那么 Web服务器接收请求时需要判断该请求里带的数据是否有用户认证的信息. 使用Tornado框 ...

  2. Laravel 5 基础(十二)- 认证

    Laravel 出厂已经带有了用户认证系统,我们来看一下 routes.php,如果删除了,添加上: Route::controllers([ 'auth' => 'Auth\AuthContr ...

  3. 在Keystone V3基础上改进的分布式认证体系

    目标 使用java实现keystone v3相关功能与概念: api client authentication service discovery distributed multi-tenant ...

  4. Java认证与授权服务JAAS基础概念

    转:http://www.nosqlnotes.com/technotes/jaas-concept/ JAAS是”Java Authentication and Authorization Serv ...

  5. tornado 入门

    Overview FriendFeed是一款使用 Python 编写的,相对简单的 非阻塞式 Web 服务器.其应用程序使用的 Web 框架看起来有些像 web.py 或者 Google 的 weba ...

  6. tornado详细介绍

    Tornado Web服务器概览,tornado教程,tornado开发教程 概览 漏洞 | 漏洞目录 | 安全文档 Overview 下载和安装 模块索引 主要模块 底层模块 Tornado 攻略 ...

  7. 前端学HTTP之基本认证

    前面的话 人们用Web进行私人事务处理,访问私有的数据.通过Web可以很方便地访问这些信息,但仅仅是方便访问还是不够的.我们要保证只有特定的人能看到我们的敏感信息并且能够执行我们的特权事务 服务器需要 ...

  8. 为什么要进行阿里云云计算助理工程师认证(ACA)

    阿里云助理工程师认证(ACA - Alibaba Cloud Certification Associate)是面向使用阿里云基础产品的专业技术认证,主要涉及阿里云的计算.存储.网络.安全类的核心产品 ...

  9. Apache Shiro:【1】Shiro基础及Web集成

    Apache Shiro:[1]Shiro基础及Web集成 Apache Shiro是什么 Apache Shiro是一个强大且易于使用的Java安全框架,提供了认证.授权.加密.会话管理,与spri ...

随机推荐

  1. python jieba分词小说与词频统计

    1.知识点 """ 1)cut() a) codecs.open() 解决编码问题 b) f.readline() 读取一行,也可以使用f.readlines()读取多行 ...

  2. OpenStack 的单元测试

    目录 文章目录 目录 前言 单元测试能提高生产率 Python 单元测试工具清单 unittest Test Discover Test Fixture Test Suite Assert(断言) m ...

  3. springboot子模块 @Autowired无法找到其他模块的接口和类的解决方法

    在main的启动类上添加 @SpringBootApplication(scanBasePackages = {"com.shangsheng"})或者@ComponentScan ...

  4. JavaScript DOM 编程艺术(第二版) 常用JS小脚本

    function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') ...

  5. 【.NET】ASP.Net IE10+ SCRIPT:XXX_doPostBack 未定义

    问题描述 GridView中分页控件,点击分页无反应,Linkbutton点击无反应,打开Web控制台,发现如下错误:SCRIPTXXX:_doPostBack 未定义:查询后得知,是由于.NET F ...

  6. ArcEngine二次开发之提取外包矩

    1.通过ITopologicalOperator接口,此方法适用于需要获得包含几个或多个要素的最小外包矩形 public IEnvelope GetEnvelope(IGeometryCollecti ...

  7. 【AMAD】schema -- 使用pythonic的方式进行schema验证

    动机 简介 用法 个人评分 动机 验证数据是否符合规范是很有用的,比如: 用于单元测试 用于验证用户提交的数据是否合法 简介 schema1是一个用来验证python数据结构的库. 可以用来验证诸如: ...

  8. C# Tcp协议收发数据(TCPClient发,Socket收)

    转载自:http://www.cnblogs.com/WTFly/p/5340617.html 运行这个程序前需要先关闭Windows防火墙,Win7系统关闭防火墙的方法是在控制面板的"控制 ...

  9. 1.docker 慕课入门

    本文是学习慕课网的实战https://www.imooc.com/learn/824  同时结合菜鸟教程的思想https://www.runoob.com/docker/docker-architec ...

  10. PHP Smarty模板的安装

    最近开发中用到了PHP中smarty模板..作为一个长久以来的前端,开始学习PHP模板..下面将安装教程分享给大家.. 1. 下载Smarty最新版: http://www.smarty.NET/do ...