用tornado做網(wǎng)站(5)

2018-02-24 15:48 更新

模板繼承

用前面的方法,已經(jīng)能夠很順利地編寫模板了。讀者如果留心一下,會(huì)覺得每個(gè)模板都有相同的部分內(nèi)容。在python中,有一種被稱之為“繼承”的機(jī)制(請(qǐng)閱讀本教程第貳季第肆章中的[類(4)(./209.md)中有關(guān)“繼承”講述]),它的作用之一就是能夠讓代碼重用。

在tornado的模板中,也能這樣。

先建立一個(gè)文件,命名為base.html,代碼如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Learning Python</title>
</head>
<body>
    <header>
        {% block header %}{% end %}
    </header>
    <content>
        {% block body %}{% end %}
    </content>
    <footer>
        {% set website = "<a >welcome to my website</a>" %}
        {% raw website %}
    </footer>
    <script src="https://atts.w3cschool.cn/attachments/image/cimg/jquery.min.js")}}"></script>
    <script src="https://atts.w3cschool.cn/attachments/image/cimg/script.js")}}"></script>
</body>
</html>

接下來(lái)就以base.html為父模板,依次改寫已經(jīng)有的index.html和user.html模板。

index.html代碼如下:

{% extends "base.html" %}

{% block header %}
    <h2>登錄頁(yè)面</h2>
    <p>用用戶名為:{{user}}登錄</p> 
{% end %}
{% block body %}
    <form method="POST">
        <p><span>UserName:</span><input type="text" id="username"/></p>
        <p><span>Password:</span><input type="password" id="password" /></p>
        <p><input type="BUTTON" value="登錄" id="login" /></p>
    </form>
{% end %}

user.html的代碼如下:

{% extends "base.html" %}

{% block header %}
    <h2>Your informations are:</h2>
{% end %}

{% block body %}
    <ul>
        {% for one in users %}
            <li>username:{{one[1]}}</li>
            <li>password:{{one[2]}}</li>
            <li>email:{{one[3]}}</li>
        {% end %}
    </ul>
{% end %}

看以上代碼,已經(jīng)沒有以前重復(fù)的部分了。{% extends "base.html" %}意味著以base.html為父模板。在base.html中規(guī)定了形式如同{% block header %}{% end %}這樣的塊語(yǔ)句。在index.html和user.html中,分別對(duì)塊語(yǔ)句中的內(nèi)容進(jìn)行了重寫(或者說(shuō)填充)。這就相當(dāng)于在base.html中做了一個(gè)結(jié)構(gòu),在子模板中按照這個(gè)結(jié)構(gòu)填內(nèi)容。

CSS

基本上的流程已經(jīng)差不多了,如果要美化前端,還需要使用css,它的使用方法跟js類似,也是在靜態(tài)目錄中建立文件即可。然后把下面這句加入到base.html的<head></head>中:

 <link rel="stylesheet" type="text/css" href="{{static_url("css/style.css")}}">

當(dāng)然,要在style.css中寫一個(gè)樣式,比如:

body {
    color:red;
}

然后看看前端顯示什么樣子了,我這里是這樣的:

關(guān)注字體顏色。

至于其它關(guān)于CSS方面的內(nèi)容,本教程就不重點(diǎn)講解了。讀者可以參考關(guān)于CSS的資料。

至此,一個(gè)簡(jiǎn)單的基于tornado的網(wǎng)站就做好了,雖然它很丑,但是它很有前途。因?yàn)樽x者只要按照上述的討論,可以在里面增加各種自己認(rèn)為可以增加的內(nèi)容。

建議讀者在上述學(xué)習(xí)基礎(chǔ)上,可以繼續(xù)完成下面的幾個(gè)功能:

  • 用戶注冊(cè)
  • 用戶發(fā)表文章
  • 用戶文章列表,并根據(jù)文章標(biāo)題查看文章內(nèi)容
  • 用戶重新編輯文章

