一、Cookie 在前端中的實踐
1.搭建Demo環境
找個文件夾,npm init,然后如果沒有安裝過express,再npm install express -D,然后新建一個main.js,執行node main.js即可啟動服務。注意如果修改了js腳本,需要重新執行node main.js。
const express = require('express')
const app = express()
app.listen(3000, err => {
if (err) {
return console.log(err)
}
console.log('---- 打開 http://localhost:3000 吧----')
})
app.get('/', (req, res) => {
res.send('<h1>hello world!</h1>')
})
2.在介紹 Cookie 是什么之前,我們來看看 Cookie 是如何工作的
- 首先,我們假設當前域名下還是沒有 Cookie 的
- 接下來,瀏覽器發送了一個請求給服務器(這個請求是還沒帶上 Cookie 的)
- 服務器設置 Cookie 并發送給瀏覽器(當然也可以不設置)
- 瀏覽器將 Cookie 保存下來
- 接下來,以后的每一次請求,都會帶上這些 Cookie,發送給服務器
app.get('/', (req, res) => {
// 服務器接收到請求,在給響應設置一個 Cookie
// 這個 Cookie 的 name 為 testName
// value 為 testValue
res.cookie('testName', 'testValue')
res.send('<h1>hello world!</h1>')
})
改一下代碼,就可以驗證以上邏輯了,第一次Request Headers 并沒有 Cookie 這個字段,后面刷新就一直有了。當然,如果我們多設置幾個cookie,請求時也會匯總帶上。
app.get('/', (req, res) => {
res.cookie('testName', 'testValue')
res.cookie('testName22', 'testValue')
res.cookie('testName33', 'testValue')
res.cookie('myName', 'cuixu')
res.send('<h1>hello world!</h1>')
})
3.什么是 Cookie
說了這么多,大家應該知道 Cookie 是什么吧。整理一下有以下幾個點:
- Cookie 就是瀏覽器儲存在用戶電腦上的一小段文本文件
- Cookie 是純文本格式,不包含任何可執行的代碼
- Cookie 由鍵值對構成,由分號和空格隔開
- Cookie 雖然是存儲在瀏覽器,但是通常由服務器端進行設置
- Cookie 的大小限制在 4kb 左右
4.Cookie 的屬性選項
expires / max-age 都是控制 Cookie 失效時刻的選項。
// 這個 Cookie 設置十秒后失效
res.cookie('testName0', 'testValue0', {
expires: new Date(Date.now() + 10000)
})
在控制臺輸入下面的代碼
console.log(`現在的 cookie 是:${document.cookie}`)
setTimeout(() => {
console.log(`5 秒后的 cookie 是:${document.cookie}`)
}, 5000)
setTimeout(() => {
console.log(`10 秒后的 cookie 是:${document.cookie}`)
}, 10000)
可以發現,10秒失效后,自動就沒了,當然以后發送請求也不會再帶上這個失效的 Cookie 了。
expires 是 http/1.0 協議中的選項,在新的 http/1.1 協議中 expires 已經由 max-age 選項代替,兩者的作用都是限制 Cookie 的有效時間。expires 的值是一個時間點 (Cookie 失效時刻 = expires),而 max-age 的值是一個以秒為單位時間段 (Cookie 失效時刻 = 創建時刻 + max-age)。如果同時設置了 max-age 和 expires,以 max-age 的時間為準。
res.cookie('testName0', 'testValue0', {
// express 這個參數是以毫秒來做單位的
// 實際發送給瀏覽器就會轉換為秒
// 十秒后失效
maxAge: 10000
})
name
、domain
和 path
可以標識一個唯一的 Cookie。domain
和 path
兩個選項共同決定了 Cookie 何時被瀏覽器自動添加到請求頭部中發送出去。具體是什么原理請看 Cookie 的作用域和作用路徑 這個章節。如果沒有設置這兩個選項,則會使用默認值。domain
的默認值為設置該 Cookie 的網頁所在的域名,path
默認值為設置該 Cookie 的網頁所在的目錄。
secure 選項用來設置 Cookie 只在確保安全的請求中才會發送。當請求是 HTTPS 或者其他安全協議時,包含 secure 選項的 Cookie 才能被保存到瀏覽器或者發送至服務器。默認情況下,Cookie 不會帶 secure 選項(即為空)。所以默認情況下,不管是 HTTPS 協議還是 HTTP 協議的請求,Cookie 都會被發送至服務端。
httpOnly這個選項用來設置 Cookie 是否能通過 js 去訪問。默認情況下,Cookie 不會帶 httpOnly 選項(即為空),客戶端是可以通過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie 的。當 Cookie 帶 httpOnly 選項時,客戶端則無法通過 js 代碼去訪問(包括讀取、修改、刪除等)這個 Cookie。
5.設置 Cookie
明確一點:Cookie 可以由服務端設置,也可以由客戶端設置??吹竭@里相信大家都可以理解了吧。在網頁即客戶端中我們也可以通過 js 代碼來設置 Cookie。
- 設置
document.cookie = 'name=value'
- 可以設置 Cookie 的下列選項:expires、domain、path,各個鍵值對之間都要用 ; 和 空格 隔開:
document.cookie='name=value; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/';
- 只有在 https 協議的網頁中,客戶端設置 secure 類型的 Cookie 才能成功
- 客戶端中無法設置 HttpOnly 選項
- Cookie 的 name、path 和 domain 是唯一標識一個 Cookie 的。我們只要將一個 Cookie 的 max-age 設置為 0,就可以刪除一個 Cookie 了。
let removeCookie = (name, path, domain) => {
document.cookie = `${name}=; path=${path}; domain=${domain}; max-age=0`
}
6.Cookie 的作用域
在說這個作用域之前,我們先來對域名做一個簡單的了解。
子域,是相對父域來說的,指域名中的每一個段。各子域之間用小數點分隔開。放在域名最后的子域稱為最高級子域,或稱為一級域,在它前面的子域稱為二級域。
以下圖為例,news.163.com 和 sports.163.com 是子域,163.com 是父域。當 Cookie 的 domain 為 news.163.com,那么訪問 news.163.com 的時候就會帶上 Cookie;當 Cookie 的 domain 為 163.com,那么訪問 news.163.com 和 sports.163.com 就會帶上 Cookie
7.Cookie 的作用路徑
當 Cookie 的 domain 是相同的情況下,也有是否帶上 Cookie 也有一定的規則。在子路徑內可以訪問訪問到父路徑的 Cookie,反過來就不行。
看看例子,還是先修改 main.js
app.get('/parent', (req, res) => {
res.cookie('parent-name', 'parent-value', {
path: '/parent'
})
res.send('<h1>父路徑!</h1>')
})
app.get('/parent/childA', (req, res) => {
res.cookie('child-name-A', 'child-value-A', {
path: '/parent/childA'
})
res.send('<h1>子路徑A!</h1>')
})
app.get('/parent/childB', (req, res) => {
res.cookie('child-name-B', 'child-value-B', {
path: '/parent/childB'
})
res.send('<h1>子路徑B!</h1>')
})
參考文章
二、一文帶你看懂cookie,面試前端不用愁
1.存放哪些數據
當客戶端要發送http請求時,瀏覽器會先檢查下是否有對應的cookie。有的話,則自動地添加在request header中的cookie字段。注意,每一次的http請求時,如果有cookie,瀏覽器都會自動帶上cookie發送給服務端。那么把什么數據放到cookie中就很重要了,因為很多數據并不是每次請求都需要發給服務端,畢竟會增加網絡開銷,浪費帶寬。所以對于那設置“每次請求都要攜帶的信息(最典型的就是身份認證信息)”就特別適合放在cookie中,其他類型的數據就不適合了。
2.localStorage和sessionStorage
在較高版本的瀏覽器中,js提供了兩種存儲方式:sessionStorage和globalStorage。在H5中,用localStorage取代了globalStorage。
sessionStorage用于本地存儲一個會話中的數據,這些數據只有在同一個會話中的頁面才能訪問,并且當會話結束后,數據也隨之銷毀。所以sessionStorage僅僅是會話級別的存儲,而不是一種持久化的本地存儲。
localStorage是持久化的本地存儲,除非是通過js刪除,或者清除瀏覽器緩存,否則數據是永遠不會過期的。
瀏覽器的支持情況:IE7及以下版本不支持web storage,其他都支持。不過在IE5、IE6、IE7中有個userData,其實也是用于本地存儲。這個持久化數據放在緩存中,只有不清理緩存,就會一直存在。
三、知乎 COOKIE和SESSION有什么區別?
由于HTTP協議是無狀態的協議,所以服務端需要記錄用戶的狀態時,就需要用某種機制來識具體的用戶,這個機制就是Session.典型的場景比如購物車,當你點擊下單按鈕時,由于HTTP協議無狀態,所以并不知道是哪個用戶操作的,所以服務端要為特定的用戶創建了特定的Session,用用于標識這個用戶,并且跟蹤用戶,這樣才知道購物車里面有幾本書。這個Session是保存在服務端的,有一個唯一標識。在服務端保存Session的方法很多,內存、數據庫、文件都有。集群的時候也要考慮Session的轉移,在大型的網站,一般會有專門的Session服務器集群,用來保存用戶會話,這個時候 Session 信息都是放在內存的,使用一些緩存服務比如Memcached之類的來放 Session。
思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次創建Session的時候,服務端會在HTTP協議中告訴客戶端,需要在 Cookie 里面記錄一個Session ID,以后每次請求把這個會話ID發送到服務器,我就知道你是誰了。有人問,如果客戶端的瀏覽器禁用了 Cookie 怎么辦?一般這種情況下,會使用一種叫做URL重寫的技術來進行會話跟蹤,即每次HTTP交互,URL后面都會被附加上一個諸如 sid=xxxxx 這樣的參數,服務端據此來識別用戶。
Cookie其實還可以用在一些方便用戶的場景下,設想你某次登陸過一個網站,下次登錄的時候不想再次輸入賬號了,怎么辦?這個信息可以寫到Cookie里面,訪問網站的時候,網站頁面的腳本可以讀取這個信息,就自動幫你把用戶名給填了,能夠方便一下用戶。這也是Cookie名稱的由來,給用戶的一點甜頭。
所以,總結一下:Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集群、數據庫、文件中;Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。
四、徹底理解cookie,session,token
1.發展史
很久很久以前,Web 基本上就是文檔的瀏覽而已, 既然是瀏覽,作為服務器, 不需要記錄誰在某一段時間里都瀏覽了什么文檔,每次請求都是一個新的HTTP協議, 就是請求加響應, 尤其是我不用記住是誰剛剛發了HTTP請求, 每個請求對我來說都是全新的。這段時間很嗨皮。
但是隨著交互式Web應用的興起,像在線購物網站,需要登錄的網站等等,馬上就面臨一個問題,那就是要管理會話,必須記住哪些人登錄系統, 哪些人往自己的購物車中放商品, 也就是說我必須把每個人區分開,這就是一個不小的挑戰,因為HTTP請求是無狀態的,所以想出的辦法就是給大家發一個會話標識(session id), 說白了就是一個隨機的字串,每個人收到的都不一樣, 每次大家向我發起HTTP請求的時候,把這個字符串給一并捎過來, 這樣我就能區分開誰是誰了
這樣大家很嗨皮了,可是服務器就不嗨皮了,每個人只需要保存自己的session id,而服務器要保存所有人的session id ! 如果訪問服務器多了, 就得由成千上萬,甚至幾十萬個。
這對服務器說是一個巨大的開銷 , 嚴重的限制了服務器擴展能力, 比如說我用兩個機器組成了一個集群, 小F通過機器A登錄了系統, 那session id會保存在機器A上, 假設小F的下一次請求被轉發到機器B怎么辦? 機器B可沒有小F的 session id啊。
有時候會采用一點小伎倆: session sticky , 就是讓小F的請求一直粘連在機器A上, 但是這也不管用, 要是機器A掛掉了, 還得轉到機器B去。
那只好做session 的復制了, 把session id 在兩個機器之間搬來搬去, 快累死了。
后來有個叫Memcached的支了招: 把session id 集中存儲到一個地方, 所有的機器都來訪問這個地方的數據, 這樣一來,就不用復制了, 但是增加了單點失敗的可能性, 要是那個負責session 的機器掛了, 所有人都得重新登錄一遍, 估計得被人罵死。
也嘗試把這個單點的機器也搞出集群,增加可靠性, 但不管如何, 這小小的session 對我來說是一個沉重的負擔
于是有人就一直在思考, 我為什么要保存這可惡的session呢, 只讓每個客戶端去保存該多好?
可是如果不保存這些session id , 怎么驗證客戶端發給我的session id 的確是我生成的呢? 如果不去驗證,我們都不知道他們是不是合法登錄的用戶, 那些不懷好意的家伙們就可以偽造session id , 為所欲為了。
嗯,對了,關鍵點就是驗證 !
比如說, 小F已經登錄了系統, 我給他發一個令牌(token), 里邊包含了小F的 user id, 下一次小F 再次通過Http 請求訪問我的時候, 把這個token 通過Http header 帶過來不就可以了。
不過這和session id沒有本質區別啊, 任何人都可以可以偽造, 所以我得想點兒辦法, 讓別人偽造不了。
那就對數據做一個簽名吧, 比如說我用HMAC-SHA256 算法,加上一個只有我才知道的密鑰, 對數據做一個簽名, 把這個簽名和數據一起作為token , 由于密鑰別人不知道, 就無法偽造token了。
這個token 我不保存, 當小F把這個token 給我發過來的時候,我再用同樣的HMAC-SHA256 算法和同樣的密鑰,對數據再計算一次簽名, 和token 中的簽名做個比較, 如果相同, 我就知道小F已經登錄過了,并且可以直接取到小F的user id , 如果不相同, 數據部分肯定被人篡改過, 我就告訴發送者: 對不起,沒有認證。
Token 中的數據是明文保存的(雖然我會用Base64做下編碼, 但那不是加密), 還是可以被別人看到的, 所以我不能在其中保存像密碼這樣的敏感信息。
當然, 如果一個人的token 被別人偷走了, 那我也沒辦法, 我也會認為小偷就是合法用戶, 這其實和一個人的session id 被別人偷走是一樣的。
這樣一來, 我就不保存session id 了, 我只是生成token , 然后驗證token , 我用我的CPU計算時間獲取了我的session存儲空間 !
解除了session id這個負擔, 可以說是無事一身輕, 我的機器集群現在可以輕松地做水平擴展, 用戶訪問量增大, 直接加機器就行。 這種無狀態的感覺實在是太好了!
以下參考前端應該知道的web登錄
前面說到sessionId的方式本質是把用戶狀態信息維護在server端,token的方式就是把用戶的狀態信息加密成一串token傳給前端,然后每次發請求時把token帶上,傳回給服務器端;服務器端收到請求之后,解析token并且驗證相關信息;
所以跟第一種登錄方式最本質的區別是:通過解析token的計算時間換取了session的存儲空間
業界通用的加密方式是jwt(json web token),jwt的具體格式如圖:
簡單的介紹一下jwt,它主要由3部分組成:
header 頭部
{
"alg": "HS256",
"typ": "JWT"
}
payload 負載
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1555341649998
}
signature 簽名
header里面描述加密算法和token的類型,類型一般都是JWT;
payload里面放的是用戶的信息,也就是第一種登錄方式中需要維護在服務器端session中的信息;
signature是對前兩部分的簽名,也可以理解為加密;實現需要一個密鑰(secret),這個secret只有服務器才知道,然后使用header里面的算法按照如下方法來加密:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
總之,最后的 jwt = base64url(header) + "." + base64url(payload) + "." + signature
jwt可以放在response中返回,也可以放在cookie中返回,這都是具體的返回方式,并不重要。
客戶端發起請求時,官方推薦放在HTTP header中:
Authorization: Bearer <token>
這樣子確實也可以解決cookie跨域的問題,不過具體放在哪兒還是根據業務場景來定,并沒有一定之規。
五、Web技術:Token與Session究竟是什么呢
本文通俗易懂,基本是上述內容的重述。文末總結有點意思:
wa: 先不提問,我再說幾個結論,在提問。。
- token 是無狀態的,后端不需要記錄信息,每次請求每次解密就行。
- session 是有狀態的,需要后端每次去檢索id的有效性。不同的session都需要進行保存哦。但讓也可以設置單點登錄,減少保存的數據。
- session與token的問題是空間與時間博弈,為什么這么說呢,是因為token不需要保存,直接獲取,每次訪問都需要進行解密。
好了就先說這幾條吧,以后有了在補充。
琪琪:總結了這么多了,那我們開始提問了。首先,為啥客戶端ios與Andriod基本上沒見過用session的?
ff: 這個我來回答吧。因為在這些客戶端上啊,原生接口都是每一次建立一個會話,這就出問題了,這樣會導致登錄功能失效了,登錄每次把信息放到session中,session都不一樣了,每次登錄都成新的一個人了,這就不ok了。
琪琪:哦哦原來客戶端是每次用原生接口都是新建立一個會話,好尷尬的設計。那我們采用什么方式解決這個問題呢?
ff: 在用戶登錄后,重點哦我們可以通過cookie嘛,我們在app端也可以是存儲cookie的,我們知道cookie將sessionID保存好,返回給客戶端,服務器最后也是通過SessionId來標識的。但是利用token的話,內容我們可以自定義,并且不用再服務端進行保存。方便我們處理。
琪琪:那么就是token可以保存到cookie中,如果禁止的話我們也可以保存到body中每次都請求上或者header中。