JWT

如下內容 翻譯、擇抄、適當修改自 JWT官網,當了一次大天然的搬運工html


打開官網你就會看到這麼一個介紹:java

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. JWT.IO allows you to decode, verify and generate JWTweb

翻譯過來就是:算法

JWT是一個開源的、行業標準的RFC 7519方法,用於安全地聲明雙方。JWT.IO容許你解碼,驗證,生成JWT(JWT.IO是官網網頁內嵌的一個JWT生成器)數據庫



1. 什麼是 JSON Web Token(JWT)


JWT是一個開源標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間安全地傳遞信息(此信息是一個JSON對象)。此信息是通過數字簽名的,所以能夠被驗證和信任。JWT可使用密匙簽名(兼用HMAC算法)或使用RSA或ECDSA的公用/專用密鑰對來進行簽名編程


儘管JWT能夠進行加密以便在各方之間提供保密性,可是咱們將重點關注已簽名的令牌(指JWT)。已簽名的令牌能夠驗證其中聲明的完整性,而加密的令牌的這些聲明則對其餘各方隱藏。當使用公鑰/私鑰對來對令牌進行簽名時,簽名還證實只有持有私鑰的一方纔是對令牌進行簽名的一方(即身份認證)json



2. 咱們何時應該使用JWT


  • 受權:這是JWT的最多見用法。一旦用戶登陸,每一個後續請求將包括JWT,從而容許用戶訪問該令牌容許的路由,服務和資源。單點登陸是當今普遍使用的一項功能,由於它的開銷很小而且輕鬆跨域

  • 信息交換:JWT是在各方之間安全地傳輸信息的好方法。由於能夠對JWT進行簽名(例如,使用公鑰/私鑰對),因此您能夠肯定發件人是他們所說的人。此外,因爲簽名是使用頭部和有效負載計算的,所以您還能夠驗證內容是否遭到篡改


3. JWT的結構


JWT以緊湊的形式由三部分組成,這些部分由點 . 分隔,分別是:跨域

  • Header
  • Payload
  • Signature

所以,JWT一般以下所示瀏覽器

xxxxx.yyyyy.zzzzz安全


讓咱們來分解不一樣的部分:



3.1 Header(頭部)

頭部一般由兩部分組成:令牌的類型和所使用的簽名算法(如HMAC SHA256或RSA)


例如:

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

而後,上面的JSON被Base64Url編碼以造成JWT的第一部分



3.2 Payload(有效負載)

令牌的第二部分是有效負載,其中包含聲明,而聲明是有關實體的(一般是用戶)和其餘數據的聲明,聲明有三種類型:註冊的、公共的、私有的


  • 註冊聲明(建議但不強制使用)
    • iss: 簽發者
    • exp: 到期時間
    • sub: 主題
    • aud: 受衆羣衆
    • jti: 身份標識(用於迴避重放攻擊)
    • others

請注意,聲明名稱僅是三個字符,由於JWT是緊湊的


  • 公開聲明(能夠添加任何信息,不建議添加敏感信息)

  • 私有聲明(爲共享信息而建立的自定義聲明)

有效負載的事例:

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

而後,對有效負載進行Base64Url編碼,以造成JSON Web令牌的第二部分


請注意,對於已簽名的令牌,此信息儘管能夠防止篡改,但任何人均可以讀取。除非將其加密,不然請勿將機密信息放入JWT的有效負載或頭部中



3.3 Signature(簽名)

要建立簽名部分,你必須獲取編碼後的頭部編碼後的有效負載密匙以及頭部聲明的加密算法,並對他們進行簽名


例如:若要用HMAC SHA256算法,則將經過如下方式建立簽名:

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

簽名用於驗證消息在此過程當中沒有更改,而且對於使用私鑰進行簽名的令牌,它還能夠驗證JWT的發送者是它所說的真實身份



3.4 放在一塊兒組成JWT

輸出是三個由點分隔的Base64-URL字符串,能夠在HTML和HTTP環境中輕鬆傳遞這些字符串,與基於XML的標準(例如SAML)相比,它更緊湊


下面顯示了一個JWT,它已對先前的標頭和有效負載進行了編碼,並用一個祕密進行了簽名

base64UrlEncode(header) + . + base64UrlEncode(payload) + . + Signature

若是您想使用JWT並將這些概念付諸實踐,則可使用jwt.io Debugger解碼(官網的JWT編輯器),驗證和生成JWT



4. JWT如何工做?

在身份驗證中,當用戶使用其憑據成功登陸時,將返回 JWT。因爲令牌是憑據,所以必須格外當心以防止安全問題。一般,令牌的保留時間不該超過要求的時間


因爲缺少安全性,你也不該該將敏感的會話數據存儲在瀏覽器中


每當用戶想要訪問受保護的路由或資源時,用戶代理一般應使用持有者模式,在HTTP請求頭中設Authorization爲JWT,請求頭內容應以下所示:

Authorization: Bearer <token>

在某些狀況下,這能夠是無狀態受權機制。服務器的受保護路由將在Authorization標頭中檢查有效的JWT ,若是存在,則將容許用戶訪問受保護的資源。若是JWT包含必要的數據,則能夠減小查詢數據庫中某些操做的需求(好比用戶名),儘管這種狀況並不是老是如此


