認識和使用JWT

1.前言:

後端項目中總會有長期保存登錄狀態的需求,相對於保存帳號密碼來說,使用token來保存登錄狀態已經成爲業內共識。在如今微服務和多終端以及Restful Api流行的情況下,cookie和session無法應付,又使得token更加流行。不過如何安全的發放token呢?jwt給了一種思路。

2.什麼是token?

Token是服務端生成的一串字符串,以作客戶端進行請求的一個令牌,當第一次登錄後,服務器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求數據即可,無需再次帶上用戶名和密碼。(抄自:Jwt的使用場景

3.什麼是jwt:

官網:https://jwt.io/

Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).定義了一種簡潔的,自包含的方法用於通信雙方之間以JSON對象的形式安全的傳遞信息。因爲數字簽名的存在,這些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私祕鑰對進行簽名,是目前最流行的跨域認證解決方案。(抄自:SpringBoot集成JWT實現token驗證

4.爲什麼使用jwt:

4.1和cookie,session來對比一下:

互聯網服務離不開用戶認證。一般流程是下面這樣。

1、用戶向服務器發送用戶名和密碼。

2、服務器驗證通過後,在當前對話(session)裏面保存相關數據,比如用戶角色、登錄時間等等。

3、服務器向用戶返回一個 session_id,寫入用戶的 Cookie。

4、用戶隨後的每一次請求,都會通過 Cookie,將 session_id 傳回服務器。

5、服務器收到 session_id,找到前期保存的數據,由此得知用戶的身份。

這種模式的問題在於,擴展性(scaling)不好。單機當然沒有問題,如果是服務器集羣,或者是跨域的服務導向架構,就要求 session 數據共享,每臺服務器都能夠讀取 session。而且存在僞造跨站請求僞造攻擊的風險

舉例來說,A 網站和 B 網站是同一家公司的關聯服務。現在要求,用戶只要在其中一個網站登錄,再訪問另一個網站就會自動登錄,請問怎麼實現?

一種解決方案是 session 數據持久化,寫入數據庫或別的持久層。各種服務收到請求後,都向持久層請求數據。這種方案的優點是架構清晰,缺點是工程量比較大。另外,持久層萬一掛了,就會單點失敗。

另一種方案是服務器索性不保存 session 數據了,所有數據都保存在客戶端,每次請求都發回服務器。JWT 就是這種方案的一個代表。抄自JSON Web Token 入門教程

4.2長什麼樣:

解碼前和解碼後:

左半部分是生成的token,右半部分是加密前的明文。分爲三部分(左邊的三種顏色,以.分隔):header,payload,verify signature。

下面依次介紹這三個部分。

4.2.1 Header

Header 部分是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子。


{
  "alg": "HS256",
  "typ": "JWT"
}

上面代碼中,alg屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫爲JWT

最後,將上面的 JSON 對象使用 Base64URL 算法(詳見後文)轉成字符串。

4.2.2 Payload

Payload 部分也是一個 JSON 對象,用來存放實際需要傳遞的數據。JWT 規定了7個官方字段,供選用。

  • iss (issuer):簽發人
  • exp (expiration time):過期時間
  • sub (subject):主題
  • aud (audience):受衆
  • nbf (Not Before):生效時間
  • iat (Issued At):簽發時間
  • jti (JWT ID):編號

除了官方字段,你還可以在這個部分定義私有字段,下面就是一個例子。


{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把祕密信息放在這個部分。

這個 JSON 對象也要使用 Base64URL 算法轉成字符串。

4.2.3 Signature

Signature 部分是對前兩部分的簽名,防止數據篡改。

首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出簽名以後,把 Header、Payload、Signature 三個部分拼成一個字符串,每個部分之間用"點"(.)分隔,就可以返回給用戶。

4.2.4 Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。

JWT 作爲一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+/=,在 URL 裏面有特殊含義,所以要被替換掉:=被省略、+替換成-/替換成_ 。這就是 Base64URL 算法。

抄自JSON Web Token 入門教程

5.注意事項:

(1)JWT 默認是不加密,但也是可以加密的。生成原始 Token 以後,可以用密鑰再加密一次。

(2)JWT 不加密的情況下,不能將祕密數據寫入 JWT。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。爲了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

(6)爲了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。抄自JSON Web Token 入門教程

5.1它是如何做身份驗證的?

首先,JWT 的 Token 相當是明文,是可以解密的,任何存在 payload 的東西,都沒有祕密可言,所以隱私數據不能簽發 token。

而服務端,拿到 token 後解密,即可知道用戶信息,例如本例中的uuid

有了 uuid,那麼你就知道這個用戶是誰,是否有權限進行下一步的操作。抄自:JWT的使用流程

5.2安全:

  1. 縮短 token 有效時間
  2. 使用安全係數高的加密算法
  3. token 不要放在 Cookie 中,有 CSRF 風險
  4. 使用 HTTPS 加密協議
  5. 對標準字段 iss、sub、aud、nbf、exp 進行校驗
  6. 使用成熟的開源庫,不要手賤造輪子
  7. 特殊場景下可以把用戶的 UA、IP 放進 payload 進行校驗(不推薦)

抄自:JWT的使用流程

5.3如何防止 Token 被串改?

此時 signature字段就是關鍵了,能被解密出明文的,只有headerpayload

假如黑客/中間人串改了payload,那麼服務器可以通過signature去驗證是否被篡改過。

在服務端在執行一次 signature = 加密算法(header + "." + payload, 密鑰);, 然後對比 signature 是否一致,如果一致則說明沒有被篡改。

所以爲什麼說服務器的密鑰不能被泄漏。

如果泄漏,將存在以下風險:

  • 客戶端可以自行簽發 token
  • 黑客/中間人可以肆意篡改 token

抄自:JWT的使用流程

5.4jwt時效

jwt在有效時間內,都可以認證成功。這樣則有可能會出現:

每次登陸都發一個jwt,但是這幾次登陸發來的jwt都可以用。
對於多端登錄每一端都會生成不同的jwt,所以不要在jwt中存儲容易變化的信息。
對於用戶註銷登錄後,之前的token仍然可以用。

如果想要設置只有一個jwt可用,或者是一端登錄其他端自動失效,則需要配合數據庫操作,將最新的jwt保存到數據庫來驗證或者是把想要失效的jwt保存到數據庫中,驗證jwt後再從數據庫取出來驗證有效性。

6.使用場景:

  • Authorization (授權) : 這是使用JWT的最常見場景。一旦用戶登錄,後續每個請求都將包含JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是現在廣泛使用的JWT的一個特性,因爲它的開銷很小,並且可以輕鬆地跨域使用。
  • Information Exchange (信息交換) : 對於安全的在各方之間傳輸信息而言,JSON Web Tokens無疑是一種很好的方式。因爲JWTs可以被簽名,例如,用公鑰/私鑰對,你可以確定發送人就是它們所說的那個人。另外,由於簽名是使用頭和有效負載計算的,您還可以驗證內容沒有被篡改。

抄自:認識JWT

7.怎麼用?

客戶端收到服務器返回的 JWT,可以儲存在 Cookie 裏面,也可以儲存在 localStorage。

此後,客戶端每次與服務器通信,都要帶上這個 JWT。你可以把它放在 Cookie 裏面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭信息Authorization字段裏面。


Authorization: Bearer <token>
 

另一種做法是,跨域的時候,JWT 就放在 POST 請求的數據體裏面。抄自JSON Web Token 入門教程

 

java來說有兩個常用的jwt庫:jjwtjava-jwt

兩個庫用起來差不多,我平時用的是java-jwt,所以就以java-jwt來舉例。

使用起來還是很簡單的,看github中的介紹就可以用。推薦博客SpringBoot集成JWT實現token驗證

一般來說jwt編碼和解碼放到util工具包中,簽名時使用統一的祕鑰。但是我看其他博主(上面推薦的博客)也有使用用戶個人密碼來對jwt簽名,並把jwt編碼放到service來處理。

流程:

1.前端輸入帳號密碼請求後臺接口,後臺驗證成功後頒發token:

 //使用該加密算法
   Algorithm algorithm = Algorithm.HMAC256(SECRET);
        JWTCreator.Builder builder = JWT.create()
                .withIssuer(ISSUER) //設置發佈者
                .withExpiresAt(new Date(System.currentTimeMillis()+SHORT_TIME))//設置過期時間
                .withClaim("userID",userID);//設置負載字段
        return builder.sign(algorithm);//使用之前的加密算法生成token

2.前端在請求頭中攜帶了此token,後臺通過攔截器驗證token

注意jwt會自動驗證時間是否過期。

   

    Algorithm algorithm = null;
        algorithm = Algorithm.HMAC256(SECRET);
        log.debug("jwt驗證access_token通過userid"+userID+"appid:"+appID);
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER) .withClaim("userID",userID).build();
        verifier.verify(token);

也可以不驗證負載信息,而是獲取負載信息:

     

  Algorithm algorithm = null;
        algorithm = Algorithm.HMAC256(SECRET);
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER) .build();
        DecodedJWT jwt =  verifier.verify(token);
        Map<String, Claim> map = jwt.getClaims();
        return map.get("userID").asString();

其他參考文章:

web實現自動登錄功能

8.個人理解:

對於一般的不帶附加信息的接口,獲取到了jwt,就可以訪問此接口。

如果對於安全有強烈要求的話,可以要求接口上帶上有關jwt封裝時的信息:比如jwt頒發時在負載上加上了加密的受衆字段 (audience),然後接口要求請求時帶上此受衆信息,在服務器中驗證此帶上的受衆信息和jwt中的手中信息是否相同。

這樣的情況下即使其他人獲取了jwt,仍然不能通過此jwt來正常訪問接口。

但是注意的是jwt要加密,如果使用的默認的base64,仍可以通過獲取到的jwt,通過base64解密得到負載中的受衆信息,然後在訪問接口時帶上此字段通過驗證。

不過一般是沒必要這樣做的。

 

本篇文章還是抄襲文章,不過因爲抄的太多了,設置爲轉載的時候不太方便,所以就厚着臉皮設置爲原創文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章