c++學習之類模板
1、token是什麼?
token是一種使用者標識,表示使用者身份,類似於我們的身份證件。
Token 是在服務端產生的。如果前端使用使用者名稱/密碼向服務端請求認證,服務端認證成功,那麼在服務端會返回 Token 給前端。前端可以在每次請求的時候帶上 Token 證明自己的合法地位。如果這個 Token 在服務端持久化(比如存入資料庫),那它就是一個永久的身份令牌。
2、為什麼要使用token?
先來了解一下token認證與session認證的區別:
a) Session認證Session認證的方案為:第一次使用者認證請求通過時,伺服器端儲存使用者的登入資訊,然後在響應時傳遞給瀏覽器,瀏覽器儲存為cookie,下次請求時把cookie傳送給伺服器,伺服器根據cookie資訊來識別是哪個使用者。Session認證適用於單個伺服器的場合,如果使用者增多,需要部署多個伺服器時,Session認證就會暴露問題。因為每個使用者在認證後,伺服器都會記錄Session,一般儲存在記憶體中,隨著認證使用者增多,伺服器開銷也會增大;而且認證使用者的後續請求都需要到這臺儲存了自己Session的伺服器上驗證Cookie。在多叢集分散式的場合,這就造成了效能瓶頸,對負載均衡、應用的擴充套件都會造成影響。(不能多服務共享)
b) Token認證Token認證也是無狀態的,不需要在服務端儲存認證使用者的資訊,它的認證流程為:使用者使用使用者名稱密碼請求伺服器;伺服器驗證使用者,如果驗證通過就發給使用者一個token;客戶端儲存token,並在之後的每次請求都附上token;服務端驗證token、返回資料。Token認證機制不需要儲存認證使用者的資訊、也不需要考錄用戶在那臺伺服器登入,可以很好地適應應用的擴充套件(可以多服務共享)。token的體積很小,請求服務端時,可以被附在URL、Header或是Post引數中。而且token中包含了使用者相關的必要、非私密的相信,免去了服務端再次查詢資料庫的開銷
另外,token認證能解決以下問題:
- Token 完全由應用管理,所以它可以避開同源策略
- Token 可以避免 CSRF 攻擊
- Token 可以是無狀態的,可以在多個服務間共享
現在詳細說明以下roken為什麼能夠避免CSRF攻擊呢?CSRF攻擊又是什麼意思?
2.1、CSRF攻擊
CSRF(Cross-site request forgery)也被稱為 one-click attack或者 session riding,中文全稱是叫跨站請求偽造。一般來說,攻擊者通過偽造使用者的瀏覽器的請求,向訪問一個使用者自己曾經認證訪問過的網站傳送出去,使目標網站接收並誤以為是使用者的真實操作而去執行命令。常用於盜取賬號、轉賬、傳送虛假訊息等。攻擊者利用網站對請求的驗證漏洞而實現這樣的攻擊行為,網站能夠確認請求來源於使用者的瀏覽器,卻不能驗證請求是否源於使用者的真實意願下的操作行為。
2.2、token如何避免CSRF攻擊
CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都是存在於 cookie 中,因此黑客可以在不知道這些驗證資訊的情況下直接利用使用者自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於 cookie 之中。可以在 HTTP 請求中以引數的形式加入一個隨機產生的 token,並在伺服器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。
3、使用JWT生成和驗證token
以下程式碼僅僅用於示意,並不是完整版。
3.1、採用HS256演算法生成和驗證
後臺生成token:
const jwt = require('jsonwebtoken')
const fs = require('fs')
// Token 資料
const payload = {
name: 'wanghao',
admin: true
}
/**
* HS256
* 用 HS256 演算法生成與驗證 JWT
*/
// 金鑰
const secret = 'ILOVENINGHAO'
// 簽發 Token
const token = jwt.sign(payload, secret, { expiresIn: '1day' })
// 輸出簽發的 Token
console.log('HS256 演算法:', token)
攔截器驗證:
// 驗證 Token
jwt.verify(token, secret, (error, decoded) => {
if (error) {
console.log(error.message)
return
}
console.log(decoded)//輸出解碼後的資料
})
3.2、採用RS256演算法生成和驗證
後臺生成:
/**
* RS256
* 用 RS256 演算法生成與驗證 JWT
* ssh-keygen -t rsa -b 2048 -f private.key//生成私鑰
* openssl rsa -in private.key -pubout -outform PEM -out public.key//生成公鑰
*/
// 獲取簽發 JWT 時需要用的金鑰
const privateKey = fs.readFileSync('./config/private.key')
// 簽發 Token
const tokenRS256 = jwt.sign(payload, privateKey, { algorithm: 'RS256' })
// 輸出簽發的 Token
console.log('RS256 演算法:', tokenRS256)
// 獲取驗證 JWT 時需要用的公鑰
const publicKey = fs.readFileSync('./config/public.key')
攔截器驗證:
// 驗證 Token
jwt.verify(tokenRS256, publicKey, (error, decoded) => {
if (error) {
console.log(error.message)
return
}
console.log(decoded)
})
資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com
4、token進階
需要設定有效期嗎?
對於這個問題,我們不妨先看兩個例子。一個例子是登入密碼,一般要求定期改變密碼,以防止洩漏,所以密碼是有有效期的;另一個例子是安全證書。SSL 安全證書都有有效期,目的是為了解決吊銷的問題,對於這個問題的詳細情況,來看看知乎的回答(http://dwz.cn/7joMhq)。所以無論是從安全的角度考慮,還是從吊銷的角度考慮,Token 都需要設有效期。
那麼有效期多長合適呢?
只能說,根據系統的安全需要,儘可能的短,但也不能短得離譜——想像一下手機的自動熄屏時間,如果設定為 10 秒鐘無操作自動熄屏,再次點亮需要輸入密碼,會不會瘋?如果你覺得不會,那就親自試一試,設定成可以設定的最短時間,堅持一週就好(不排除有人適應這個時間,畢竟手機廠商也是有使用者體驗研究的)。
然後新問題產生了,如果使用者在正常操作的過程中,Token 過期失效了,要求使用者重新登入……使用者體驗豈不是很糟糕?
為了解決在操作過程不能讓使用者感到 Token 失效這個問題,有一種方案是在伺服器端儲存 Token 狀態,使用者每次操作都會自動重新整理(推遲) Token 的過期時間——Session 就是採用這種策略來保持使用者登入狀態的。然而仍然存在這樣一個問題,在前後端分離、單頁 App 這些情況下,每秒種可能發起很多次請求,每次都去重新整理過期時間會產生非常大的代價。如果 Token 的過期時間被持久化到資料庫或檔案,代價就更大了。所以通常為了提升效率,減少消耗,會把 Token 的過期時儲存在快取或者記憶體中。
還有另一種方案,使用 Refresh Token,它可以避免頻繁的讀寫操作。這種方案中,服務端不需要重新整理 Token 的過期時間,一旦 Token 過期,就反饋給前端,前端使用 Refresh Token 申請一個全新 Token 繼續使用。這種方案中,服務端只需要在客戶端請求更新 Token 的時候對 Refresh Token 的有效性進行一次檢查,大大減少了更新有效期的操作,也就避免了頻繁讀寫。當然 Refresh Token 也是有有效期的,但是這個有效期就可以長一點了,比如,以天為單位的時間。
時序圖表示
使用 Token 和 Refresh Token 的時序圖如下:
1)登入
2)業務請求
3)Token 過期,重新整理 Token
上面的時序圖中並未提到 Refresh Token 過期怎麼辦。不過很顯然,Refresh Token 既然已經過期,就該要求使用者重新登入了。
當然還可以把這個機制設計得更復雜一些,比如,Refresh Token 每次使用的時候,都更新它的過期時間,直到與它的建立時間相比,已經超過了非常長的一段時間(比如三個月),這等於是在相當長一段時間內允許 Refresh Token 自動續期。
到目前為止,Token 都是有狀態的,即在服務端需要儲存並記錄相關屬性。那說好的無狀態呢,怎麼實現?
無狀態 Token
如果我們把所有狀態資訊都附加在 Token 上,伺服器就可以不儲存。但是服務端仍然需要認證 Token 有效。不過只要服務端能確認是自己簽發的 Token,而且其資訊未被改動過,那就可以認為 Token 有效——“簽名”可以作此保證。平時常說的簽名都存在一方簽發,另一方驗證的情況,所以要使用非對稱加密演算法。但是在這裡,簽發和驗證都是同一方,所以對稱加密演算法就能達到要求,而對稱演算法比非對稱演算法要快得多(可達數十倍差距)。
更進一步思考,對稱加密演算法除了加密,還帶有還原加密內容的功能,而這一功能在對 Token 簽名時並無必要——既然不需要解密,摘要(雜湊)演算法就會更快。可以指定密碼的雜湊演算法,自然是 HMAC。
上面說了這麼多,還需要自己去實現嗎?不用!JWT 已經定義了詳細的規範,而且有各種語言的若干實現。
不過在使用無狀態 Token 的時候在服務端會有一些變化,服務端雖然不儲存有效的 Token 了,卻需要儲存未到期卻已登出的 Token。如果一個 Token 未到期就被使用者主動登出,那麼伺服器需要儲存這個被登出的 Token,以便下次收到使用這個仍在有效期內的 Token 時判其無效。有沒有感到一點沮喪?
在前端可控的情況下(比如前端和服務端在同一個專案組內),可以協商:前端一但登出成功,就丟掉本地儲存(比如儲存在記憶體、LocalStorage 等)的 Token 和 Refresh Token。基於這樣的約定,伺服器就可以假設收到的 Token 一定是沒登出的(因為登出之後前端就不會再使用了)。
如果前端不可控的情況,仍然可以進行上面的假設,但是這種情況下,需要儘量縮短 Token 的有效期,而且必須在使用者主動登出的情況下讓 Refresh Token 無效。這個操作存在一定的安全漏洞,因為使用者會認為已經登出了,實際上在較短的一段時間內並沒有登出。如果應用設計中,這點漏洞並不會造成什麼損失,那採用這種策略就是可行的。
在使用無狀態 Token 的時候,有兩點需要注意:
Refresh Token 有效時間較長,所以它應該在伺服器端有狀態,以增強安全性,確保使用者登出時可控
應該考慮使用二次認證來增強敏感操作的安全性
到此,關於 Token 的話題似乎差不多了——然而並沒有,上面說的只是認證服務和業務服務整合在一起的情況,如果是分離的情況呢?
分離認證服務
當 Token 無狀態之後,單點登入就變得容易了。前端拿到一個有效的 Token,它就可以在任何同一體系的服務上認證通過——只要它們使用同樣的金鑰和演算法來認證 Token 的有效性。就樣這樣:
當然,如果 Token 過期了,前端仍然需要去認證服務更新 Token:
可見,雖然認證和業務分離了,實際即並沒產生多大的差異。當然,這是建立在認證伺服器信任業務伺服器的前提下,因為認證伺服器產生 Token 的金鑰和業務伺服器認證 Token 的金鑰和演算法相同。換句話說,業務伺服器同樣可以建立有效的 Token。
如果業務伺服器不能被信任,該怎麼辦?
不受信的業務伺服器
遇到不受信的業務伺服器時,很容易想到的辦法是使用不同的金鑰。認證伺服器使用金鑰1簽發,業務伺服器使用金鑰2驗證——這是典型非對稱加密簽名的應用場景。認證伺服器自己使用私鑰對 Token 簽名,公開公鑰。信任這個認證伺服器的業務伺服器儲存公鑰,用於驗證簽名。幸好,JWT 不僅可以使用 HMAC 簽名,也可以使用 RSA(一種非對稱加密演算法)簽名。
不過,當業務伺服器已經不受信任的時候,多個業務伺服器之間使用相同的 Token 對使用者來說是不安全的。因為任何一個伺服器拿到 Token 都可以仿冒使用者去另一個伺服器處理業務……悲劇隨時可能發生。
為了防止這種情況發生,就需要在認證伺服器產生 Token 的時候,把使用該 Token 的業務伺服器的資訊記錄在 Token 中,這樣當另一個業務伺服器拿到這個 Token 的時候,發現它並不是自己應該驗證的 Token,就可以直接拒絕。
現在,認證伺服器不信任業務伺服器,業務伺服器相互也不信任,但前端是信任這些伺服器的——如果前端不信任,就不會拿 Token 去請求驗證。那麼為什麼會信任?可能是因為這些是同一家公司或者同一個專案中提供的若干服務構成的服務體系。
但是,前端信任不代表使用者信任。如果 Token 不沒有攜帶使用者隱私(比如姓名),那麼使用者不會關心信任問題。但如果 Token 含有使用者隱私的時候,使用者得關心信任問題了。這時候認證服務就不得不再囉嗦一些,當用戶請求 Token 的時候,問上一句,你真的要授權給某某某業務服務嗎?而這個“某某某”,使用者怎麼知道它是不是真的“某某某”呢?使用者當然不知道,甚至認證服務也不知道,因為公鑰已經公開了,任何一個業務都可以宣告自己是“某某某”。
為了得到使用者的信任,認證服務就不得不幫助使用者來甄別業務服務。所以,認證服器決定不公開公鑰,而是要求業務服務先申請註冊並通過稽核。只有通過稽核的業務伺服器才能得到認證服務為它建立的,僅供它使用的公鑰。如果該業務服務洩漏公鑰帶來風險,由該業務服務自行承擔。現在認證服務可以清楚的告訴使用者,“某某某”服務是什麼了。如果使用者還是不夠信任,認證服務甚至可以問,某某某業務服務需要請求 A、B、C 三項個人資料,其中 A 是必須的,不然它不工作,是否允許授權?如果你授權,我就把你授權的幾項資料加密放在 Token 中……