You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2015/09/30 15:58:42 UTC

knox git commit: KNOX-602 JWT/SSO Cookie Based Federation Provider

Repository: knox
Updated Branches:
  refs/heads/master 48b4ad74d -> 87fcd4d65


KNOX-602 JWT/SSO Cookie Based Federation Provider

Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/87fcd4d6
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/87fcd4d6
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/87fcd4d6

Branch: refs/heads/master
Commit: 87fcd4d65bf6ec1bc97e346cd6003d41399b272f
Parents: 48b4ad7
Author: Larry McCay <lm...@hortonworks.com>
Authored: Wed Sep 30 09:58:23 2015 -0400
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Wed Sep 30 09:58:23 2015 -0400

----------------------------------------------------------------------
 CHANGES                                         |   1 +
 gateway-provider-security-jwt/pom.xml           |   5 +
 .../provider/federation/jwt/JWTMessages.java    |  19 +-
 .../deploy/SSOCookieFederationContributor.java  |  64 +++++
 .../jwt/filter/SSOCookieFederationFilter.java   | 283 +++++++++++++++++++
 ...gateway.deploy.ProviderDeploymentContributor |   1 +
 .../impl/DefaultTokenAuthorityService.java      |   8 +
 .../service/knoxsso/KnoxSSOMessages.java        |   6 +
 .../gateway/service/knoxsso/WebSSOResource.java |  25 +-
 .../security/token/JWTokenAuthority.java        |   3 +
 .../services/security/token/impl/JWT.java       |   6 +
 .../services/security/token/impl/JWTToken.java  |  53 +++-
 pom.xml                                         |   6 +
 13 files changed, 463 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index cb802ad..51925ad 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,7 @@ Release Notes - Apache Knox - Version 0.7.0
     * [KNOX-548] - KnoxCLI adds a new system-user-auth-test command to test a topology's system username and password
     * [KNOX-549] - New Service-Test API can be added to topology. Accessible via Http call or KnoxCLI
     * [KNOX-579] - Regex based identity assertion provider with static dictionary lookup
+    * [KNOX-602] - JWT/SSO Cookie Based Federation Provider
 
 ** Improvement
     * [KNOX-553] - Added topology validation from KnoxCLI to TopologyService deployment.

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-provider-security-jwt/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/pom.xml b/gateway-provider-security-jwt/pom.xml
index 1700c99..40077d7 100644
--- a/gateway-provider-security-jwt/pom.xml
+++ b/gateway-provider-security-jwt/pom.xml
@@ -52,6 +52,11 @@
         </dependency>
 
         <dependency>
