class JwtValidator {

  Jwt validate(String algorithm, ToBeSignedJwt target)
      throws InvalidAlgorithmParameterException, JwtExpiredException, JwtNotBeforeException,
          JwtInvalidException {
    validateTimestampClaims(target);

    if (!target.getAlgorithm().equals(algorithm)) {
      throw new InvalidAlgorithmParameterException(
          String.format(
              "invalid algorithm; expected %s, got %s", algorithm, target.getAlgorithm()));
    }

    @SuppressWarnings("unchecked") // keys() returns Iterator, not Iterator<String>
    Iterator<String> headerIterator = this.header.keys();
    while (headerIterator.hasNext()) {
      String name = headerIterator.next();
      if (name.equals(JwtNames.HEADER_ALGORITHM)) {
        continue;
      }
      String value = target.getHeader(name);
      if (value == null || !value.equals(this.getHeader(name))) {
        throw new JwtInvalidException(
            String.format(
                "invalid JWT; expected header '%s' with value %s, but got %s",
                name, value, this.getHeader(name)));
      }
    }

    @SuppressWarnings("unchecked") // keys() returns Iterator, not Iterator<String>
    Iterator<String> payloadIterator = this.payload.keys();
    while (payloadIterator.hasNext()) {
      String name = payloadIterator.next();
      if (name.equals(JwtNames.CLAIM_AUDIENCE)) {
        // This is checked below.
        continue;
      }
      Object value = target.getClaim(name);
      if (value == null || !value.equals(this.getClaim(name))) {
        throw new JwtInvalidException(
            String.format(
                "invalid JWT; expected claim '%s' with value %s, but got %s",
                name, value, this.getClaim(name)));
      }
    }

    // Check that the validator's audience is in the list of claimed audiences.
    List<String> audiences = target.getAudiences();
    String audience = (String) this.getClaim(JwtNames.CLAIM_AUDIENCE);
    if ((audiences == null && audience != null)
        || (audiences != null && !audiences.contains(audience))) {
      throw new JwtInvalidException(
          String.format(
              "invalid JWT; cannot find the expected audience %s in claimed audiences %s",
              audience, audiences));
    }

    return new Jwt(target.getHeader(), target.getPayload(), this.clock, this.clockSkew);
  }

}

class JwtValidatorTest {

  @Test
  public void validate_notExpired_success() throws Exception {

    String algo = "HS256";
    Clock clock = Clock.systemUTC();
    // This token expires in 1 minute in the future.
    Instant expiration = clock.instant().plus(Duration.ofMinutes(1));
    ToBeSignedJwt unverified =
        new ToBeSignedJwt.Builder().setAlgorithm(algo).setExpiration(expiration).build();
    JwtValidator validator = new JwtValidator.Builder().build();
    Jwt token = validator.validate(algo, unverified);

    assertThat(token.getExpiration().getEpochSecond()).isEqualTo(expiration.getEpochSecond());
  }

}