Merge branch 'develop' of https://github.com/mifosio/anubis into develop
diff --git a/api/src/main/java/io/mifos/anubis/api/v1/TokenConstants.java b/api/src/main/java/io/mifos/anubis/api/v1/TokenConstants.java
index dfd45ac..e2b2eba 100644
--- a/api/src/main/java/io/mifos/anubis/api/v1/TokenConstants.java
+++ b/api/src/main/java/io/mifos/anubis/api/v1/TokenConstants.java
@@ -24,6 +24,7 @@
   String PREFIX = "Bearer ";
 
   String JWT_SIGNATURE_TIMESTAMP_CLAIM = "/mifos.io/signatureTimestamp";
+  String JWT_ENDPOINT_SET_CLAIM = "/mifos.io/endpointSet";
   String JWT_CONTENT_CLAIM = "/mifos.io/tokenContent";
 
   String REFRESH_TOKEN_COOKIE_NAME = "org.apache.fineract.refreshToken";
diff --git a/component-test/src/main/java/TestAnubisInitialize.java b/component-test/src/main/java/TestAnubisInitialize.java
index 9d6343d..69ad24f 100644
--- a/component-test/src/main/java/TestAnubisInitialize.java
+++ b/component-test/src/main/java/TestAnubisInitialize.java
@@ -16,6 +16,7 @@
 
 import io.mifos.anubis.api.v1.client.Anubis;
 import io.mifos.anubis.api.v1.client.AnubisApiFactory;
+import io.mifos.anubis.api.v1.domain.AllowedOperation;
 import io.mifos.anubis.api.v1.domain.Signature;
 import io.mifos.anubis.example.simple.Example;
 import io.mifos.anubis.example.simple.ExampleConfiguration;
@@ -158,6 +159,20 @@
     }
   }
 
+  @Test(expected = InvalidTokenException.class)
+  public void testAuthenticateWithoutInitialize() {
+    try (final TenantDataStoreTestContext ignored = TenantDataStoreTestContext.forRandomTenantName(cassandraInitializer)) {
+
+      final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+              = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment);
+      final String permissionToken = tenantApplicationSecurityEnvironment.getPermissionToken("bubba", "foo", AllowedOperation.READ);
+      try (final AutoUserContext ignored2 = new AutoUserContext("bubba", permissionToken)) {
+        Assert.assertFalse(example.foo());
+        Assert.fail("Not found exception should be thrown when authentication is attempted ");
+      }
+    }
+  }
+
   private void initialize() {
     final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
             = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment);
diff --git a/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/ExampleRestController.java b/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/ExampleRestController.java
index a88dffa..e9b4e0c 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/ExampleRestController.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/ExampleRestController.java
@@ -53,7 +53,7 @@
 
     final ApplicationSignatureSet applicationSignatureSet = new ApplicationSignatureSet(identityManagerKeyPair.getTimestamp(), applicationSignature, identityManagerSignature);
 
-    this.specialTenantSignatureRepository.addSignatureSet(applicationSignatureSet);
+    this.specialTenantSignatureRepository.addSignatureSet(applicationSignatureSet, applicationKeyPair);
     initialized = true;
     return new ResponseEntity<>(HttpStatus.OK);
   }
diff --git a/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/SpecialTenantSignatureRepository.java b/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/SpecialTenantSignatureRepository.java
index 679d4e3..ba6794a 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/SpecialTenantSignatureRepository.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/nokeystorage/SpecialTenantSignatureRepository.java
@@ -18,6 +18,7 @@
 import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet;
 import io.mifos.anubis.api.v1.domain.Signature;
 import io.mifos.anubis.config.TenantSignatureRepository;
+import io.mifos.core.lang.security.RsaKeyPairFactory;
 import org.springframework.stereotype.Component;
 
 import java.util.HashMap;