在后續(xù)教程內(nèi)容中,也會(huì)涉及到上述功能。

cookie和安全

cookie是現(xiàn)在網(wǎng)站重要的內(nèi)容,特別是當(dāng)有用戶登錄的時(shí)候。所以,要了解cookie。維基百科如是說(shuō):

Cookie(復(fù)數(shù)形態(tài)Cookies),中文名稱為小型文字檔案或小甜餅,指某些網(wǎng)站為了辨別用戶身份而儲(chǔ)存在用戶本地終端(Client Side)上的數(shù)據(jù)(通常經(jīng)過(guò)加密)。定義於RFC2109。是網(wǎng)景公司的前雇員Lou Montulli在1993年3月的發(fā)明。

關(guān)于cookie的作用,維基百科已經(jīng)說(shuō)的非常詳細(xì)了(讀者還能正常訪問(wèn)這么偉大的網(wǎng)站嗎?):

因?yàn)镠TTP協(xié)議是無(wú)狀態(tài)的,即服務(wù)器不知道用戶上一次做了什么,這嚴(yán)重阻礙了交互式Web應(yīng)用程序的實(shí)現(xiàn)。在典型的網(wǎng)上購(gòu)物場(chǎng)景中,用戶瀏覽了幾個(gè)頁(yè)面,買了一盒餅干和兩瓶飲料。最后結(jié)帳時(shí),由于HTTP的無(wú)狀態(tài)性,不通過(guò)額外的手段,服務(wù)器并不知道用戶到底買了什么。 所以Cookie就是用來(lái)繞開HTTP的無(wú)狀態(tài)性的“額外手段”之一。服務(wù)器可以設(shè)置或讀取Cookies中包含信息,借此維護(hù)用戶跟服務(wù)器會(huì)話中的狀態(tài)。

在剛才的購(gòu)物場(chǎng)景中,當(dāng)用戶選購(gòu)了第一項(xiàng)商品,服務(wù)器在向用戶發(fā)送網(wǎng)頁(yè)的同時(shí),還發(fā)送了一段Cookie,記錄著那項(xiàng)商品的信息。當(dāng)用戶訪問(wèn)另一個(gè)頁(yè)面,瀏覽器會(huì)把Cookie發(fā)送給服務(wù)器,于是服務(wù)器知道他之前選購(gòu)了什么。用戶繼續(xù)選購(gòu)飲料,服務(wù)器就在原來(lái)那段Cookie里追加新的商品信息。結(jié)帳時(shí),服務(wù)器讀取發(fā)送來(lái)的Cookie就行了。

Cookie另一個(gè)典型的應(yīng)用是當(dāng)?shù)卿浺粋€(gè)網(wǎng)站時(shí),網(wǎng)站往往會(huì)請(qǐng)求用戶輸入用戶名和密碼,并且用戶可以勾選“下次自動(dòng)登錄”。如果勾選了,那么下次訪問(wèn)同一網(wǎng)站時(shí),用戶會(huì)發(fā)現(xiàn)沒輸入用戶名和密碼就已經(jīng)登錄了。這正是因?yàn)榍耙淮蔚卿洉r(shí),服務(wù)器發(fā)送了包含登錄憑據(jù)(用戶名加密碼的某種加密形式)的Cookie到用戶的硬盤上。第二次登錄時(shí),(如果該Cookie尚未到期)瀏覽器會(huì)發(fā)送該Cookie,服務(wù)器驗(yàn)證憑據(jù),于是不必輸入用戶名和密碼就讓用戶登錄了。

和任何別的事物一樣,cookie也有缺陷,比如來(lái)自偉大的維基百科也列出了三條:

  1. cookie會(huì)被附加在每個(gè)HTTP請(qǐng)求中,所以無(wú)形中增加了流量。
  2. 由于在HTTP請(qǐng)求中的cookie是明文傳遞的,所以安全性成問(wèn)題。(除非用HTTPS)
  3. Cookie的大小限制在4KB左右。對(duì)于復(fù)雜的存儲(chǔ)需求來(lái)說(shuō)是不夠用的。

