JUnit 測試中使用 JwtDecoder 模擬 JWT
1. 概述
在本教程中,我們將探索如何有效地模擬JWT (JSON Web 令牌),以對使用 JWT 身份驗證的 Spring Security 應用程式進行單元測試。測試受 JWT 保護的端點通常需要模擬不同的 JWT 場景,而不依賴實際的令牌產生或驗證。這種方法使我們能夠編寫強大的單元測試,而無需在測試期間管理真實 JWT 令牌的複雜性。
模擬 JWT 解碼在單元測試中非常重要,因為它允許我們將身份驗證邏輯與外部依賴項(例如令牌生成服務或第三方身分提供者)隔離。透過模擬不同的 JWT 場景,我們可以確保我們的應用程式正確處理有效令牌、自訂聲明、無效令牌和過期令牌。
我們將學習如何使用Mockito模擬JwtDecoder
、建立自訂 JWT 聲明以及測試各種場景。在本教學結束時,我們將能夠為基於 Spring Security JWT 的身份驗證邏輯編寫全面的單元測試。
2. 設定和配置
在開始編寫測試之前,讓我們使用必要的依賴項來設定測試環境。我們將使用Spring Security OAuth2, Mockito,
和JUnit 5
進行測試。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>6.4.2</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>
spring-security-oauth2-jose
依賴項支援 Spring Security 中的 JWT,包括JwtDecoder
接口,用於解碼和驗證 JWT。 mockito-core
相依性讓我們在測試中模擬依賴項,確保我們可以將被測單元UserController,
與外部系統隔離。
讓我們建立一個測試類別MockJwtDecoderJUnitTest
並使用Mockito
來模擬JwtDecoder
。這是初始設定:
@ExtendWith(MockitoExtension.class)
public class MockJwtDecoderJUnitTest {
@Mock
private JwtDecoder jwtDecoder;
@InjectMocks
private UserController userController;
@BeforeEach
void setUp() {
SecurityContextHolder.clearContext();
}
}
在此設定中,我們使用@ExtendWith(MockitoExtension.class)
在 JUnit 測試中啟用 Mockito。使用 @Mock 模擬JwtDecoder
@Mock,
並使用 @InjectMocks 向UserController
注入模擬的JwtDecoder
@InjectMocks.
SecurityContextHolder
在每次測試之前都會被清除,以確保乾淨的狀態。
3. 模擬JWT解碼
透過我們的環境設置,我們編寫測試來模擬 JWT 解碼。我們首先測試有效的 JWT 令牌。
3.1.測試有效令牌
當提供有效令牌時,應用程式應返回使用者資訊。以下是我們測試此場景的方法:
@Test
void whenValidToken_thenReturnsUserInfo() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "john.doe");
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
ResponseEntity<String> response = userController.getUserInfo(jwt);
assertEquals("Hello, john.doe", response.getBody());
assertEquals(HttpStatus.OK, response.getStatusCode());
}
在此測試中,我們建立一個sub
(主題)聲明的模擬 JWT。 JwtAuthenticationToken
用於設定安全上下文, UserController
處理令牌並回傳回應。我們使用斷言來驗證回應。
3.2.測試自訂聲明
有時,JWT 包含自訂聲明,例如角色或電子郵件地址。我們測試我們的應用程式如何處理這些自訂聲明:
@Test
void whenTokenHasCustomClaims_thenProcessesCorrectly() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "john.doe");
claims.put("roles", Arrays.asList("ROLE_USER", "ROLE_ADMIN"));
claims.put("email", "[email protected]");
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
ResponseEntity<String> response = userController.getUserInfo(jwt);
assertEquals("Hello, john.doe", response.getBody());
assertEquals(HttpStatus.OK, response.getStatusCode());
}
在此測試中,我們將自訂聲明( role
和email
)新增至 JWT。控制器處理令牌並傳回預期回應。
4. 處理不同場景
4.1.測試無效令牌
當提供無效令牌時,應用程式應拋出JwtValidationException.
以下是我們測試此場景的方法:
@Test
void whenInvalidToken_thenThrowsException() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "invalid.user");
Jwt invalidJwt = Jwt.withTokenValue("invalid_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
when(jwtDecoder.decode("invalid_token"))
.thenThrow(new JwtValidationException(
"Invalid token",
Arrays.asList(new OAuth2Error("invalid_token"))
));
JwtValidationException thrown = assertThrows(
JwtValidationException.class,
() -> jwtDecoder.decode("invalid_token")
);
assertEquals("Invalid token", thrown.getMessage());
}
在此測試中,我們模擬JwtDecoder
在解碼無效令牌時拋出JwtValidationException
。我們驗證是否使用正確的訊息引發了異常。
4.2.測試過期的令牌
在這種情況下,當提供過期令牌時,應用程式也應該拋出JwtValidationException.
以下是我們測試此場景的方法:
@Test
void whenTokenExpired_thenThrowsException() {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "expired.user");
claims.put("exp", Instant.now().minusSeconds(3600));
claims.put("iat", Instant.now().minusSeconds(7200));
Jwt expiredJwt = Jwt.withTokenValue("expired_token")
.header("alg", "none")
.claims(existingClaims -> existingClaims.putAll(claims))
.build();
when(jwtDecoder.decode("expired_token"))
.thenThrow(new JwtValidationException(
"Token expired",
Arrays.asList(new OAuth2Error("invalid_token"))
));
JwtValidationException thrown = assertThrows(
JwtValidationException.class,
() -> jwtDecoder.decode("expired_token")
);
assertEquals("Token expired", thrown.getMessage());
}
在此測試中,我們建立一個過期時間 ( exp
) 已過去的 JWT。我們模擬JwtDecoder
在解碼過期令牌時拋出JwtValidationException
。我們驗證是否使用正確的訊息引發了異常。
5. 結論
在本教程中,我們學習如何使用 Mockito 在 JUnit 測試中模擬 JWT 解碼。我們涵蓋了各種場景,包括使用自訂聲明測試有效令牌、處理無效令牌以及管理過期令牌。透過模擬 JWT 解碼,我們可以為 Spring Security 應用程式編寫單元測試,而無需依賴外部令牌產生或驗證服務。這種方法確保我們的測試快速、可靠且獨立於外部相依性。
本文的完整原始碼可在 GitHub 上取得。