@@ -31,15 +32,38 @@
  */
 @Component
 public class SpecialTenantSignatureRepository implements TenantSignatureRepository {
-  private final Map<String, ApplicationSignatureSet> applicationSignatureSetMap = new HashMap<>();
+  private static class AllTheKeyInfos {
+    final ApplicationSignatureSet applicationSignatureSet;
+    final RsaKeyPairFactory.KeyPairHolder applicationKeyPair;
 
-  void addSignatureSet(final ApplicationSignatureSet applicationSignatureSet) {
-    applicationSignatureSetMap.put(applicationSignatureSet.getTimestamp(), applicationSignatureSet);
+    private AllTheKeyInfos(
+            final ApplicationSignatureSet applicationSignatureSet,
+            final RsaKeyPairFactory.KeyPairHolder applicationKeyPair) {
+      this.applicationSignatureSet = applicationSignatureSet;
+      this.applicationKeyPair = applicationKeyPair;
+    }
+
+    ApplicationSignatureSet getApplicationSignatureSet() {
+      return applicationSignatureSet;
+    }
+
+    RsaKeyPairFactory.KeyPairHolder getApplicationKeyPair() {
+      return applicationKeyPair;
+    }
+  }
+  private final Map<String, AllTheKeyInfos> applicationSignatureSetMap = new HashMap<>();
+
+  void addSignatureSet(final ApplicationSignatureSet applicationSignatureSet,
+                       final RsaKeyPairFactory.KeyPairHolder applicationKeyPair) {
+    applicationSignatureSetMap.put(applicationSignatureSet.getTimestamp(),
+            new AllTheKeyInfos(applicationSignatureSet, applicationKeyPair));
   }
 
   @Override
   public Optional<Signature> getIdentityManagerSignature(final String timestamp) throws IllegalArgumentException {
-    final Optional<ApplicationSignatureSet> sigset = Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
+    final Optional<ApplicationSignatureSet> sigset =
+            Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
+                    .map(AllTheKeyInfos::getApplicationSignatureSet);
     return sigset.map(ApplicationSignatureSet::getIdentityManagerSignature);
   }
 
@@ -50,7 +74,8 @@
 
   @Override
   public Optional<ApplicationSignatureSet> getSignatureSet(final String timestamp) {
-    return Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
+    return Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
+            .map(AllTheKeyInfos::getApplicationSignatureSet);
   }
 
   @Override
@@ -60,24 +85,34 @@
 
   @Override
   public Optional<Signature> getApplicationSignature(final String timestamp) {
-    final Optional<ApplicationSignatureSet> sigset = Optional.ofNullable(applicationSignatureSetMap.get(timestamp));
+    final Optional<ApplicationSignatureSet> sigset
+            = Optional.ofNullable(applicationSignatureSetMap.get(timestamp))
+            .map(AllTheKeyInfos::getApplicationSignatureSet);
     return sigset.map(ApplicationSignatureSet::getApplicationSignature);
   }
 
   @Override
   public Optional<ApplicationSignatureSet> getLatestSignatureSet() {
-    Optional<String> timestamp = getMostRecentTimestamp();
+    final Optional<String> timestamp = getMostRecentTimestamp();
     return timestamp.flatMap(this::getSignatureSet);
   }
 
   @Override
   public Optional<Signature> getLatestApplicationSignature() {
-    Optional<String> timestamp = getMostRecentTimestamp();
+    final Optional<String> timestamp = getMostRecentTimestamp();
     return timestamp.flatMap(this::getApplicationSignature);
   }
 
+  @Override
+  public Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair() {
+    final Optional<String> timestamp = getMostRecentTimestamp();
+    return timestamp
+            .flatMap(x -> Optional.ofNullable(applicationSignatureSetMap.get(x)))
+            .map(AllTheKeyInfos::getApplicationKeyPair);
+  }
+
   private Optional<String> getMostRecentTimestamp() {
     return getAllSignatureSetKeyTimestamps().stream()
             .max(String::compareTo);
   }
-}
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java b/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
index 928a41f..4a3f29c 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/simple/Example.java
@@ -30,4 +30,7 @@
 
   @RequestMapping(value = "initialize", method = RequestMethod.DELETE)
   void uninitialize();
+
+  @RequestMapping(value = "foo", method = RequestMethod.GET)
+  boolean foo();
 }
diff --git a/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java b/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
index acc4229..d988f27 100644
--- a/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
+++ b/component-test/src/main/java/io/mifos/anubis/example/simple/ExampleRestController.java
@@ -54,4 +54,10 @@
     initialized = false;
     return new ResponseEntity<>(HttpStatus.OK);
   }
+
+  @RequestMapping(value = "/foo", method = RequestMethod.GET)
+  @Permittable(AcceptedTokenType.TENANT)
+  public ResponseEntity<Boolean> foo() {
+    return ResponseEntity.ok(false);
+  }
 }
diff --git a/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java b/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
index 923f66a..6ca7835 100644
--- a/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
+++ b/library/src/main/java/io/mifos/anubis/config/AnubisSecurityConfigurerAdapter.java
@@ -21,6 +21,7 @@
 import io.mifos.anubis.security.IsisAuthenticatedAuthenticationProvider;
 import io.mifos.anubis.security.UrlPermissionChecker;
 import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -42,6 +43,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static io.mifos.anubis.config.AnubisConstants.LOGGER_NAME;
+
 /**
  * @author Myrle Krantz
  */
@@ -49,6 +52,11 @@
 @Configuration
 @EnableWebSecurity
 public class AnubisSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+  final private Logger logger;
+
+  public AnubisSecurityConfigurerAdapter(final @Qualifier(LOGGER_NAME) Logger logger) {
+    this.logger = logger;
+  }
 
   @PostConstruct
   public void configureSecurityContext()
@@ -83,7 +91,7 @@
 
   private AccessDecisionManager defaultAccessDecisionManager() {
     final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
-    voters.add(new UrlPermissionChecker());
+    voters.add(new UrlPermissionChecker(logger));
     return new UnanimousBased(voters);
   }
 
diff --git a/library/src/main/java/io/mifos/anubis/config/TenantSignatureRepository.java b/library/src/main/java/io/mifos/anubis/config/TenantSignatureRepository.java
index 5ab3d40..07d4b4e 100644
--- a/library/src/main/java/io/mifos/anubis/config/TenantSignatureRepository.java
+++ b/library/src/main/java/io/mifos/anubis/config/TenantSignatureRepository.java
@@ -18,7 +18,9 @@
 
 import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet;
 import io.mifos.anubis.api.v1.domain.Signature;
+import io.mifos.core.lang.security.RsaKeyPairFactory;
 
+import java.security.interfaces.RSAPrivateKey;
 import java.util.List;
 import java.util.Optional;
 
@@ -42,4 +44,6 @@
   Optional<Signature> getApplicationSignature(String timestamp);
 
   Optional<Signature> getLatestApplicationSignature();
+
+  Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair();
 }
diff --git a/library/src/main/java/io/mifos/anubis/repository/TenantAuthorizationDataRepository.java b/library/src/main/java/io/mifos/anubis/repository/TenantAuthorizationDataRepository.java
index d07aed7..5d64422 100644
--- a/library/src/main/java/io/mifos/anubis/repository/TenantAuthorizationDataRepository.java
+++ b/library/src/main/java/io/mifos/anubis/repository/TenantAuthorizationDataRepository.java
@@ -16,6 +16,7 @@
 package io.mifos.anubis.repository;
 
 import com.datastax.driver.core.*;
+import com.datastax.driver.core.exceptions.InvalidQueryException;
 import com.datastax.driver.core.querybuilder.QueryBuilder;
 import com.datastax.driver.core.querybuilder.Select;
 import com.datastax.driver.core.querybuilder.Update;
