1. 概述
JSON Web Tokens (JWT) 是用於安全無狀態應用程序的默認標準。 Spring Security 框架提供將 JWT 集成到安全 REST API 的方法。 生成令牌的關鍵過程之一是應用簽名以確保真實性。
在本教程中,我們將探索一個使用 JWT 身份驗證的無狀態 Spring Boot 應用程序。 我們將設置必要的組件並創建一個用於對 JWT 進行簽名和驗證的加密 SecretKey 實例。
2. 項目設置
首先,讓我們使用 Spring Security 和 JWT 令牌對一個無狀態的 Spring Boot 應用程序進行初始化。 值得注意的是,為了簡化和簡潔,我們不會展示完整的設置代碼。
2.1. Maven 依賴
首先,讓我們添加 spring-boot-starter-web,spring-boot-starter-security,spring-boot-starter-data-jpa,以及 h2 數據庫 依賴到 pom.xml 中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>
Spring Boot Starter Web 提供用於構建 REST API 的 API。 此外,Spring Boot Starter Security 依賴項提供身份驗證和授權。 我們添加了一個內存數據庫以進行快速原型設計。
接下來,讓我們添加 jjwt-api,jjwt-impl 和 jjwt-jackson 依賴項到 pom.xml 中:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
</dependency>
這些依賴項提供生成和對 JWT 進行簽名,並將其集成到 Spring Security 中的 API。
2.2. JWT 配置
首先,讓我們創建一個身份驗證入口點:
@Component
class AuthEntryPointJwt implements AuthenticationEntryPoint {
// ...
}
在這裏,我們創建一個類來處理 Spring Security 應用程序中 JWT 身份驗證的授權訪問嘗試。 它充當網關,確保只有具有有效訪問權限的用户才能訪問受保護的資源。
然後,讓我們創建一個名為 AuthTokenFilter 的類,該類攔截傳入的請求,驗證 JWT 令牌,並在存在有效令牌時驗證用户:
class AuthTokenFilter extends OncePerRequestFilter {
// ...
}
最後,讓我們創建一個 JwtUtil 類,該類提供用於創建和驗證令牌的方法:
@Component
class JwtUtils {
// ...
}
此類包含使用 signWith() 方法的邏輯。
2.3. 安全配置
最後,讓我們定義 SecurityConfiguration 類並集成 JWT:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfiguration {
// ...
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(req -> req.requestMatchers(WHITE_LIST_URL)
.permitAll()
.anyRequest()
.authenticated())
.exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(
authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class
);
return http.build();
}
// ...
}
在代碼中,我們集成 JWT 入口點和過濾器以啓用 JWT 身份驗證。
3. signWith() 方法
JJWT 庫提供 signWith() 方法,用於使用特定的加密算法和密鑰對 JWT 進行簽名。此簽名過程對於確保 JWT 的完整性和真實性至關重要。
signWith() 方法接受 Key 或 SecretKey 實例以及簽名算法作為參數。 哈希消息認證碼 (HMAC) 算法是常用的簽名算法之一。
重要的是,該方法需要一個密鑰,通常是一個字節數組,用於簽名過程。我們可以使用 Key 或 SecretKey 實例將密鑰字符串轉換為密鑰。
值得注意的是,我們可以將普通字符串作為密鑰傳遞。但是,這缺乏 Key 或 SecretKey 實例的安全保證和隨機性。
使用 SecretKey 實例可確保 JWT 的完整性和真實性。
4. Signing JWT
我們可以創建一個強大的密鑰來使用 Key 和 SecretKey 實例對 JWT 進行簽名。
4.1. 使用 Key 實例
本質上,我們可以將一個秘密字符串轉換為 Key 實例以進一步對其進行加密之前使用它來對 JWT 進行簽名。
首先,確保秘密字符串已進行 Base64 編碼:
private String jwtSecret = "4261656C64756E67";
接下來,創建一個 Key 對象:
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(this.jwtSecret);
return Keys.hmacShaKeyFor(keyBytes);
}
在上面的代碼中,我們解碼 jwtSecret 到一個字節數組。接下來,我們調用 hmacShaKeyFor(),該方法接受 keyBytes 作為參數,在 Keys 實例上。這會生成基於 HMAC 算法的密鑰。
如果密鑰未進行 Base64 編碼,我們可以調用字符串的 getByte() 方法:
private Key getSigningKey() {
byte[] keyBytes = this.jwtSecret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
但是,不建議這樣做,因為密鑰可能格式不正確,字符串可能包含非 UTF-8 字符。因此,我們必須確保密鑰字符串在生成密鑰之前已進行 Base64 編碼。
4.2. 使用 SecretKey 實例
此外,我們可以使用 HMAC-SHA 算法創建一個強大的密鑰,從而創建一個 SecretKey 實例。讓我們創建一個返回密鑰的 SecretKey 實例:
SecretKey getSigningKey() {
return Jwts.SIG.HS256.key().build();
}
在這裏,我們直接使用 HMAC-SHA 算法,而無需使用字節數組。這會生成一個強簽名密鑰。接下來,我們可以通過將 getSigningKey() 作為參數傳遞到 signWith() 方法來更新它。
或者,我們可以從 Base16 編碼字符串創建 SecretKey 實例:
SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
return Keys.hmacShaKeyFor(keyBytes);
}
這會生成一個強類型的 SecretKey 類型的密鑰,用於對 JWT 進行簽名和驗證。
值得注意的是,由於新的 verifyWith() 方法接受 SecretKey 類型作為參數來驗證令牌,因此使用 SecretKey 實例而不是 Key 實例是建議的。
4.3. 應用密鑰
現在,讓我們將密鑰應用於我們應用程序的 JWT:
String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.subject((userPrincipal.getUsername()))
.issuedAt(new Date())
.expiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(key)
.compact();
}
signWith() 方法接受 SecretKey 實例作為參數,以將唯一的簽名附加到令牌。
5. 結論
在本文中,我們學習瞭如何使用 Java Key 和 SecretKey 實例創建密鑰。 此外,我們還看到了一個無狀態 Spring Boot 應用程序,它利用 JWT 令牌進行令牌完整性驗證,並使用 Key 或 SecretKey 實例進行簽名和驗證。 使用純字符串已不再推薦。