【譯】發送表單數據

多數時候,HTML表單的目的只是爲了把數據發給服務器,以後服務器再處理這些數據併發送響應給用戶。雖然看起來挺簡單的,但咱們仍是得注意一些事情以確保傳送的數據不會破壞服務器、或者給你的用戶製造麻煩。前端

數據會到哪裏去

關於客戶端/服務器架構

整個web都是基於一種基本的客戶端/服務器架構,該架構能夠概括以下:python

一個客戶端(一般是Web瀏覽器)使用HTTP協議發送一個請求給服務器(一般是web服務器程序,譬如Apache, Nginx, IIS, Tomcat等等),而服務器則以相同的協議響應這個請求。mysql

在客戶端,HTML表單只是提供一種比較方便且用戶友好的方式,用來配置發送給服務器的HTTP請求。這樣用戶就能夠本身提供能被HTTP請求傳送的信息。nginx

客戶端:定義如何發送數據

<form>元素可以定義其數據如何被髮送,它全部的特性都是爲了在用戶點擊發送按鈕時,讓你配置要發送的請求。其中最重要的兩個特性是actionmethodgit

action特性

該特性定義了數據會被髮往何處,它的值必須是個合法的URL。若該特性未指定,則數據會發送到包含該表單的頁面所在的URL。web

示例
在下面的例子中,數據會發送至http://foo.comsql

<form action="http://foo.com">

這裏,數據會被髮送到表單頁所在的服務器,但到達的倒是服務器上不一樣的URL:chrome

<form action="/somewhere_else">

以下,當不指定任何特性時,表單數據會給發送到包含該表單的的頁面:

<form>

許多老舊的頁面會使用下面的符號來代表,數據得被髮送到包含該表單的的頁面;這在當時是必須的,由於直到HTML5以前,action特性都是必填的。但如今就再也不須要了。

<form action="#">

注意:能夠指定一個使用HTTPS(安全的HTTP)協議的URL,此時數據會隨請求的其餘部分一塊兒加密,即便表單自己位於一個經過HTTP訪問的不安全頁面。此外,若表單位於一個安全的頁面,而你卻給action特性指定了一個不安全的HTTP URL,則全部的瀏覽器會在每次用戶要發送數據時給他們一個安全警告,由於此時這些數據將不被加密。

method特性

該特性定義了數據如何被髮送。HTTP協議提供了幾種方式來執行一個請求;HTML表單數據能夠經過其中至少方式來發送:GET和POST。

要理解這兩種方式的不一樣,咱們得回過頭來來看下HTTP是如何工做的。當你想取得Web上的某個資源時,瀏覽器會發送一個請求給指定的URL。一個HTTP請求含有兩個部分:包含和瀏覽器功能有關的一系列全局字段的請求頭,以及包含要給服務器處理的信息的請求體。

GET方法

瀏覽器使用GET方法來請求服務器發回指定的資源:「嘿服務器,我想得到這個資源」。這種狀況下,瀏覽器只會發送一個空的請求體,而正因如此,若瀏覽器使用該方式,那麼發給服務器的數據會給追加到URL後面。

示例
考慮以下表單:

<form action="http://foo.com" method="get">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

使用GET方法時,HTTP請求看起來就這樣:

GET /?say=Hi&to=Mom HTTP/1.1
Host: foo.com
POST方法

POST方法則稍有不一樣,瀏覽器發送這個方法給服務器,用以請求一個和HTTP請求體裏數據有關的響應:「嘿服務器,看看這些數據而後給我發回一個適當的結果」。若表單使用該方法發送,則數據會給追加到HTTP請求體裏。

示例
考慮以下表單(和上面那個同樣):

<form action="http://foo.com" method="post">
  <input name="say" value="Hi">
  <input name="to" value="Mom">
  <button>Send my greetings</button>
</form>

使用POST方法時,HTTP請求看起來就這樣:

POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

Content-length頭部字段指示了請求體的大小,而Content-Type字段則標識了發往服務器的資源類型。咱們將在稍後討論下這些請求頭。