@@ -27,6 +28,8 @@
 import io.mifos.core.cassandra.core.CassandraSessionProvider;
 import io.mifos.core.lang.ApplicationName;
 import io.mifos.core.lang.security.RsaKeyPairFactory;
+import io.mifos.core.lang.security.RsaPrivateKeyBuilder;
+import io.mifos.core.lang.security.RsaPublicKeyBuilder;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -35,6 +38,10 @@
 
 import javax.annotation.Nonnull;
 import java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -94,6 +101,9 @@
     Assert.notNull(identityManagerSignature);
 
     //TODO: add validation to make sure this timestamp is more recent than any already stored.
+    logger.info("Creating application signature set for timestamp '" + timestamp +
+            "'. Identity manager signature is: " + identityManagerSignature);
+
     final RsaKeyPairFactory.KeyPairHolder applicationSignature = RsaKeyPairFactory.createKeyPair();
 
     final Session session = cassandraSessionProvider.getTenantSession();
@@ -115,6 +125,7 @@
     Assert.notNull(timestamp);
     //Don't actually delete, just invalidate, so that if someone starts coming at me with an older keyset, I'll
     //know what's happening.
+    logger.info("Invalidationg signature set for timestamp '" + timestamp + "'.");
     final Session session = cassandraSessionProvider.getTenantSession();
     invalidateEntry(session, timestamp);
   }
@@ -250,13 +261,18 @@
     final Session tenantSession = cassandraSessionProvider.getTenantSession();
     final Select.Where query = timestampToSignatureQueryMap.computeIfAbsent(timestamp, timestampKey ->
             QueryBuilder.select().from(tableName).where(QueryBuilder.eq(TIMESTAMP_COLUMN, timestampKey)));
-    final Row row = tenantSession.execute(query).one();
-    final Optional<Row> ret = Optional.ofNullable(row);
-    ret.map(TenantAuthorizationDataRepository::mapRowToValid).ifPresent(valid -> {
-      if (!valid)
-        logger.warn("Invalidated keyset for timestamp '" + timestamp + "' requested. Pretending no keyset exists.");
-    });
-    return ret.filter(TenantAuthorizationDataRepository::mapRowToValid);
+    try {
+      final Row row = tenantSession.execute(query).one();
+      final Optional<Row> ret = Optional.ofNullable(row);
+      ret.map(TenantAuthorizationDataRepository::mapRowToValid).ifPresent(valid -> {
+        if (!valid)
+          logger.warn("Invalidated keyset for timestamp '" + timestamp + "' requested. Pretending no keyset exists.");
+      });
+      return ret.filter(TenantAuthorizationDataRepository::mapRowToValid);
+    }
+    catch (final InvalidQueryException authorizationDataTableProbablyIsntConfiguredYet) {
+      throw new IllegalArgumentException("Tenant not found.");
+    }
   }
 
   private static Boolean mapRowToValid(final @Nonnull Row row) {
@@ -283,6 +299,24 @@
     return getSignature(row, APPLICATION_PUBLIC_KEY_MOD_COLUMN, APPLICATION_PUBLIC_KEY_EXP_COLUMN);
   }
 
+  private static RsaKeyPairFactory.KeyPairHolder mapRowToKeyPairHolder(final @Nonnull Row row) {
+    final BigInteger publicKeyModulus = row.get(APPLICATION_PUBLIC_KEY_MOD_COLUMN, BigInteger.class);
+    final BigInteger publicKeyExponent = row.get(APPLICATION_PUBLIC_KEY_EXP_COLUMN, BigInteger.class);
+    final BigInteger privateKeyModulus = row.get(APPLICATION_PRIVATE_KEY_MOD_COLUMN, BigInteger.class);
+    final BigInteger privateKeyExponent = row.get(APPLICATION_PRIVATE_KEY_EXP_COLUMN, BigInteger.class);
+
+    final PublicKey publicKey = new RsaPublicKeyBuilder()
+            .setPublicKeyMod(publicKeyModulus)
+            .setPublicKeyExp(publicKeyExponent)
+            .build();
+    final PrivateKey privateKey = new RsaPrivateKeyBuilder()
+            .setPrivateKeyMod(privateKeyModulus)
+            .setPrivateKeyExp(privateKeyExponent)
+            .build();
+    final String timestamp = row.get(TIMESTAMP_COLUMN, String.class);
+    return new RsaKeyPairFactory.KeyPairHolder(timestamp, (RSAPublicKey)publicKey, (RSAPrivateKey)privateKey);
+  }
+
   private static ApplicationSignatureSet mapRowToSignatureSet(final @Nonnull Row row) {
     final String timestamp = row.get(TIMESTAMP_COLUMN, String.class);
     final Signature identityManagerSignature = mapRowToIdentityManagerSignature(row);
@@ -316,6 +350,12 @@
     return timestamp.flatMap(this::getApplicationSignature);
   }
 
