Skip to content

Commit

Permalink
Check exp date in jwt (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkondratowicz authored Dec 19, 2017
1 parent 7c753e5 commit e3902f4
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ To use the services provided by this clients, they need to be instantiated in sp
@Value("${idam.s2s-auth.tokenTimeToLiveInSeconds:14400}") final int ttl) {
return new CachedServiceAuthTokenGenerator(serviceAuthTokenGenerator, ttl);
}

@Bean
public AuthTokenGenerator autorefreshingJwtAuthTokenGenerator(
@Qualifier("serviceAuthTokenGenerator") final AuthTokenGenerator serviceAuthTokenGenerator,
@Value("${idam.s2s-auth.refreshTimeDeltaInSeconds}") final int refreshDeltaInSeconds) {
return new AutorefreshingJwtAuthTokenGenerator(
serviceAuthTokenGenerator,
Duration.of(refreshDeltaInSeconds, SECONDS)
);
}
}
```

Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group 'uk.gov.hmcts.reform'
version '0.0.8'
version '0.1.0'

checkstyle {
maxWarnings = 0
Expand Down Expand Up @@ -124,6 +124,7 @@ dependencies {
compile group: 'com.netflix.feign', name: 'feign-jackson', version: '8.18.0'
compile group: 'com.google.guava', name: 'guava', version: '23.2-jre'
compile group: 'com.warrenstrange', name: 'googleauth', version: '1.1.2'
compile group: 'com.auth0', name: 'java-jwt', version: '3.3.0'

testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.8.0'
Expand Down
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);
}
}
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);
}
}
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());
}
}

0 comments on commit e3902f4

Please sign in to comment.