對(duì)于用戶來(lái)講,可以通過(guò)改變?yōu)g覽器設(shè)置,來(lái)禁用cookie,也可以刪除歷史的cookie。但就目前而言,禁用cookie的可能不多了,因?yàn)樗傄诰W(wǎng)上買點(diǎn)東西吧。

Cookie最讓人擔(dān)心的還是由于它存儲(chǔ)了用戶的個(gè)人信息,并且最終這些信息要發(fā)給服務(wù)器,那么它就會(huì)成為某些人的目標(biāo)或者工具,比如有cookie盜賊,就是搜集用戶cookie,然后利用這些信息進(jìn)入用戶賬號(hào),達(dá)到個(gè)人的某種不可告人之目的;還有被稱之為cookie投毒的說(shuō)法,是利用客戶端的cookie傳給服務(wù)器的機(jī)會(huì),修改傳回去的值。這些行為常常是通過(guò)一種被稱為“跨站指令腳本(Cross site scripting)”(或者跨站指令碼)的行為方式實(shí)現(xiàn)的。偉大的維基百科這樣解釋了跨站腳本:

跨網(wǎng)站腳本(Cross-site scripting,通常簡(jiǎn)稱為XSS或跨站腳本或跨站腳本攻擊)是一種網(wǎng)站應(yīng)用程序的安全漏洞攻擊,是代碼注入的一種。它允許惡意用戶將代碼注入到網(wǎng)頁(yè)上,其他用戶在觀看網(wǎng)頁(yè)時(shí)就會(huì)受到影響。這類攻擊通常包含了HTML以及用戶端腳本語(yǔ)言。

XSS攻擊通常指的是通過(guò)利用網(wǎng)頁(yè)開發(fā)時(shí)留下的漏洞,通過(guò)巧妙的方法注入惡意指令代碼到網(wǎng)頁(yè),使用戶加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁(yè)程序。這些惡意網(wǎng)頁(yè)程序通常是JavaScript,但實(shí)際上也可以包括Java, VBScript, ActiveX, Flash 或者甚至是普通的HTML。攻擊成功后,攻擊者可能得到更高的權(quán)限(如執(zhí)行一些操作)、私密網(wǎng)頁(yè)內(nèi)容、會(huì)話和cookie等各種內(nèi)容。

cookie是好的,被普遍使用。在tornado中,也提供對(duì)cookie的讀寫函數(shù)。

set_cookie()get_cookie()是默認(rèn)提供的兩個(gè)方法,但是它是明文不加密傳輸?shù)摹?/p>

在index.py文件的IndexHandler類的post()方法中,當(dāng)用戶登錄,驗(yàn)證用戶名和密碼后,將用戶名和密碼存入cookie,代碼如下: def post(self): username = self.get_argument("username") password = self.get_argument("password") user_infos = mrd.select_table(table="users",column="*",condition="username",value=username) if user_infos: db_pwd = user_infos[0][2] if db_pwd == password: self.set_cookie(username,db_pwd) #設(shè)置cookie self.write(username) else: self.write("your password was not right.") else: self.write("There is no thi user.")

上面代碼中,較以前只增加了一句self.set_cookie(username,db_pwd),在回到登錄頁(yè)面,等候之后就成為:

看圖中箭頭所指,從左開始的第一個(gè)是用戶名,第二個(gè)是存儲(chǔ)的該用戶密碼。將我在登錄是的密碼就以明文的方式存儲(chǔ)在cookie里面了。

明文存儲(chǔ),顯然不安全。

tornado提供另外一種安全的方法:set_secure_cookie()和get_secure_cookie(),稱其為安全cookie,是因?yàn)樗悦魑募用芊绞絺鬏?。此外,跟set_cookie()的區(qū)別還在于, set_secure_cookie()執(zhí)行后的cookie保存在磁盤中,直到它過(guò)期為止。也是因?yàn)檫@個(gè)原因,即使關(guān)閉瀏覽器,在失效時(shí)間之間,cookie都一直存在。

