天天看點

java token攔截器_Java基于JWT的token認證

一、背景引入

由于Http協定本身是無狀态的,那麼伺服器是怎麼識别兩次請求是不是來自同一個用戶端呢,傳統使用者識别是基于seesion和cookie實作的。大緻流程如下:

java token攔截器_Java基于JWT的token認證

使用者向伺服器發送使用者名和密碼請求使用者進行校驗,校驗通過後建立session繪畫,并将使用者相關資訊儲存到session中伺服器将sessionId回寫到使用者浏覽器cookie中使用者以後的請求,都會鞋帶cookie發送到伺服器伺服器得到cookie中的sessionId,從session集合中找到該使用者的session回話,識别使用者

這種模式有很多缺點,對于分布式架構的支援以及擴充性不是很好。而且session是儲存在記憶體中,單台伺服器部署如果登陸使用者過多占用伺服器資源也多,做叢集必須得實作session共享的話,叢集數量又不易太多,否則伺服器之間頻繁同步session也會非常耗性能。當然也可以引入持久層,将session儲存在資料庫或者redis中,儲存資料庫的話效率不高,存redis效率高,但是對redis依賴太重,如果redis挂了,影響整個應用。還有一種辦法就是不存伺服器,而是把使用者辨別資料存在浏覽器,浏覽器每次請求都攜帶該資料,伺服器做校驗,這也是JWT的思想。

二、JWT介紹

2.1 概念介紹

Json Web Token(JWT)是目前比較流行的跨域認證解決方案,是一種基于JSON的開發标準,由于資料是可以經過簽名加密的,比較安全可靠,一般用于前端和伺服器之間傳遞資訊,也可以用在移動端和背景傳遞認證資訊。

2.2 組成結構

JWT就是一段字元串,格式如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.qfd0GelhE1aGr15LrnYlIZ_3UToaOM5HeMcXrmDG

由于三部分組成,之間用"."接。第一部分是頭資訊Header,中間部分是載荷Payload,最後部分是簽名資訊Signature。

頭資訊Header:描述JWT基本資訊,typ表示采用JWT令牌,alg(algorithm)表示采用什麼算法進行簽名,常見算法有HmacSHA256(HS256)、HmacSHA384(HS384)、HmacSHA512(HS512)、SHA256withECDSA(ES256)、SHA256withRSA(RS256)、SHA512withRSA(RS512)等。如果采用HS256則頭資訊結構為:{

"typ": "JWT",

"alg": "HS256

}

載荷Payload:載荷(也可以叫載體)是具體的傳輸内容,包括一些标準屬性,iss: 該JWT的簽發者,exp: 過期時間戳,iat: 簽發時間戳,jti: JWTID等等。也可以添加其他需要傳遞的内容資料。結構為:{

"iss": "kkk",

"iat": 1548818203,

"exp": 1548818212,

"sub": "test.com

}

簽名Signature:對頭資訊和載荷進行簽名,保證傳輸過程中資訊不被篡改,比如:将頭資訊和載荷分别進行base64加密得到字元串a和b,将字元串a和b以點相連并簽名得到字元串c,将字元串a、b、c以點相連得到最終token。

2.3 驗證流程

使用JWT的驗證流程為:使用者送出使用者名,密碼到伺服器背景背景驗證通過,伺服器端生成Token字元串,傳回到用戶端用戶端儲存Token,下一次請求資源時,附帶上Token資訊伺服器端驗證Token是否由伺服器簽發的(一般在攔截器中驗證),若Token驗證通過,則傳回需要的資源

驗證流程和基于session大體相同,隻不過不是基于session,而是采用攔截器在代碼中實驗驗證,傳回給用戶端的也不是sessionid,而是經過一定算法得出來的token字元串。

2.4 源碼分析

Java中有封裝好的開源哭JWT可以直接使用,下面就分析下關鍵代碼驗證以下内容。

Header頭資訊結構分析關鍵源碼如下://token生成方法