+  @Override
+  public Optional<RsaKeyPairFactory.KeyPairHolder> getLatestApplicationSigningKeyPair() {
+    Optional<String> timestamp = getMostRecentTimestamp();
+    return timestamp.flatMap(this::getRow).map(TenantAuthorizationDataRepository::mapRowToKeyPairHolder);
+  }
+
   private Optional<String> getMostRecentTimestamp() {
     return getAllSignatureSetKeyTimestamps().stream()
             .max(String::compareTo);
diff --git a/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java b/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
index 2396214..a34e94e 100644
--- a/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
+++ b/library/src/main/java/io/mifos/anubis/security/ApplicationPermission.java
@@ -25,8 +25,10 @@
 import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.BiPredicate;
-import java.util.stream.IntStream;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -71,21 +73,24 @@
     final boolean opMatches = allowedOperation.containsHttpMethod(method);
     final String[] requestPathSegments = servletPath.split("/");
 
-    if (servletPathSegmentMatchers.size() == requestPathSegments.length + 1)
-      return lastSegmentIsStarSegment(servletPathSegmentMatchers);
-
-    if (servletPathSegmentMatchers.size() > requestPathSegments.length)
+    if (servletPathSegmentMatchers.size() > requestPathSegments.length + 1)
       return false;
 
+    if (servletPathSegmentMatchers.size() == requestPathSegments.length + 1)
+      if (!lastSegmentIsStarSegment(servletPathSegmentMatchers))
+        return false;
+
     if (servletPathSegmentMatchers.size() < requestPathSegments.length)
-      return lastSegmentIsStarSegment(servletPathSegmentMatchers);
+      if (!lastSegmentIsStarSegment(servletPathSegmentMatchers))
+        return false;
 
-    final boolean aNonMappableSegmentExistsInServletPath =
-        IntStream.range(0, servletPathSegmentMatchers.size())
-            .filter(i -> !segmentMatcher.test(servletPathSegmentMatchers.get(i), requestPathSegments[i]))
-            .findFirst().isPresent();
+    final Optional<Integer> indexOfFirstNonMappableSegment =
+            Stream.iterate(0, n -> n + 1)
+                    .limit(Math.min(servletPathSegmentMatchers.size(), requestPathSegments.length))
+                    .filter(i -> !segmentMatcher.test(servletPathSegmentMatchers.get(i), requestPathSegments[i]))
+                    .findFirst();
 
-    return opMatches && !aNonMappableSegmentExistsInServletPath;
+    return opMatches && !indexOfFirstNonMappableSegment.isPresent();
   }
 
   private static boolean lastSegmentIsStarSegment(
@@ -106,4 +111,12 @@
   @Override public int hashCode() {
     return Objects.hash(servletPathSegmentMatchers, allowedOperation);
   }
+
+  @Override
+  public String toString() {
+    return "ApplicationPermission{" +
+            "servletPathSegmentMatchers='" + servletPathSegmentMatchers.stream().map(PermissionSegmentMatcher::getPermissionSegment).collect(Collectors.joining("/")) +
+            "', allowedOperation=" + allowedOperation +
+            '}';
+  }
 }
diff --git a/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
index 533868c..6c9270a 100644
--- a/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/GuestAuthenticator.java
@@ -18,27 +18,36 @@
 import io.mifos.anubis.annotation.AcceptedTokenType;
 import io.mifos.anubis.api.v1.RoleConstants;
 import io.mifos.anubis.service.PermittableService;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
 import java.util.Set;
 
+import static io.mifos.anubis.config.AnubisConstants.LOGGER_NAME;
+
 /**
  * @author Myrle Krantz
  */
 @Component
 public class GuestAuthenticator {
   private Set<ApplicationPermission> permissions;
+  private final Logger logger;
 
   @Autowired
-  public GuestAuthenticator(final PermittableService permittableService) {
+  public GuestAuthenticator(final PermittableService permittableService,
+                            final @Qualifier(LOGGER_NAME) Logger logger) {
     this.permissions = permittableService.getPermittableEndpointsAsPermissions(AcceptedTokenType.GUEST);
+    this.logger = logger;
   }
 
   AnubisAuthentication authenticate(final String user) {
     if (!user.equals(RoleConstants.GUEST_USER_IDENTIFIER))
       throw AmitAuthenticationException.invalidHeader();
 
+    logger.info("Guest access \"authenticated\" successfully.", user);
+
     return new AnubisAuthentication(null, RoleConstants.GUEST_USER_IDENTIFIER, permissions);
   }
 }
diff --git a/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
index 49fd679..3031dfd 100644
--- a/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/SystemAuthenticator.java
@@ -62,28 +62,31 @@
   public AnubisAuthentication authenticate(
       final String user,
       final String token,
-      final String timestamp) {
+      final String keyTimestamp) {
     if (!user.equals(ApiConstants.SYSTEM_SU))
       throw AmitAuthenticationException.invalidHeader();
 
     try {
       final JwtParser jwtParser = Jwts.parser()
-          .setSigningKey(systemRsaKeyProvider.getPublicKey(timestamp))
+          .setSigningKey(systemRsaKeyProvider.getPublicKey(keyTimestamp))
           .requireAudience(applicationName.toString())
           .requireIssuer(TokenType.SYSTEM.getIssuer())
-          .require(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, timestamp);
+          .require(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, keyTimestamp);
 
       TenantContextHolder.identifier().ifPresent(jwtParser::requireSubject);
 
       jwtParser.parse(token);
+      logger.info("System token for user {}, with key timestamp {} authenticated successfully.", user, keyTimestamp);
 
       return new AnubisAuthentication(token, user, permissions);
     }
     catch (final JwtException e) {
       logger.debug("token = {}", token);
+      logger.info("System token for user {}, with key timestamp {} failed to authenticate. Exception was {}", user, keyTimestamp, e);
       throw AmitAuthenticationException.invalidToken();
     } catch (final InvalidKeyTimestampException e) {
-      throw AmitAuthenticationException.invalidTokenKeyTimestamp("system", timestamp);
+      logger.info("System token for user {}, with key timestamp {} failed to authenticate. Exception was {}", user, keyTimestamp, e);
+      throw AmitAuthenticationException.invalidTokenKeyTimestamp("system", keyTimestamp);
     }
   }
 }
diff --git a/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java b/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
index d604ba3..9cf42a0 100644
--- a/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
+++ b/library/src/main/java/io/mifos/anubis/security/TenantAuthenticator.java
@@ -26,6 +26,7 @@
 import io.mifos.anubis.service.PermittableService;
 import io.mifos.anubis.token.TokenType;
 import io.mifos.core.lang.ApplicationName;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
@@ -36,6 +37,8 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static io.mifos.anubis.config.AnubisConstants.LOGGER_NAME;
+
 /**
  * @author Myrle Krantz
  */