要是用set_secure_cookie()方法設(shè)置cookie,要先在application.py文件的setting中進(jìn)行如下配置:

setting = dict(
    template_path = os.path.join(os.path.dirname(__file__), "templates"),
    static_path = os.path.join(os.path.dirname(__file__), "statics"),
    cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
    )

其中cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E="是為此增加的,但是,它并不是這正的加密,僅僅是一個(gè)障眼法罷了。

因?yàn)閠ornado會(huì)將cookie值編碼為Base-64字符串,并增加一個(gè)時(shí)間戳和一個(gè)cookie內(nèi)容的HMAC簽名。所以,cookie_secret的值,常常用下面的方式生成(這是一個(gè)隨機(jī)的字符串):

>>> import base64, uuid
>>> base64.b64encode(uuid.uuid4().bytes)
'w8yZud+kRHiP9uABEXaQiA=='

如果嫌棄上面的簽名短,可以用base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)獲取。這里得到的是一個(gè)隨機(jī)字符串,用它作為 cookie_secret值。

然后修改index.py中設(shè)置cookie那句話,變成:

self.set_secure_cookie(username,db_pwd)

從新跑一個(gè),看看效果。

啊哈,果然“密”了很多。

如果要獲取此cookie,用self.get_secure_cookie(username)即可。

這是不是就安全了。如果這樣就安全了,你太低估黑客們的技術(shù)實(shí)力了,甚至于用戶自己也會(huì)修改cookie值。所以,還不安全。所以,又有了httponly和secure屬性,用來(lái)防范cookie投毒。設(shè)置方法是:

self.set_secure_cookie(username, db_pwd, httponly=True, secure=True)

要獲取cookie,可以使用self.set_secure_cookie(username)方法,將這句放在user.py中某個(gè)適合的位置,并且可以用print語(yǔ)句打印出結(jié)果,就能看到變量username對(duì)應(yīng)的cookie了。這時(shí)候已經(jīng)不是那個(gè)“密”過(guò)的,是明文顯示。

用這樣的方法,瀏覽器通過(guò)SSL連接傳遞cookie,能夠在一定程度上防范跨站腳本攻擊。

XSRF

XSRF的含義是Cross-site request forgery,即跨站請(qǐng)求偽造,也稱之為"one click attack",通??s寫成CSRF或者XSRF,可以讀作"sea surf"。這種對(duì)網(wǎng)站的攻擊方式跟上面的跨站腳本(XSS)似乎相像,但攻擊方式不一樣。XSS利用站點(diǎn)內(nèi)的信任用戶,而XSRF則通過(guò)偽裝來(lái)自受信任用戶的請(qǐng)求來(lái)利用受信任的網(wǎng)站。與XSS攻擊相比,XSRF攻擊往往不大流行(因此對(duì)其 進(jìn)行防范的資源也相當(dāng)稀少)和難以防范,所以被認(rèn)為比XSS更具危險(xiǎn)性。

讀者要詳細(xì)了解XSRF,推薦閱讀:CSRF | XSRF 跨站請(qǐng)求偽造

對(duì)于防范XSRF的方法,上面推薦閱讀的文章中有明確的描述。還有一點(diǎn)需要提醒讀者,就是在開發(fā)應(yīng)用時(shí)需要深謀遠(yuǎn)慮。任何會(huì)產(chǎn)生副作用的HTTP請(qǐng)求,比如點(diǎn)擊購(gòu)買按鈕、編輯賬戶設(shè)置、改變密碼或刪除文檔,都應(yīng)該使用post()方法。這是良好的RESTful做法。

又一個(gè)新名詞:REST。這是一種web服務(wù)實(shí)現(xiàn)方案。偉大的維基百科中這樣描述:

