# 6.1 session和cookie
session和cookie是網站瀏覽中較為常見的兩個概念,也是比較難以辨析的兩個概念,但它們在瀏覽需要認證的服務頁面以及頁面統計中卻相當關鍵。我們先來了解一下session和cookie怎么來的?考慮這樣一個問題:
如何抓取一個訪問受限的網頁?如新浪微博好友的主頁,個人微博頁面等。
顯然,通過瀏覽器,我們可以手動輸入用戶名和密碼來訪問頁面,而所謂的“抓取”,其實就是使用程序來模擬完成同樣的工作,因此我們需要了解“登陸”過程中到底發生了什么。
當用戶來到微博登陸頁面,輸入用戶名和密碼之后點擊“登錄”后瀏覽器將認證信息POST給遠端的服務器,服務器執行驗證邏輯,如果驗證通過,則瀏覽器會跳轉到登錄用戶的微博首頁,在登錄成功后,服務器如何驗證我們對其他受限制頁面的訪問呢?因為HTTP協議是無狀態的,所以很顯然服務器不可能知道我們已經在上一次的HTTP請求中通過了驗證。當然,最簡單的解決方案就是所有的請求里面都帶上用戶名和密碼,這樣雖然可行,但大大加重了服務器的負擔(對于每個request都需要到數據庫驗證),也大大降低了用戶體驗(每個頁面都需要重新輸入用戶名密碼,每個頁面都帶有登錄表單)。既然直接在請求中帶上用戶名與密碼不可行,那么就只有在服務器或客戶端保存一些類似的可以代表身份的信息了,所以就有了cookie與session。
cookie,簡而言之就是在本地計算機保存一些用戶操作的歷史信息(當然包括登錄信息),并在用戶再次訪問該站點時瀏覽器通過HTTP協議將本地cookie內容發送給服務器,從而完成驗證,或繼續上一步操作。

圖6.1 cookie的原理圖
session,簡而言之就是在服務器上保存用戶操作的歷史信息。服務器使用session id來標識session,session id由服務器負責產生,保證隨機性與唯一性,相當于一個隨機密鑰,避免在握手或傳輸中暴露用戶真實密碼。但該方式下,仍然需要將發送請求的客戶端與session進行對應,所以可以借助cookie機制來獲取客戶端的標識(即session id),也可以通過GET方式將id提交給服務器。

圖6.2 session的原理圖
## cookie
Cookie是由瀏覽器維持的,存儲在客戶端的一小段文本信息,伴隨著用戶請求和頁面在Web服務器和瀏覽器之間傳遞。用戶每次訪問站點時,Web應用程序都可以讀取cookie包含的信息。瀏覽器設置里面有cookie隱私數據選項,打開它,可以看到很多已訪問網站的cookies,如下圖所示:

圖6.3 瀏覽器端保存的cookie信息
cookie是有時間限制的,根據生命期不同分成兩種:會話cookie和持久cookie;
如果不設置過期時間,則表示這個cookie生命周期為從創建到瀏覽器關閉止,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽會話期的cookie被稱為會話cookie。會話cookie一般不保存在硬盤上而是保存在內存里。
如果設置了過期時間(setMaxAge(60*60*24)),瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie依然有效直到超過設定的過期時間。存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內存的cookie,不同的瀏覽器有不同的處理方式。
### Go設置cookie
Go語言中通過net/http包中的SetCookie來設置:
http.SetCookie(w ResponseWriter, cookie *Cookie)
w表示需要寫入的response,cookie是一個struct,讓我們來看一下cookie對象是怎么樣的
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs
}
我們來看一個例子,如何設置cookie
expiration := time.Now()
expiration = expiration.AddDate(1, 0, 0)
cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration}
http.SetCookie(w, &cookie)
### Go讀取cookie
上面的例子演示了如何設置cookie數據,我們這里來演示一下如何讀取cookie
cookie, _ := r.Cookie("username")
fmt.Fprint(w, cookie)
還有另外一種讀取方式
for _, cookie := range r.Cookies() {
fmt.Fprint(w, cookie.Name)
}
可以看到通過request獲取cookie非常方便。
## session
session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話是從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個session。然而當session一詞與網絡協議相關聯時,它又往往隱含了“面向連接”和/或“保持狀態”這樣兩個含義。
session在Web開發環境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器端之間保持狀態的解決方案。有時候Session也用來指這種解決方案的存儲結構。
session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。
但程序需要為某個客戶端的請求創建一個session的時候,服務器首先檢查這個客戶端的請求里是否包含了一個session標識-稱為session id,如果已經包含一個session id則說明以前已經為此客戶創建過session,服務器就按照session id把這個session檢索出來使用(如果檢索不到,可能會新建一個,這種情況可能出現在服務端已經刪除了該用戶對應的session對象,但用戶人為地在請求的URL后面附加上一個JSESSION的參數)。如果客戶請求不包含session id,則為此客戶創建一個session并且同時生成一個與此session相關聯的session id,這個session id將在本次響應中返回給客戶端保存。
session機制本身并不復雜,然而其實現和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗當作普遍適用的。
## 小結
如上文所述,session和cookie的目的相同,都是為了克服http協議無狀態的缺陷,但完成的方法不同。session通過cookie,在客戶端保存session id,而將用戶的其他會話消息保存在服務端的session對象中,與此相對的,cookie需要將所有信息都保存在客戶端。因此cookie存在著一定的安全隱患,例如本地cookie中保存的用戶名密碼被破譯,或cookie被其他網站收集(例如:1. appA主動設置域B cookie,讓域B cookie獲取;2. XSS,在appA上通過javascript獲取document.cookie,并傳遞給自己的appB)。
通過上面的一些簡單介紹我們了解了cookie和session的一些基礎知識,知道他們之間的聯系和區別,做web開發之前,有必要將一些必要知識了解清楚,才不會在用到時捉襟見肘,或是在調bug時候如無頭蒼蠅亂轉。接下來的幾小節我們將詳細介紹session相關的知識。
## links
* [目錄](<preface.md>)
* 上一節: [session和數據存儲](<06.0.md>)
* 下一節: [Go如何使用session](<06.2.md>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料