1+ package com .tinyengine .it .login .utils ;
2+
3+ import cn .hutool .core .util .ReflectUtil ;
4+ import com .tinyengine .it .login .service .TokenBlacklistService ;
5+
6+ import io .jsonwebtoken .Jwts ;
7+ import io .jsonwebtoken .security .Keys ;
8+ import org .springframework .core .env .Environment ;
9+ import org .junit .jupiter .api .BeforeEach ;
10+ import org .junit .jupiter .api .Test ;
11+ import org .mockito .InjectMocks ;
12+ import org .mockito .Mock ;
13+ import org .mockito .MockitoAnnotations ;
14+
15+ import javax .crypto .SecretKey ;
16+ import java .nio .charset .StandardCharsets ;
17+ import java .util .*;
18+
19+ import static org .junit .jupiter .api .Assertions .*;
20+ import static org .mockito .Mockito .when ;
21+
22+ class JwtUtilTest {
23+
24+ @ Mock
25+ private TokenBlacklistService tokenBlacklistService ;
26+ @ Mock
27+ private Environment environment ;
28+
29+
30+ @ InjectMocks
31+ private JwtUtil jwtUtil ;
32+
33+ @ BeforeEach
34+ void setUp () {
35+ MockitoAnnotations .openMocks (this );
36+
37+ ReflectUtil .setFieldValue (jwtUtil , "tokenBlacklistService" , tokenBlacklistService );
38+ ReflectUtil .setFieldValue (jwtUtil , "environment" , environment );
39+ String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm" ;
40+ ReflectUtil .setFieldValue (jwtUtil , "cachedSecret" , testSecret );
41+
42+ when (environment .getActiveProfiles ()).thenReturn ("dev" .split ("," ));
43+ // Also set expiration time if it's not a constant but an injected value
44+
45+ }
46+ @ Test
47+ void validateSecretConfigurationThrowsExceptionWhenSecretIsMissingInNonDevProfile () {
48+ when (environment .getActiveProfiles ()).thenReturn (new String []{"prod" });
49+
50+ assertThrows (IllegalStateException .class , jwtUtil ::validateSecretConfiguration );
51+ }
52+
53+ @ Test
54+ void validateSecretConfigurationGeneratesSecretInDevProfile () {
55+ when (environment .getActiveProfiles ()).thenReturn (new String []{"dev" });
56+ assertDoesNotThrow (jwtUtil ::validateSecretConfiguration );
57+ }
58+
59+
60+ @ Test
61+ void validateSecretConfigurationThrowsExceptionForInvalidSecret () {
62+ when (environment .getActiveProfiles ()).thenReturn (new String []{"prod" });
63+ System .setProperty ("SECRET_STRING" , "short" );
64+ assertThrows (IllegalStateException .class , jwtUtil ::validateSecretConfiguration );
65+ }
66+ @ Test
67+ void getSecretKeyReturnsValidKey () {
68+ // 使用足够长的密钥(>32字节)
69+ String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm" ;
70+
71+ jwtUtil .validateSecretConfiguration (); // 现在不会 NPE
72+ SecretKey secretKey = jwtUtil .getSecretKey ();
73+
74+ assertNotNull (secretKey );
75+ assertEquals ("HmacSHA256" , secretKey .getAlgorithm ());
76+
77+ }
78+
79+ @ Test
80+ void generateTokenReturnsValidToken () {
81+ String username = "testUser" ;
82+ String roles = "USER" ;
83+ String userId = "123" ;
84+ Integer platformId = 1 ;
85+
86+ String token = jwtUtil .generateToken (username , roles , userId , null , platformId );
87+
88+ assertNotNull (token );
89+ assertFalse (token .isEmpty ());
90+ assertEquals (username , jwtUtil .getUsernameFromToken (token ));
91+ assertEquals (roles , jwtUtil .getRolesFromToken (token ));
92+ assertEquals (userId , jwtUtil .getUserIdFromToken (token ));
93+ assertEquals (platformId , jwtUtil .getPlatformIdFromToken (token ));
94+ }
95+
96+ @ Test
97+ void generateTokenThrowsExceptionForNullUsername () {
98+ assertThrows (IllegalArgumentException .class , () -> jwtUtil .generateToken (null , "USER" , "123" , null , 1 ),
99+ "Null username should throw IllegalArgumentException" );
100+ }
101+
102+ @ Test
103+ void generateTokenHandlesEmptyRoles () {
104+ String token = jwtUtil .generateToken ("testUser" , "" , "123" , null , 1 );
105+
106+ assertNotNull (token );
107+ assertFalse (token .isEmpty ());
108+ assertEquals ("" , jwtUtil .getRolesFromToken (token ));
109+ }
110+
111+ @ Test
112+ void generateTokenHandlesNullUserId () {
113+ // Given
114+ String username = "testUser" ;
115+ String roles = "USER" ;
116+ Integer platformId = 1 ;
117+ Object tenants = null ; // 显式传入 null
118+ // When
119+ String token = jwtUtil .generateToken (username , roles , null , tenants , platformId );
120+
121+ // Then
122+ assertNotNull (token );
123+ assertFalse (token .isEmpty ());
124+
125+ // 使用 assertAll 聚合断言,避免单个失败中断其余检查
126+ assertAll ("Token claims validation" ,
127+ () -> assertNull (jwtUtil .getUserIdFromToken (token ), "userId should be null" ),
128+ () -> assertEquals (username , jwtUtil .getUsernameFromToken (token ), "username mismatch" ),
129+ () -> assertEquals (roles , jwtUtil .getRolesFromToken (token ), "roles mismatch" ),
130+ () -> assertEquals (platformId , jwtUtil .getPlatformIdFromToken (token ), "platformId mismatch" ),
131+ () -> assertEquals (Collections .emptyList (), jwtUtil .getTenantsFromToken (token ), "tenants should be empty list" )
132+ );
133+ }
134+
135+ @ Test
136+ void generateTokenHandlesNullTenants () {
137+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
138+
139+ assertNotNull (token );
140+ assertFalse (token .isEmpty ());
141+ assertTrue (jwtUtil .getTenantsFromToken (token ).isEmpty ());
142+ }
143+
144+ @ Test
145+ void generateTokenHandlesEmptyTenantsList () {
146+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , new ArrayList <>(), 1 );
147+
148+ assertNotNull (token );
149+ assertFalse (token .isEmpty ());
150+ assertTrue (jwtUtil .getTenantsFromToken (token ).isEmpty ());
151+ }
152+
153+ @ Test
154+ void generateTokenHandlesNullPlatformId () {
155+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , null );
156+
157+ assertNotNull (token );
158+ assertFalse (token .isEmpty ());
159+ assertNull (jwtUtil .getPlatformIdFromToken (token ));
160+ }
161+
162+ @ Test
163+ void generateTokenHandlesSpecialCharactersInUsername () {
164+ String username = "user!@#$%^&*()" ;
165+ String token = jwtUtil .generateToken (username , "USER" , "123" , null , 1 );
166+
167+ assertNotNull (token );
168+ assertFalse (token .isEmpty ());
169+ assertEquals (username , jwtUtil .getUsernameFromToken (token ));
170+ }
171+
172+ @ Test
173+ void generateTokenHandlesLongUsername () {
174+ String username = "a" .repeat (256 );
175+ String token = jwtUtil .generateToken (username , "USER" , "123" , null , 1 );
176+
177+ assertNotNull (token );
178+ assertFalse (token .isEmpty ());
179+ assertEquals (username , jwtUtil .getUsernameFromToken (token ));
180+ }
181+
182+ @ Test
183+ void generateTokenHandlesCombinationOfNullAndValidParameters () {
184+ assertThrows (IllegalArgumentException .class , () -> jwtUtil .generateToken (null , "USER" , "123" , null , 1 ),
185+ "Null username should throw IllegalArgumentException" );
186+
187+ }
188+
189+ @ Test
190+ void validateTokenReturnsTrueForValidToken () {
191+ String token = jwtUtil .generateToken ("validUser" , "USER" , "123" , null , 1 );
192+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (false );
193+
194+ boolean isValid = jwtUtil .validateToken (token );
195+
196+ assertTrue (isValid , "Valid token should return true" );
197+ }
198+
199+ @ Test
200+ void validateTokenReturnsFalseForBlacklistedToken () {
201+ String token = "blacklistedToken" ;
202+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
203+
204+ boolean isValid = jwtUtil .validateToken (token );
205+
206+ assertFalse (isValid , "Blacklisted token should return false" );
207+ }
208+
209+ @ Test
210+ void validateTokenReturnsFalseForExpiredToken () {
211+ String expiredToken = createExpiredToken ("expiredUser" , "USER" , "999" );
212+
213+ when (tokenBlacklistService .isTokenBlacklisted (expiredToken )).thenReturn (false );
214+
215+ // Simulate token expiration by waiting or mocking expiration
216+ boolean isValid = jwtUtil .validateToken (expiredToken );
217+
218+ assertFalse (isValid , "Expired token should return false" );
219+ }
220+
221+ @ Test
222+ void validateTokenReturnsFalseForMalformedToken () {
223+ String malformedToken = "malformedToken" ;
224+
225+ boolean isValid = jwtUtil .validateToken (malformedToken );
226+
227+ assertFalse (isValid , "Malformed token should return false" );
228+ }
229+
230+ @ Test
231+ void validateTokenReturnsFalseForInvalidSignature () {
232+ String token = jwtUtil .generateToken ("validUser" , "USER" , "123" , null , 1 );
233+ String tamperedToken = token + "tampered" ;
234+
235+ boolean isValid = jwtUtil .validateToken (tamperedToken );
236+
237+ assertFalse (isValid , "Token with invalid signature should return false" );
238+ }
239+
240+ @ Test
241+ void validateTokenThrowsExceptionForNullToken () {
242+ assertThrows (IllegalArgumentException .class , () -> jwtUtil .validateToken (null ),
243+ "Null token should throw IllegalArgumentException" );
244+ }
245+
246+ @ Test
247+ void validateTokenReturnsFalseForEmptyToken () {
248+ String emptyToken = "" ;
249+
250+ boolean isValid = jwtUtil .validateToken (emptyToken );
251+
252+ assertFalse (isValid , "Empty token should return false" );
253+ }
254+
255+ @ Test
256+ void validateTokenReturnsFalseForTokenWithMissingClaims () {
257+ String token = jwtUtil .generateToken ("validUser" , null , null , null , null );
258+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
259+
260+ boolean isValid = jwtUtil .validateToken (token );
261+
262+ assertFalse (isValid , "Token with missing claims should return false" );
263+ }
264+
265+ @ Test
266+ void validateTokenHandlesTokenWithExtraClaims () {
267+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
268+ // Add extra claims manually
269+ String tokenWithExtraClaims = token + ".extraClaims" ;
270+
271+ boolean isValid = jwtUtil .validateToken (tokenWithExtraClaims );
272+
273+ assertFalse (isValid , "Token with extra claims should return false" );
274+ }
275+
276+ @ Test
277+ void validateTokenHandlesTokenWithInvalidExpirationDate () {
278+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
279+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
280+
281+ // Simulate invalid expiration date
282+ String tamperedToken = token .replace ("exp" , "invalidExp" );
283+
284+ boolean isValid = jwtUtil .validateToken (tamperedToken );
285+
286+ assertFalse (isValid , "Token with invalid expiration date should return false" );
287+ }
288+
289+ @ Test
290+ void validateTokenHandlesTokenWithNullClaims () {
291+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
292+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
293+
294+ // Simulate null claims
295+ String tamperedToken = token .replace ("claims" , "null" );
296+
297+ boolean isValid = jwtUtil .validateToken (tamperedToken );
298+
299+ assertFalse (isValid , "Token with null claims should return false" );
300+ }
301+
302+ @ Test
303+ void validateTokenHandlesTokenWithEmptyClaims () {
304+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
305+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
306+
307+ // Simulate empty claims
308+ String tamperedToken = token .replace ("claims" , "{}" );
309+
310+ boolean isValid = jwtUtil .validateToken (tamperedToken );
311+
312+ assertFalse (isValid , "Token with empty claims should return false" );
313+ }
314+
315+ @ Test
316+ void validateTokenHandlesTokenWithInvalidAlgorithm () {
317+ String token = jwtUtil .generateToken ("testUser" , "USER" , "123" , null , 1 );
318+ when (tokenBlacklistService .isTokenBlacklisted (token )).thenReturn (true );
319+
320+ // Simulate invalid algorithm
321+ String tamperedToken = token .replace ("HS256" , "RS256" );
322+
323+ boolean isValid = jwtUtil .validateToken (tamperedToken );
324+
325+ assertFalse (isValid , "Token with invalid algorithm should return false" );
326+ }
327+
328+ /**
329+ * 辅助方法:生成一个已经过期的JWT(不通过JwtUtil,直接使用相同的密钥)
330+ */
331+ private String createExpiredToken (String username , String roles , String userId ) {
332+ Map <String , Object > claims = new HashMap <>();
333+ claims .put ("username" , username );
334+ claims .put ("roles" , roles );
335+ claims .put ("userId" , userId );
336+ claims .put ("platformId" , 1 );
337+ claims .put ("tenants" , new ArrayList <>());
338+
339+ // 使用与JwtUtil中完全相同的密钥
340+ String testSecret = "myTestSecretKeyThatIsLongEnoughForHS256Algorithm" ;
341+ SecretKey key = Keys .hmacShaKeyFor (testSecret .getBytes (StandardCharsets .UTF_8 ));
342+
343+ return Jwts .builder ()
344+ .claims (claims )
345+ .subject (username )
346+ .issuedAt (new Date (System .currentTimeMillis () - 120_000 )) // 2分钟前签发
347+ .expiration (new Date (System .currentTimeMillis () - 60_000 )) // 1分钟前过期
348+ .signWith (key )
349+ .compact ();
350+ }
351+
352+ }
0 commit comments