表徵性狀態(tài)傳輸(英文:Representational State Transfer,簡(jiǎn)稱REST)是Roy Fielding博士在2000年他的博士論文中提出來(lái)的一種軟件架構(gòu)風(fēng)格。目前在三種主流的Web服務(wù)實(shí)現(xiàn)方案中,因?yàn)镽EST模式與復(fù)雜的SOAP和XML-RPC相比更加簡(jiǎn)潔,越來(lái)越多的web服務(wù)開始采用REST風(fēng)格設(shè)計(jì)和實(shí)現(xiàn)。例如,Amazon.com提供接近REST風(fēng)格的Web服務(wù)進(jìn)行圖書查找;雅虎提供的Web服務(wù)也是REST風(fēng)格的。

更詳細(xì)的內(nèi)容,讀者可網(wǎng)上搜索來(lái)了解。

此外,在tornado中,還提供了XSRF保護(hù)的方法。

在application.py文件中,使用xsrf_cookies參數(shù)開啟XSRF保護(hù)。

setting = dict(
    template_path = os.path.join(os.path.dirname(__file__), "templates"),
    static_path = os.path.join(os.path.dirname(__file__), "statics"),
    cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
    xsrf_cookies = True,
)

這樣設(shè)置之后,Tornado將拒絕請(qǐng)求參數(shù)中不包含正確的_xsrf值的post/put/delete請(qǐng)求。tornado會(huì)在后面悄悄地處理_xsrf?cookies,所以,在表單中也要包含XSRF令牌以卻表請(qǐng)求合法。比如index.html的表單,修改如下:

{% extends "base.html" %}

{% block header %}
    <h2>登錄頁(yè)面</h2>
    <p>用用戶名為:{{user}}登錄</p> 
{% end %}
{% block body %}
    <form method="POST">
        {% raw xsrf_form_html() %}
        <p><span>UserName:</span><input type="text" id="username"/></p>
        <p><span>Password:</span><input type="password" id="password" /></p>
        <p><input type="BUTTON" value="登錄" id="login" /></p>
    </form>
{% end %}

{% raw xsrf_form_html() %}是新增的,目的就在于實(shí)現(xiàn)上面所說(shuō)的授權(quán)給前端以合法請(qǐng)求。

前端向后端發(fā)送的請(qǐng)求是通過(guò)ajax(),所以,在ajax請(qǐng)求中,需要一個(gè)_xsrf參數(shù)。

以下是script.js的代碼

 function getCookie(name){
    var x = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return x ? x[1]:undefined;
}

$(document).ready(function(){
    $("#login").click(function(){
        var user = $("#username").val();
        var pwd = $("#password").val();
        var pd = {"username":user, "password":pwd, "_xsrf":getCookie("_xsrf")};
        $.ajax({
            type:"post",
            url:"/",
            data:pd,
            cache:false,
            success:function(data){
                window.location.href = "/user?user="+data;
            },
            error:function(){
                alert("error!");
            },
        });
    });
});

函數(shù)getCookie()的作用是得到cookie值,然后將這個(gè)值放到向后端post的數(shù)據(jù)中var pd = {"username":user, "password":pwd, "_xsrf":getCookie("_xsrf")};。運(yùn)行的結(jié)果:

這是tornado提供的XSRF防護(hù)方法。是不是這樣做就高枕無(wú)憂了呢?沒這么簡(jiǎn)單。要做好一個(gè)網(wǎng)站,需要考慮的事情還很多。特別推薦閱讀WebAppSec/Secure Coding Guidelines

常常聽到人說(shuō)做個(gè)網(wǎng)站怎么怎么簡(jiǎn)單,客戶用這種說(shuō)辭來(lái)壓低價(jià)格,老板用這種說(shuō)辭來(lái)縮短工時(shí)成本,從上面的簡(jiǎn)單敘述中,你覺得網(wǎng)站還是隨便幾個(gè)頁(yè)面就完事了嗎?除非那個(gè)網(wǎng)站不是給人看的,是在那里擺著的。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)