@@ -45,18 +48,21 @@
   private final String applicationNameWithVersion;
   private final Gson gson;
   private final Set<ApplicationPermission> guestPermissions;
+  private final Logger logger;
 
   @Autowired
   public TenantAuthenticator(
       final TenantRsaKeyProvider tenantRsaKeyProvider,
       final ApplicationName applicationName,
       final PermittableService permittableService,
-      final @Qualifier("anubisGson") Gson gson) {
+      final @Qualifier("anubisGson") Gson gson,
+      final @Qualifier(LOGGER_NAME) Logger logger) {
     this.tenantRsaKeyProvider = tenantRsaKeyProvider;
     this.applicationNameWithVersion = applicationName.toString();
     this.gson = gson;
     this.guestPermissions
         = permittableService.getPermittableEndpointsAsPermissions(AcceptedTokenType.GUEST);
+    this.logger = logger;
   }
 
   AnubisAuthentication authenticate(
@@ -79,13 +85,17 @@
       final Set<ApplicationPermission> permissions = translatePermissions(tokenContent.getTokenPermissions());
       permissions.addAll(guestPermissions);
 
+      logger.info("Tenant token for user {}, with key timestamp {} authenticated successfully.", user, keyTimestamp);
+
       return new AnubisAuthentication(token,
           jwt.getBody().getSubject(), permissions
       );
     }
     catch (final JwtException e) {
+      logger.info("Tenant token for user {}, with key timestamp {} failed to authenticate. Exception was {}", user, keyTimestamp, e);
       throw AmitAuthenticationException.invalidToken();
     } catch (final InvalidKeyTimestampException e) {
+      logger.info("Tenant token for user {}, with key timestamp {} failed to authenticate. Exception was {}", user, keyTimestamp, e);
       throw AmitAuthenticationException.invalidTokenKeyTimestamp("tenant", keyTimestamp);
     }
   }
diff --git a/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java b/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
index 957279e..22c185d 100644
--- a/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
+++ b/library/src/main/java/io/mifos/anubis/security/UrlPermissionChecker.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.anubis.security;
 
+import org.slf4j.Logger;
 import org.springframework.security.access.AccessDecisionVoter;
 import org.springframework.security.access.ConfigAttribute;
 import org.springframework.security.core.Authentication;
@@ -22,11 +23,18 @@
 import org.springframework.security.web.FilterInvocation;
 
 import java.util.Collection;
+import java.util.Optional;
 
 /**
  * @author Myrle Krantz
  */
 public class UrlPermissionChecker implements AccessDecisionVoter<FilterInvocation> {
+  private final Logger logger;
+
+  public UrlPermissionChecker(final Logger logger) {
+    this.logger = logger;
+  }
+
   @Override public boolean supports(final ConfigAttribute attribute) {
     return attribute.getAttribute().equals(ApplicationPermission.URL_AUTHORITY);
   }
@@ -48,10 +56,14 @@
     final AnubisAuthentication authentication = (AnubisAuthentication) unAuthentication;
 
     final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
-    return authorities.stream()
-        .map(x -> (ApplicationPermission)x)
-        .filter(x -> x.matches(filterInvocation, authentication.getPrincipal()))
-        .findAny()
-        .map(x -> ACCESS_GRANTED).orElse(ACCESS_DENIED);
+    final Optional<ApplicationPermission> matchedPermission = authorities.stream()
+            .map(x -> (ApplicationPermission) x)
+            .filter(x -> x.matches(filterInvocation, authentication.getPrincipal()))
+            .findAny();
+
+    matchedPermission.ifPresent(x -> logger.debug("Authorizing access to {} based on permission: {}"
+            , filterInvocation.getRequestUrl(),  x));
+
+    return matchedPermission.map(x -> ACCESS_GRANTED).orElse(ACCESS_DENIED);
   }
 }
diff --git a/library/src/main/java/io/mifos/anubis/service/PermissionSegmentMatcher.java b/library/src/main/java/io/mifos/anubis/service/PermissionSegmentMatcher.java
index 80eea7a..a997558 100644
--- a/library/src/main/java/io/mifos/anubis/service/PermissionSegmentMatcher.java
+++ b/library/src/main/java/io/mifos/anubis/service/PermissionSegmentMatcher.java
@@ -42,7 +42,7 @@
     return permissionSegment.startsWith("{") && permissionSegment.endsWith("}");
   }
 
