鍍金池/ 教程/ Python/ 用 tornado 做網(wǎng)站 (5)
標(biāo)準(zhǔn)庫 (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫 (6)
標(biāo)準(zhǔn)庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實(shí)戰(zhàn)
運(yùn)算符
類(3)
字典(2)
語句(1)
數(shù)和四則運(yùn)算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級
字符串(3)
為計算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫 (2)
列表(2)
元組

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

模板繼承

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

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

先建立一個文件,命名為 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="{{static_url("js/jquery.min.js")}}"></script>
    <script src="{{static_url("js/script.js")}}"></script>
</body>
</html>

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

index.html 代碼如下:

{% extends "base.html" %}

{% block header %}
    <h2>登錄頁面</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 %}這樣的塊語句。在 index.html 和 user.html 中,分別對塊語句中的內(nèi)容進(jìn)行了重寫(或者說填充)。這就相當(dāng)于在 base.html 中做了一個結(jié)構(gòu),在子模板中按照這個結(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 中寫一個樣式,比如:

body {
    color:red;
}

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

http://wiki.jikexueyuan.com/project/start-learning-python/images/30701.png" alt="" />

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

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

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

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

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

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

cookie 和安全

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

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

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

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

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

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

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

  1. cookie 會被附加在每個 HTTP 請求中,所以無形中增加了流量。
  2. 由于在 HTTP 請求中的 cookie 是明文傳遞的,所以安全性成問題。(除非用 HTTPS)
  3. Cookie 的大小限制在 4KB 左右。對于復(fù)雜的存儲需求來說是不夠用的。

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

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

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

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

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

set_cookie()get_cookie() 是默認(rèn)提供的兩個方法,但是它是明文不加密傳輸?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),在回到登錄頁面,等候之后就成為:

http://wiki.jikexueyuan.com/project/start-learning-python/images/30702.png" alt="" />

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

明文存儲,顯然不安全。

tornado 提供另外一種安全的方法:set_secure_cookie() 和 get_secure_cookie(),稱其為安全 cookie,是因?yàn)樗悦魑募用芊绞絺鬏敗4送?,?set_cookie() 的區(qū)別還在于, set_secure_cookie() 執(zhí)行后的 cookie 保存在磁盤中,直到它過期為止。也是因?yàn)檫@個原因,即使關(guān)閉瀏覽器,在失效時間之間,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="是為此增加的,但是,它并不是這正的加密,僅僅是一個障眼法罷了。

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

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

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

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

self.set_secure_cookie(username,db_pwd)

從新跑一個,看看效果。

http://wiki.jikexueyuan.com/project/start-learning-python/images/30703.png" alt="" />

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

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

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

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

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

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

XSRF

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

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

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

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

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

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

此外,在 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 將拒絕請求參數(shù)中不包含正確的_xsrf 值的 post/put/delete 請求。tornado 會在后面悄悄地處理_xsrf cookies,所以,在表單中也要包含 XSRF 令牌以卻表請求合法。比如 index.html 的表單,修改如下:

{% extends "base.html" %}

{% block header %}
    <h2>登錄頁面</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)上面所說的授權(quán)給前端以合法請求。

前端向后端發(fā)送的請求是通過 ajax(),所以,在 ajax 請求中,需要一個 _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 值,然后將這個值放到向后端 post 的數(shù)據(jù)中 var pd = {"username":user, "password":pwd, "_xsrf":getCookie("_xsrf")};。運(yùn)行的結(jié)果:

http://wiki.jikexueyuan.com/project/start-learning-python/images/30704.png" alt="" />

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

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


總目錄   |   上節(jié):用 tornado 做網(wǎng)站 (4)   |   下節(jié):用 tornado 做網(wǎng)站 (6)

如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。