+            <groupId>com.thetransactioncompany</groupId>
+            <artifactId>cors-filter</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/JWTMessages.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/JWTMessages.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/JWTMessages.java
index 08561ff..f6969c6 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/JWTMessages.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/JWTMessages.java
@@ -1,4 +1,5 @@
 /**
+
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -24,16 +25,16 @@ import org.apache.hadoop.gateway.i18n.messages.StackTrace;
 
 @Messages(logger="org.apache.hadoop.gateway.provider.federation.jwt")
 public interface JWTMessages {
-  @Message( level = MessageLevel.INFO, text = "Failed to validate the audience attribute." )
+  @Message( level = MessageLevel.WARN, text = "Failed to validate the audience attribute." )
   void failedToValidateAudience();
 
-  @Message( level = MessageLevel.INFO, text = "Failed to verify the token signature." )
+  @Message( level = MessageLevel.WARN, text = "Failed to verify the token signature." )
   void failedToVerifyTokenSignature();
 
   @Message( level = MessageLevel.INFO, text = "Access token has expired; a new one must be acquired." )
   void tokenHasExpired();
 
-  @Message( level = MessageLevel.INFO, text = "Expected Bearer token is missing." )
+  @Message( level = MessageLevel.WARN, text = "Expected Bearer token is missing." )
   void missingBearerToken();
 
   @Message( level = MessageLevel.INFO, text = "Unable to verify token: {0}" )
@@ -41,4 +42,16 @@ public interface JWTMessages {
 
   @Message( level = MessageLevel.ERROR, text = "Unable to verify token: {0}" )
   void unableToIssueToken(@StackTrace( level = MessageLevel.DEBUG) Exception e);
+
+  @Message( level = MessageLevel.DEBUG, text = "Sending redirect to: {0}" )
+  void sendRedirectToLoginURL(String loginURL);
+
+  @Message( level = MessageLevel.ERROR, text = "Required configuration element for authentication provider is missing." )
+  void missingAuthenticationProviderUrlConfiguration();
+
+  @Message( level = MessageLevel.DEBUG, text = "{0} Cookie has been found and is being processed." )
+  void cookieHasBeenFound(String cookieName);
+
+  @Message( level = MessageLevel.DEBUG, text = "Audience claim has been validated." )
+  void jwtAudienceValidated();
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/SSOCookieFederationContributor.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/SSOCookieFederationContributor.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/SSOCookieFederationContributor.java
new file mode 100644
index 0000000..12b35e1
--- /dev/null
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/SSOCookieFederationContributor.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.gateway.provider.federation.jwt.deploy;
+
+import org.apache.hadoop.gateway.deploy.DeploymentContext;
+import org.apache.hadoop.gateway.deploy.ProviderDeploymentContributorBase;
+import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
+import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
+import org.apache.hadoop.gateway.topology.Provider;
+import org.apache.hadoop.gateway.topology.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class SSOCookieFederationContributor extends ProviderDeploymentContributorBase {
+
+  private static final String FILTER_CLASSNAME = "org.apache.hadoop.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter";
+  private static final String CORS_FILTER_CLASSNAME = "com.thetransactioncompany.cors.CORSFilter";
+
+  @Override
+  public String getRole() {
+    return "federation";
+  }
+
+  @Override
+  public String getName() {
+    return "SSOCookieProvider";
+  }
+
+  @Override
+  public void contributeProvider( DeploymentContext context, Provider provider ) {
+  }
+
+  @Override
+  public void contributeFilter( DeploymentContext context, Provider provider, Service service, ResourceDescriptor resource, List<FilterParamDescriptor> params ) {
+    // blindly add all the provider params as filter init params
+    if (params == null) {
+      params = new ArrayList<FilterParamDescriptor>();
+    }
+    Map<String, String> providerParams = provider.getParams();
+    for(Entry<String, String> entry : providerParams.entrySet()) {
+      params.add( resource.createFilterParam().name( entry.getKey().toLowerCase() ).value( entry.getValue() ) );
+    }
+    resource.addFilter().name( "CORS" ).role( getRole() ).impl( CORS_FILTER_CLASSNAME ).params( params );
+    resource.addFilter().name( getName() ).role( getRole() ).impl( FILTER_CLASSNAME ).params( params );
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
new file mode 100644
index 0000000..18a9eea
--- /dev/null
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
@@ -0,0 +1,283 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.gateway.provider.federation.jwt.filter;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.provider.federation.jwt.JWTMessages;
+import org.apache.hadoop.gateway.security.PrimaryPrincipal;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority;
+import org.apache.hadoop.gateway.services.security.token.TokenServiceException;
+import org.apache.hadoop.gateway.services.security.token.impl.JWTToken;
+
+public class SSOCookieFederationFilter implements Filter {
+  private static JWTMessages log = MessagesFactory.get( JWTMessages.class );
+  private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
+  private static final String SSO_COOKIE_NAME = "sso.cookie.name";
+  private static final String SSO_EXPECTED_AUDIENCES = "sso.expected.audiences";
+  private static final String SSO_AUTHENTICATION_PROVIDER_URL = "sso.authentication.provider.url";
+  private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt";
+
+  private JWTokenAuthority authority = null;
+  private String cookieName = null;
+  private List<String> audiences = null;
+  private String authenticationProviderUrl = null;
+
+  @Override
+  public void init( FilterConfig filterConfig ) throws ServletException {
+    GatewayServices services = (GatewayServices) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+    authority = (JWTokenAuthority) services.getService(GatewayServices.TOKEN_SERVICE);
+
+    // configured cookieName
+    cookieName = filterConfig.getInitParameter(SSO_COOKIE_NAME);
+    if (cookieName == null) {
+      cookieName = DEFAULT_SSO_COOKIE_NAME;
+    }
+
+    // expected audiences or null
+    String expectedAudiences = filterConfig.getInitParameter(SSO_EXPECTED_AUDIENCES);
+    if (expectedAudiences != null) {
+      audiences = parseExpectedAudiences(expectedAudiences);
+    }
+
+    // url to SSO authentication provider
+    authenticationProviderUrl = filterConfig.getInitParameter(SSO_AUTHENTICATION_PROVIDER_URL);
+    if (authenticationProviderUrl == null) {
+      log.missingAuthenticationProviderUrlConfiguration();
+    }
+  }
+
+  /**
+   * @param expectedAudiences
+   * @return
+   */
+  private List<String> parseExpectedAudiences(String expectedAudiences) {
+    ArrayList<String> audList = null;
+    // setup the list of valid audiences for token validation
+    if (expectedAudiences != null) {
+      // parse into the list
+      String[] audArray = expectedAudiences.split(",");
+      audList = new ArrayList<String>();
+      for (String a : audArray) {
+        audList.add(a);
+      }
+    }
+    return audList;
+  }
+
+  public void destroy() {
+  }
+
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
+      throws IOException, ServletException {
+    String wireToken = null;
+    HttpServletRequest req = (HttpServletRequest) request;
+
+    String loginURL = constructLoginURL(req);
+    wireToken = getJWTFromCookie(req);
+    if (wireToken == null) {
+      if (req.getMethod().equals("OPTIONS")) {
+        // CORS preflight requests to determine allowed origins and related config
+        // must be able to continue without being redirected
+        Subject sub = new Subject();
+        sub.getPrincipals().add(new PrimaryPrincipal("anonymous"));
+        continueWithEstablishedSecurityContext(sub, req, (HttpServletResponse) response, chain);
+      }
+      log.sendRedirectToLoginURL(loginURL);
+      ((HttpServletResponse) response).sendRedirect(loginURL);
+    }
+    else {
+      JWTToken token = new JWTToken(wireToken);
+      boolean verified = false;
+      try {
+        verified = authority.verifyToken(token);
+        if (verified) {
+          Date expires = token.getExpiresDate();
+          if (expires != null && new Date().before(expires)) {
+            boolean audValid = validateAudiences(token);
+            if (audValid) {
+              Subject subject = createSubjectFromToken(token);
+              continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain);
+            }
+            else {
+              log.failedToValidateAudience();
+              ((HttpServletResponse) response).sendRedirect(loginURL);
+            }
+          }
+          else {
+            log.tokenHasExpired();
+          ((HttpServletResponse) response).sendRedirect(loginURL);
+          }
+        }
+        else {
+          log.failedToVerifyTokenSignature();
+        ((HttpServletResponse) response).sendRedirect(loginURL);
+        }
+      } catch (TokenServiceException e) {
+        log.unableToVerifyToken(e);
+      ((HttpServletResponse) response).sendRedirect(loginURL);
+      }
+    }
+  }
+
+  /**
+   * Encapsulate the acquisition of the JWT token from HTTP cookies within the
+   * request.
+   *
+   * @param req servlet request to get the JWT token from
+   * @return serialized JWT token
+   */
+  protected String getJWTFromCookie(HttpServletRequest req) {
+    String serializedJWT = null;
+    Cookie[] cookies = req.getCookies();
+    if (cookies != null) {
+      for (Cookie cookie : cookies) {
+        if (cookieName.equals(cookie.getName())) {
+          log.cookieHasBeenFound(cookieName);
+          serializedJWT = cookie.getValue();
+          break;
+        }
+      }
+    }
+    return serializedJWT;
+  }
+
+  /**
+   * Create the URL to be used for authentication of the user in the absence of
+   * a JWT token within the incoming request.
+   *
+   * @param request for getting the original request URL
+   * @return url to use as login url for redirect
+   */
+  protected String constructLoginURL(HttpServletRequest request) {
+    String delimiter = "?";
+    if (authenticationProviderUrl.contains("?")) {
+      delimiter = "&";
+    }
+    String loginURL = authenticationProviderUrl + delimiter
+        + ORIGINAL_URL_QUERY_PARAM
+        + request.getRequestURL().toString() + "?" + request.getQueryString();
+    return loginURL;
+  }
+
+  /**
+   * Validate whether any of the accepted audience claims is present in the
+   * issued token claims list for audience. Override this method in subclasses
+   * in order to customize the audience validation behavior.
+   *
+   * @param jwtToken
+   *          the JWT token where the allowed audiences will be found
+   * @return true if an expected audience is present, otherwise false
+   */
+  protected boolean validateAudiences(JWTToken jwtToken) {
+    boolean valid = false;
+    
+    String[] tokenAudienceList = jwtToken.getAudienceClaims();
+    // if there were no expected audiences configured then just
+    // consider any audience acceptable
+    if (audiences == null) {
+      valid = true;
+    } else {
+      // if any of the configured audiences is found then consider it
+      // acceptable
+      for (String aud : tokenAudienceList) {
+        if (audiences.contains(aud)) {
+          //log.debug("JWT token audience has been successfully validated");
+          log.jwtAudienceValidated();
+          valid = true;
+          break;
+        }
+      }
+    }
+    return valid;
+  }
+
+  private void sendUnauthorized(ServletResponse response) throws IOException {
+    ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+    return;
+  }
+
+  private void continueWithEstablishedSecurityContext(Subject subject, final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
+    try {
+      Subject.doAs(
+        subject,
+        new PrivilegedExceptionAction<Object>() {
+          @Override
+          public Object run() throws Exception {
+            chain.doFilter(request, response);
+            return null;
+          }
+        }
+        );
+    }
+    catch (PrivilegedActionException e) {
+      Throwable t = e.getCause();
+      if (t instanceof IOException) {
+        throw (IOException) t;
+      }
+      else if (t instanceof ServletException) {
+        throw (ServletException) t;
+      }
+      else {
+        throw new ServletException(t);
+      }
+    }
+  }
+
+  private Subject createSubjectFromToken(JWTToken token) {
+    final String principal = token.getSubject();
+
+    @SuppressWarnings("rawtypes")
+    HashSet emptySet = new HashSet();
+    Set<Principal> principals = new HashSet<Principal>();
+    Principal p = new PrimaryPrincipal(principal);
+    principals.add(p);
+    
+//        The newly constructed Sets check whether this Subject has been set read-only 
+//        before permitting subsequent modifications. The newly created Sets also prevent 
+//        illegal modifications by ensuring that callers have sufficient permissions.
+//
+//        To modify the Principals Set, the caller must have AuthPermission("modifyPrincipals"). 
+//        To modify the public credential Set, the caller must have AuthPermission("modifyPublicCredentials"). 
+//        To modify the private credential Set, the caller must have AuthPermission("modifyPrivateCredentials").
+    javax.security.auth.Subject subject = new javax.security.auth.Subject(true, principals, emptySet, emptySet);
+    return subject;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-provider-security-jwt/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor b/gateway-provider-security-jwt/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
index 2300504..cd69d46 100644
--- a/gateway-provider-security-jwt/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
+++ b/gateway-provider-security-jwt/src/main/resources/META-INF/services/org.apache.hadoop.gateway.deploy.ProviderDeploymentContributor
@@ -20,3 +20,4 @@ org.apache.hadoop.gateway.provider.federation.jwt.deploy.JWTFederationContributo
 org.apache.hadoop.gateway.provider.federation.jwt.deploy.JWTAccessTokenAssertionContributor
 org.apache.hadoop.gateway.provider.federation.jwt.deploy.JWTAuthCodeAssertionContributor
 org.apache.hadoop.gateway.provider.federation.jwt.deploy.AccessTokenFederationContributor
+org.apache.hadoop.gateway.provider.federation.jwt.deploy.SSOCookieFederationContributor

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java
index d4e5c5f..d28efa7 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityService.java
@@ -72,6 +72,14 @@ public class DefaultTokenAuthorityService implements JWTokenAuthority, Service {
     return issueToken(p, null, algorithm);
   }
   
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.provider.federation.jwt.JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, long expires)
+   */
+  @Override
+  public JWTToken issueToken(Principal p, String algorithm, long expires) throws TokenServiceException {
+    return issueToken(p, null, algorithm, expires);
+  }
+
   public JWTToken issueToken(Principal p, String audience, String algorithm)
       throws TokenServiceException {
     return issueToken(p, audience, algorithm, -1);

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/KnoxSSOMessages.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/KnoxSSOMessages.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/KnoxSSOMessages.java
index 2c0b933..598fb99 100644
--- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/KnoxSSOMessages.java
+++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/KnoxSSOMessages.java
@@ -53,4 +53,10 @@ public interface KnoxSSOMessages {
 
   @Message( level = MessageLevel.WARN, text = "The SSO cookie max age configuration is invalid: {0} - using default.")
   void invalidMaxAgeEncountered(String age);
+
+  @Message( level = MessageLevel.WARN, text = "The SSO token time to live - ttl is invalid: {0} - using default.")
+  void invalidTokenTTLEncountered(String ttl);
+
+  @Message( level = MessageLevel.INFO, text = "The cookie max age is being set to: {0}.")
+  void setMaxAge(String age);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
----------------------------------------------------------------------
diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
index 056fdf2..644d650 100644
--- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
+++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java
@@ -48,13 +48,15 @@ import static javax.ws.rs.core.MediaType.APPLICATION_XML;
 public class WebSSOResource {
   private static final String SSO_COOKIE_SECURE_ONLY_INIT_PARAM = "knoxsso.cookie.secure.only";
   private static final String SSO_COOKIE_MAX_AGE_INIT_PARAM = "knoxsso.cookie.max.age";
+  private static final String SSO_COOKIE_TOKEN_TTL_PARAM = "knoxsso.token.ttl";
   private static final String ORIGINAL_URL_REQUEST_PARAM = "originalUrl";
   private static final String ORIGINAL_URL_COOKIE_NAME = "original-url";
   private static final String JWT_COOKIE_NAME = "hadoop-jwt";
   static final String RESOURCE_PATH = "/knoxsso/api/v1/websso";
   private static KnoxSSOMessages log = MessagesFactory.get( KnoxSSOMessages.class );
   private boolean secureOnly = true;
-  private int maxAge = 120;
+  private int maxAge = -1;
+  private long tokenTTL = 30000l;
 
   @Context 
   private HttpServletRequest request;
@@ -70,18 +72,31 @@ public class WebSSOResource {
     String secure = context.getInitParameter(SSO_COOKIE_SECURE_ONLY_INIT_PARAM);
     if (secure != null) {
       secureOnly = ("false".equals(secure) ? false : true);
-      log.cookieSecureOnly(secureOnly);
+      if (!secureOnly) {
+        log.cookieSecureOnly(secureOnly);
+      }
     }
 
     String age = context.getInitParameter(SSO_COOKIE_MAX_AGE_INIT_PARAM);
     if (age != null) {
       try {
+        log.setMaxAge(age);
         maxAge = Integer.parseInt(age);
       }
       catch (NumberFormatException nfe) {
         log.invalidMaxAgeEncountered(age);
       }
     }
+
+    String ttl = context.getInitParameter(SSO_COOKIE_TOKEN_TTL_PARAM);
+    if (ttl != null) {
+      try {
+        tokenTTL = Long.parseLong(ttl);
+      }
+      catch (NumberFormatException nfe) {
+        log.invalidTokenTTLEncountered(ttl);
+      }
+    }
   }
 
   @GET
@@ -116,7 +131,7 @@ public class WebSSOResource {
     Principal p = ((HttpServletRequest)request).getUserPrincipal();
 
     try {
-      JWT token = ts.issueToken(p, "RS256");
+      JWT token = ts.issueToken(p, "RS256", System.currentTimeMillis() + tokenTTL);
       
       addJWTHadoopCookie(original, token);
       
@@ -150,7 +165,9 @@ public class WebSSOResource {
       if (secureOnly) {
         c.setSecure(true);
       }
-      c.setMaxAge(maxAge);
+      if (maxAge != -1) {
+        c.setMaxAge(maxAge);
+      }
       response.addCookie(c);
       log.addedJWTCookie();
     }

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/JWTokenAuthority.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/JWTokenAuthority.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/JWTokenAuthority.java
index bb978bf..7ed3ab5 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/JWTokenAuthority.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/JWTokenAuthority.java
@@ -21,6 +21,7 @@ import java.security.Principal;
 
 import javax.security.auth.Subject;
 
+import org.apache.hadoop.gateway.services.security.token.impl.JWT;
 import org.apache.hadoop.gateway.services.security.token.impl.JWTToken;
 
 public interface JWTokenAuthority {
@@ -38,4 +39,6 @@ public interface JWTokenAuthority {
 
   JWTToken issueToken(Principal p, String audience, String algorithm,
       long expires) throws TokenServiceException;
+
+  JWT issueToken(Principal p, String audience, long l) throws TokenServiceException;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWT.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWT.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWT.java
index ca9e912..4321c0d 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWT.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWT.java
@@ -17,6 +17,8 @@
  */
 package org.apache.hadoop.gateway.services.security.token.impl;
 
+import java.util.Date;
+
 import com.nimbusds.jose.JWSSigner;
 
 /**
@@ -45,8 +47,12 @@ public interface JWT {
 
   public abstract String getAudience();
 
+  public String[] getAudienceClaims();
+
   public abstract String getExpires();
 
+  public abstract Date getExpiresDate();
+
   public abstract String getSubject();
 
   public abstract String getHeader();

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
index b6c8a1b..71d5020 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/security/token/impl/JWTToken.java
@@ -49,6 +49,14 @@ public class JWTToken implements JWT {
     }
   }
 
+  public JWTToken(String serializedJWT) {
+    try {
+      jwt = SignedJWT.parse(serializedJWT);
+    } catch (ParseException e) {
+      e.printStackTrace();
+    }
+  }
+
   public JWTToken(String alg, String[] claimsArray) {
     JWSHeader header = new JWSHeader(new JWSAlgorithm(alg));
     JWTClaimsSet claims = new JWTClaimsSet();
@@ -96,7 +104,7 @@ public class JWTToken implements JWT {
   public String toString() {
     return jwt.serialize();
   }
-  
+
   /* (non-Javadoc)
    * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#setSignaturePayload(byte[])
    */
@@ -104,7 +112,7 @@ public class JWTToken implements JWT {
   public void setSignaturePayload(byte[] payload) {
 //    this.payload = payload;
   }
-  
+
   /* (non-Javadoc)
    * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#getSignaturePayload()
    */
@@ -128,7 +136,7 @@ public class JWTToken implements JWT {
     
     return jwt;
   }
-  
+
   /* (non-Javadoc)
    * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#getClaim(java.lang.String)
    */
@@ -169,18 +177,30 @@ public class JWTToken implements JWT {
   public String getAudience() {
     String[] claim = null;
     String c = null;
-    
+
+    claim = getAudienceClaims();
+    if (claim != null) {
+      c = claim[0];
+    }
+
+    return c;
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#getAudienceClaims()
+   */
+  @Override
+  public String[] getAudienceClaims() {
+    String[] claims = null;
+
     try {
-      claim = jwt.getJWTClaimsSet().getStringArrayClaim(JWT.AUDIENCE);
-      if (claim != null) {
-        c = claim[0];
-      }
+      claims = jwt.getJWTClaimsSet().getStringArrayClaim(JWT.AUDIENCE);
     } catch (ParseException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     }
-    
-    return c;
+
+    return claims;
   }
 
   /* (non-Javadoc)
@@ -191,6 +211,18 @@ public class JWTToken implements JWT {
     return getClaim(JWT.EXPIRES);
   }
 
+  @Override
+  public Date getExpiresDate() {
+    Date date = null;
+    try {
+      date = jwt.getJWTClaimsSet().getExpirationTime();
+    } catch (ParseException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return date;
+  }
+
   /* (non-Javadoc)
    * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#getPrincipal()
    */
@@ -198,6 +230,7 @@ public class JWTToken implements JWT {
   public String getPrincipal() {
     return getClaim(JWT.PRINCIPAL);
   }
+
   
   /* (non-Javadoc)
    * @see org.apache.hadoop.gateway.services.security.token.impl.JWT#getPrincipal()

http://git-wip-us.apache.org/repos/asf/knox/blob/87fcd4d6/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f03fd26..0921d65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -655,6 +655,12 @@
                 <version>2.5.2</version>
             </dependency>
 
+           <dependency>
+                <groupId>com.thetransactioncompany</groupId>
+                <artifactId>cors-filter</artifactId>
+                <version>2.4</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.codehaus.groovy</groupId>
                 <artifactId>groovy</artifactId>