1. JWT是什么
JSON Web Token(縮寫 JWT)晕拆,一個規(guī)范用于用戶和服務(wù)器之間傳遞安全可靠的信息。原理為服務(wù)端生成一個帶有用戶標(biāo)識的令牌返回給客戶端嘀掸,客戶端請求的時候再帶上這個令牌紫岩,服務(wù)端根據(jù)這個令牌來確認(rèn)當(dāng)前用戶是誰。同時使用簽名保證數(shù)據(jù)的安全性(不被篡改)睬塌。這樣做就能省掉服務(wù)端的session記錄泉蝌,連用戶狀態(tài)都不需要再關(guān)心。
思考下可能存在的問題:
- jwt泄露揩晴,導(dǎo)致用戶信息泄露勋陪,以及操作權(quán)被使用
- jwt不能涉及用戶關(guān)鍵數(shù)據(jù),如果涉及需要將jwt進(jìn)行加密硫兰。
- 縮短jwt有效期
- 使用https
- 重要服務(wù)進(jìn)行二次驗(yàn)證
- jwt存儲在哪诅愚?客戶端能否統(tǒng)一處理讓所有請求帶上這個令牌?
- 存儲在cookie中,自動實(shí)現(xiàn)統(tǒng)一處理劫映。但沒法解決跨域問題
- 設(shè)在http-header中违孝,如 Authorization: Bearer <token> 。所有的http請求包含表單泳赋、ajax雌桑、同步跳轉(zhuǎn)都要加上這個header.
- jwt偽造,如果簽名密鑰泄露出去了那么jwt就可以被偽造
- 簽名密鑰泄露并且被偽造token摹蘑,這個在服務(wù)端是無法感知的筹燕。只能加強(qiáng)對secret的管理,并且對重要服務(wù)進(jìn)行二次校驗(yàn)。
- 后管怎么強(qiáng)制將某個jwt失效撒踪?無法做到類似踢出某個用戶的動作
- 使用JWT的方式就是存在這種問題过咬、無法解決。因?yàn)閿?shù)據(jù)都不存儲在服務(wù)端制妄。一旦頒發(fā)token只有等待失效掸绞。
2. JWT結(jié)構(gòu)
- Header(頭部)
- Payload(消息體)
- Signature(簽名)
JWT由上面3個部分組成。
頭信息
指定了該JWT使用的簽名算法:
header = '{"alg":"HS256","typ":"JWT"}'
HS256表示使用了 HMAC-SHA256 來生成簽名耕捞。
消息體
包含了令牌的意圖:
payload = '{"sub":"admin","iat":1422779638}'//iat表示令牌生成的時間
- iss (issuer):簽發(fā)人
- exp (expiration time):過期時間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時間
- iat (Issued At):簽發(fā)時間
- jti (JWT ID):編號
簽名
是用base64url
編碼的頭信息和消息體拼接(使用”.”分隔) 再通過私有的secret計(jì)算而來
key = 'secretkey'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)
最后得到整個token數(shù)據(jù)為
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
3. JWT使用場景
一次性驗(yàn)證
比如用戶注冊后需要發(fā)一封郵件讓其激活賬戶衔掸,通常郵件中需要有一個鏈接,這個鏈接需要具備以下的特性:能夠標(biāo)識用戶俺抽,該鏈接具有時效性(通常只允許幾小時之內(nèi)激活)敞映,不能被篡改以激活其他可能的賬戶…這種場景就和 jwt 的特性非常貼近,jwt 的 payload 中固定的參數(shù):iss 簽發(fā)者和 exp 過期時間正是為其做準(zhǔn)備的磷斧。restful api的無狀態(tài)認(rèn)證
使用 jwt 來做 restful api 的身份認(rèn)證也是值得推崇的一種使用方案振愿。客戶端和服務(wù)端共享 secret弛饭;過期時間由服務(wù)端校驗(yàn)冕末,客戶端定時刷新;簽名信息不可被修改…spring security oauth jwt 提供了一套完整的 jwt 認(rèn)證體系侣颂,以筆者的經(jīng)驗(yàn)來看:使用 oauth2 或 jwt 來做 restful api 的認(rèn)證都沒有大問題档桃,oauth2 功能更多,支持的場景更豐富憔晒,后者實(shí)現(xiàn)簡單(后者的實(shí)現(xiàn)還是需要覆蓋認(rèn)證的場景藻肄。如果不認(rèn)證,jwt就沒有用戶標(biāo)識)。使用 jwt 做單點(diǎn)登錄+會話管理(不推薦)
4. JWT特點(diǎn)
(1)JWT 默認(rèn)是不加密拒担,但也是可以加密的仅炊。生成原始 Token 以后,可以用密鑰再加密一次澎蛛。
(2)JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫入 JWT蜕窿。
(3)JWT 不僅可以用于認(rèn)證谋逻,也可以用于交換信息。有效使用 JWT桐经,可以降低服務(wù)器查詢數(shù)據(jù)庫的次數(shù)毁兆。
(4)JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài)阴挣,因此無法在使用過程中廢止某個 token气堕,或者更改 token 的權(quán)限。也就是說,一旦 JWT 簽發(fā)了茎芭,在到期之前就會始終有效揖膜,除非服務(wù)器部署額外的邏輯。
(5)JWT 本身包含了認(rèn)證信息梅桩,一旦泄露壹粟,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用宿百,JWT 的有效期應(yīng)該設(shè)置得比較短趁仙。對于一些比較重要的權(quán)限,使用時應(yīng)該再次對用戶進(jìn)行認(rèn)證垦页。
(6)為了減少盜用雀费,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸痊焊。
5. JWT和cookie-session對比
JWT更關(guān)注的是實(shí)體之間安全的消息傳輸盏袄。無狀態(tài)
、快速響應(yīng)
(不涉及第三方存儲).
cookie-session更側(cè)重的是用戶信息安全宋光、基于http協(xié)議貌矿。
如果使用JWT實(shí)現(xiàn)單點(diǎn)登錄,那么需要考慮以下幾點(diǎn)問題罪佳。
secret如何設(shè)計(jì)
jwt 唯一存儲在服務(wù)端的只有一個 secret逛漫,個人認(rèn)為這個 secret 應(yīng)該設(shè)計(jì)成和用戶相關(guān)的,而不是一個所有用戶公用的統(tǒng)一值赘艳。這樣可以有效的避免一些注銷和修改密碼時遇到的窘境酌毡。
注銷和修改密碼
傳統(tǒng)的 session+cookie 方案用戶點(diǎn)擊注銷,服務(wù)端清空 session 即可蕾管,因?yàn)闋顟B(tài)保存在服務(wù)端枷踏。但 jwt 的方案就比較難辦了,因?yàn)?jwt 是無狀態(tài)的掰曾,服務(wù)端通過計(jì)算來校驗(yàn)有效性旭蠕。沒有存儲起來,所以即使客戶端刪除了 jwt旷坦,但是該 jwt 還是在有效期內(nèi)掏熬,只不過處于一個游離狀態(tài)。分析下痛點(diǎn):注銷變得復(fù)雜的原因在于 jwt 的無狀態(tài)秒梅。我提供幾個方案旗芬,視具體的業(yè)務(wù)來決定能不能接受。
- 僅僅清空客戶端的 cookie捆蜀,這樣用戶訪問時就不會攜帶 jwt疮丛,服務(wù)端就認(rèn)為用戶需要重新登錄幔嫂。這是一個典型的假注銷,對于用戶表現(xiàn)出退出的行為誊薄,實(shí)際上這個時候攜帶對應(yīng)的 jwt 依舊可以訪問系統(tǒng)履恩。
- 清空或修改服務(wù)端的用戶對應(yīng)的 secret,這樣在用戶注銷后暇屋,jwt 本身不變似袁,但是由于 secret 不存在或改變,則無法完成校驗(yàn)咐刨。這也是為什么將 secret 設(shè)計(jì)成和用戶相關(guān)的原因昙衅。
- 借助第三方存儲自己管理 jwt 的狀態(tài),可以以 jwt 為 key定鸟,實(shí)現(xiàn)去 redis 一類的緩存中間件中去校驗(yàn)存在性而涉。方案設(shè)計(jì)并不難,但是引入 redis 之后联予,就把無狀態(tài)的 jwt 硬生生變成了有狀態(tài)了啼县,違背了 jwt 的初衷。實(shí)際上這個方案和 session 都差不多了沸久。
修改密碼則略微有些不同季眷,假設(shè)號被到了,修改密碼(是用戶密碼卷胯,不是 jwt 的 secret)之后子刮,盜號者在原 jwt 有效期之內(nèi)依舊可以繼續(xù)訪問系統(tǒng),所以僅僅清空 cookie 自然是不夠的窑睁,這時挺峡,需要強(qiáng)制性的修改 secret。在我的實(shí)踐中就是這樣做的担钮。
續(xù)簽問題
續(xù)簽問題可以說是我抵制使用 jwt 來代替?zhèn)鹘y(tǒng) session 的最大原因橱赠,因?yàn)?jwt 的設(shè)計(jì)中我就沒有發(fā)現(xiàn)它將續(xù)簽認(rèn)為是自身的一個特性。傳統(tǒng)的 cookie 續(xù)簽方案一般都是框架自帶的箫津,session 有效期 30 分鐘狭姨,30 分鐘內(nèi)如果有訪問,session 有效期被刷新至 30 分鐘苏遥。而 jwt 本身的 payload 之中也有一個 exp 過期時間參數(shù)送挑,來代表一個 jwt 的時效性,而 jwt 想延期這個 exp 就有點(diǎn)身不由己了暖眼,因?yàn)?payload 是參與簽名的,一旦過期時間被修改纺裁,整個 jwt 串就變了诫肠,jwt 的特性天然不支持續(xù)簽司澎!
如果你一定要使用 jwt 做會話管理(payload 中存儲會話信息),也不是沒有解決方案栋豫,但個人認(rèn)為都不是很令人滿意
- 每次請求刷新 jwt
jwt 修改 payload 中的 exp 后整個 jwt 串就會發(fā)生改變挤安,那…就讓它變好了,每次請求都返回一個新的 jwt 給客戶端丧鸯。太暴力了蛤铜,不用我贅述這樣做是多么的不優(yōu)雅,以及帶來的性能問題丛肢。
但围肥,至少這是最簡單的解決方案。
- 只要快要過期的時候刷新 jwt
一個上述方案的改造點(diǎn)是蜂怎,只在最后的幾分鐘返回給客戶端一個新的 jwt穆刻。這樣做,觸發(fā)刷新 jwt 基本就要看運(yùn)氣了杠步,如果用戶恰巧在最后幾分鐘訪問了服務(wù)器氢伟,觸發(fā)了刷新,萬事大吉幽歼;如果用戶連續(xù)操作了 27 分鐘朵锣,只有最后的 3 分鐘沒有操作,導(dǎo)致未刷新 jwt甸私,無疑會令用戶抓狂诚些。
- 完善 refreshToken
借鑒 oauth2 的設(shè)計(jì),返回給客戶端一個 refreshToken颠蕴,允許客戶端主動刷新 jwt泣刹。一般而言,jwt 的過期時間可以設(shè)置為數(shù)小時犀被,而 refreshToken 的過期時間設(shè)置為數(shù)天椅您。
我認(rèn)為該方案并可行性是存在的,但是為了解決 jwt 的續(xù)簽把整個流程改變了寡键,為什么不考慮下 oauth2 的 password 模式和 client 模式呢掀泳?
- 使用 redis 記錄獨(dú)立的過期時間
那么和使用session存儲也沒有太多區(qū)別了
6. CSRF是什么
(Cross-site request forgery)跨站偽造請求。
攻擊者誘導(dǎo)受害者進(jìn)入第三方網(wǎng)站西轩,在第三方網(wǎng)站中员舵,向被攻擊網(wǎng)站發(fā)送跨站請求。利用受害者在被攻擊網(wǎng)站已經(jīng)獲取的注冊憑證藕畔,繞過后臺的用戶驗(yàn)證马僻,達(dá)到冒充用戶對被攻擊的網(wǎng)站執(zhí)行某項(xiàng)操作的目的
常見攻擊流程:
- 受害者登錄a.com,并保留了登錄憑證(Cookie)注服。
- 攻擊者引誘受害者訪問了b.com韭邓。
- b.com向a.com發(fā)送了一個請求:a.com/act=xx措近。瀏覽器會默認(rèn)攜帶a.com的cookie
- a.com接收到請求后,對請求進(jìn)行驗(yàn)證女淑,并確認(rèn)是受害者的憑證瞭郑,誤以為是受害者自己發(fā)送的請求。
- a.com以受害者的名義執(zhí)行了act=xx鸭你。
- 攻擊完成屈张,攻擊者在受害者不知情的情況下,冒充受害者袱巨,讓a.com執(zhí)行了自己定義的操作阁谆。
CSRF的特點(diǎn)
- 攻擊一般發(fā)起在第三方網(wǎng)站,而不是被攻擊的網(wǎng)站瓣窄。被攻擊的網(wǎng)站無法防止攻擊發(fā)生笛厦。
- 攻擊利用受害者在被攻擊網(wǎng)站的登錄憑證,冒充受害者提交操作俺夕;而不是直接竊取數(shù)據(jù)裳凸。
- 整個過程攻擊者并不能獲取到受害者的登錄憑證,僅僅是“冒用”劝贸。
- 跨站請求可以用各種方式:圖片URL姨谷、超鏈接、CORS映九、Form提交等等梦湘。部分請求方式可以直接嵌入在第三方論壇、文章中件甥,難以進(jìn)行追蹤捌议。
7. CSRF的解決方案
根據(jù)CSRF特點(diǎn)來處理,如果后端是restful服務(wù)引有,禁止跨域請求就可以杜絕絕大部分請求了瓣颅,然后可以再加上同源檢測(校驗(yàn)referer和origin)。
參考:如何防CSRF攻擊