Compare commits

...

9 Commits

Author SHA1 Message Date
Pieter Van Eeckhout ad8f010902
Merge 4a76b69d59 into 2ad964a3f1 2024-09-27 12:07:55 +02:00
Andy Boothe 2ad964a3f1
add maven bom #967 (#968) 2024-09-24 10:17:15 -04:00
pveeckhout 4a76b69d59
Merge remote-tracking branch 'origin/master' into 60-convenience_expiration_setter_which_takes_a_duration 2024-02-17 00:46:53 +01:00
pveeckhout a6a79508b0
#60 Fix typo in JwtBuilder comments
A typographical error in the comments of the 'exp' function in JwtBuilder was corrected. The phrase "specified it if" was changed to "specified if it", making the comments clearer and easier to understand.
2024-01-10 09:06:59 +01:00
pveeckhout 5a4992b91a
Merge remote-tracking branch 'origin/60-convenience_expiration_setter_which_takes_a_duration' into 60-convenience_expiration_setter_which_takes_a_duration 2024-01-10 09:02:56 +01:00
pveeckhout de900cd1ab
#60 Refactor token expiry calculation in DefaultJwtBuilder
The token expiry calculation logic has been cleaned up and optimized in DefaultJwtBuilder. A previously used optional stream has been replaced with a more straightforward if-else structure, leading to ease of code maintenance and improved readability.
2024-01-10 09:02:34 +01:00
Pieter Van Eeckhout 034ae864b6
Merge branch 'jwtk:master' into 60-convenience_expiration_setter_which_takes_a_duration 2024-01-10 08:44:42 +01:00
pveeckhout e91b0425bf
#60 Add validation tests for JWT expiration
Two new tests have been added to DefaultJwtParserTest to validate JWT expiration behavior. The tests ensure that for the 'expireAfter()' method, duration must be more than 0 and timeUnit cannot be null. The error messages for these validation checks have also been modified for clarity.
2023-12-25 09:30:12 +01:00
pveeckhout ff6e2dfbc6
#60 Add expireAfter functionality to JWT Builder
The `expireAfter` method, accepting duration and timeUnit parameters, has been added to the JwtBuilder interface. This method calculates the JWT expiration date as either the issue time plus the duration or the system current time plus the duration if an issuedAt time hasn't been set. Additional tests for this feature have been included in `DefaultJwtParserTest.groovy`.
2023-12-25 03:31:07 +01:00
5 changed files with 191 additions and 1 deletions

View File

