JWT: JSON Web Token
1. jwt概述
用戶登錄成功后,服務端?如何知道客戶端的每次請求對應的是哪個用戶呢?怎么做:目前有兩種方式實現.
1.1. 一是通過sessionId的方式,登錄成功后服務端返回sessionId給客戶端,然后瀏覽器將sessionId保存在Cookie中,這樣每次瀏覽器請求的時候都帶上sessionId,通過sessionId就能在服務端找到對應的session,就能知道是哪個用戶。 (session里記錄了用戶的相關信息,登錄時間等)
1.2. 二是用戶登錄成功后,服務端根本就不存用戶的session信息,用戶登錄成功后,服務端將用戶的信息返回給客戶端,客戶端自己保存用戶信息,然后每次客戶端請求的時候,將用戶信息傳給服務端,這就是jwt的原理。
2. JWT 的原理
JWT 的原理是,服務器認證以后,生成一個 JSON 對象,發回給用戶,就像下面這樣。
{"name": "jack","role": "admin","id": 123456 }
以后,用戶與服務端通信的時候,都要發回這個 JSON 對象。服務器完全只靠這個對象認定用戶身份。為了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名(詳見后文)。
服務器就不保存任何 session 數據了,也就是說,服務器變成無狀態了,從而比較容易實現擴展。
3. jwt數據結構
jwt是一個很長的字符串,中間用點(.
)分隔成三個部分。注意,JWT 內部是沒有換行的,這里只是為了便于展示,將它寫成了幾行。
JWT 的三個部分依次如下。
- Header(頭部)
- Payload(負載)
- Signature(簽名)
類似這樣的:
?
3.1 Header
Header 部分是一個 JSON 對象,描述 JWT 的元數據,通常是下面的樣子。
{"alg": "HS256","typ": "JWT" }
上面代碼中,alg
屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ
屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫為JWT
。
最后,將上面的 JSON 對象使用 Base64URL 算法(詳見后文)轉成字符串。
3.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","username": "John Doe","userId": 123654 }
注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把秘密信息放在這個部分。
這個 JSON 對象也要使用 Base64URL 算法轉成字符串。
3.3 Signature
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字符串,每個部分之間用"點"(.
)分隔,就可以返回給用戶。
3.4 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。
JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+
、/
和=
,在 URL 里面有特殊含義,所以要被替換掉:=
被省略、+
替換成-
,/
替換成_
?。這就是 Base64URL 算法。
4、 使用 jwt?
客戶端收到服務器返回的 JWT,可以儲存在 Cookie 里面,也可以儲存在 localStorage。
此后,客戶端每次與服務器通信,都要帶上這個 JWT。你可以把它放在 Cookie 里面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭信息Authorization
字段里面。
類似這樣,這些數據傳輸:
Authorization: Bearer <token>
5、 自定義實現jwt?
說了這么多,結下來我們自己來實現定義一個jwt數據的生成,jwt數據的解碼工作,通過目前市面上開源的java-jwt jar包,還是很方便的
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.gfm.asset.base.exception.InteractException;
import lombok.SneakyThrows;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @author xxx */
public class JwtHelper {private static final Logger logger = LoggerFactory.getLogger(JwtHelper.class);/*** 密鑰加密token*/@SneakyThrowspublic static String generateToken(String userKey, String secretKey, int expire) {Algorithm algorithm = Algorithm.HMAC256(secretKey);String token = JWT.create().withIssuer(userKey).withIssuedAt(new Date()).withExpiresAt(DateTime.now().plusSeconds(expire).toDate()).sign(algorithm);logger.info("==> 生成jwtToken, userKey={},secretKey={},過期時間={}s,token={}", userKey, secretKey, expire, token);return token;}/*** 公鑰解析token*/public static String parserToken(String token, String secretKey) {try {Algorithm algorithm = Algorithm.HMAC256(secretKey);DecodedJWT jwt = JWT.require(algorithm).build().verify(token);return jwt.getIssuer();} catch (TokenExpiredException e) {throw new InteractException("token has expired");} catch (Exception e) {e.printStackTrace();throw new InteractException("token is invalid");}}public static void main(String[] args) { String userKey = "center_sys";String secretKey = "center_sys@#$";String token = generateToken(userKey, secretKey, 60 * 60 * 24 * 7);System.out.println(token);
// String key = parserToken(token, secretKey);
// System.out.println(key);}}
pom.xml文件
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.19.1</version><exclusions><exclusion><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></exclusion></exclusions></dependency>
參考:
JSON Web Token 入門教程 - 阮一峰的網絡日志