若是令牌是在Authorization請求頭中發送的,則跨域資源共享(CORS)不會成爲問題,由於它不使用cookie

可將JWT存於LocalStoage(我的補充)


請注意,使用簽名的令牌,令牌中包含的全部信息都會暴露給用戶或其餘方,即便他們沒法更改它。這意味着您不該將機密信息放入令牌中



5. 爲何要使用JWT


因爲JSON沒有XML冗長,所以在編碼時JSON也較小,從而使JWT比SAML更爲緊湊。這使得JWT是在HTML和HTTP環境中傳遞的不錯的選擇


JSON解析器在大多數編程語言中都很常見,由於它們直接映射到對象。相反,XML沒有天然的文檔到對象映射。與SAML斷言相比,這使使用JWT更加容易


關於用法,JWT是在Internet規模上使用的。這強調了在多個平臺(尤爲是移動平臺)上對JSON Web令牌進行客戶端處理的簡便性


cookie+session這種模式一般是保存在服務器內存中,並且服務從單服務到多服務會面臨的session共享問題,隨着用戶量的增多,開銷就會越大。而JWT不是這樣的,只須要服務端生成token,客戶端保存這個token,每次請求攜帶這個token,服務端認證解析便可(我的補充)



6. 缺點(我的補充)

  • 註銷後JWT還有效,因爲JWT存放於客戶端,用戶點擊註銷後沒法操做客戶端的JWT,致使在JWT的過時時間前仍是有效,筆者的解決方法是在服務器端創建一個黑名單,在用戶點擊註銷後將該用戶放入黑名單,下次進入先去查看黑名單中是否存在該用戶,這又和JWT背道而馳,在服務器端存儲數據
  • 續簽,若每次發現快過了有效期,則服務器端生成一個新的JWT發送給客戶端,客戶端檢查新舊JWT不一致則替換


7. 簡單事例

筆者就使用JWT官網排名靠前的java-jwt來舉例說明了,覺得就一個包而沒有使用maven和Springboot管理,一個個依賴獨自去倉庫下載,血的教訓,那麼列出所需的包

  • java-jwt-3.9.0
  • commons-codec-1.12
  • jackson-databind-2.10.0.pr3
  • jackson-annotations-2.10.0.pr3
  • jackson-core-2.10.0.pr3

定義JWT工具類

public class JWTUtil {
	
	// 密匙,本應從配置文件讀取
	private static String secret = "1234567890";
	
	
	/**
	 * 建立一個JWT
	 * @param username
	 * @return token
	 */
	public static String createJWT(String username){
		
		// 過時多少秒
		long expSecond = 60 * 60;	// 一小時
		
		// 單純爲了使用1.8新時間API
		LocalDateTime nowLocalDateTime = LocalDateTime.now();
		LocalDateTime expLocalDateTime = nowLocalDateTime.plusSeconds(expSecond);
		
		// 時間戳
		Instant nowInstant = nowLocalDateTime.atZone(ZoneId.systemDefault()).toInstant();
		Instant expInstant = expLocalDateTime.atZone(ZoneId.systemDefault()).toInstant();
		
		// 轉成jwt的date類型
		Date nowDate = Date.from(nowInstant);
		Date expDate = Date.from(expInstant);
		
		// 變量提高
		String token = null;
		try {
			token = JWT.create()
					.withIssuer("Howl")			// 簽發者
					.withIssuedAt(nowDate)		// 簽發時間
					.withExpiresAt(expDate)		// 到期時間
					.withClaim("username", username)	// 自定義聲明
					.sign(Algorithm.HMAC256(secret));	// 簽名
		} catch (JWTVerificationException  e) {
			System.out.println("Claim不能轉成json或密匙無效將拋出JWTCreationException");
		}
		return token;
	}
	
	
	/**
	 * 驗證token是否正確
	 * @param token
	 * @return T/F
	 */
	public static boolean verifyJWT(String token){
		
		JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
				.withIssuer("Howl")
				.build();		// 匹配指定的token發佈者 Howl
		try {
			verifier.verify(token);		// 經過驗證
		} catch (Exception e) {
			return false;				// 驗證失敗
		}
		return true;
	}
	
	
	/**
	 * 返回聲明的集合,用來獲取值
	 * @param token
	 * @return Claims集合
	 */
	public static Map getClaim(String token){
		
		// 解密
		JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
				.withIssuer("Howl")
				.build();
				
		// 解密後的JWT
		 DecodedJWT decodedJWT = verifier.verify(token);	
		
		return decodedJWT.getClaims();
	}
}

簡單使用

public static void main(String[] args) {
		
		// 根據傳入的username生成token
		String token = JWTUtil.createJWT("Howl");
		
		// 驗證token是否正確
		if(JWTUtil.verifyJWT(token)){
			System.out.println("驗證正確");
		}else{
			System.out.println("驗證失敗");
		}
		
		// 獲取聲明的集合
		Map<String,Claim> map = JWTUtil.getClaim(token);
		
		// 獲取聲明
		System.out.println(map.get("username").asString());		// 獲取用戶名
		System.out.println(map.get("exp").asDate());			//獲取過時時間
		
}
驗證正確
Howl
Tue Feb 11 11:06:56 GMT+08:00 2020


本文分享 CNBlog - Howlet。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索