public static void main(String[] args) {

String token= JWT.create().withAudience("audience")

.withIssuedAt(new Date())

.withSubject("subject")

.withExpiresAt(new Date()).withJWTId("jtiid")

.sign(Algorithm.HMAC256(user.getPassword()));

}

public abstract class Algorithm {

private final String name;

private final String description;

//...其他方法省略...

public static Algorithm HMAC256(String secret) throws IllegalArgumentException {

return new HMACAlgorithm("HS256", "HmacSHA256", secret);

}

//...其他方法省略...

}

class HMACAlgorithm extends Algorithm {

private final CryptoHelper crypto;

private final byte[] secret;

//...其他方法省略...

HMACAlgorithm(String id, String algorithm, byte[] secretBytes)

throws IllegalArgumentException {

this(new CryptoHelper(), id, algorithm, secretBytes);

}

//...其他方法省略..

}

public String sign(Algorithm algorithm) throws IllegalArgumentException,

JWTCreationException {

if (algorithm == null) {

throw new IllegalArgumentException("The Algorithm cannot be null.");

} else {

this.headerClaims.put("alg", algorithm.getName());

this.headerClaims.put("typ", "JWT");

String signingKeyId = algorithm.getSigningKeyId();

if (signingKeyId != null) {

this.withKeyId(signingKeyId);

}

public final class JWTCreator {

private final Algorithm algorithm;

private final String headerJson;

private final String payloadJson;

private JWTCreator(Algorithm algorithm,

Map headerClaims,

Map payloadClaims) throws JWTCreationException {

this.algorithm = algorithm;

try {

ObjectMapper mapper = new ObjectMapper();

SimpleModule module = new SimpleModule();

module.addSerializer(ClaimsHolder.class, new PayloadSerializer());

mapper.registerModule(module);

mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);

this.headerJson = mapper.writeValueAsString(headerClaims);

this.payloadJson =

mapper.writeValueAsString(new ClaimsHolder(payloadClaims));

} catch (JsonProcessingException var6) {

throw new JWTCreationException(

"Some of the Claims couldn't be converted to a valid JSON format.",

var6);

}

}

//...其他方法省略...

headerClaims是一個Map,包括兩個屬性typ和alg,typ值固定JWT,alg傳過來的簽名算法這裡使用的

HmacSHA256簡稱HS256。typ和alg組成Header頭資訊。

Payload載荷結構分析關鍵源碼如下:public abstract class JWT {

public JWT() {

}

public static DecodedJWT decode(String token) throws JWTDecodeException {

return new JWTDecoder(token);

}

public static Verification require(Algorithm algorithm) {

return JWTVerifier.init(algorithm);

}

public static Builder create() {

return JWTCreator.init();

}

}

public static class Builder {

private final Map payloadClaims = new HashMap();

private Map headerClaims = new HashMap();

Builder() {

}

public JWTCreator.Builder withHeader(Map headerClaims) {

this.headerClaims = new HashMap(headerClaims);

return this;

}

public JWTCreator.Builder withKeyId(String keyId) {

this.headerClaims.put("kid", keyId);

return this;

}

public JWTCreator.Builder withIssuer(String issuer) {

this.addClaim("iss", issuer);//簽發人

return this;

}

public JWTCreator.Builder withSubject(String subject) {

this.addClaim("sub", subject);//主題

return this;

}

public JWTCreator.Builder withAudience(String... audience) {

this.addClaim("aud", audience);//接受一方

return this;

}

public JWTCreator.Builder withExpiresAt(Date expiresAt) {

this.addClaim("exp", expiresAt);//過期時間

return this;

}

public JWTCreator.Builder withNotBefore(Date notBefore) {

this.addClaim("nbf", notBefore);//生效時間

return this;

}

public JWTCreator.Builder withIssuedAt(Date issuedAt) {

this.addClaim("iat", issuedAt);//簽發時間

return this;

}

public JWTCreator.Builder withJWTId(String jwtId) {

this.addClaim("jti", jwtId);//編号

return this;

}

public JWTCreator.Builder withClaim(String name, Boolean value)

throws IllegalArgumentException {

this.assertNonNull(name);

this.addClaim(name, value);

return this;

}

public JWTCreator.Builder withClaim(String name, Integer value)

throws IllegalArgumentException {

this.assertNonNull(name);

this.addClaim(name, value);

return this;

}

//...其他方法省略...

}

Payload是一個json對象,存放需要傳遞的資料,JTW預設規定了幾個屬性,如果需要添加其他屬性可以調用其重載方法witchClaim()添加。

Signature簽名部分源碼如下:private String sign() throws SignatureGenerationException {

String header = Base64.encodeBase64URLSafeString(

this.headerJson.getBytes(StandardCharsets.UTF_8));

String payload = Base64.encodeBase64URLSafeString(

this.payloadJson.getBytes(StandardCharsets.UTF_8));

String content = String.format("%s.%s", header, payload);

byte[] signatureBytes = this.algorithm.sign(

content.getBytes(StandardCharsets.UTF_8));

String signature = Base64.encodeBase64URLSafeString(signatureBytes);

return String.format("%s.%s", content, signature);

}

從這裡可以看出,所謂token就是分别對header和payload的json字元串做Base64加密得到a和b,并将結果拼接一起,在進行簽名得到c,最終把a、b、c三部分内容以點拼接起來形成token,傳回用戶端儲存,用戶端以後每次請求都在header中加入token,伺服器采用攔截器方式擷取header中的token做校驗,識别使用者。

三、示例

3.1 資料準備

建立使用者表

java token攔截器_Java基于JWT的token認證

3.2 搭建springboot工程

java token攔截器_Java基于JWT的token認證

設定工程Group、Artifact、Version、Name等資訊

java token攔截器_Java基于JWT的token認證

Spring Boot的版本選擇2.0.8,選擇導入web的起步器

java token攔截器_Java基于JWT的token認證

建立工程成功之後,将各個包建立出來,工程目錄結構如下:

java token攔截器_Java基于JWT的token認證

3.3 引入pom依賴

java token攔截器_Java基于JWT的token認證
java token攔截器_Java基于JWT的token認證

3.4 編寫application.yml配置檔案

java token攔截器_Java基于JWT的token認證

3.5 編寫User實體類

java token攔截器_Java基于JWT的token認證

Result類:用于統一傳回消息的封裝

java token攔截器_Java基于JWT的token認證

TokenUtil類,用于生成token

java token攔截器_Java基于JWT的token認證
java token攔截器_Java基于JWT的token認證

VerifyToken注解類:加到controller方法上表示該方法需要驗證token。

java token攔截器_Java基于JWT的token認證

3.6 編寫mapper接口和service層

mapper類:

java token攔截器_Java基于JWT的token認證

UserService接口:

java token攔截器_Java基于JWT的token認證

UserServiceImpl實作類:

java token攔截器_Java基于JWT的token認證

3.7 編寫攔截器和全局異常處理器

AuthInterceptor攔截器類:用于token驗證。

java token攔截器_Java基于JWT的token認證
java token攔截器_Java基于JWT的token認證
java token攔截器_Java基于JWT的token認證

全局異常處理器GloabllExceptionHandler:用于異常的捕獲。

java token攔截器_Java基于JWT的token認證

3.8 編寫配置類及controller

攔截器配置類InterceptorConfig:配置攔截所有請求

java token攔截器_Java基于JWT的token認證
java token攔截器_Java基于JWT的token認證

UserController類:

java token攔截器_Java基于JWT的token認證

3.9 測試

測試1:使用postman發送get請求http://localhost:8088/user/getUser?id=1

java token攔截器_Java基于JWT的token認證

測試2:發送post請求http://localhost:8088/user/login 密碼故意輸錯

java token攔截器_Java基于JWT的token認證

測試3:發送post請求http://localhost:8088/user/login 填正确的使用者名密碼