翻译: introduce to tornado - Writing Secure Appli...
2013-06-07 01:47
369 查看
编写加密的应用
很多时候,加密应用是一件非常繁琐的事情(也很让开发人员头疼).tornado 的web服务端设计之初已经考虑到了这些事情,内置了很多加密模块,让我们可以轻松地对容易出现问题的地方进行处理.一个可靠的 cookies 可以避免用户的状态被浏览器的恶意代码进行修改.此外浏览器的 cookies 还可以配合 http 请求的变量来避免一些伪造的跨站恶意攻击.在这一章节,我们将会了解到 tornado 的这些功能是如何防止攻击的,同时我们还会看到一个用户认证的例子和相关的功能.危险的cookie
许多网页会使用浏览器的cookies来存储用户的身份标识与sessions信息.通过浏览器的sessions状态来确定用户的状态是非常简单且常用的手段,具有非常广泛的应用.不幸的是,浏览器的cookies非常容易被跨站攻击.这里会有一小段内容展示tornado是如何防止恶意脚本篡改你应用存储的cookies的.伪造cookies
有许多途径可以截取到浏览器中的cookies信息,网页中的javascript和flash都有读写这个域中的cookies信息的权限.浏览器插件同样也可以通过程序去获取这些cookies信息.这些通过网页完成的脚本攻击,可以直接篡改用户浏览器中的cookies数据.加密cookies
tornado使用加密的签名验证cookies的值,以保证这些cookies的数值不能够被服务器以外的第三方修改.恶意的攻击脚本并不知道加密的key,所以它们将无法修改应用中的任何cookies数据.使用加密的cookies
tornado的 set_secure_cookies() 和 get_secure_cookes() 功能用于发送和接受浏览器cookies数据,防止这些数据在浏览器中被篡改.要想使用这些功能,你必须在应用的构造函数中声明 cookies_secret 变量.让我们来看一个简单的例子.例子6-1中的应用将会返回一个浏览器中累计重载网页的次数.如果cookies没有设置过(或cookies被修改过),这个应用将会设置一个新的cookies值 1. 否则应用将会获取cookies的值并进行自增操作(对原数值加一). 例子6-1 cookie_counter.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import tornado . httpserver import tornado . ioloop import tornado . web import tornado . options from tornado . options import define , options define ( "port" , default = 8000 , help = "run on the given port" , type = int ) class MainHandler ( tornado . web . RequestHandler ) : def get ( self ) : cookie = self . get_secure_cookie ( "count" ) count = int ( cookie ) + 1 if cookie else 1 countString = "1 time" if count == 1 else "%d times" % count self . set_secure_cookie ( "count" , str ( count ) ) self . write ( '' '<h1> You’ve viewed this page %s times. </h1>' % countString '' ) if __name__ == "__main__" : tornado . options . parse_command_line ( ) settings = { "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" } application = tornado . web . Application ( [ ( r '/' , MainHandler ) ] , * * settings ) http_server = tornado . httpserver . HTTPServer ( application ) http_server . listen ( options . port ) tornado . ioloop . IOLoop . instance ( ) . start ( ) |
这个 cookie_secret 值将会做为一个唯一的随机字符串传送给应用的构造函数.在 python shell 中执行下面的代码,将会生成一个字符串给你.
1 2 3 | >>> import base64 , uuid >>> base64 . b64encode ( uuid . uuid4 ( ) . bytes + uuid . uuid4 ( ) . bytes ) 'bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=' |
cookies 的 httponly 和 SSL
tornado 的 cookie 功能是基于 python 的 cookie 模块构建的.所以我们可以使用一些加密功能提供的高级特性, 可以只加密 http cookie 中的部分信息.并且它通过运行的脚本去告诉浏览器可以暴露哪些 cookie 数据给,通过什么方式连接服务器.例如,我们可以只通过 SSL 连接去传送 cookie的数据,减小网络请求被截取的概率.我们还可以通过 javascript 要求浏览器隐藏 cookie 的数据. 设置 secure 属性告诉浏览器只能通过 SSL 连接去传送加密的 cookie.(这可能有些令人困惑, tornado 加密的 cookies 与其它的不太一样,准确的说,这是一个 signed cookies).从 python2.6 的版本开始, cookie 对象还支持 httponly 属性.包括告诉浏览器如何让 cookie 更不容易被 javascript 调用.这可以防止跨站攻击脚本去获取 cookie 的值. 要使用这些功能,你必须传送一个 keyword 参数给 set_cookie 和 set_secure_cookie 方法.例如,一个加密, http-only 的 cookie (没有使用 tornado 的签署功能)可以通过 self.set_cookie(‘foo’, ‘bar’, httponly=Ture, secure=True) 进行发送. 现在我们来探讨如何保护 cookie 中存储的持久化数据的策略.我们可以看到通过另一个公共的攻击变量,”脆弱的请求”在96页将会看到如何防止恶意网页伪造的请求攻击你的应用.脆弱的请求
对于任何 web 应用一个主要的安全漏洞是跨站请求的攻击.通常缩写为 CSRF 或 XSRF, 它的发音是 “sea surf”. 这个是通过浏览器的一个安全漏洞.将恶意代码注入到受害者的网站,伪造非法的请求危害登录网站的用户利益.让我们来看一个例子.剖析一个伪造的跨站请求
我们假设有一个正常的 burt 书店的用户 alice.当她使用她的帐号登录到在线商店后.网站将会标识她的浏览器 cookie.现在假设她是一个不小心的用户, melvin 想要增加他店中书籍的销售量,在 alice 经常访问的一个网络社区, melvin 已经发表了一篇带有 HTML 图片标记的文章,这个 HTML 图片标记的代码中包含一个在线书店中的购买链接,例如: <img src="http://store.burts-books.com/purchase?title=Melvins+Web+Sploitz" /> alice 的浏览器在试图获取这个图片资源的时候,将会把合法的 cookie 添加到请求中.并不知道这个kitten的图片已经被替换掉了, 这条 URL 将会对在线商店发起一次购买请求.防止伪造的请求
有许多预防这类攻击的方法.首先在你开发的时候,必须要考虑的是只能接受来自同一个域的请求.这会对所有 HTTP 请求带来副作用,类似点击按钮去提交购买订单,编辑账户设置,修改密码,或者删除的操作,都应该使用 HTTP 的 POST 方法.这是一个很好的 RESTful 实践,同时它还有有个额外的优点,避免一些类似于刚才看到的恶意图片带来的 XSRF 攻击,当然这是远远不够的,一些恶意的网站仍然可以通过比如 HTML 表单或 XMLHTTPRequest 的 API 伪造 POST 请求去攻击你的应用.我们需要通过其它策略去保护 POST 请求. 一个防止伪造 POST 请求的策略是,我们在每一个请求中包含一个变量 token,这个变量将会与发起请求的cookie相匹配,我们的应用将会为每一个页面提供不同的 token , 服务通过 cookie 头和网页中隐藏的 HTML 元素给用户的浏览器提供 token. 如果两个都匹配,我们的应用就会认为这个请求是合法的.使用tornado的 XSRF 防护
你还可以在应用的构造函数中包含 xsrf_cookies 变量去启用 XSRF 保护.1 2 3 4 5 6 7 8 | settings = { "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" , "xsrf_cookies" : True } application = tornado . web . Application ( [ ( r '/' , MainHandler ) , ( r '/purchase' , PurchaseHandler ) , ] , * * settings ) |
1 |
XSRF tokens 和 AJAX 请求
AJAX 请求同样也要声明 _xsrf 变量,而且要在返回的页面中显式声明 _xsrf 的值.这个脚本可以查询客户端浏览器中的 cookie 值. 通过两个功能可以清楚得看到, AJAX POST 的请求中添加了 token 的值.第一个函数通过用户名获取 cookie ,这样第二个函数就可以很方便地添加 _xsrf 变量到存储数据的对象中,并传送给 postJSON 函数.1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function getCookie ( name ) { var c = document . cookie . match ( "\\b" + name + "=([^;]*)\\b" ) ; return c ? c [ 1 ] : undefined ; } jQuery . postJSON = function ( url , data , callback ) { data . _xsrf = getCookie ( "_xsrf" ) ; jQuery . ajax ( { url : url , data : jQuery . param ( data ) , dataType : "json" , type : "POST" , success : callback } ) ; } |
用户验证
现在我们来了解一下 XSRF 攻击的原理,然后学习如何设置和恢复安全的 cookies,我们以一个简单的用户验证系统做为示例,在这一段中,我们将会构建一个应用,询问访问者的用户名并将其存储到加密的 cookie 中以及如何恢复 cookie 数据.接下来的请求将会记录回访者的信息并显式一个特定的用户页面给回访者.你需要学习一些 login_url 的变量和 tornado.web.authenticated 修饰符的知识,这样可以去掉应用中一些常用的 headaches .例子:欢迎回来
在这个例子中,我们将通过加密的 cookie 中存储的数据直接识别用户信息.当某人通过特定的浏览器第一次访问我们的网站时(或者她的 cookie 已经过期),应用将会展现一个带有登录表单的页面,这个表单将会以 POST 请求的方式向 LoginHandler 地址提交信息.表单中的 POST 方法会调用 set_secure_cookie() 去存储请求中提交的用户名等参数. 这个有用户验证功能的 tornado 应用示例代码可以查看例6-2.我们将会在这一段落深入讨论其中的细节. LoginHandler 类会返回登录表单并设置 cookie 直到 LogoutHandler 类将其删除掉. 例子6-2:cookies.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import tornado . httpserver import tornado . ioloop import tornado . web import tornado . options import os.path from tornado . options import define , options define ( "port" , default = 8000 , help = "run on the given port" , type = int ) class BaseHandler ( tornado . web . RequestHandler ) : def get_current_user ( self ) : return self . get_secure_cookie ( "username" ) class LoginHandler ( BaseHandler ) : def get ( self ) : self . render ( 'login.html' ) def post ( self ) : self . set_secure_cookie ( "username" , self . get_argument ( "username" ) ) self . redirect ( "/" ) class WelcomeHandler ( BaseHandler ) : @ tornado . web . authenticated def get ( self ) : self . render ( 'index.html' , user = self . current_user ) class LogoutHandler ( BaseHandler ) : def get ( self ) : if ( self . get_argument ( "logout" , None ) ) : self . clear_cookie ( "username" ) self . redirect ( "/" ) if __name__ == "__main__" : tornado . options . parse_command_line ( ) settings = { "template_path" : os.path . join ( os.path . dirname ( __file__ ) , "templates" ) , "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" , "xsrf_cookies" : True , "login_url" : "/login" } application = tornado . web . Application ( [ ( r '/' , WelcomeHandler ) , ( r '/login' , LoginHandler ) , ( r '/logout' , LogoutHandler ) ] , * * settings ) http_server = tornado . httpserver . HTTPServer ( application ) http_server . listen ( options . port ) tornado . ioloop . IOLoop . instance ( ) . start ( ) |
1 |
1 2 3 4 | < h1 > Welcome back , { { user } } < / h1 > |
authenticated 修饰符
要想使用 tornado 的验证功能,我们必须要标记一个专用的 handlers 处理我们的用户登录请求.使用 @tornado.web.authenticated 修饰符可以让我们更方便地实现这个需求.只要在编写处理方法的时候附加上这个修饰符,tornado 将会在校验用户信息正确之后才会调用这个方法.让我们来看看例子中的 welcomehandler.它只有确认用户登录之后才会返回 index.html 的页面.1 2 3 4 | class WelcomeHandler ( BaseHandler ) : @ tornado . web . authenticated def get ( self ) : self . render ( 'index.html' , user = self . current_user ) |
current_user 属性
这个请求处理的类有 current_user 属性(它也可以用在其它 template 的上)可以用于存储正常请求中的用户身份验证信息.在默认情况下,这个值是None,如果要让 authenticated 修饰符成功识别用户信息,你必须重载正常用户请求处理的 get_current_user() 方法中的默认值. 在这个例子中,启用这个接口之后,我们可以从访问者的 cookie 中快速地恢复他的用户信息.很显然,大家都希望使用健壮性更强的技术,为了达到这个目的,我们必须使用下面几个方法:1 2 3 | class BaseHandler ( tornado . web . RequestHandler ) : def get_current_user ( self ) : return self . get_secure_cookie ( "username" ) |
login_url 设置
请留意这个应用中构造方法的内容.记住我们传给应用的新设置: Login_url 在应用中的地址为 login 形式.如果 get_current_user 方法返回了一个 falsy 值, authenticated 修饰符将会把请求处理重定向浏览器到登录页面的地址上.1 2 3 4 5 6 7 8 9 10 11 | settings = { "template_path" : os.path . join ( os.path . dirname ( __file__ ) , "templates" ) , "cookie_secret" : "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=" , "xsrf_cookies" : True , "login_url" : "/login" } application = tornado . web . Application ( [ ( r '/' , WelcomeHandler ) , ( r '/login' , LoginHandler ) , ( r '/logout' , LogoutHandler ) ] , * * settings ) |
总结
目前我们只看到使用两种技术来帮助我们保护 tornado应用.如何使用 @tornado.web.authenticated 修饰符去实现用户的身份验证.在第七章中,我们将会着眼于如何扩展我们已经讨论过的技术,将其应用到外部的 web 服务中.通过外部的web server例如 facebook 或 twitter 完成用户身份验证.博主渣基础,对于web应用还不够了解,未必能将作者的意图都翻译过来,如果其中有不够完善的地方,请帮忙纠正. 原创翻译:发布于http://blog.xihuan.de/tech/web/tornado/tornadowritingsecure_applications.html
上一篇: 翻译:introduce to tornado-Asynchronous Web Services
下一篇: 翻译: introduce to tornado - Authenticating with External Services
相关文章推荐
- 翻译:introduce to tornado-Asynchronous Web Services
- 翻译:introduce to tornado - form and template
- 翻译:introduce to tornado - Databases
- 翻译:introduce to tornado - Extending Templates
- 翻译:introduce to tornado - introduce
- 翻译:introduce to tornado - a simple example
- 解决eclipse存储git密码时Writing to secure store failed No password provided.错误
- Permission denial: writing to secure settings requires android.permission.WRITE_SECURE_SETTINGS
- Ways to introduce secure multi-tenancy to VMware/vCenter clusters via open source network virtualiza
- 解决 eclipse GIT Writing to secure store failed 问题 提交代码认证不通过
- 解决GIT Writing to secure store failed
- How to Setup and Secure Linux SSH Logins to use Private PEM Keys
- (翻译) 《C# to IL》第二章 IL基础
- (翻译) LINQ to SQL(Part 4 更新数据库) 自ScottGu
- Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously
- 【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程)
- ORA-01034: ORACLE not available/writing audit records to windows event log failed
- An Introduction to Conditional Random Fields论文摘要(翻译:Trey;审校:Shooya)
- 试着翻译一下ScottGu's Blog,LINQ to SQL (Part 5 - Binding UI using the ASP:LinqDataSource Control)
- ndk-build 编译报错 fatal error: error writing to -: Invalid argument } ^