@ -42,6 +42,7 @@ import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's).
@ -585,6 +586,26 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
// for better/targeted JavaDoc
JwtBuilder id(String jti);
/**
* Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4">
* <code>exp</code></a> (expiration) claim. It will set the expiration Date to the issuedAt time plus the duration
* specified if it has been set, otherwise it will use the current system time plus the duration specified
*
* <p>A JWT obtained after this timestamp should not be used.</p>
*
* <p>This is a convenience wrapper for:</p>
* <blockquote><pre>
* {@link #claims()}.{@link ClaimsMutator#expiration(Date) expiration(exp)}.{@link BuilderClaims#and() and()}</pre></blockquote>
*
* @param duration The duration after the issue time that the JWT should expire. It is added to the issue time to
* calculate the expiration time.
* @param timeUnit The time unit of the duration parameter. This specifies the unit of measurement for the
* duration (e.g., seconds, minutes, hours, etc.), determining how the duration value should
* be interpreted when calculating the expiration time.
* @return the builder instance for method chaining.
*/
JwtBuilder expireAfter(long duration, TimeUnit timeUnit);
/**
* Signs the constructed JWT with the specified key using the key's <em>recommended signature algorithm</em>
* as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs,

74
bom/pom.xml Normal file
View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018 JWTK
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.12.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>jjwt-bom</artifactId>
<name>JJWT :: BOM</name>
<packaging>pom</packaging>
<properties>
<jjwt.root>${basedir}/..</jjwt.root>
</properties>
<dependencyManagement>
<dependencies>
<!-- Core -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
<!-- Extensions -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-orgjson</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-gson</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.7-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -31,6 +31,7 @@ import io.jsonwebtoken.impl.io.UncloseableInputStream;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.Functions;
import io.jsonwebtoken.impl.lang.JwtDateConverter;
import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.Services;
import io.jsonwebtoken.impl.security.DefaultAeadRequest;
@ -76,7 +77,9 @@ import java.security.SecureRandom;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class DefaultJwtBuilder implements JwtBuilder {
@ -477,6 +480,27 @@ public class DefaultJwtBuilder implements JwtBuilder {
return claims().id(jti).and();
}
@Override
public JwtBuilder expireAfter(final long duration, final TimeUnit timeUnit) { // TODO: use java.time and optionals from jdk 8 for version 1.0
Assert.gt(duration, 0L, "duration must be > 0.");
Assert.notNull(timeUnit, "timeUnit cannot be null.");
Date issuedAtDate = this.claimsBuilder.get(DefaultClaims.ISSUED_AT);
long expiryEpochMillis;
if (null != issuedAtDate) {
expiryEpochMillis = issuedAtDate.getTime() + timeUnit.toMillis(duration);
} else {
expiryEpochMillis = (System.currentTimeMillis() + timeUnit.toMillis(duration));
}
Date expiryDate = JwtDateConverter.INSTANCE.applyFrom(expiryEpochMillis / 1000L);
/*Instant expiryInstant = Optional.ofNullable(this.claimsBuilder.get(DefaultClaims.ISSUED_AT)) // this should return an instant I guess
.orElseGet(() -> Instant.now())
.plus(duration, timeUnit);*/
return claims().expiration(expiryDate).and();
}
private void assertPayloadEncoding(String type) {
if (!this.encodePayload) {
String msg = "Payload encoding may not be disabled for " + type + "s, only JWSs.";

View File

@ -32,6 +32,7 @@ import org.junit.Test
import javax.crypto.Mac
import javax.crypto.SecretKey
import java.util.concurrent.TimeUnit
import static org.junit.Assert.*
@ -277,6 +278,75 @@ class DefaultJwtParserTest {
}
}
@Test
void testExpiredAfterDurationValidationMessage() {
def duration = -1L
def timeUnit = TimeUnit.MINUTES
try {
Jwts.builder().expireAfter(duration, timeUnit).compact()
} catch (IllegalArgumentException expected) {
String msg = "duration must be > 0."
assertEquals msg, expected.message
}
}
@Test
void testExpiredAfterTimeUnitValidationMessage() {
def duration = 15L
def timeUnit = null
try {
Jwts.builder().expireAfter(duration, timeUnit).compact()
} catch (IllegalArgumentException expected) {
String msg = "timeUnit cannot be null."
assertEquals msg, expected.message
}
}
@Test
void testExpiredAfterExceptionMessage() {
long differenceMillis = 781 // arbitrary, anything > 0 is fine
def duration = 15L
def timeUnit = TimeUnit.MINUTES
def expectedExpiry = JwtDateConverter.INSTANCE.applyFrom((System.currentTimeMillis() + timeUnit.toMillis(duration)) / 1000L)
def later = new Date(expectedExpiry.getTime() + differenceMillis)
def s = Jwts.builder().expireAfter(duration, timeUnit).compact()
try {
Jwts.parser().unsecured().clock(new FixedClock(later)).build().parse(s)
} catch (ExpiredJwtException expected) {
def exp8601 = DateFormats.formatIso8601(expectedExpiry, true)
def later8601 = DateFormats.formatIso8601(later, true)
String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " +
"Current time: ${later8601}. Allowed clock skew: 0 milliseconds."
assertEquals msg, expected.message
}
}
@Test
void testExpiredAfterWithIssuedAtExceptionMessage() {
long differenceMillis = 781 // arbitrary, anything > 0 is fine
def duration = 15L
def timeUnit = TimeUnit.MINUTES
def issuedAt = JwtDateConverter.INSTANCE.applyFrom((System.currentTimeMillis() + timeUnit.toMillis(-1L)) / 1000L) //set it to one minute earlier
def expectedExpiry = JwtDateConverter.INSTANCE.applyFrom((System.currentTimeMillis() + timeUnit.toMillis(duration - 1L)) / 1000L) // we expect it to expire a minute earlier
def later = new Date(expectedExpiry.getTime() + differenceMillis)
def s = Jwts.builder()
.issuedAt(issuedAt)
.expireAfter(duration, timeUnit)
.compact()
try {
Jwts.parser().unsecured().clock(new FixedClock(later)).build().parse(s)
} catch (ExpiredJwtException expected) {
def exp8601 = DateFormats.formatIso8601(expectedExpiry, true)
def later8601 = DateFormats.formatIso8601(later, true)
String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " +
"Current time: ${later8601}. Allowed clock skew: 0 milliseconds."
assertEquals msg, expected.message
}
}
@Test
void testNotBeforeExceptionMessage() {
@ -291,7 +361,7 @@ class DefaultJwtParserTest {
def nbf8601 = DateFormats.formatIso8601(nbf, true)
def earlier8601 = DateFormats.formatIso8601(earlier, true)
String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " +
"Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds.";
"Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds."
assertEquals msg, expected.message
}
}

View File

@ -146,6 +146,7 @@
<module>impl</module>
<module>extensions</module>
<module>tdjar</module>
<module>bom</module>
</modules>
<dependencyManagement>