在開發網路應用程式的過程中,我們有時候可能會有一些儲存資訊的需求,比如暫時儲存會員登入的資訊、保留待購清單等等議題,有些可能需要長期保存或是較為敏感的資料不適合公開,此時我們就會選擇放在資料庫當中來保存。
然而,並非所有的儲存需求都適合存在資料庫當中,也不是所有的儲存需求都會需要長時間的保存,因此瀏覽器也提供了一些儲存機制來讓我們使用,而今天我們要來看看在瀏覽器中有哪些常用到的儲存機制!
在瀏覽器中儲存機制主要可分為需要與伺服器溝通與不需要與伺服器溝通的兩種:
說到儲存機制一定要提的是 Cookie,而它的出現最主要是因為當我們在進行連線通訊時的協定是處於一種 無狀態(Stateless) 的設計。
也就是說當我輸入了 https://ithelp.ithome.com.tw/
網址後在與伺服器請求回應的階段當中,另一個人或甚至自己又開了一個頁試著請求同個網址,再向伺服器請求回應時,該網址所屬的伺服器並沒有辦法知道這三個連線狀態中是不是同一個人。
因此在認不出是誰的情況下,伺服器自然沒有辦法提供與個人有關的相關資訊,比如保持登入所需的驗證資料,總不可能你一開網站就平白無故幫你自動登入某個帳號,要這麼做的話他勢必一定要有個管道來取得與驗證這些資料。
以我最愛吃的麥當勞為例,當我們在櫃臺點好餐點後,櫃臺並不會問你說你是誰、也不會留下你的電話以及各種資料,而是發給你一個號碼牌。
有關於這個號碼牌的設計其實也沒有太複雜,他可以單純就只是一個數字加上發給你時的日期等等。而它唯一的作用就是透過它提供的大螢幕中,若你的號碼從 準備中
的列表移到 請取餐
的列表時,你就可以藉由這張號碼牌去領餐,他們也根本不會管是誰來拿。
在上面買餐的流程中,號碼牌就相當於扮演 cookie
的角色,而你(瀏覽器)便是藉由這張號碼牌來與配餐人員(伺服器)溝通,藉由這樣的方式,配餐人員可以大幅降低辨識的成本。
除此之外,cookie
所儲存的容量也真的就如同現實中的號碼牌一樣小而輕巧,同個網域最多大約能儲存 4k
容量的資料,再多的資訊也不可能全塞在同一小紙上,因此只會用來儲存一些較為關鍵的資訊,剩下的則是由其他儲存機制來負責。
這時候你可能會好奇,那他們要怎麼記得我們點的餐點呢?
而這個部分也不會太難,基本上在他替你點餐的時候,他的系統會將餐點的資訊與這個號碼綁定,比如我點了一個大杯的肥宅快樂水跟大薯條,此時伺服器就會存著這筆資料:
C8763: 大杯肥宅快樂水+大薯條
接著他只要負責給我號碼牌,下次我拿個號碼牌到櫃檯時,他就可以透過在系統中所儲存的 C8763
得知我要 大杯肥宅快樂水+大薯條
。
而 Session 就是指 櫃檯他們怎麼去記得這項資訊的方法,他可以是單純用紙條的方式去記(早餐店配桌號),他也可以是直接用喊的(小吃店),而有規模一點的店通常都是採用電腦記帳系統配發號碼牌。
有關於 Session 要怎麼實作就看各家廠商來決定了,例如我的拉麵愛店「樂麵屋」所採用的方式是 人數單位加 3 碼,例如一個人就是從 1001
開始配發,兩個人就從 2001
開始,而七個人以上就用 7001
開始算起。
會這麼做的原因在於他們可以透過號碼開頭來進行分配位置的分流,比如有兩個空位的情況下,雖然 3011
的顧客可能比 2006
的人還要早到,但位置就只適合分配給兩個人,因此他們可以選擇先讓 2006
的客人先行入座,這麼一來除了好安排之外,也讓不同人數的客人能夠知道目前所安排的進度。
然而看似很棒的號碼牌機制中,卻還是潛藏著一些問題⋯⋯
在發號碼牌的過程中,雖然主要是透過伺服器來設定 Set-Cookie
來發取號碼牌,但是號碼單其實是個可以被竄改的東西。
比如你隨便拿一張紙手寫 9
號,那麼他可以將牌轉過來變成 6
號或是自己拿筆改成 8
號。
而在瀏覽器中操作這些「號碼牌」更加地容易,我們只需要可以藉由 document.cookie
來取得當下網域的所有 cookie
。而 cookie
所儲存的方式是採用 String
的方式,因此在操作我們通常是藉由已經封裝好的函式來操作。
封裝取得 Cookie 的方法:
function getCookie (cname) {
let value = "; " + document.cookie;
let parts = value.split("; " + cname + "=");
if (parts.length == 2)
return parts.pop().split(";").shift();
}
封裝修改 Cookie 的方法:
function setCookie (cname, cvalue, exdays){
let d = new Date()
d.setTime(d.getTime() + (exdays*24*60*60*1000))
let expires = `expires=${d.toUTCString()}`
return document.cookie = `${cname}=${cvalue};${expires}`
}
封裝刪除 Cookie 的方法,而對於 Cookie 來說,當 expires
的時間到期時,它就會自動被刪除,因此我們可以藉由給他一個過期的時間來達成刪除的效果:
function deleteCookie (cname) {
return document.cookie = cname + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
透過上面的操作,我們可以很輕易的修改這些內容,並傳遞給伺服器,使其得到偽造過後的紀錄。
顯然這種 明文 紀錄的方式是不太安全的,儘管伺服器可以透過設定 HttpOnly
來限定只能用於資訊傳遞時的標頭(header)中,甚至設定 Secure
確保只在連線時符合 Https
協定的情況下才會顯示,但我們終究還是可以透過肉眼得知裡面的一些訊息。
這時候我們要做的事情就是在實作 Session 的過程當中,將這些資訊編碼,使人的肉眼沒辦法看出其中的規律,藉此來防止透過肉眼來得知一些資訊。
以上便是有關於需要與伺服器溝通的儲存機制,而明天我們繼續來看看另外一些不需要與伺服器溝通的儲存機制大概是在做什麼的。