JWT,全稱(JSON Web Tokens),是一種認證授權(quán)的機制。當用戶發(fā)送請求后,服務(wù)器端會根據(jù)請求中的token,解析并判斷該用戶是否可以調(diào)用接口,從而避免接口被直接調(diào)用引發(fā)安全問題。
例如,有一個刪除用戶的接口,只有管理員才能調(diào)用。服務(wù)器端需要知道發(fā)送刪除用戶的請求是否是管理員發(fā)出的。那么,客戶端可以在請求頭放置一個token,服務(wù)器端將會從token中解析用戶信息,判斷該用戶是否是管理員,從而決定是否調(diào)用刪除用戶接口。
有了token,我們避免了每次發(fā)送刪除請求都發(fā)送用戶名密碼讓服務(wù)器判斷用戶角色,因為token已經(jīng)承載了這些信息。并且,與另一個認證機制session相比,服務(wù)器解析token只需要判斷token是否有效,而不需要在服務(wù)器中比對用戶登錄的信息,這將大大減少服務(wù)器的開銷。
那么token是怎么生成的,而用戶是怎么獲取到,服務(wù)器是怎么解析的呢。
原理
token生成
首先我們得知道,token由這三部分構(gòu)成,頭部、載荷、簽證
-
頭部(header)
聲明了類型和加密的算法{ 'typ': 'JWT', 'alg': 'HS256' }
頭部是上述信息經(jīng)過base64轉(zhuǎn)換所得的字符串。
- 載荷(payload)
存儲一些有效信息。
當用戶登錄成功時,服務(wù)器將去數(shù)據(jù)庫查找該用戶,把該用戶的信息放在載荷中,并在載荷中附加其他信息,如jwt的簽發(fā)時間iat,jwt的過期時間exp,jwt的簽發(fā)者iss等,但這些附加信息并不強制加上。然后,服務(wù)器將載荷進行base64轉(zhuǎn)換。 - 簽證(signature)
簽證這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行秘鑰secret組合加密,然后就構(gòu)成了jwt的第三部分。
token發(fā)送
然后,服務(wù)器將此token信息發(fā)在響應(yīng)體的header中。當客戶端接收到此響應(yīng),則獲取響應(yīng)體的token信息,下次發(fā)送請求則帶上token。
token接收并解析
當服務(wù)器下次收到用戶請求,獲取到請求中的token信息后,需要經(jīng)歷幾個步驟。
- 判斷token是否過期,這一部分,服務(wù)器將根據(jù)token中前兩部分的字符串解析出頭部和載荷的json格式,根據(jù)解析后的載荷信息判斷token是否過期。
- 之后再將頭部和載荷重新加密得到簽證。token的簽證和計算后的簽證進行比對,不一致則無效。
- 根據(jù)payload信息得到用戶信息,判斷該用戶是否滿足調(diào)用接口的權(quán)限。
代碼實現(xiàn)思路
1.準備配置文件
2.token的生成,時間:在用戶登錄。
-
驗證用戶名密碼
image.png
AuthService調(diào)用AuthenticationManager的authenticate方法,該方法有兩個步驟:loadUserByUsername,verify
- loadUserByUsername:根據(jù)用戶名去數(shù)據(jù)庫查找用戶,查找不到返回用戶未找到信息,找到則將用戶信息合成JWTUser(用于生成payload)。
- verify:驗證JWTUser的密碼是否與登錄發(fā)送的密碼匹配。
獲取JWTUser,可調(diào)用Authentication類的getPrincipal方法
將獲取到的JWTUser生成payload。
-
將payload生成token,可調(diào)用authRepository的generateToken方法
public String generateToken(Map<String, Object> payload) { return Jwts.builder() .setClaims(payload) .setExpiration(new Date(System.currentTimeMillis() + expirationInSeconds * 1000)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); }
3.token的發(fā)送,時間:用戶登錄.
將獲取后的token放置在響應(yīng)體頭部,可調(diào)用response.addHeader的方法。
4.token的解析
在配置文件JWTAuthenticationFilter:
獲取token: String authorizationHeader = request.getHeader(header)
-
判斷token是否存在
- 不存在:準備調(diào)用接口
- 存在:判斷token是否有效,有效則到下一步,無效則返回無效信息
-
生成payload:
public String extractAuthorizedPayload(String jwtToken) { return StringUtils.writeObjectAsJsonString(Jwts.parser().setSigningKey(jwtSecret) .parseClaimsJws(jwtToken) .getBody()); } 設(shè)置上下文
判斷用戶是否有權(quán)限調(diào)用接口
調(diào)用接口
