JWT 訪問令牌
JWT 訪問令牌
在學習jwt之前我們首先了解一下使用者身份驗證
1 單一伺服器認證模式
一般過程如下:
- 使用者向伺服器傳送使用者名稱和密碼。
- 驗證伺服器後,相關資料(如使用者名稱,使用者角色等)將儲存在當前會話(session)中。
- 伺服器向用戶返回session_id,session資訊都會寫入到使用者的Cookie
- 使用者的每個後續請求都將通過在Cookie中取出session_id傳給伺服器。
- 伺服器收到session_id並對比之前儲存的資料,確認使用者的身份。
缺點:
顯而易見 它的缺點也很明顯
- 單點效能打壓力很大 無法對其擴充套件
- 分散式應用中需要session共享 session共享方案存在效能瓶頸。
session共享方案:
session廣播:效能瓶頸,不推薦
redis代替session:推薦,效能高
2、SSO(Single Sign On)模式
分散式,SSO(single sign on)模式:單點登入英文全稱Single Sign On,簡稱就是SSO。它的解釋是:在多個應用系統中,只需要登入一次,就可以訪問其他相互信任的應用系統。
- 如圖所示,圖中有3個系統,分別是業務A、業務B、和SSO。
- 業務A、業務B沒有登入模組。
- 而SSO只有登入模組,沒有其他的業務模組。
一般流程如下
- 當業務A、業務B需要登入時,將跳到SSO系統。
- SSO從使用者資訊資料庫中獲取使用者資訊並校驗使用者資訊,SSO系統完成登入。
- 然後將使用者資訊存入快取(例如redis)。
- 當用戶訪問業務A或業務B,需要判斷使用者是否登入時,將跳轉到SSO系統中進行使用者身份驗證,SSO判斷快取中是否存在使用者身份資訊。
- 這樣,只要其中一個系統完成登入,其他的應用系統也就隨之登入了。這就是單點登入(SSO)的定義。
優點:
使用者身份資訊獨立管理,更好的分散式管理。可以擴充套件自己的安全策略
缺點:
顯而易見這樣的缺點也比較明顯 只有一臺認證伺服器 要是掛了呢 訪問服務壓力比較大
3、Token模式(無狀態)
校驗流程
優點:
- 無狀態: token是無狀態,session是有狀態的
- 基於標準化:你的API可以採用標準化的 JSON Web Token (JWT)
缺點:
- 佔用頻寬
- 無法在伺服器端銷燬
一、訪問令牌的型別
本文采用的是自包含令牌
二、JWT令牌的介紹
1、什麼是JWT令牌
JWT是JSON Web Token的縮寫,即JSON Web令牌,是一種自包含令牌。
jwt使用場景
-
一種情況是webapi,類似之前的阿里雲播放憑證的功能
-
另一種情況是多web伺服器下實現無狀態分散式身份驗證
-
- JWT官網有一張圖描述了JWT的認證過程(可以看上面token認證的那張圖)
jwt的作用:
- JWT 最重要的作用就是對 token資訊的防偽作用
JWT的原理:
- 一個JWT由三個部分組成:JWT頭、有效載荷、簽名雜湊
- 最後由這三者組合進行base64編碼得到JWT
2、JWT令牌的組成
典型的,一個JWT看起來如下圖:
該物件為一個很長的字串,字元之間通過"."分隔符分為三個子串。
每一個子串表示了一個功能塊,總共有以下三個部分:JWT頭、有效載荷和簽名
注意:該字串只有jwt頭部分不能被解析(通過加密的方式) 其他的兩個部分 都可以(只通過Base64 URL編碼 並沒有被加密)
JWT頭
JWT頭部分是一個描述JWT元資料的JSON物件,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的程式碼中,alg屬性表示簽名使用的演算法,預設為HMAC SHA256(寫為HS256);typ屬性表示令牌的型別,JWT令牌統一寫為JWT。最後,使用Base64 URL演算法將上述JSON物件轉換為字串儲存。
有效載荷
有效載荷部分,是JWT的主體內容部分,也是一個JSON物件,包含需要傳遞的資料。 JWT指定七個預設欄位供選擇(存放的使用者資訊就是在這裡)
sub: 主題
iss: jwt簽發者
aud: 接收jwt的一方
iat: jwt的簽發時間
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
除以上預設欄位外,我們還可以自定義私有欄位,如下例:
{
"name": "Helen",
"admin": true,
"avatar": "helen.jpg"
}
請注意,預設情況下JWT是未加密的,任何人都可以解讀其內容,因此不要構建隱私資訊欄位,存放保密資訊,以防止資訊洩露。
JSON物件也使用Base64 URL演算法轉換為字串儲存。
簽名雜湊
簽名雜湊部分是對上面兩部分資料簽名,通過指定的演算法生成雜湊,以確保資料不會被篡改。
首先,需要指定一個密碼(secret)(就是鹽)。該密碼僅僅為儲存在伺服器中,並且不能向用戶公開。然後,使用標頭中指定的簽名演算法(預設情況下為HMAC SHA256)根據以下公式生成簽名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret) ==> 簽名hash
在計算出簽名雜湊後,JWT頭,有效載荷和簽名雜湊的三個部分組合成一個字串,每個部分用"."分隔,就構成整個JWT物件。
Base64URL演算法
簡單描述 就是會替換url中具有特殊含義的字元 用其他代替
如前所述,JWT頭和有效載荷序列化的演算法都用到了Base64URL。該演算法和常見Base64演算法類似,稍有差別。
作為令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字元是"+","/"和"=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"="去掉,"+"用"-"替換,"/"用"_"替換,這就是Base64URL演算法。
注意:base64編碼,並不是加密,只是把明文資訊變成了不可見的字串。但是其實只要用一些工具就可以把base64編碼解成明文,所以不要在JWT中放入涉及私密的資訊。
3、JWT的用法
客戶端接收伺服器返回的JWT,將其儲存在Cookie或localStorage中。
此後,客戶端將在與伺服器互動中都會帶JWT。如果將它儲存在Cookie中,就可以自動傳送,但是不會跨域,因此一般是將它放入HTTP請求的Header Authorization欄位中。
當跨域時,也可以將JWT放置於POST請求的資料主體中。
三、JWT問題和趨勢
1、JWT預設不加密,但可以加密。生成原始令牌後,可以使用該令牌再次對其進行加密。
2、當JWT未加密時,一些私密資料無法通過JWT傳輸。
3、JWT不僅可用於認證,還可用於資訊交換。善用JWT有助於減少伺服器請求資料庫的次數。
4、JWT的最大缺點是伺服器不儲存會話狀態,所以在使用期間不可能取消令牌或更改令牌的許可權。也就是說,一旦JWT簽發,在有效期內將會一直有效。
5、JWT本身包含認證資訊,因此一旦資訊洩露,任何人都可以獲得令牌的所有許可權。為了減少盜用,JWT的有效期不宜設定太長。對於某些重要操作,使用者在使用時應該每次都進行身份驗證。
6、為了減少盜用和竊取,JWT不建議使用HTTP協議來傳輸程式碼,而是使用加密的HTTPS協議進行傳輸。
二 測試jwt
一、建立Maven專案
1、專案
專案型別:Maven
groupId:look.word
artifactId:jwt
2、基本依賴
<dependencies>
<!--jwt依賴-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
JWT工具類
/**
* JWT工具類
*/
public class JwtUtil {
//有效期為
public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000 一個小時
//設定祕鑰明文
public static final String JWT_KEY = "sangeng";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的資料(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設定過期時間
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的資料(json格式)
* @param ttlMillis token超時時間
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設定過期時間
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主題 可以是JSON資料
.setIssuer("sg") // 簽發者
.setIssuedAt(now) // 簽發時間
.signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密演算法簽名, 第二個引數為祕鑰
.setExpiration(expDate);
}
/**
* 建立token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設定過期時間
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密後的祕鑰 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}