Skip to content

Commit 8d11d84

Browse files
committed
feat(security): add clock skew support and enhanced secret configuration to JWTManager with tests
Introduce configurable clock skew (withClockSkew(long)) to allow expiration leeway during JWT validation Apply parser.clockSkewSeconds() to improve tolerance for time drift between distributed systems Add withBase64Secret(String) to support direct Base64-encoded secret keys Clarify documentation of withSecret(String) to explicitly denote plain text secret usage Strengthen token validation behavior with new unit tests: Verify expired tokens correctly throw ExpiredJwtException Validate that tokens within configured clock skew are accepted Ensure parsed claims remain correct when skew is applied
1 parent d6a6192 commit 8d11d84

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

src/main/java/org/tinystruct/http/security/JWTManager.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ public class JWTManager {
1414
// A default secret key for signing JWTs
1515
private static final SecretKey SECRET_KEY = Jwts.SIG.HS256.key().build();
1616
private SecretKey base64Key;
17+
private long clockSkew = 0;
1718

1819
/**
19-
* Sets the secret key using a base64 encoded string.
20+
* Sets the secret key using a plain text string.
2021
* This method allows for a custom secret key to be used instead of the default one.
2122
*
2223
* @param secret The secret key as a plain text string
@@ -25,6 +26,26 @@ public void withSecret(String secret) {
2526
this.base64Key = Keys.hmacShaKeyFor(Base64.getEncoder().encode(secret.getBytes()));
2627
}
2728

29+
/**
30+
* Sets the secret key using a base64 encoded string.
31+
* This method allows for a custom secret key to be used instead of the default one.
32+
*
33+
* @param base64Secret The secret key as a base64 string
34+
*/
35+
public void withBase64Secret(String base64Secret) {
36+
this.base64Key = Keys.hmacShaKeyFor(base64Secret.getBytes());
37+
}
38+
39+
/**
40+
* Sets the clock skew in seconds.
41+
* This method allows for a leeway in the token's expiration time to account for clock differences between machines.
42+
*
43+
* @param clockSkew The clock skew in seconds
44+
*/
45+
public void withClockSkew(long clockSkew) {
46+
this.clockSkew = clockSkew;
47+
}
48+
2849
/**
2950
* Builds a JWT with the given subject and claims and returns it as a JWS signed compact String.
3051
*
@@ -79,6 +100,8 @@ public Jws<Claims> parseToken(final String compactToken)
79100
else
80101
parser.verifyWith(SECRET_KEY);
81102

103+
parser.clockSkewSeconds(this.clockSkew);
104+
82105
// Parse the JWT and return the claims
83106
return parser.build().parseSignedClaims(compactToken);
84107
}

src/test/java/org/tinystruct/http/security/JWTManagerTest.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,23 @@ public void testExpiredToken() {
5252
Builder builder = new Builder();
5353
builder.put("role", "admin");
5454

55-
// Create a JWT with 1 second validity
55+
// Create a JWT with -1 hour validity (already expired)
56+
String token = jwtManager.createToken(SUBJECT, builder, -1);
57+
58+
// Parsing an expired token should throw ExpiredJwtException
59+
assertThrows(ExpiredJwtException.class, () -> jwtManager.parseToken(token));
60+
}
61+
62+
@Test
63+
public void testTokenWithClockSkew() {
64+
assertNotNull(jwtManager); // Ensure jwtManager is not null
65+
jwtManager.withSecret(SECRET);
66+
67+
// Create a builder with some claims
68+
Builder builder = new Builder();
69+
builder.put("role", "admin");
70+
71+
// Create a JWT with 0 validity (expires immediately)
5672
String token = jwtManager.createToken(SUBJECT, builder, 0);
5773

5874
// Sleep for 1 second to ensure token expiration
@@ -62,8 +78,14 @@ public void testExpiredToken() {
6278
e.printStackTrace();
6379
}
6480

65-
// Parsing an expired token should throw ExpiredJwtException
66-
assertThrows(ExpiredJwtException.class, () -> jwtManager.parseToken(token));
81+
// Set clock skew to 60 seconds
82+
jwtManager.withClockSkew(60);
83+
84+
// Parsing the token should NOT throw ExpiredJwtException because of clock skew
85+
assertDoesNotThrow(() -> jwtManager.parseToken(token));
86+
87+
Jws<Claims> parsedToken = jwtManager.parseToken(token);
88+
assertEquals(SUBJECT, parsedToken.getPayload().getSubject());
6789
}
6890

6991
@Test

0 commit comments

Comments
 (0)