者:古時的風箏
原文鏈接:https://www.cnblogs.com/fengzheng/p/13527425.html
JWT 全稱是 JSON Web Token,是目前非常流行的跨域認證解決方案,在單點登錄場景中經常使用到。
有些人覺得它非常好用,用了它之后就不用在服務端借助 redis 實現認證過程了,但是,還有一部分人認為它生來就有缺陷,根本不能用。
這是為什么呢?
你平時用過那么多網站和 APP,其中有很多都是需要登錄的吧,那咱們就選一個場景出來說說。
以一個電商系統為例,如果你想要下單,首先需要注冊一個賬號,擁有了賬號之后,需要輸入用戶名(比如手機號或郵箱)、密碼完成登錄過程。之后你在一段時間內再次進入系統,是不需要輸入用戶名和密碼的,只有在連續長時間不登錄的情況下(例如一個月沒登錄過)訪問系統,才需要再次輸入用戶名和密碼。
對于那些使用頻率很高的網站或應用,通常是很長時間都不需要輸入密碼的,以至于你在換了一臺電腦或者一部手機之后,一些經常使用的網站或 APP 的密碼都不記得了。
早期互聯網以 web 為主,客戶端是瀏覽器 ,所以 Cookie-Session 方式是早期最常用的認證方式,直到現在,一些 web 網站依然用這種方式做認證。
認證過程大致如下:
但是為什么說它是傳統的認證方式,因為現在人手一部智能手機,很多人都不用電腦,平時都是使用手機上的各種 APP,比如淘寶、拼多多等。
在這種潮流之下,傳統的 Cookie-Session 就遇到了一些問題:
1、首先,Cookie-Session 只能在 web 場景下使用,如果是 APP 呢,APP 可沒有地方存 cookie。
現在的產品基本上都同時提供 web 端和 APP 兩種使用方式,有點產品甚至只有 APP。
2、退一萬步說,你做的產品只支持 web,也要考慮跨域問題, 但Cookie 是不能跨域的。
拿天貓商城來說,當你進入天貓商城后,會看到頂部有天貓超市、天貓國際、天貓會員這些菜單。而點擊這些菜單都會進入不同的域名,不同的域名下的 cookie 都是不一樣的,你在 A 域名下是沒辦法拿到 B 域名的 cookie 的,即使是子域也不行。
3、如果是分布式服務,需要考慮 Session 同步問題。
現在的互聯網網站和 APP 基本上都是分布式部署,也就是服務端不止一臺機器。當某個用戶在頁面上進行登錄操作后,這個登錄動作必定是請求到了其中某一臺服務器上。你的身份信息得保存下來吧,傳統方式就是存 Session。
接下來,問題來了。你訪問了幾個頁面,這時,有個請求經過負載均衡,路由到了另外一臺服務器(不是你登錄的那臺)。當后臺接到請求后,要檢查用戶身份信息和權限,于是接口開始從從 Session 中獲取用戶信息。但是,這臺服務器不是當時登錄的那臺,并沒存你的 Session ,這樣后臺服務就認為你是一個非登錄的用戶,也就不能給你返回數據了。
所以,為了避免這種情況的發生,就要做 Session 同步。一臺服務器接收到登錄請求后,在當前服務器保存 Session 后,也要向其他幾個服務器同步。
4、cookie 存在 CSRF(跨站請求偽造)的風險。 跨站請求偽造,是一種挾制用戶在當前已登錄的Web應用程序上執行非本意的操作的攻擊方法。CSRF 利用的是網站對用戶網頁瀏覽器的信任。簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并運行一些操作(比如購買商品)。由于瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶發起的操作。
比如說我是一個黑客,我發現你經常訪問的一個技術網站存在 CSRF 漏洞。發布文章支持 html 格式,進而我在 html 中加入一些危險內容,例如
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
假設 src 指向的地址是一個你平時用的購物網站的付款地址(當然只是舉例,真正的攻擊可沒這么簡單),如果你之前登錄過并且標識你身份信息的 cookie 已經保存下來了。當你刷到我發布的這篇文章的時候,img 標簽一加載,這個 CSRF 攻擊就會起作用,在你不知情的情況下向這個網站付款了。
由于傳統的 Cookie-Session 認證存在諸多問題,那可以把上面的方案改造一下。
1、改造 Cookie 既然 Cookie 不能在 APP 等非瀏覽器中使用,那就不用 cookie 做客戶端存儲,改用其他方式。
改成什么呢?
web 中可以使用 local storage,APP 中使用客戶端數據庫,這樣既能這樣就實現了跨域,并且避免了 CSRF 。
2、服務端也不存 Session 了,把 Session 信息拿出來存到 Redis 等內存數據庫中,這樣即提高了速度,又避免了 Session 同步問題;
經過改造之后變成了如下的認證過程:
下面兩張圖分別演示了首次登錄和非首次登錄的過程。
經過一頓猛如虎的改造,解決了傳統 Cookie-Session 方式存在的問題。這種改造需要開發者在項目中自行完成。改造起來肯定是費時費力的,而且還有可能存在漏洞。
這時,JWT 就可以上場了,JWT 就是一種Cookie-Session改造版的具體實現,讓你省去自己造輪子的時間,JWT 還有個好處,那就是你可以不用在服務端存儲認證信息(比如 token),完全由客戶端提供,服務端只要根據 JWT 自身提供的解密算法就可以驗證用戶合法性,而且這個過程是安全的。
如果你是剛接觸 JWT,最有疑問的一點可能就是: JWT 為什么可以完全依靠客戶端(比如瀏覽器端)就能實現認證功能,認證信息全都存在客戶端,怎么保證安全性?
JWT 最后的形式就是個字符串,它由頭部、載荷與簽名這三部分組成,中間以「.」分隔。像下面這樣:
頭部以 JSON 格式表示,用于指明令牌類型和加密算法。形式如下,表示使用 JWT 格式,加密算法采用 HS256,這是最常用的算法,除此之外還有很多其他的。
{
"alg": "HS256",
"typ": "JWT"
}
對應上圖的紅色 header 部分,需要 Base64 編碼。
用來存儲服務器需要的數據,比如用戶信息,例如姓名、性別、年齡等,要注意的是重要的機密信息最好不要放到這里,比如密碼等。
{
"name": "古時的風箏",
"introduce": "英俊瀟灑"
}
另外,JWT 還規定了 7 個字段供開發者選用。
這部分信息也是要用 Base64 編碼的。
簽名有一個計算公式。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Secret
)
使用HMACSHA256算法計算得出,這個方法有兩個參數,前一個參數是 (base64 編碼的頭部 + base64 編碼的載荷)用點號相連,后一個參數是自定義的字符串密鑰,密鑰不要暴露在客戶端,而應該服務器知道。
了解了 JWT 的結構和算法后,那怎么使用呢?假設我這兒有個網站。
1、在用戶登錄網站的時候,需要輸入用戶名、密碼或者短信驗證的方式登錄,登錄請求到達服務端的時候,服務端對賬號、密碼進行驗證,然后計算出 JWT 字符串,返回給客戶端。
2、客戶端拿到這個 JWT 字符串后,存儲到 cookie 或者 瀏覽器的 LocalStorage 中。
3、再次發送請求,比如請求用戶設置頁面的時候,在 HTTP 請求頭中加入 JWT 字符串,或者直接放到請求主體中。
4、服務端拿到這串 JWT 字符串后,使用 base64的頭部和 base64 的載荷部分,通過HMACSHA256算法計算簽名部分,比較計算結果和傳來的簽名部分是否一致,如果一致,說明此次請求沒有問題,如果不一致,說明請求過期或者是非法請求。
保證安全性的關鍵就是 HMACSHA256 或者與它同類型的加密算法,因為加密過程是不可逆的,所以不能根據傳到前端的 JWT 傳反解到密鑰信息。
另外,不同的頭部和載荷加密之后得到的簽名都是不同的,所以,如果有人改了載荷部分的信息,那最后加密出的結果肯定就和改之前的不一樣的,所以,最后驗證的結果就是不合法的請求。
假設載荷部分存儲了權限級別相關的字段,強盜拿到 JWT 串后想要修改為更高權限的級別,上面剛說了,這種情況下是肯定不會得逞的,因為加密出來的簽名會不一樣,服務器可能很容易的判別出來。
那如果強盜拿到后不做更改,直接用呢,那就沒有辦法了,為了更大程度上防止被強盜盜取,應該使用 HTTPS 協議而不是 HTTP 協議,這樣可以有效的防止一些中間劫持攻擊行為。
有同學就要說了,這一點也不安全啊,拿到 JWT 串就可以輕松模擬請求了。確實是這樣,但是前提是你怎么樣能拿到,除了上面說的中間劫持外,還有什么辦法嗎?
除非強盜直接拿了你的電腦,那這樣的話,對不起,不光 JWT 不安全了,其他任何網站,任何認證方式都不安全。
雖然這樣的情況很少,但是在使用 JWT 的時候仍然要注意合理的設置過期時間,不要太長。
JWT 有個問題,導致很多開發團隊放棄使用它,那就是一旦頒發一個 JWT 令牌,服務端就沒辦法廢棄掉它,除非等到它自身過期。有很多應用默認只允許最新登錄的一個客戶端正常使用,不允許多端登錄,JWT 就沒辦法做到,因為頒發了新令牌,但是老的令牌在過期前仍然可用。這種情況下,就需要服務端增加相應的邏輯。
JWT 官網列出了各種語言對應的庫,其中 Java 的如下幾個。
以 java-jwt為例。
1、引入對應的 Maven 包。
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2、在登錄時,調用 create 方法得到一個令牌,并返回給前端。
public static String create(){
try {
Algorithm algorithm=Algorithm.HMAC256("secret");
String token=JWT.create()
.withIssuer("auth0")
.withSubject("subject")
.withClaim("name","古時的風箏")
.withClaim("introduce","英俊瀟灑")
.sign(algorithm);
System.out.println(token);
return token;
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
throw exception;
}
}
3、登錄成功后,再次發起請求的時候將 token 放到 header 或者請求體中,服務端對 token 進行驗證。
public static Boolean verify(String token){
try {
Algorithm algorithm=Algorithm.HMAC256("secret");
JWTVerifier verifier=JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt=verifier.verify(token);
String payload=jwt.getPayload();
String name=jwt.getClaim("name").asString();
String introduce=jwt.getClaim("introduce").asString();
System.out.println(payload);
System.out.println(name);
System.out.println(introduce);
return true;
} catch (JWTVerificationException exception){
//Invalid signature/claims
return false;
}
}
4、用 create 方法生成 token,并用 verify 方法驗證一下。
public static void main(String[] args){
String token=create();
Boolean result=verify(token);
System.out.println(result);
}
得到下面的結果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0.ooQ1K_XyljjHf34Nv5iJvg1MQgVe6jlphxv4eeFt8pA
eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0
古時的風箏
英俊瀟灑
true
使用 create 方法創建的 JWT 串可以通過驗證。
而如果我將 JWT 串中的載荷部分,兩個點號中間的部分修改一下,然后再調用 verify 方法驗證,會出現 JWTVerificationException異常,不能通過驗證。
關面試題如下:
JWT (JSON Web Token) 是目前最流行的跨域認證解決方案,是一種基于 Token 的認證授權機制。從 JWT 的全稱可以看出,JWT 本身也是 Token,一種規范化之后的 JSON 結構的 Token。
Token 自身包含了身份驗證所需要的所有信息,因此,我們的服務器不需要存儲 Session 信息。這顯然增加了系統的可用性和伸縮性,大大減輕了服務端的壓力。
可以看出,JWT 更符合設計 RESTful API 時的「Stateless(無狀態)」原則 。
并且, 使用 Token 認證可以有效避免 CSRF 攻擊,因為 Token 一般是存在在 localStorage 中,使用 JWT 進行身份驗證的過程中是不會涉及到 Cookie 的。
我在 JWT 優缺點分析[1]這篇文章中有詳細介紹到使用 JWT 做身份認證的優勢和劣勢。
下面是 RFC 7519[2] 對 JWT 做的較為正式的定義。
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)[3]
JWT 本質上就是一組字串,通過(.)切分成三個為 Base64 編碼的部分:
JWT 通常是這樣的:xxxxx.yyyyy.zzzzz。
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
你可以在 jwt.io[4] 這個網站上對其 JWT 進行解碼,解碼之后得到的就是 Header、Payload、Signature 這三部分。
Header 和 Payload 都是 JSON 格式的數據,Signature 由 Payload、Header 和 Secret(密鑰)通過特定的計算公式和加密算法得到。
Header 通常由兩部分組成:
示例:
{
"alg": "HS256",
"typ": "JWT"
}
JSON 形式的 Header 被轉換成 Base64 編碼,成為 JWT 的第一部分。
Payload 也是 JSON 格式數據,其中包含了 Claims(聲明,包含 JWT 的相關信息)。
Claims 分為三種類型:
下面是一些常見的注冊聲明:
示例:
{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232,
"iat": 1516239022,
"scope": ["admin", "user"]
}
Payload 部分默認是不加密的,一定不要將隱私信息存放在 Payload 當中?。。?/span>
JSON 形式的 Payload 被轉換成 Base64 編碼,成為 JWT 的第二部分。
Signature 部分是對前兩部分的簽名,作用是防止 Token(主要是 payload) 被篡改。
這個簽名的生成需要用到:
簽名的計算公式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字符串,每個部分之間用"點"(.)分隔,成為 JWT 的第三部分。
在基于 Token 進行身份驗證的的應用程序中,服務器通過 Payload、Header 和Secret(密鑰)創建Token(令牌)并將 Token 發送給客戶端??蛻舳私邮盏?Token 之后,會將其保存在 Cookie 或者 localStorage 里面,以后客戶端發出的所有請求都會攜帶這個令牌。
簡化后的步驟如下:
兩點建議:
spring-security-jwt-guide[6] 就是一個基于 JWT 來做身份認證的簡單案例,感興趣的可以看看。
有了簽名之后,即使 Token 被泄露或者解惑,黑客也沒辦法同時篡改 Signature 、Header 、Payload。
這是為什么呢?因為服務端拿到 Token 之后,會解析出其中包含的 Header、Payload 以及 Signature 。服務端會根據 Header、Payload、密鑰再次生成一個 Signature。拿新生成的 Signature 和 Token 中的 Signature 作對比,如果一樣就說明 Header 和 Payload 沒有被修改。
不過,如果服務端的秘鑰也被泄露的話,黑客就可以同時篡改 Signature 、Header 、Payload 了。黑客直接修改了 Header 和 Payload 之后,再重新生成一個 Signature 就可以了。
密鑰一定保管好,一定不要泄露出去。JWT 安全的核心在于簽名,簽名安全的核心在密鑰。
[1]
JWT 優缺點分析: ./advantages&disadvantages-of-jwt.md
[2]
RFC 7519: https://tools.ietf.org/html/rfc7519
[3]
JSON Web Token (JWT): https://tools.ietf.org/html/rfc7519
[4]
jwt.io: https://jwt.io/
[5]
IANA JSON Web Token Registry: https://www.iana.org/assignments/jwt/jwt.xhtml
[6]
spring-security-jwt-guide: https://github.com/Snailclimb/spring-security-jwt-guide
本文內容來源于官方網站,讓我們來了解下JWT
https://jwt.io/
JWT是一個開放標準(RFC 7519),完整的名稱是JSON Web Token,以下都簡稱JWT,它是一種緊湊且獨立的以JSON對象的形式在各種場景下安全地傳輸信息。因為它是數字簽名的,可以對它進行驗證。可以使用一個秘密對JWT進行簽名(使用HMAC算法)或使用RSA或ECDSA。
以下是一些適合使用JWT的場景:
認證授權是使用JWT最常見的場景。一旦用戶登錄,每個后續請求都將包括JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是當今廣泛使用的一種特性,因為它開銷小,而且能夠很容易在不同的域下使用,也就是我們常說的跨域。
JWT是在各場景之間安全地傳輸信息的一種好方法。因為JWT可以進行簽名,例如,使用公鑰/私鑰對,你可以根據公鑰/私鑰對來判斷請求信息是否有效。此外,由于簽名是使用報頭和負載計算的,你還可以驗證內容有沒有被篡改。
JWT以其緊湊的形式由點分隔的三個部分組成(.),它們是:
因此,JWT通常是下面這樣的形勢
Header.Payload.Signature
我們針對不同的部分別來介紹:
標頭(Header):
標頭典型由兩個部分組成:令牌的類型(即JWT)正在使用的簽名算法(如HMAC SHA 256或RSA)。
例如:
{ "alg": "HS256", "typ": "JWT" }
負載(Payload)
令牌的第二部分是負載,它包含了以下部分:
除了官方提供的字段之外,你還可以自定義字段:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
注意:對于簽名的令牌,這些信息雖然受到保護,但任何人都可以閱讀。除非加密,否則不要將秘密信息放入JWT的有效負載或頭元素中,如登錄密碼等。
簽名
要創建簽名部分,你必須接受編碼的頭部、編碼的負載、表頭中的加密算法,并對其進行簽名。
例如,如果要使用HMAC SHA 256算法,則將以下列方式創建簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名用于驗證消息在過程中沒有被更改,對于用私鑰簽名的令牌,它還可以驗證JWT的發送方是否是它所稱的發送方。
Base64Url
生成的結果是三個Base64-URL字符串,由點分隔,可以在HTML和HTTP環境中輕松地傳遞,同時與基于XML的標準(如SAML)相比更加緊湊。
在身份驗證中,當用戶成功地使用他們的憑據登錄時,將返回一個JWT。由于令牌是憑據,因此必須非常小心地防止安全問題。通常,你不應將令牌保存的時間超過所需時間。
每當用戶想訪問受保護的路由或資源時,用戶代理應該發送JWT,通常在請求頭帶上如下:
Authorization: Bearer <token>
在某些情況下,這可能是一種無狀態授權機制。服務器的受保護路由將在授權標頭,如果它存在,則允許用戶訪問受保護的資源。如果JWT包含必要的數據,那么查詢數據庫中某些操作的需求可能會減少,盡管情況可能并不總是這樣。
下圖顯示了如何獲取JWT并用于訪問API或資源:
請注意,使用簽名的令牌,令牌中包含的所有信息都將公開給用戶或其他各方,即使他們無法更改它。這意味著不應該將秘密信息放入令牌中。
JWT在當前的Web或HTTP應用開發中非常受用,本文通過介紹JWT能讓我們更容易的理解什么是JWT,以及如何使用,并且JWT的工作原理,JWT針對各個平臺都有對應的多個可選框架使用,因此在理解了JWT的結構和原理之后,我們可以更加簡單的使用它,如果本文對你有幫助,請麻煩幫忙轉發、點贊加關注哦!謝謝!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。