固然,HTTP請求是不會展現給用戶看的(若你想看到它們,還得使用諸如火狐的Web Console或者chrome Developer Tools等工具),惟一展現給用戶的,只有訪問的URL。因此使用GET請求時,用戶將會在他們的地址欄看到數據,而使用POST請求則看不到。這點相當重要,緣由以下:

  1. 若你要發送密碼(或者任何敏感數據),那千萬別用GET方法,不然該數據會不安全地展現在地址欄上。

  2. 若你想要發送大量數據,最好用POST方法,由於一些瀏覽器會限制URL的大小。此外,許多服務器也會限制接收的URL長度。

服務器端:處理數據

不論你選擇哪一種HTTP方法,服務器只會接收到一個字符串並將其解析,再以鍵/值對列表的形式獲取數據。而如何訪問這個列表,取決於你基於何種開發平臺、以及用了何種框架。你使用的技術也會決定如何處理重複的鍵名,一般某個鍵名最後接收到的值是優先被選取的。

示例:原生PHP

PHP提供了幾個全局對象來處理數據。假設你使用POST方法,下面的示例會直接提取你的數據並展現給用戶。固然,要如何處理數據取決於你,你能夠展現它、將其存進數據庫、用郵件發送它、或者其餘任何方式。

<?php
  // 全局變量$_POST讓你可以訪問用POST方法發送的數據
  // 要訪問用GET方法發送的數據,可使用$_GET
  $say = htmlspecialchars($_POST['say']);
  $to  = htmlspecialchars($_POST['to']);

  echo  $say, ' ', $to;

這個示例會用咱們發送的數據生成一個頁面。考慮咱們前面用的表單示例數據,輸出結果會是:

Hi Mom

示例:原生Python

下面的示例使用Python來作相同的事---將給定的數據展現到web頁面上。其中使用了CGI Python package 來處理表單數據。

#!/usr/bin/env python
import html
import cgi
import cgitb; cgitb.enable()     # 用於處理錯誤

print("Content-Type: text/html") # 請求頭字段,標識後面的內容是HTML
print()                          # 空行,表示請求頭的結束

form = cgi.FieldStorage()
say  = html.escape(form["say"].value);
to   = html.escape(form["to"].value);

print(say, " ", to)

結果和以前用PHP處理是同樣的:

Hi Mom

其它語言和框架

還有許多其餘的服務端技術能夠用來處理表單,好比Perl, Java, .Net, Ruby等等,選擇你最喜歡的一種就好。咱們不多直接使用這些技術,由於這麼作得須要不少技巧來填坑;一般咱們會在衆多好用的框架中選擇一種,這樣會讓表單的處理更容易些,好比:

值得注意的是,就算用了這些框架,處理表單是不必定就會變得輕鬆。但至少這樣用起來會更好些,還能節省你很多時間。

特殊案例:發送文件

文件是HTML表單中一個特殊的例子,其餘數據都是文本數據,而文件則通常是、或者被認爲是二進制數據。因爲HTTP是個文本協議,因此對處理二進制數據得有特別的要求。

enctype特性

該特性能讓你指定HTTP請求頭中的Content-Type字段值,這個字段的重要性在於,它能告訴服務器要發送的數據類型。其默認值是application/x-www-form-urlencoded,對應的解釋是:「這份表單數據已被編碼爲URL格式」。

而當你想發送文件時,得先作兩件事:

  • 將method特性設置爲POST,由於使用表單時,文件內容是不能被放到URL參數裏的

  • enctype特性的值設爲multipart/form-data,這樣數據就會被分割爲多個部分,每一個文件都會追加上和他們一塊兒發送的表單有關的文本。

示例:

<form method="post" enctype="multipart/form-data">
  <input type="file" name="myFile">
  <button>Send the file</button>
</form>

注意:某些瀏覽器支持<input>元素的multiple特性,以便讓一個input元素能發送多個文件。至於服務器會如何處理這些文件,就得取決於它用來什麼技術了。如前所述,使用框架能讓你的活的輕鬆些~

警告:爲防止濫用,許多服務器會對文件和HTTP請求設置大小限制。因此,最好在發送文件以前和服務器管理員覈實一下這個限制。