-  String getPermissionSegment() { return permissionSegment; }
+  public String getPermissionSegment() { return permissionSegment; }
 
   public boolean matches(final String requestSegment, final String principal, boolean isSu) {
     if (isStarSegment())
diff --git a/library/src/main/java/io/mifos/anubis/token/TenantApplicationRsaKeyProvider.java b/library/src/main/java/io/mifos/anubis/token/TenantApplicationRsaKeyProvider.java
new file mode 100644
index 0000000..9c4e905
--- /dev/null
+++ b/library/src/main/java/io/mifos/anubis/token/TenantApplicationRsaKeyProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * 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.
+ */
+package io.mifos.anubis.token;
+
+import io.mifos.anubis.provider.InvalidKeyTimestampException;
+
+import java.security.PublicKey;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public interface TenantApplicationRsaKeyProvider {
+  PublicKey getApplicationPublicKey(String issuingApplication, String timestamp) throws InvalidKeyTimestampException;
+}
diff --git a/library/src/main/java/io/mifos/anubis/token/TenantRefreshTokenSerializer.java b/library/src/main/java/io/mifos/anubis/token/TenantRefreshTokenSerializer.java
index 97a597e..600bdeb 100644
--- a/library/src/main/java/io/mifos/anubis/token/TenantRefreshTokenSerializer.java
+++ b/library/src/main/java/io/mifos/anubis/token/TenantRefreshTokenSerializer.java
@@ -18,9 +18,7 @@
 import io.jsonwebtoken.*;
 import io.mifos.anubis.api.v1.TokenConstants;
 import io.mifos.anubis.provider.InvalidKeyTimestampException;
-import io.mifos.anubis.provider.TenantRsaKeyProvider;
 import io.mifos.anubis.security.AmitAuthenticationException;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Nonnull;
@@ -36,15 +34,6 @@
 @SuppressWarnings("WeakerAccess")
 @Component
 public class TenantRefreshTokenSerializer {
-
-  final private TenantRsaKeyProvider tenantRsaKeyProvider;
-
-  @SuppressWarnings("SpringJavaAutowiringInspection")
-  @Autowired
-  TenantRefreshTokenSerializer(final TenantRsaKeyProvider tenantRsaKeyProvider) {
-    this.tenantRsaKeyProvider = tenantRsaKeyProvider;
-  }
-
   @SuppressWarnings("WeakerAccess")
   public static class Specification {
     private String keyTimestamp;
@@ -52,6 +41,7 @@
     private String user;
     private long secondsToLive;
     private String sourceApplication;
+    private String endpointSet = null; //Optional
 
     public Specification setKeyTimestamp(final String keyTimestamp) {
       this.keyTimestamp = keyTimestamp;
@@ -77,6 +67,11 @@
       this.sourceApplication = sourceApplication;
       return this;
     }
+
+    public Specification setEndpointSet(String endpointSet) {
+      this.endpointSet = endpointSet;
+      return this;
+    }
   }
 
   public TokenSerializationResult build(final Specification specification)
@@ -92,6 +87,11 @@
     if (specification.sourceApplication == null) {
       throw new IllegalArgumentException("token source application must not be null.");
     }
+    if (specification.secondsToLive <= 0) {
+      throw new IllegalArgumentException("token secondsToLive must be positive.");
+    }
+
+    final Date expiration = new Date(issued + TimeUnit.SECONDS.toMillis(specification.secondsToLive));
 
     final JwtBuilder jwtBuilder =
             Jwts.builder()
@@ -99,18 +99,15 @@
                     .setSubject(specification.user)
                     .claim(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, specification.keyTimestamp)
                     .setIssuedAt(new Date(issued))
-                    .signWith(SignatureAlgorithm.RS512, specification.privateKey);
-    if (specification.secondsToLive <= 0) {
-      throw new IllegalArgumentException("token secondsToLive must be positive.");
-    }
-
-    final Date expiration = new Date(issued + TimeUnit.SECONDS.toMillis(specification.secondsToLive));
-    jwtBuilder.setExpiration(expiration);
+                    .signWith(SignatureAlgorithm.RS512, specification.privateKey)
+                    .setExpiration(expiration);
+    if (specification.endpointSet != null)
+      jwtBuilder.claim(TokenConstants.JWT_ENDPOINT_SET_CLAIM, specification.endpointSet);
 
     return new TokenSerializationResult(TokenConstants.PREFIX + jwtBuilder.compact(), expiration);
   }
 
-  public TokenDeserializationResult deserialize(final String refreshToken)
+  public TokenDeserializationResult deserialize(final TenantApplicationRsaKeyProvider tenantRsaKeyProvider, final String refreshToken)
   {
     final Optional<String> tokenString = getJwtTokenString(refreshToken);
 
@@ -121,9 +118,10 @@
       final JwtParser parser = Jwts.parser().setSigningKeyResolver(new SigningKeyResolver() {
         @Override public Key resolveSigningKey(final JwsHeader header, final Claims claims) {
           final String keyTimestamp = getKeyTimestampFromClaims(claims);
+          final String issuingApplication = getIssuingApplicationFromClaims(claims);
 
           try {
-            return tenantRsaKeyProvider.getPublicKey(keyTimestamp);
+            return tenantRsaKeyProvider.getApplicationPublicKey(issuingApplication, keyTimestamp);
           }
           catch (final IllegalArgumentException e)
           {
@@ -142,7 +140,11 @@
 
       @SuppressWarnings("unchecked") Jwt<Header, Claims> jwt = parser.parse(token);
 
-      return new TokenDeserializationResult(jwt.getBody().getSubject(), jwt.getBody().getExpiration(), jwt.getBody().getIssuer());
+      return new TokenDeserializationResult(
+              jwt.getBody().getSubject(),
+              jwt.getBody().getExpiration(),
+              jwt.getBody().getIssuer(),
+              jwt.getBody().get(TokenConstants.JWT_ENDPOINT_SET_CLAIM, String.class));
     }
     catch (final JwtException e) {
       throw AmitAuthenticationException.invalidToken();
@@ -165,4 +167,9 @@
   String getKeyTimestampFromClaims(final Claims claims) {
     return claims.get(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, String.class);
   }
+
+  private @Nonnull
+  String getIssuingApplicationFromClaims(final Claims claims) {
+    return claims.getIssuer();
+  }
 }
diff --git a/library/src/main/java/io/mifos/anubis/token/TokenDeserializationResult.java b/library/src/main/java/io/mifos/anubis/token/TokenDeserializationResult.java
index 012d155..df659c8 100644
--- a/library/src/main/java/io/mifos/anubis/token/TokenDeserializationResult.java
+++ b/library/src/main/java/io/mifos/anubis/token/TokenDeserializationResult.java
@@ -25,11 +25,17 @@
   final private String userIdentifier;
   final private Date expiration;
   final private String sourceApplication;
+  final private String endpointSet;
 
-  TokenDeserializationResult(final String userIdentifier, final Date expiration, final String sourceApplication) {
+  TokenDeserializationResult(
+          final String userIdentifier,
+          final Date expiration,
+          final String sourceApplication,
+          final String endpointSet) {
     this.userIdentifier = userIdentifier;
     this.expiration = expiration;
     this.sourceApplication = sourceApplication;
+    this.endpointSet = endpointSet;
   }
 
   public String getUserIdentifier() {
@@ -43,4 +49,8 @@
   public String getSourceApplication() {
     return sourceApplication;
   }
+
+  public String getEndpointSet() {
+    return endpointSet;
+  }
 }
diff --git a/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java b/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
index 0872592..f7fa1e1 100644
--- a/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
+++ b/library/src/test/java/io/mifos/anubis/security/ApplicationPermissionTest.java
@@ -149,8 +149,14 @@
         .permittedPath("/{parameter}/").requestedPath("/value")
         .expectedResult(true));
     ret.add(new TestCase("{parameter} without su")
-        .permittedPath("/{parameter}/").requestedPath("/value")
-        .expectedResult(false));
+         .permittedPath("/{parameter}/").requestedPath("/value")
+         .expectedResult(false));
+    ret.add(new TestCase("* at end with request containing more segments")
+         .permittedPath("/roles/*").requestedPath("/users/antony/password")
+         .expectedResult(false));
+    ret.add(new TestCase("* at end with request containing same # segments")
+         .permittedPath("/x/y/z/*").requestedPath("/m/n/o/")
+         .expectedResult(false));
 
     return ret;
   }
diff --git a/library/src/test/java/io/mifos/anubis/token/TenantRefreshTokenSerializerTest.java b/library/src/test/java/io/mifos/anubis/token/TenantRefreshTokenSerializerTest.java
index 7713ff2..8cfd586 100644
--- a/library/src/test/java/io/mifos/anubis/token/TenantRefreshTokenSerializerTest.java
+++ b/library/src/test/java/io/mifos/anubis/token/TenantRefreshTokenSerializerTest.java
@@ -21,7 +21,6 @@
 import io.jsonwebtoken.Jwts;
 import io.mifos.anubis.api.v1.TokenConstants;
 import io.mifos.anubis.provider.InvalidKeyTimestampException;
-import io.mifos.anubis.provider.TenantRsaKeyProvider;
 import io.mifos.anubis.security.AmitAuthenticationException;
 import io.mifos.anubis.token.TenantRefreshTokenSerializer.Specification;
 import io.mifos.core.lang.security.RsaKeyPairFactory;
@@ -33,6 +32,7 @@
 
 import java.time.Duration;
 import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 
 /**
  * @author Myrle Krantz
@@ -42,6 +42,7 @@
   private static final String APPLICATION_NAME = "mifosio-core";
   private static final int SECONDS_TO_LIVE = 15;
   private static final String USER = "who";
+  private static final String ENDPOINT_SET = "what";
   private static RsaKeyPairFactory.KeyPairHolder keyPairHolder;
 
   @BeforeClass
@@ -53,7 +54,7 @@
   @Test
   public void shouldCreateValidRefreshToken() throws Exception {
     final Specification specification = getValidSpecification();
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
 
     final TimeStampChecker timeStampChecker = TimeStampChecker.inTheFuture(Duration.ofSeconds(SECONDS_TO_LIVE));
 
@@ -79,74 +80,117 @@
     Assert.assertTrue(expires > issued);
     final String signatureTimestamp = parsedToken.getBody().get(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, String.class);
     Assert.assertEquals(keyPairHolder.getTimestamp(), signatureTimestamp);
+    final String endpointSetClaim = parsedToken.getBody().get(TokenConstants.JWT_ENDPOINT_SET_CLAIM, String.class);
+    Assert.assertEquals(null, endpointSetClaim);
 
-    final TokenDeserializationResult tokenDeserializationResult = testSubject.deserialize(tokenSerializationResult.getToken());
+    final TokenDeserializationResult tokenDeserializationResult = testSubject.deserialize(getTenantApplicationRsaKeyProvider(), tokenSerializationResult.getToken());
     Assert.assertNotNull(tokenDeserializationResult);
     Assert.assertEquals(APPLICATION_NAME, tokenDeserializationResult.getSourceApplication());
     Assert.assertEquals(USER, tokenDeserializationResult.getUserIdentifier());
-    Assert.assertEquals(tokenDeserializationResult.getExpiration(), tokenDeserializationResult.getExpiration());
+    timeStampChecker.assertCorrect(LocalDateTime.ofInstant(tokenDeserializationResult.getExpiration().toInstant(), ZoneOffset.UTC));
+    Assert.assertEquals(null, tokenDeserializationResult.getEndpointSet());
+  }
+
+  @Test
+  public void shouldCreateValidRefreshTokenWithEndpointSet() throws Exception {
+    final Specification specification = getValidSpecification();
+    specification.setEndpointSet(ENDPOINT_SET);
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+
+    final TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
+
+    @SuppressWarnings("unchecked") final Jwt<Header, Claims> parsedToken = Jwts
+            .parser()
+            .setSigningKey(keyPairHolder.publicKey())
+            .parse(tokenSerializationResult.getToken().substring("Bearer ".length()).trim());
+
+
+    final String endpointSetClaim = parsedToken.getBody().get(TokenConstants.JWT_ENDPOINT_SET_CLAIM, String.class);
+    Assert.assertEquals(ENDPOINT_SET, endpointSetClaim);
+
+    final TokenDeserializationResult tokenDeserializationResult = testSubject.deserialize(getTenantApplicationRsaKeyProvider(), tokenSerializationResult.getToken());
+    Assert.assertEquals(ENDPOINT_SET, tokenDeserializationResult.getEndpointSet());
   }
 
   @Test(expected = IllegalArgumentException.class)
   public void invalidSecondsToLiveCausesException() throws Exception {
     final Specification specification = getValidSpecification().setSecondsToLive(-1);
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
     testSubject.build(specification);
   }
 
   @Test(expected = IllegalArgumentException.class)
   public void missingKeyTimestampCausesException() throws Exception {
     final Specification specification = getValidSpecification().setKeyTimestamp(null);
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
     testSubject.build(specification);
   }
 
   @Test(expected = IllegalArgumentException.class)
   public void missingApplicationCausesException() throws Exception {
     final Specification specification = getValidSpecification().setSourceApplication(null);
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
     testSubject.build(specification);
   }
 
   @Test(expected = IllegalArgumentException.class)
   public void missingPrivateKeyCausesException() throws Exception {
     final Specification specification = getValidSpecification().setPrivateKey(null);
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
     testSubject.build(specification);
   }
 
   @Test(expected = AmitAuthenticationException.class)
   public void deserializeNullCausesAmitException() throws Exception {
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
-    testSubject.deserialize(null);
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+    testSubject.deserialize(getTenantApplicationRsaKeyProvider(), null);
   }
 
   @Test(expected = AmitAuthenticationException.class)
   public void deserializeUnprefixedTokenCausesAmitException() throws Exception {
-    final TenantRefreshTokenSerializer testSubject = getTestSubject();
-    testSubject.deserialize("randostring");
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+    testSubject.deserialize(getTenantApplicationRsaKeyProvider(), "randostring");
   }
 
   @Test(expected = AmitAuthenticationException.class)
   public void tenantHasNotProvidedAPublicKeyDuringDeserializationCausesAmitException() throws Exception {
     final Specification specification = getValidSpecification();
-    final TenantRsaKeyProvider tenantRsaKeyProvider = Mockito.mock(TenantRsaKeyProvider.class);
-    Mockito.when(tenantRsaKeyProvider.getPublicKey(keyPairHolder.getTimestamp())).thenThrow(new IllegalArgumentException());
+    final TenantApplicationRsaKeyProvider tenantApplicationRsaKeyProvider
+            = Mockito.mock(TenantApplicationRsaKeyProvider.class);
+    Mockito.when(tenantApplicationRsaKeyProvider.getApplicationPublicKey(APPLICATION_NAME, keyPairHolder.getTimestamp()))
+            .thenThrow(new IllegalArgumentException());
 
-    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer(tenantRsaKeyProvider);
-    TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
-    testSubject.deserialize(tokenSerializationResult.getToken());
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+    final TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
+    testSubject.deserialize(tenantApplicationRsaKeyProvider, tokenSerializationResult.getToken());
   }
 
   @Test(expected = AmitAuthenticationException.class)
   public void tenantHasNotProvidedAPublicKeyForKeyTimestampDuringDeserializationCausesAmitException() throws Exception {
     final Specification specification = getValidSpecification();
-    final TenantRsaKeyProvider tenantRsaKeyProvider = Mockito.mock(TenantRsaKeyProvider.class);
-    Mockito.when(tenantRsaKeyProvider.getPublicKey(keyPairHolder.getTimestamp())).thenThrow(new InvalidKeyTimestampException(""));
+    final TenantApplicationRsaKeyProvider tenantApplicationRsaKeyProvider
+            = Mockito.mock(TenantApplicationRsaKeyProvider.class);
+    Mockito.when(tenantApplicationRsaKeyProvider.getApplicationPublicKey(APPLICATION_NAME, keyPairHolder.getTimestamp()))
+            .thenThrow(new InvalidKeyTimestampException(""));
 
-    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer(tenantRsaKeyProvider);
-    TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
-    testSubject.deserialize(tokenSerializationResult.getToken());
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+    final TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
+    testSubject.deserialize(tenantApplicationRsaKeyProvider, tokenSerializationResult.getToken());
+  }
+
+  @Test(expected = AmitAuthenticationException.class)
+  public void tokenIsSignedWithAWrongKeyCausesAmitException() throws Exception {
+    final RsaKeyPairFactory.KeyPairHolder otherKeyPairHolder = RsaKeyPairFactory.createKeyPair();
+
+    final Specification specification = getValidSpecification();
+    final TenantApplicationRsaKeyProvider tenantApplicationRsaKeyProvider
+            = Mockito.mock(TenantApplicationRsaKeyProvider.class);
+    Mockito.when(tenantApplicationRsaKeyProvider.getApplicationPublicKey(APPLICATION_NAME, keyPairHolder.getTimestamp()))
+            .thenReturn(otherKeyPairHolder.publicKey());
+
+    final TenantRefreshTokenSerializer testSubject = new TenantRefreshTokenSerializer();
+    final TokenSerializationResult tokenSerializationResult = testSubject.build(specification);
+    testSubject.deserialize(tenantApplicationRsaKeyProvider, tokenSerializationResult.getToken());
   }
 
   private Specification getValidSpecification() {
@@ -158,10 +202,10 @@
             .setSecondsToLive(SECONDS_TO_LIVE);
   }
 
-  private TenantRefreshTokenSerializer getTestSubject() throws InvalidKeyTimestampException {
-    final TenantRsaKeyProvider tenantRsaKeyProvider = Mockito.mock(TenantRsaKeyProvider.class);
-    Mockito.when(tenantRsaKeyProvider.getPublicKey(keyPairHolder.getTimestamp())).thenReturn(keyPairHolder.publicKey());
+  private TenantApplicationRsaKeyProvider getTenantApplicationRsaKeyProvider() throws InvalidKeyTimestampException {
+    final TenantApplicationRsaKeyProvider tenantRsaKeyProvider = Mockito.mock(TenantApplicationRsaKeyProvider.class);
+    Mockito.when(tenantRsaKeyProvider.getApplicationPublicKey(APPLICATION_NAME, keyPairHolder.getTimestamp())).thenReturn(keyPairHolder.publicKey());
 
-    return new TenantRefreshTokenSerializer(tenantRsaKeyProvider);
+    return tenantRsaKeyProvider;
   }
 }