You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/11/18 20:02:01 UTC
[4/6] nifi git commit: NIFI-655. - Changed issuer field to use FQ
class name because some classes return an empty string for getSimpleName(). -
Finished refactoring JWT logic from request parsing logic in JwtService. -
Updated AccessResource and JwtAuthe
NIFI-655. - Changed issuer field to use FQ class name because some classes return an empty string for getSimpleName(). - Finished refactoring JWT logic from request parsing logic in JwtService. - Updated AccessResource and JwtAuthenticationFilter to call new JwtService methods decoupled from request header parsing. - Added extensive unit tests for JWT logic.
Signed-off-by: Matt Gilman <ma...@gmail.com>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/7d04dfea
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/7d04dfea
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/7d04dfea
Branch: refs/heads/NIFI-655
Commit: 7d04dfeac012307201e84dd00883dd6a79e6e2cc
Parents: 3bc11e1
Author: Andy LoPresto <an...@andylopresto.com>
Authored: Wed Nov 18 00:45:38 2015 -0800
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Nov 18 08:31:39 2015 -0500
----------------------------------------------------------------------
.../admin/service/impl/StandardKeyService.java | 10 +-
.../org/apache/nifi/web/api/AccessResource.java | 54 +++---
.../security/jwt/JwtAuthenticationFilter.java | 44 ++++-
.../nifi/web/security/jwt/JwtService.java | 64 +++----
.../nifi/web/security/jwt/JwtServiceTest.java | 182 ++++++++++++++++---
5 files changed, 264 insertions(+), 90 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
index 1b2f8c9..7dff9d8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java
@@ -18,6 +18,9 @@ package org.apache.nifi.admin.service.impl;
import org.apache.nifi.admin.dao.DataAccessException;
import org.apache.nifi.admin.service.AdministrationException;
+import org.apache.nifi.admin.service.KeyService;
+import org.apache.nifi.admin.service.action.GetKeyAction;
+import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
import org.apache.nifi.admin.service.transaction.Transaction;
import org.apache.nifi.admin.service.transaction.TransactionBuilder;
import org.apache.nifi.admin.service.transaction.TransactionException;
@@ -26,9 +29,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.apache.nifi.admin.service.KeyService;
-import org.apache.nifi.admin.service.action.GetKeyAction;
-import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
/**
*
@@ -45,6 +45,8 @@ public class StandardKeyService implements KeyService {
@Override
public String getKey(String identity) {
+ // TODO: Change this service to look up by "key ID" instead of identity
+ // TODO: Change the return type to a Key POJO to support key rotation
Transaction transaction = null;
String key = null;
@@ -75,6 +77,8 @@ public class StandardKeyService implements KeyService {
@Override
public String getOrCreateKey(String identity) {
+ // TODO: Change this service to look up by "key ID" instead of identity
+ // TODO: Change the return type to a Key POJO to support key rotation
Transaction transaction = null;
String key = null;
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index b4778ad..2e1c44e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -23,6 +23,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import io.jsonwebtoken.JwtException;
import org.apache.nifi.util.NiFiProperties;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
@@ -184,30 +185,33 @@ public class AccessResource extends ApplicationResource {
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
accessStatus.setMessage("No credentials supplied, unknown user.");
} else {
- // TODO - use this token with the JWT service
+ // Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
- // TODO - do not call this method of the jwt service
- final String principal = jwtService.getAuthentication(httpServletRequest);
-
- // TODO - catch jwt exception?
- // ensure we have something we can work with (certificate or credentials)
- if (principal == null) {
- throw new IllegalArgumentException("The specific token is not valid.");
- } else {
- // set the user identity
- accessStatus.setIdentity(principal);
- accessStatus.setUsername(CertificateUtils.extractUsername(principal));
-
- // without a certificate, this is not a proxied request
- final List<String> chain = Arrays.asList(principal);
-
- // check authorization for this user
- checkAuthorization(chain);
-
- // no issues with authorization
- accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
- accessStatus.setMessage("Account is active and authorized");
+ try {
+ final String principal = jwtService.getAuthenticationFromToken(token);
+
+ // ensure we have something we can work with (certificate or credentials)
+ if (principal == null) {
+ throw new IllegalArgumentException("The specific token is not valid.");
+ } else {
+ // set the user identity
+ accessStatus.setIdentity(principal);
+ accessStatus.setUsername(CertificateUtils.extractUsername(principal));
+
+ // without a certificate, this is not a proxied request
+ final List<String> chain = Arrays.asList(principal);
+
+ // check authorization for this user
+ checkAuthorization(chain);
+
+ // no issues with authorization
+ accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
+ accessStatus.setMessage("Account is active and authorized");
+ }
+ } catch (JwtException e) {
+ // TODO: Handle the exception from a failed JWT verification
+ throw new AccessDeniedException("The JWT could not be verified", e);
}
}
} else {
@@ -334,7 +338,8 @@ public class AccessResource extends ApplicationResource {
}
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getSimpleName());
+ // TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
+ loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, loginIdentityProvider.getClass().getName());
} catch (final InvalidLoginCredentialsException ilce) {
throw new IllegalArgumentException("The supplied username and password are not valid.", ilce);
} catch (final IdentityAccessException iae) {
@@ -355,7 +360,8 @@ public class AccessResource extends ApplicationResource {
authorizeProxyIfNecessary(proxyChain);
// create the authentication token
- loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), certificateIdentityProvider.getClass().getSimpleName());
+ // TODO: Some Spring beans return "" for getClass().getSimpleName(). Using getName() temporarily
+ loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), certificateIdentityProvider.getClass().getName());
}
// generate JWT for response
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
index 5a84e93..dea5bba 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
@@ -16,9 +16,8 @@
*/
package org.apache.nifi.web.security.jwt;
-import java.util.Arrays;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import io.jsonwebtoken.JwtException;
+import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken;
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
@@ -26,12 +25,18 @@ import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+
/**
*/
public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
+ private static final String AUTHORIZATION = "Authorization";
+
private JwtService jwtService;
@Override
@@ -41,16 +46,35 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
return null;
}
+ // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
+
// get the principal out of the user token
- final String jwtPrincipal = jwtService.getAuthentication(request);
- if (jwtPrincipal == null) {
- return null;
- }
+ // look for an authorization token
+ final String authorization = request.getHeader(AUTHORIZATION);
- if (isNewAccountRequest(request)) {
- return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
+ // if there is no authorization header, we don't know the user
+ if (authorization == null) {
+ return null;
} else {
- return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
+ // Extract the Base64 encoded token from the Authorization header
+ final String token = StringUtils.substringAfterLast(authorization, " ");
+
+ try {
+ final String jwtPrincipal = jwtService.getAuthenticationFromToken(token);
+ if (jwtPrincipal == null) {
+ return null;
+ }
+
+ if (isNewAccountRequest(request)) {
+ return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request)));
+ } else {
+ return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal));
+ }
+ } catch (JwtException e) {
+ // TODO: Is this the correct way to handle an unverified token?
+ logger.error("Could not verify JWT", e);
+ return null;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
index acbbcfe..f006e5b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
@@ -33,7 +33,6 @@ import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.LoggerFactory;
-import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
@@ -44,7 +43,8 @@ public class JwtService {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
- private final static String AUTHORIZATION = "Authorization";
+ private static final String KEY_ID_CLAIM = "kid";
+ private static final String USERNAME_CLAIM = "preferred_username";
private final KeyService keyService;
@@ -52,49 +52,50 @@ public class JwtService {
this.keyService = keyService;
}
- /**
- * Gets the Authentication by extracting a JWT token from the specified request.
- *
- * @param request Request to extract the token from
- * @return The user identifier from the token
- */
- public String getAuthentication(final HttpServletRequest request) {
- // TODO: Refactor request token extraction out of this service
- // extract/verify token from incoming request
- final String authorization = request.getHeader(AUTHORIZATION);
- final String base64EncodedToken = StringUtils.substringAfterLast(authorization, " ");
-
- return getAuthenticationFromToken(base64EncodedToken);
- }
-
- public String getAuthenticationFromToken(final String base64EncodedToken) {
+ public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException {
// The library representations of the JWT should be kept internal to this service.
try {
final Jws<Claims> jws = parseTokenFromBase64EncodedString(base64EncodedToken);
+
+ if (jws == null) {
+ throw new JwtException("Unable to parse token");
+ }
+
+ // Additional validation that subject is present
+ if (StringUtils.isEmpty(jws.getBody().getSubject())) {
+ throw new JwtException("No subject available in token");
+ }
+
+ // TODO: Validate issuer against active registry?
+ if (StringUtils.isEmpty(jws.getBody().getIssuer())) {
+ // TODO: Remove after testing
+// logger.info("Decoded JWT payload: " + jws.toString());
+ throw new JwtException("No issuer available in token");
+ }
return jws.getBody().getSubject();
} catch (JwtException e) {
logger.debug("The Base64 encoded JWT: " + base64EncodedToken);
- final String errorMessage = "There was an error parsing the Base64-encoded JWT";
+ final String errorMessage = "There was an error validating the JWT";
logger.error(errorMessage, e);
- throw new JwtException(errorMessage, e);
+ throw e;
}
}
private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
try {
- // TODO: Check algorithm for validity
- // TODO: Ensure signature verification occurs
return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
final String identity = claims.getSubject();
+ // TODO: Currently the kid field is identical to identity, but will be a unique key ID when key rotation is implemented
+ final String keyId = claims.get(KEY_ID_CLAIM, String.class);
// The key is unique per identity and should be retrieved from the key service
- final String key = keyService.getKey(identity);
+ final String key = keyService.getKey(keyId);
// Ensure we were able to find a key that was previously issued by this key service for this user
if (key == null) {
- throw new UnsupportedJwtException("Unable to determine signing key for " + identity);
+ throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
}
return key.getBytes(StandardCharsets.UTF_8);
@@ -102,8 +103,7 @@ public class JwtService {
}).parseClaimsJws(base64EncodedToken);
} catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) {
// TODO: Exercise all exceptions to ensure none leak key material to logs
- final String errorMessage = "There was an error parsing the Base64-encoded JWT";
- logger.error(errorMessage, e);
+ final String errorMessage = "There was an error validating the JWT";
throw new JwtException(errorMessage, e);
}
}
@@ -111,9 +111,9 @@ public class JwtService {
/**
* Generates a signed JWT token from the provided (Spring Security) login authentication token.
*
- * @param authenticationToken
+ * @param authenticationToken an instance of the Spring Security token after login credentials have been verified against the respective information source
* @return a signed JWT containing the user identity and the identity provider, Base64-encoded
- * @throws JwtException
+ * @throws JwtException if there is a problem generating the signed token
*/
public String generateSignedToken(final LoginAuthenticationToken authenticationToken) throws JwtException {
if (authenticationToken == null) {
@@ -144,17 +144,19 @@ public class JwtService {
// TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
+ // TODO: Change kid field to key ID when KeyService is refactored
+
// Build the token
return Jwts.builder().setSubject(identity)
.setIssuer(authenticationToken.getIssuer())
.setAudience(authenticationToken.getIssuer())
- .claim("preferred_username", username)
+ .claim(USERNAME_CLAIM, username)
+ .claim(KEY_ID_CLAIM, identity)
.setExpiration(expiration.getTime())
.setIssuedAt(Calendar.getInstance().getTime())
.signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
} catch (NullPointerException | AdministrationException e) {
- // TODO: Remove exception handling and pass through
- final String errorMessage = "Could not retrieve the signing key for JWT";
+ final String errorMessage = "Could not retrieve the signing key for JWT for " + identity;
logger.error(errorMessage, e);
throw new JwtException(errorMessage, e);
}
http://git-wip-us.apache.org/repos/asf/nifi/blob/7d04dfea/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
index a7e763e..c9107b9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
@@ -9,6 +9,7 @@ import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.codehaus.jettison.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
@@ -38,14 +39,38 @@ public class JwtServiceTest {
* These constant strings were generated using the tool at http://jwt.io
*/
- private static final String VALID_SIGNED_TOKEN = "";
- private static final String INVALID_SIGNED_TOKEN = "";
- private static final String VALID_UNSIGNED_TOKEN = "";
- private static final String INVALID_UNSIGNED_TOKEN = "";
- private static final String VALID_MALSIGNED_TOKEN = "";
- private static final String INVALID_MALSIGNED_TOKEN = "";
+ private static final String VALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.8fUgH9jLvE1essgrcoV8OCyDhXvSXUH_1xqeqDqWycU";
+
+ // This token has an empty subject field
+ private static final String INVALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.jcjRBLtDzREmdjkJf3xry-ucyCmSRygBaP-HCWBkwlI";
+
+ private static final String VALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9";
+
+ // This token has an empty subject field
+ private static final String INVALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9";
+
+ // Algorithm field is "none"
+ private static final String VALID_MALSIGNED_TOKEN = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.mPO_wMNMl_zjMNevhNvUoXbSJ9Kx6jAe5OxDIAzKQbI";
+
+ // Algorithm field is "none" and no signature is present
+ private static final String VALID_MALSIGNED_NO_SIG_TOKEN = "eyJhbGciOiJub25lIn0.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.";
+
+ // This token has an empty subject field
+ private static final String INVALID_MALSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.WAwmUY4KHKV2oARNodkqDkbZsfRXGZfD2Ccy64GX9QF";
+
+ private static final String EXPIRED_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.y3M1TlzXZ80cVTkfcNxaHpq6aAlM1y2HGCZWEOcvmSU";
+
+ // Subject is "mgilman" but signed with "alopresto" key
+ private static final String IMPOSTER_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtZ2lsbWFuIiwiaXNzIjoiTW9ja0lkZW50aXR5UHJvdmlkZXIiLCJhdWQiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFsb3ByZXN0byIsImtpZCI6ImFsb3ByZXN0byIsImV4cCI6MjQ0NzgwODc2MSwiaWF0IjoxNDQ3ODA4NzAxfQ.l-9nHmYTEMgLshX8qCEqbc2O4BH_GYBVQIFkUKsJvLA";
+
+ // Issuer field is set to unknown provider
+ private static final String UNKNOWN_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJVbmtub3duSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.SAd9tyNwSaijWet9wvAWSNmpxmPSK4XQuLx7h3ARqBo";
+
+ // Issuer field is absent
+ private static final String NO_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbG9wcmVzdG8iLCJhdWQiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFsb3ByZXN0byIsImtpZCI6ImFsb3ByZXN0byIsImV4cCI6MjQ0NzgwODc2MSwiaWF0IjoxNDQ3ODA4NzAxfQ.Hdha7K69sz6224vidvuZ6A6UdGLdZ_U1egS0txuVXAk";
private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}";
+ private static final String DEFAULT_IDENTITY = "alopresto";
private static final String TOKEN_DELIMITER = ".";
@@ -93,6 +118,7 @@ public class JwtServiceTest {
@Before
public void setUp() throws Exception {
mockKeyService = Mockito.mock(KeyService.class);
+ when(mockKeyService.getKey(anyString())).thenReturn(HMAC_SECRET);
when(mockKeyService.getOrCreateKey(anyString())).thenReturn(HMAC_SECRET);
jwtService = new JwtService(mockKeyService);
}
@@ -104,45 +130,156 @@ public class JwtServiceTest {
@Test
public void testShouldGetAuthenticationForValidToken() throws Exception {
+ // Arrange
+ String token = VALID_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+ // Assert
+ assertEquals("Identity", DEFAULT_IDENTITY, identity);
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForInvalidToken() throws Exception {
// Arrange
String token = INVALID_SIGNED_TOKEN;
- String header = "{" +
- " \"alg\":\"HS256\"" +
- "}";
- String payload = "{" +
- " \"sub\":\"alopresto\"," +
- " \"preferred_username\":\"alopresto\"," +
- " \"exp\":2895419760" +
- "}";
-
// Act
- logger.info("Test token: " + generateHS256Token(header, payload, true, true));
-
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
// Assert
-
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForEmptyToken() throws Exception {
+ // Arrange
+ String token = "";
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+ // Assert
+
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForUnsignedToken() throws Exception {
+ // Arrange
+ String token = VALID_UNSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+ // Should fail
}
- @Test
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForMalsignedToken() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithm() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithmAndNoSignature() throws Exception {
+ // Arrange
+ String token = VALID_MALSIGNED_NO_SIG_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Ignore("Not yet implemented")
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenFromUnknownIdentityProvider() throws Exception {
+ // Arrange
+ String token = UNKNOWN_ISSUER_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForTokenFromEmptyIdentityProvider() throws Exception {
+ // Arrange
+ String token = NO_ISSUER_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForExpiredToken() throws Exception {
+ // Arrange
+ String token = EXPIRED_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+
+ // Should fail
+ }
+
+ @Test(expected = JwtException.class)
+ public void testShouldNotGetAuthenticationForImposterToken() throws Exception {
+ // Arrange
+ String token = IMPOSTER_SIGNED_TOKEN;
+
+ // Act
+ String identity = jwtService.getAuthenticationFromToken(token);
+ logger.debug("Extracted identity: " + identity);
+
+ // Assert
+ // Should fail
}
@Test
@@ -176,6 +313,7 @@ public class JwtServiceTest {
claims.put("iss", "MockIdentityProvider");
claims.put("aud", "MockIdentityProvider");
claims.put("preferred_username", "alopresto");
+ claims.put("kid", "alopresto");
claims.put("exp", TOKEN_EXPIRATION_SEC);
claims.put("iat", ISSUED_AT_SEC);
logger.trace("JSON Object to String: " + new JSONObject(claims).toString());