安全相關

每次要發數據給服務器前,你都得考慮下安全問題。HTML表單是針對服務器的首要攻擊載體之一,但該危害的來源並不是HTML表單自己,而在於服務器如何處理數據。

常見的安全問題

著名的安全問題有不少,如何劃分取決於你在作什麼:

XSS和CSRF

跨站腳本攻擊(XSS)和跨站請求僞造(CSRF)是最多見的攻擊類型,它們會在你展現由用戶發給用戶的數據時發生。

XSS讓攻擊者能再其餘用戶訪問的Web頁面上注入客戶端腳本。攻擊者會利用跨站腳本的脆弱性來繞過訪問控制策略,譬如同源策略。這種攻擊能夠取得從小麻煩到嚴重安全危機不等的危害效果。

CSRF很像XSS,由於它們都以相同的方式開始---注入客戶端腳本到Web頁面,但它們的攻擊目標卻不一樣。CSRF攻擊者會試着升級權限以成爲一個高權限的用戶(好比網站管理員),而後執行本不可以執行的動做(如把數據發送給不受信任的用戶)。

XSS攻擊利用了用戶對網站的信任,而CSRF攻擊則利用了網站對其用戶的信任。

要防止此類攻擊,就得時常校驗用戶發送給服務器的數據;同時(若是須要展現)也儘可能別展現用戶提供的HTML內容,而應該處理用戶提供的數據,以免將其原封不動地顯示出來。目前幾乎全部市面上的的框架,至少都會實現一個過濾器,用以移除用戶提交數據中<script>, <iframe>, <object>等標籤,這樣有助於減輕風險,但並不意味着會根除它。

SQL注入

SQL注入是一種對目標網站的數據庫執行動做的攻擊方式。一般攻擊者會發送一段SQL請求,並但願服務器能執行它(多數發生在應用服務器想存儲數據之時)。這實際上已成爲針對web站點的主要攻擊載體之一

該攻擊的危害是很嚴重的,小到數據丟失、大到被攻擊者經過權限升級訪問整個網站架構。這確實是很是重大的威脅,因此你不該該存儲那些用戶提交而沒通過特殊處理(例如,在PHP/MySQL架構下通過mysql_real_escape_string()處理)的數據。

HTTP頭部注入和郵件注入

這種攻擊會在你的應用使用用戶在表單上輸入的數據來構造HTTP頭、或者email時發生。該攻擊雖然不會危害你的服務器或者影響你的用戶,但卻會給更深處的問題大開方便之門,好比會話劫持、釣魚攻擊。

全部這些攻擊每每都是悄無聲息地發生的,並且會把你的服務器弄成肉雞)。

偏執些:永遠別信任你的用戶

因此,要如何對抗這些威脅呢?這一點已超出本指南的主題範圍了,但有幾條規則時須要咱們牢記的。最重要的一條就是:永遠別信任你的用戶,包括你本身;即便是受信任的用戶也會有被劫持可能。

全部的到達你服務器的數據都必須被校驗並處理,並且要一直保持,不能有例外。

  • 過濾潛在的危險字符。要關注的哪些特定字符,取決於使用數據的上下文、也取決於你使用的服務器平臺,而全部的服務端語言都會提供相應的功能。

  • 限制傳入的數據量,只容許有必要的。

  • 把上傳的文件放沙盒裏(將它們存儲到放到一個不一樣的服務器上,而且要訪問到它們只有經過一個不一樣的子域名、或一個徹底不一樣的域名才行)。

若你能遵循這三條規則,就應該能避免絕大多數問題,但一個更好的辦法是讓一個有資格的第三方來作安全審查,別覺得你能看透全部潛在的問題。

結論

如你所見,發送表單數據時很簡單的,但保障一個應用的安全就須要不少技術了。前端開發者不是那種去定義一個數據安全模型的角色,雖然可能得執行[客戶端數據校驗](),可是服務器也不能信任這些校驗,由於它並不能確切知道客戶端上到底發生過什麼。

參見

若你想學習更多關於wep應用安全防禦的知識,能夠深刻了解下面這些資源:

相關文章
相關標籤/搜索