Session(傳統(tǒng)的用戶認證):
關鍵:session_id & remember_user_token
session_id 是一個 key,session 可以存在 file 或 cache 中
只要開啟 session,無論用戶是否登錄,rails(或其他后端框架)每次都會向客戶端傳遞一個 session_id
一個新的訪客由于在 request 中沒有攜帶 session_id,所以服務端會生成一個新的 session 并向客戶端返回 session_id,如果 request 中攜帶了有效的 session_id,那么就不會生成新的 session
如果用戶登錄,那么舊的 session 就會被銷毀,服務端會生成新的 session,并告訴客戶端存儲新的 session_id
因為 session 不能永久存儲(否則 session 只會越來越多),所以是需要過期的,假設 session 存儲 1 小時,那么 1 小時后用戶就會自動丟失登錄信息,所以有了 remember_user_token,它是存在數(shù)據(jù)庫中的
如果用戶在 request 中沒有攜帶 session_id 或者 session_id 對應的值已經過期被銷毀了,那么就可以通過 request 中的 remember_user_token 來獲取用戶信息,并且創(chuàng)建一個新的 session,返回給客戶端一個 session_id
這就是原本的 session 用戶認證機制,相比于下面的 jwt-auth,session 的優(yōu)勢在于它可以在 session-value 中存儲額外的數(shù)據(jù)
JWT-Auth(令牌認證):
關鍵:token-ttl( token 存儲時效) && token-refresh-ttl ( token 可刷新時效)
與 session 不同,只有登錄后的用戶才會有 token
用戶登錄后,服務端給客戶端返回一個 token,這個 token 需要客戶端手動存儲起來( cookie 或 localStorage )
用戶每次 request 的時候,都要在 header 中攜帶 token,服務端通過 token 獲取用戶
token 與 session 相同,都是會過期的,這個過期時間是 token-ttl,只要超過了 token-ttl,服務端就會在存儲中刪除相關的 token,與 session 相同
如果此時用戶再次攜帶 token 來請求,那么就需要 token-refresh-ttl 了,它和 session 中的 remember_user_token 起相同的作用,只不過它不存儲在數(shù)據(jù)庫中,而是分發(fā)給了客戶端(服務端將 token 解密之后就可以拿到 token-refresh-ttl 了),只要 token-refresh-ttl 還在時效范圍內,服務端就會在 response 的 header 中返回一個新的 token,客戶端保存起來就可以了,如果 token-refresh-ttl 也過期了,那么就需要重新登錄了
相比于 session 的用戶認證機制,它的優(yōu)勢在于跨終端( web,App )
CSRF(跨站點訪問攻擊)防御:
如果使用 session 的認證機制,rails 或者其他框架會在 session-value 中存儲一個 csrf_token 的值,類似這樣(猜想):
'session_id': {
'user_id': 'xxx',
'csrf_token': 'xxx',
'expired_at': 'xxxxx',
'other': 'xxxx',
'user-agent': 'xxx',
'accept-language': 'xxx'
}
所以當用戶發(fā)送 GET-request 時,服務端就會返回兩個數(shù)據(jù)在 response 的 header 的 Set-Cookie 中,一個是 session_id,一個是 csrf_token
當用戶發(fā)送 POST 請求時,需要攜帶 csrf_token 這個頭,以及 cookie 中的 session_id,(PS:如果客戶端在 cookie 中也攜帶了 csrf_token,會優(yōu)先使用 cookie 中的值)然后服務端通過 session_id 來獲取 [server-side] csrf_token 與 [client-side] csrf_token 做比較,只要相同就是一個有效的請求
當用戶發(fā)送一個有效的 POST / PUT / DELETE 請求后,服務端原有的 csrf_token 就會更新,并且會攜帶在 response 的 header 中,讓客戶端更新,如果用戶發(fā)送 GET 請求,csrf_token 不需要驗證也不需要更新,這就是現(xiàn)有的 CSRF 的防御原理
因為 JWT-Auth 無法在服務端存儲額外的信息,所以 JWT-Auth 無法防御 CSRF,如果使用 JWT-Auth 又要防御 CSRF,就需要搭配 session 了
SSR 中的 Auth 與 CSRF:
經過上面的分析,如果 SSR 中,endpoint(API的服務端) 仍然使用的是 session 認證機制,那么需要做 CSRF 防護,只需要將 endpoint 發(fā)給 SSR 服務器的 session_id、remember_user_token、csrf_token 原樣傳給客戶端就行了,SSR 服務器只起到一個中轉的作用
部分后端框架戶對 session 的 user-agent 和 accept-language 等 header 頭做防劫持,因此最好還是把整個 header 轉發(fā)過去比較好
如果是 JWT-Auth 的 SSR,要分 endpoint 是 Node.js 和非 Node.js 的情況
待續(xù)。