-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7c753e5
commit e3902f4
Showing
5 changed files
with
203 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/java/uk/gov/hmcts/reform/authorisation/exceptions/JwtDecodingException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package uk.gov.hmcts.reform.authorisation.exceptions; | ||
|
||
public class JwtDecodingException extends RuntimeException { | ||
|
||
public JwtDecodingException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...ava/uk/gov/hmcts/reform/authorisation/generators/AutorefreshingJwtAuthTokenGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package uk.gov.hmcts.reform.authorisation.generators; | ||
|
||
import com.auth0.jwt.JWT; | ||
import com.auth0.jwt.exceptions.JWTDecodeException; | ||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import uk.gov.hmcts.reform.authorisation.exceptions.JwtDecodingException; | ||
|
||
import java.time.Duration; | ||
import java.util.Date; | ||
|
||
import static java.time.Instant.now; | ||
|
||
/** | ||
* Caches the JWT token and refreshes it once it expired. | ||
*/ | ||
public class AutorefreshingJwtAuthTokenGenerator implements AuthTokenGenerator { | ||
|
||
private final ServiceAuthTokenGenerator generator; | ||
private final Duration refreshTimeDelta; | ||
|
||
private DecodedJWT jwt = null; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param refreshTimeDelta time before actual expiry date in JWT when a new token should be requested. | ||
*/ | ||
public AutorefreshingJwtAuthTokenGenerator( | ||
ServiceAuthTokenGenerator generator, | ||
Duration refreshTimeDelta | ||
) { | ||
this.generator = generator; | ||
this.refreshTimeDelta = refreshTimeDelta; | ||
} | ||
|
||
public AutorefreshingJwtAuthTokenGenerator(ServiceAuthTokenGenerator generator) { | ||
this(generator, Duration.ZERO); | ||
} | ||
|
||
@Override | ||
public String generate() { | ||
if (jwt == null || needToRefresh(jwt.getExpiresAt())) { | ||
String newToken = generator.generate(); | ||
|
||
try { | ||
jwt = JWT.decode(newToken); | ||
} catch (JWTDecodeException exc) { | ||
throw new JwtDecodingException(exc.getMessage(), exc); | ||
} | ||
} | ||
|
||
return jwt.getToken(); | ||
} | ||
|
||
private boolean needToRefresh(Date expDate) { | ||
return expDate != null && Date.from(now().plus(refreshTimeDelta)).after(expDate); | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
...uk/gov/hmcts/reform/authorisation/generators/AutorefreshingJwtAuthTokenGeneratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package uk.gov.hmcts.reform.authorisation.generators; | ||
|
||
import com.auth0.jwt.JWT; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.Mock; | ||
import org.mockito.runners.MockitoJUnitRunner; | ||
import uk.gov.hmcts.reform.authorisation.exceptions.JwtDecodingException; | ||
|
||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.Date; | ||
|
||
import static com.auth0.jwt.algorithms.Algorithm.HMAC256; | ||
import static java.time.Instant.now; | ||
import static java.time.temporal.ChronoUnit.HOURS; | ||
import static java.time.temporal.ChronoUnit.MINUTES; | ||
import static java.util.stream.IntStream.range; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.catchThrowable; | ||
import static org.mockito.BDDMockito.given; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
|
||
@RunWith(MockitoJUnitRunner.class) | ||
public class AutorefreshingJwtAuthTokenGeneratorTest { | ||
|
||
@Mock private ServiceAuthTokenGenerator generator; | ||
|
||
private AutorefreshingJwtAuthTokenGenerator jwtAuthTokenGenerator; | ||
|
||
@Before | ||
public void setUp() { | ||
this.jwtAuthTokenGenerator = new AutorefreshingJwtAuthTokenGenerator(generator); | ||
} | ||
|
||
@Test | ||
public void should_request_new_token_on_from_passed_generator_on_first_usage() throws Exception { | ||
// given | ||
String tokenFromS2S = jwtTokenWithExpDate(now()); | ||
|
||
given(generator.generate()) | ||
.willReturn(tokenFromS2S); | ||
|
||
// when | ||
String token = jwtAuthTokenGenerator.generate(); | ||
|
||
// then | ||
assertThat(token).isEqualTo(tokenFromS2S); | ||
} | ||
|
||
@Test | ||
public void should_not_request_new_token_if_cached_token_is_still_valid() throws Exception { | ||
// given | ||
given(generator.generate()) | ||
.willReturn(jwtTokenWithExpDate(now().plus(2, HOURS))); | ||
|
||
// when | ||
repeat(5, () -> jwtAuthTokenGenerator.generate()); | ||
|
||
// then | ||
verify(generator, times(1)).generate(); | ||
} | ||
|
||
@Test | ||
public void should_request_new_token_once_it_expires() throws Exception { | ||
// given | ||
given(generator.generate()) | ||
.willReturn(jwtTokenWithExpDate(now().minus(2, HOURS))); | ||
|
||
// when | ||
repeat(3, () -> jwtAuthTokenGenerator.generate()); | ||
|
||
// then | ||
verify(generator, times(3)).generate(); | ||
} | ||
|
||
@Test | ||
public void should_throw_an_exception_if_s2s_token_is_not_a_jwt_token() { | ||
// given | ||
given(generator.generate()) | ||
.willReturn("clearly not a valid JWT token"); | ||
|
||
// when | ||
Throwable exc = catchThrowable(() -> jwtAuthTokenGenerator.generate()); | ||
|
||
// then | ||
assertThat(exc) | ||
.isNotNull() | ||
.isInstanceOf(JwtDecodingException.class); | ||
} | ||
|
||
@Test | ||
public void should_request_a_new_token_if_delta_is_larger_than_time_left_to_expiry_date() throws Exception { | ||
// given | ||
// retrieved token is valid for one more minute | ||
given(generator.generate()) | ||
.willReturn(jwtTokenWithExpDate(now().plus(1, MINUTES))); | ||
|
||
// but we want to refresh 2 minutes before it expires | ||
AutorefreshingJwtAuthTokenGenerator jwtGenerator = new AutorefreshingJwtAuthTokenGenerator( | ||
generator, | ||
Duration.of(2, MINUTES) | ||
); | ||
|
||
// when | ||
repeat(2, () -> jwtGenerator.generate()); | ||
|
||
// then | ||
// it should request a new token | ||
verify(generator, times(2)).generate(); | ||
} | ||
|
||
private String jwtTokenWithExpDate(Instant expAtDate) throws Exception { | ||
return JWT | ||
.create() | ||
.withExpiresAt(Date.from(expAtDate)) | ||
.sign(HMAC256("secret")); | ||
} | ||
|
||
private void repeat(int times, Runnable action) { | ||
range(0, times).forEach(i -> action.run()); | ||
} | ||
} |