You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2022/04/13 19:39:35 UTC

[knox] branch master updated: KNOX-2731 Allow group membership information to be included in issued JWTs (#554)

This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new c4699b994 KNOX-2731 Allow group membership information to be included in issued JWTs (#554)
c4699b994 is described below

commit c4699b994f5d020c6613970595f8b4048d892426
Author: Attila Magyar <m....@gmail.com>
AuthorDate: Wed Apr 13 21:39:31 2022 +0200

    KNOX-2731 Allow group membership information to be included in issued JWTs (#554)
---
 .../token/impl/DefaultTokenAuthorityService.java   |  52 +++---
 .../token/impl/DefaultTokenStateServiceTest.java   |  12 +-
 .../service/knoxsso/WebSSOResourceTest.java        |  76 ++++-----
 .../gateway/service/knoxtoken/TokenResource.java   |  68 ++++++--
 .../service/knoxtoken/JWKSResourceTest.java        |   4 +-
 .../knoxtoken/TokenServiceResourceTest.java        | 187 +++++++++++++++------
 .../services/security/token/JWTokenAttributes.java |  44 ++++-
 .../security/token/JWTokenAttributesBuilder.java   |  25 ++-
 .../services/security/token/impl/JWTToken.java     |  74 +++-----
 .../services/security/token/impl/JWTTokenTest.java | 105 ++----------
 10 files changed, 340 insertions(+), 307 deletions(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
index 6ab734269..275e2ec97 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
@@ -36,23 +36,6 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
-import org.apache.knox.gateway.GatewayResources;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
-import org.apache.knox.gateway.services.Service;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.AliasService;
-import org.apache.knox.gateway.services.security.AliasServiceException;
-import org.apache.knox.gateway.services.security.KeystoreService;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
-import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
-import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
-import org.apache.knox.gateway.services.security.token.TokenServiceException;
-import org.apache.knox.gateway.services.security.token.TokenUtils;
-import org.apache.knox.gateway.services.security.token.impl.JWT;
-import org.apache.knox.gateway.services.security.token.impl.JWTToken;
-
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
@@ -75,6 +58,22 @@ import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
 import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
 import com.nimbusds.jwt.proc.DefaultJWTProcessor;
 import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
+import org.apache.knox.gateway.GatewayResources;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
+import org.apache.knox.gateway.services.Service;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.KeystoreService;
+import org.apache.knox.gateway.services.security.KeystoreServiceException;
+import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
+import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
+import org.apache.knox.gateway.services.security.token.TokenServiceException;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 
 public class DefaultTokenAuthorityService implements JWTokenAuthority, Service {
   private static final GatewayResources RESOURCES = ResourcesFactory.get(GatewayResources.class);
@@ -104,26 +103,15 @@ public class DefaultTokenAuthorityService implements JWTokenAuthority, Service {
 
   @Override
   public JWT issueToken(JWTokenAttributes jwtAttributes) throws TokenServiceException {
-    String[] claimArray = new String[6];
-    claimArray[0] = "KNOXSSO";
-    claimArray[1] = jwtAttributes.getUserName();
-    claimArray[2] = null;
-    if (jwtAttributes.getExpires() == -1) {
-      claimArray[3] = null;
-    }
-    else {
-      claimArray[3] = String.valueOf(jwtAttributes.getExpires());
-    }
     final String algorithm = jwtAttributes.getAlgorithm();
     if(SUPPORTED_HMAC_SIG_ALGS.contains(algorithm)) {
-      claimArray[4] = null;
-      claimArray[5] = null;
+      jwtAttributes.setKid(null);
+      jwtAttributes.setJku(null);
     } else {
-      claimArray[4] = cachedSigningKeyID.isPresent() ? cachedSigningKeyID.get() : null;
-      claimArray[5] = jwtAttributes.getJku();
+      jwtAttributes.setKid(cachedSigningKeyID.isPresent() ? cachedSigningKeyID.get() : null);
     }
     final JWT token = SUPPORTED_PKI_SIG_ALGS.contains(algorithm) || SUPPORTED_HMAC_SIG_ALGS.contains(algorithm)
-        ? new JWTToken(algorithm, claimArray, jwtAttributes.getAudiences(), jwtAttributes.isManaged(), jwtAttributes.getType())
+        ? new JWTToken(jwtAttributes)
         : null;
     if (token != null) {
       if (SUPPORTED_HMAC_SIG_ALGS.contains(algorithm)) {
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
index ab9103c52..419d077c6 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateServiceTest.java
@@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
 import org.apache.knox.gateway.services.security.token.TokenMetadata;
 import org.apache.knox.gateway.services.security.token.TokenStateService;
 import org.apache.knox.gateway.services.security.token.TokenUtils;
@@ -369,16 +370,7 @@ public class DefaultTokenStateServiceTest {
 
   /* create a test JWT token */
   protected JWT getJWTToken(final long expiry) {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    if(expiry > 0) {
-      claims[3] = Long.toString(expiry);
-    }
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWT token = new JWTToken("RS256", claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setExpires(expiry).setAlgorithm("RS256").build());
     // Sign the token
     JWSSigner signer = new RSASSASigner(privateKey);
     token.sign(signer);
diff --git a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
index be85e7648..407813a21 100644
--- a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
+++ b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
@@ -17,6 +17,37 @@
  */
 package org.apache.knox.gateway.service.knoxsso;
 
+import static org.apache.knox.gateway.services.GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.net.HttpCookie;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Principal;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSSigner;
@@ -44,37 +75,6 @@ import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import javax.servlet.ServletContext;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
-import java.lang.reflect.Field;
-import java.net.HttpCookie;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.Principal;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static org.apache.knox.gateway.services.GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Some tests for the Knox SSO service.
  */
@@ -770,19 +770,7 @@ public class WebSSOResourceTest {
     @Override
     public JWT issueToken(JWTokenAttributes jwtAttributes)
         throws TokenServiceException {
-      String[] claimArray = new String[6];
-      claimArray[0] = "KNOXSSO";
-      claimArray[1] = jwtAttributes.getUserName();
-      claimArray[2] = null;
-      if (jwtAttributes.getExpires() == -1) {
-        claimArray[3] = null;
-      } else {
-        claimArray[3] = String.valueOf(jwtAttributes.getExpires());
-      }
-      claimArray[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-      claimArray[5] = null;
-
-      JWT token = new JWTToken(jwtAttributes.getAlgorithm(), claimArray, jwtAttributes.getAudiences());
+      JWT token = new JWTToken(jwtAttributes);
       try {
         JWSSigner signer = useHMAC ? new MACSigner(HMAC_SECRET)
             : new RSASSASigner(getPrivateKey(jwtAttributes.getSigningKeystoreName(), jwtAttributes.getSigningKeystoreAlias(), jwtAttributes.getSigningKeystorePassphrase()));
diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 2d227cd9a..375e12e8c 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -18,6 +18,7 @@
 package org.apache.knox.gateway.service.knoxtoken;
 
 import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
 import java.security.KeyStoreException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
@@ -34,11 +35,14 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 import javax.annotation.PostConstruct;
 import javax.inject.Singleton;
+import javax.security.auth.Subject;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.DELETE;
@@ -62,6 +66,7 @@ import org.apache.hadoop.security.authorize.AuthorizationException;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.security.GroupPrincipal;
 import org.apache.knox.gateway.security.SubjectUtils;
 import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.GatewayServices;
@@ -103,27 +108,29 @@ public class TokenResource {
   private static final String TARGET_URL = "target_url";
   private static final String ENDPOINT_PUBLIC_CERT = "endpoint_public_cert";
   private static final String BEARER = "Bearer";
-  private static final String TOKEN_TTL_PARAM = "knox.token.ttl";
-  private static final String TOKEN_TYPE_PARAM = "knox.token.type";
-  private static final String TOKEN_AUDIENCES_PARAM = "knox.token.audiences";
-  private static final String TOKEN_TARGET_URL = "knox.token.target.url";
-  static final String TOKEN_CLIENT_DATA = "knox.token.client.data";
-  private static final String TOKEN_CLIENT_CERT_REQUIRED = "knox.token.client.cert.required";
-  private static final String TOKEN_ALLOWED_PRINCIPALS = "knox.token.allowed.principals";
-  private static final String TOKEN_SIG_ALG = "knox.token.sigalg";
-  private static final String TOKEN_EXP_RENEWAL_INTERVAL = "knox.token.exp.renew-interval";
-  private static final String TOKEN_EXP_RENEWAL_MAX_LIFETIME = "knox.token.exp.max-lifetime";
-  private static final String TOKEN_EXP_TOKENGEN_ALLOWED_TSS_BACKENDS = "knox.token.exp.tokengen.allowed.tss.backends";
-  private static final String TOKEN_RENEWER_WHITELIST = "knox.token.renewer.whitelist";
+  private static final String TOKEN_PARAM_PREFIX = "knox.token.";
+  private static final String TOKEN_TTL_PARAM = TOKEN_PARAM_PREFIX + "ttl";
+  private static final String TOKEN_TYPE_PARAM = TOKEN_PARAM_PREFIX + "type";
+  private static final String TOKEN_AUDIENCES_PARAM = TOKEN_PARAM_PREFIX + "audiences";
+  public static final String TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED = TOKEN_PARAM_PREFIX + "include.groups.allowed";
+  private static final String TOKEN_TARGET_URL = TOKEN_PARAM_PREFIX + "target.url";
+  static final String TOKEN_CLIENT_DATA = TOKEN_PARAM_PREFIX + "client.data";
+  private static final String TOKEN_CLIENT_CERT_REQUIRED = TOKEN_PARAM_PREFIX + "client.cert.required";
+  private static final String TOKEN_ALLOWED_PRINCIPALS = TOKEN_PARAM_PREFIX + "allowed.principals";
+  private static final String TOKEN_SIG_ALG = TOKEN_PARAM_PREFIX + "sigalg";
+  private static final String TOKEN_EXP_RENEWAL_INTERVAL = TOKEN_PARAM_PREFIX + "exp.renew-interval";
+  private static final String TOKEN_EXP_RENEWAL_MAX_LIFETIME = TOKEN_PARAM_PREFIX + "exp.max-lifetime";
+  private static final String TOKEN_EXP_TOKENGEN_ALLOWED_TSS_BACKENDS = TOKEN_PARAM_PREFIX + "exp.tokengen.allowed.tss.backends";
+  private static final String TOKEN_RENEWER_WHITELIST = TOKEN_PARAM_PREFIX + "renewer.whitelist";
   private static final String TSS_STATUS_IS_MANAGEMENT_ENABLED = "tokenManagementEnabled";
   private static final String TSS_STATUS_CONFIFURED_BACKEND = "configuredTssBackend";
   private static final String TSS_STATUS_ACTUAL_BACKEND = "actualTssBackend";
   private static final String TSS_ALLOWED_BACKEND_FOR_TOKENGEN = "allowedTssForTokengen";
   private static final String TSS_MAXIMUM_LIFETIME_SECONDS = "maximumLifetimeSeconds";
   private static final String TSS_MAXIMUM_LIFETIME_TEXT = "maximumLifetimeText";
-  private static final String LIFESPAN_INPUT_ENABLED_PARAM = "knox.token.lifespan.input.enabled";
+  private static final String LIFESPAN_INPUT_ENABLED_PARAM = TOKEN_PARAM_PREFIX + "lifespan.input.enabled";
   private static final String LIFESPAN_INPUT_ENABLED_TEXT = "lifespanInputEnabled";
-  static final String KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION = "knox.token.user.limit.exceeded.action";
+  static final String KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION = TOKEN_PARAM_PREFIX + "user.limit.exceeded.action";
   private static final String METADATA_QUERY_PARAM_PREFIX = "md_";
   private static final long TOKEN_TTL_DEFAULT = 30000L;
   static final String TOKEN_API_PATH = "knoxtoken/api/v1";
@@ -134,9 +141,10 @@ public class TokenResource {
   static final String REVOKE_PATH = "/revoke";
   static final String ENABLE_PATH = "/enable";
   static final String DISABLE_PATH = "/disable";
-  private static final String TARGET_ENDPOINT_PULIC_CERT_PEM = "knox.token.target.endpoint.cert.pem";
+  private static final String TARGET_ENDPOINT_PULIC_CERT_PEM = TOKEN_PARAM_PREFIX + "target.endpoint.cert.pem";
   static final String QUERY_PARAMETER_DOAS = "doAs";
-  static final String PROXYUSER_PREFIX = "knox.token.proxyuser";
+  static final String PROXYUSER_PREFIX = TOKEN_PARAM_PREFIX + "proxyuser";
+  public static final String KNOX_TOKEN_INCLUDE_GROUPS = TOKEN_PARAM_PREFIX + "include.groups";
 
   private static TokenServiceMessages log = MessagesFactory.get(TokenServiceMessages.class);
   private long tokenTTL = TOKEN_TTL_DEFAULT;
@@ -160,6 +168,7 @@ public class TokenResource {
   private Optional<Long> maxTokenLifetime = Optional.empty();
 
   private int tokenLimitPerUser;
+  private boolean includeGroupsInTokenAllowed;
 
   enum UserLimitExceededAction {REMOVE_OLDEST, RETURN_ERROR};
   private UserLimitExceededAction userLimitExceededAction = UserLimitExceededAction.RETURN_ERROR;
@@ -228,6 +237,11 @@ public class TokenResource {
       }
     }
 
+    String includeGroupsInTokenAllowedParam = context.getInitParameter(TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED);
+    includeGroupsInTokenAllowed = includeGroupsInTokenAllowedParam == null
+            ? true
+            : Boolean.parseBoolean(includeGroupsInTokenAllowedParam);
+
     this.tokenType = context.getInitParameter(TOKEN_TYPE_PARAM);
 
     tokenTTLAsText = getTokenTTLAsText();
@@ -756,6 +770,16 @@ public class TokenResource {
       if (!targetAudiences.isEmpty()) {
         jwtAttributesBuilder.setAudiences(targetAudiences);
       }
+      if (shouldIncludeGroups()) {
+        if (includeGroupsInTokenAllowed) {
+          jwtAttributesBuilder.setGroups(groups());
+        } else {
+          return Response
+                  .status(Response.Status.BAD_REQUEST)
+                  .entity("{\n  \"error\": \"Including group information in tokens is disabled\"\n}\n")
+                  .build();
+        }
+      }
 
       jwtAttributes = jwtAttributesBuilder.build();
       token = ts.issueToken(jwtAttributes);
@@ -813,6 +837,18 @@ public class TokenResource {
     return Response.ok().entity("{ \"Unable to acquire token.\" }").build();
   }
 
+  private boolean shouldIncludeGroups() {
+    return Boolean.parseBoolean(request.getParameter(KNOX_TOKEN_INCLUDE_GROUPS));
+  }
+
+  protected Set<String> groups() {
+    Subject subject = Subject.getSubject(AccessController.getContext());
+    Set<String> groups = subject.getPrincipals(GroupPrincipal.class).stream()
+            .map(GroupPrincipal::getName)
+            .collect(Collectors.toSet());
+    return groups;
+  }
+
   private void addArbitraryTokenMetadata(TokenMetadata tokenMetadata) {
     final Enumeration<String> paramNames = request.getParameterNames();
     while (paramNames.hasMoreElements()) {
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/JWKSResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/JWKSResourceTest.java
index 27963ab24..966ca8554 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/JWKSResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/JWKSResourceTest.java
@@ -37,6 +37,7 @@ import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.KeystoreService;
+import org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
 import org.apache.knox.gateway.services.security.token.impl.JWT;
 import org.apache.knox.gateway.services.security.token.impl.JWTToken;
 import org.easymock.EasyMock;
@@ -141,8 +142,7 @@ public class JWKSResourceTest {
     claimArray[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
     claimArray[5] = null;
 
-    final JWT token = new JWTToken(algorithm, claimArray,
-        Collections.singletonList("aud"), false);
+    final JWT token = new JWTToken(new JWTokenAttributesBuilder().setAlgorithm(algorithm).setAudiences(Collections.singletonList("aud")).setManaged(false).build());
     final JWSSigner signer = new RSASSASigner(privateKey, true);
     token.sign(signer);
     return token;
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index a44310667..49b8772ae 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -20,6 +20,8 @@ package org.apache.knox.gateway.service.knoxtoken;
 import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.KNOX_TOKEN_USER_LIMIT;
 import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.KNOX_TOKEN_USER_LIMIT_DEFAULT;
 import static org.apache.knox.gateway.service.knoxtoken.TokenResource.KNOX_TOKEN_USER_LIMIT_EXCEEDED_ACTION;
+import static org.apache.knox.gateway.service.knoxtoken.TokenResource.TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED;
+import static org.apache.knox.gateway.services.security.token.impl.JWTToken.KNOX_GROUPS_CLAIM;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -27,6 +29,44 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Predicate;
+import javax.security.auth.Subject;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.JOSEObjectType;
@@ -36,14 +76,13 @@ import com.nimbusds.jose.JWSVerifier;
 import com.nimbusds.jose.KeyLengthException;
 import com.nimbusds.jose.crypto.RSASSASigner;
 import com.nimbusds.jose.crypto.RSASSAVerifier;
-
 import org.apache.commons.codec.digest.HmacAlgorithms;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
 import org.apache.knox.gateway.services.ServiceType;
-import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
 import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
@@ -62,44 +101,6 @@ import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import javax.security.auth.Subject;
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MultivaluedHashMap;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.Principal;
-import java.security.PrivilegedAction;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.function.Predicate;
-
 /**
  * Some tests for the token service
  */
@@ -159,6 +160,9 @@ public class TokenServiceResourceTest {
     if (contextExpectations.containsKey(TokenResource.LIFESPAN)) {
       EasyMock.expect(request.getParameter(TokenResource.LIFESPAN)).andReturn(contextExpectations.get(TokenResource.LIFESPAN)).anyTimes();
     }
+    if (contextExpectations.containsKey(TokenResource.KNOX_TOKEN_INCLUDE_GROUPS)) {
+      EasyMock.expect(request.getParameter(TokenResource.KNOX_TOKEN_INCLUDE_GROUPS)).andReturn(contextExpectations.get(TokenResource.KNOX_TOKEN_INCLUDE_GROUPS)).anyTimes();
+    }
     if (contextExpectations.containsKey(TokenResource.QUERY_PARAMETER_DOAS)) {
       EasyMock.expect(request.getParameter(TokenResource.QUERY_PARAMETER_DOAS)).andReturn(contextExpectations.get(TokenResource.QUERY_PARAMETER_DOAS)).anyTimes();
     }
@@ -653,8 +657,8 @@ public class TokenServiceResourceTest {
     validateSuccessfulRenewalResponse(renewalResponse);
     String responseContent = (String) renewalResponse.getEntity();
     assertNotNull(responseContent);
-    Map<String, String> json = parseJSONResponse(responseContent);
-    assertTrue(Boolean.parseBoolean(json.get("renewed")));
+    Map<String, Object> json = parseJSONResponse(responseContent);
+    assertTrue(Boolean.parseBoolean((String)json.get("renewed")));
     assertNotNull(json.get("expires")); // Should get back the original expiration from the token itself
   }
 
@@ -670,8 +674,8 @@ public class TokenServiceResourceTest {
     validateSuccessfulRenewalResponse(renewalResponse);
     String responseContent = (String) renewalResponse.getEntity();
     assertNotNull(responseContent);
-    Map<String, String> json = parseJSONResponse(responseContent);
-    assertTrue(Boolean.parseBoolean(json.get("renewed")));
+    Map<String, Object> json = parseJSONResponse(responseContent);
+    assertTrue(Boolean.parseBoolean((String)json.get("renewed")));
     assertNotNull(json.get("expires")); // Should get back the original expiration from the token itself
   }
 
@@ -687,8 +691,8 @@ public class TokenServiceResourceTest {
     validateSuccessfulRenewalResponse(renewalResponse);
     String responseContent = (String) renewalResponse.getEntity();
     assertNotNull(responseContent);
-    Map<String, String> json = parseJSONResponse(responseContent);
-    assertTrue(Boolean.parseBoolean(json.get("renewed")));
+    Map<String, Object> json = parseJSONResponse(responseContent);
+    assertTrue(Boolean.parseBoolean((String)json.get("renewed")));
     assertNotNull(json.get("expires")); // Should get back the original expiration from the token itself
   }
 
@@ -1117,6 +1121,78 @@ public class TokenServiceResourceTest {
     assertEquals(metadata.get("userName"), impersonatedUser);
   }
 
+  @Test
+  public void testGroupsAddedToToken() throws Exception {
+    Set<String> groups = new HashSet<>(Arrays.asList("group1", "group2"));
+    Map<String, String> contextExpectations = new HashMap<>();
+    contextExpectations.put(TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED, "true");
+    contextExpectations.put(TokenResource.KNOX_TOKEN_INCLUDE_GROUPS, "true");
+    configureCommonExpectations(contextExpectations, Boolean.TRUE);
+
+    TokenResource tr = new TokenResource() {
+      @Override
+      protected Set<String> groups() {
+        return groups;
+      }
+    };
+    tr.request = request;
+    tr.context = context;
+    tr.init();
+
+    Response response = tr.doGet();
+    assertEquals(200, response.getStatus());
+
+    String accessToken = getTagValue(response.getEntity().toString(), "access_token");
+    Map<String, Object> payload = parseJSONResponse(JWTToken.parseToken(accessToken).getPayload());
+    assertEquals(new ArrayList<>(groups), payload.get(KNOX_GROUPS_CLAIM));
+  }
+
+  @Test
+  public void testNoGroupsAddedToTokenByDefault() throws Exception {
+    Map<String, String> contextExpectations = new HashMap<>();
+    contextExpectations.put(TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED, "true");
+    configureCommonExpectations(contextExpectations, Boolean.TRUE);
+
+    TokenResource tr = new TokenResource() {
+      @Override
+      protected Set<String> groups() {
+        return new HashSet<>(Arrays.asList("group1", "group2"));
+      }
+    };
+    tr.request = request;
+    tr.context = context;
+    tr.init();
+
+    Response response = tr.doGet();
+    assertEquals(200, response.getStatus());
+
+    String accessToken = getTagValue(response.getEntity().toString(), "access_token");
+    Map<String, Object> payload = parseJSONResponse(JWTToken.parseToken(accessToken).getPayload());
+    assertFalse(payload.containsKey(KNOX_GROUPS_CLAIM));
+  }
+
+  @Test
+  public void testBadRequestWhenGroupsAreRequestedToBeIncludedInTokenButItIsDisabledByServer() throws Exception {
+    Set<String> groups = new HashSet<>(Arrays.asList("group1", "group2"));
+    Map<String, String> contextExpectations = new HashMap<>();
+    contextExpectations.put(TOKEN_INCLUDE_GROUPS_IN_JWT_ALLOWED, "false");
+    contextExpectations.put(TokenResource.KNOX_TOKEN_INCLUDE_GROUPS, "true");
+    configureCommonExpectations(contextExpectations, Boolean.TRUE);
+
+    TokenResource tr = new TokenResource() {
+      @Override
+      protected Set<String> groups() {
+        return groups;
+      }
+    };
+    tr.request = request;
+    tr.context = context;
+    tr.init();
+
+    Response response = tr.doGet();
+    assertEquals(400, response.getStatus());
+  }
+
   /**
    *
    * @param isTokenStateServerManaged true, if server-side token state management should be enabled; Otherwise, false or null.
@@ -1332,12 +1408,12 @@ public class TokenServiceResourceTest {
     String responseContent = (String) response.getEntity();
     assertNotNull(responseContent);
     assertFalse(responseContent.isEmpty());
-    Map<String, String> json = parseJSONResponse(responseContent);
-    boolean result = Boolean.valueOf(json.get("renewed"));
+    Map<String, Object> json = parseJSONResponse(responseContent);
+    boolean result = Boolean.valueOf((String)json.get("renewed"));
     assertEquals(expectedResult, result);
     assertEquals(expectedMessage, json.get("error"));
     if (expectedCode != null) {
-      assertEquals(expectedCode.toInt(), Integer.parseInt(json.get("code")));
+      assertEquals(expectedCode.toInt(), json.get("code"));
     }
   }
 
@@ -1355,12 +1431,12 @@ public class TokenServiceResourceTest {
     String responseContent = (String) response.getEntity();
     assertNotNull(responseContent);
     assertFalse(responseContent.isEmpty());
-    Map<String, String> json = parseJSONResponse(responseContent);
-    boolean result = Boolean.valueOf(json.get("revoked"));
+    Map<String, Object> json = parseJSONResponse(responseContent);
+    boolean result = Boolean.valueOf((String)json.get("revoked"));
     assertEquals(expectedResult, result);
     assertEquals(expectedMessage, json.get("error"));
     if (expectedCode != null) {
-      assertEquals(expectedCode.toInt(), Integer.parseInt(json.get("code")));
+      assertEquals(expectedCode.toInt(), json.get("code"));
     }
   }
 
@@ -1396,8 +1472,8 @@ public class TokenServiceResourceTest {
     return s;
   }
 
-  private static Map<String, String> parseJSONResponse(final String response) throws IOException {
-    return (new ObjectMapper()).readValue(response, new TypeReference<Map<String, String>>(){});
+  private static Map<String, Object> parseJSONResponse(final String response) throws IOException {
+    return (new ObjectMapper()).readValue(response, new TypeReference<Map<String, Object>>(){});
   }
 
 
@@ -1583,7 +1659,8 @@ public class TokenServiceResourceTest {
       claimArray[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
       claimArray[5] = jwtAttributes.getJku();
 
-      JWT token = new JWTToken(jwtAttributes.getAlgorithm(), claimArray, jwtAttributes.getAudiences());
+      jwtAttributes.setKid("E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA");
+      JWT token = new JWTToken(jwtAttributes);
       JWSSigner signer = new RSASSASigner(privateKey);
       token.sign(signer);
 
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
index b7e7a82aa..a644c0a1f 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
@@ -17,10 +17,13 @@
  */
 package org.apache.knox.gateway.services.security.token;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 public class JWTokenAttributes {
-
   private final String userName;
   private final List<String> audiences;
   private final String algorithm;
@@ -29,11 +32,14 @@ public class JWTokenAttributes {
   private final String signingKeystoreAlias;
   private final char[] signingKeystorePassphrase;
   private final boolean managed;
-  private final String jku;
+  private String jku;
   private final String type;
+  private final Set<String> groups;
+  private final String issuer;
+  private String kid;
 
   JWTokenAttributes(String userName, List<String> audiences, String algorithm, long expires, String signingKeystoreName, String signingKeystoreAlias,
-      char[] signingKeystorePassphrase, boolean managed, String jku, String type) {
+      char[] signingKeystorePassphrase, boolean managed, String jku, String type, Set<String> groups, String kid, String issuer) {
     this.userName = userName;
     this.audiences = audiences;
     this.algorithm = algorithm;
@@ -44,6 +50,9 @@ public class JWTokenAttributes {
     this.managed = managed;
     this.jku = jku;
     this.type = type;
+    this.groups = groups;
+    this.kid = kid;
+    this.issuer = issuer;
   }
 
   public String getUserName() {
@@ -62,6 +71,10 @@ public class JWTokenAttributes {
     return expires;
   }
 
+  public Date getExpiresDate() {
+    return expires == -1 ? null : new Date(expires);
+  }
+
   public String getSigningKeystoreName() {
     return signingKeystoreName;
   }
@@ -78,12 +91,35 @@ public class JWTokenAttributes {
     return managed;
   }
 
-  public String getJku() {
+  public URI getJkuUri() throws URISyntaxException {
+    return jku != null ? new URI(jku) : null;
+  }
+
+  public String getJku(){
     return jku;
   }
 
+  public void setJku(String jku) {
+    this.jku = jku;
+  }
+
   public String getType() {
     return type;
   }
 
+  public Set<String> getGroups() {
+    return groups;
+  }
+
+  public void setKid(String kid) {
+    this.kid = kid;
+  }
+
+  public String getKid() {
+    return kid;
+  }
+
+  public String getIssuer() {
+    return issuer;
+  }
 }
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
index faf4bf362..4d86ff48a 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
@@ -17,8 +17,10 @@
  */
 package org.apache.knox.gateway.services.security.token;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 public class JWTokenAttributesBuilder {
 
@@ -32,6 +34,9 @@ public class JWTokenAttributesBuilder {
   private boolean managed;
   private String jku;
   private String type;
+  private Set<String> groups;
+  private String kid;
+  private String issuer = "KNOXSSO";
 
   public JWTokenAttributesBuilder setUserName(String userName) {
     this.userName = userName;
@@ -87,9 +92,23 @@ public class JWTokenAttributesBuilder {
     return this;
   }
 
-  public JWTokenAttributes build() {
-    return new JWTokenAttributes(userName, (audiences == null ? Collections.emptyList() : audiences), algorithm, expires, signingKeystoreName, signingKeystoreAlias,
-        signingKeystorePassphrase, managed, jku, type);
+  public JWTokenAttributesBuilder setGroups(Set<String> groups) {
+    this.groups = groups;
+    return this;
+  }
+
+  public JWTokenAttributesBuilder setKid(String kid) {
+    this.kid = kid;
+    return this;
   }
 
+  public JWTokenAttributesBuilder setIssuer(String issuer) {
+    this.issuer = issuer;
+    return this;
+  }
+
+  public JWTokenAttributes build() {
+    return new JWTokenAttributes(userName, (audiences == null ? new ArrayList<>() : audiences), algorithm, expires, signingKeystoreName, signingKeystoreAlias,
+        signingKeystorePassphrase, managed, jku, type, groups, kid, issuer);
+  }
 }
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java
index 281bed24f..7920bfcf5 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/impl/JWTToken.java
@@ -16,16 +16,11 @@
  */
 package org.apache.knox.gateway.services.security.token.impl;
 
-import java.net.URI;
 import java.net.URISyntaxException;
 import java.text.ParseException;
 import java.util.Date;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.UUID;
 
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
@@ -36,6 +31,8 @@ import com.nimbusds.jose.Payload;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
 
 public class JWTToken implements JWT {
   private static JWTProviderMessages log = MessagesFactory.get( JWTProviderMessages.class );
@@ -44,6 +41,7 @@ public class JWTToken implements JWT {
   public static final String MANAGED_TOKEN_CLAIM = "managed.token";
   public static final String KNOX_KID_CLAIM = "kid";
   public static final String KNOX_JKU_CLAIM = "jku";
+  public static final String KNOX_GROUPS_CLAIM = "knox.groups";
 
   SignedJWT jwt;
 
@@ -60,79 +58,53 @@ public class JWTToken implements JWT {
     }
   }
 
-  public JWTToken(String alg, String[] claimsArray) {
-    this(alg, claimsArray, null);
-  }
-
-  public JWTToken(String alg, String[] claimsArray, List<String> audiences) {
-    this(alg, claimsArray, audiences, false);
-  }
-
-  public JWTToken(String alg, String[] claimsArray, List<String> audiences, boolean managed) {
-    this(alg, claimsArray, audiences, managed, null);
-  }
-
-  public JWTToken(String alg, String[] claimsArray, List<String> audiences, boolean managed, String type) {
-    if(claimsArray == null) {
-      log.missingClaims(-1);
-    } else if (claimsArray.length < 6){
-      log.missingClaims(claimsArray.length);
-    }
-
+  public JWTToken(JWTokenAttributes jwtAttributes) {
     JWSHeader header = null;
     try {
-      header = new JWSHeader(new JWSAlgorithm(alg),
-      type == null ? null : new JOSEObjectType(type),
+      header = new JWSHeader(new JWSAlgorithm(jwtAttributes.getAlgorithm()),
+              jwtAttributes.getType() == null ? null : new JOSEObjectType(jwtAttributes.getType()),
       null,
       null,
-      getClaimValue(claimsArray, 5) != null ? new URI(getClaimValue(claimsArray, 5)) : null, // JKU
+      jwtAttributes.getJkuUri(),
       null,
       null,
       null,
       null,
       null,
-      getClaimValue(claimsArray, 4) != null ? getClaimValue(claimsArray, 4) : null, // KID
+      jwtAttributes.getKid(),
       null,
       null);
     } catch (URISyntaxException e) {
       /* in event of bad URI exception fall back to using just algo in header */
-      header = new JWSHeader(new JWSAlgorithm(alg));
-    }
-
-    if (getClaimValue(claimsArray, 2) != null) {
-      if (audiences == null) {
-        audiences = new ArrayList<>();
-      }
-      audiences.add(getClaimValue(claimsArray, 2));
+      header = new JWSHeader(new JWSAlgorithm(jwtAttributes.getAlgorithm()));
     }
     JWTClaimsSet claims;
     JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
-    .issuer(getClaimValue(claimsArray, 0))
-    .subject(getClaimValue(claimsArray, 1))
-    .audience(audiences);
-    if(getClaimValue(claimsArray, 3) != null) {
-      builder = builder.expirationTime(new Date(Long.parseLong(claimsArray[3])));
+      .issuer(jwtAttributes.getIssuer())
+      .subject(jwtAttributes.getUserName())
+      .audience(jwtAttributes.getAudiences());
+    if(jwtAttributes.getExpiresDate() != null) {
+      builder = builder.expirationTime(jwtAttributes.getExpiresDate());
+    }
+    if(jwtAttributes.getKid() != null) {
+      builder.claim(KNOX_KID_CLAIM, jwtAttributes.getKid());
     }
-    if(getClaimValue(claimsArray, 4) != null) {
-      builder.claim(KNOX_KID_CLAIM, getClaimValue(claimsArray, 4));
+    if(jwtAttributes.getJku() != null) {
+      builder.claim(KNOX_JKU_CLAIM, jwtAttributes.getJku());
     }
-    if(getClaimValue(claimsArray, 5) != null) {
-      builder.claim(KNOX_JKU_CLAIM, getClaimValue(claimsArray, 5));
+    if (jwtAttributes.getGroups() != null) {
+      builder.claim(KNOX_GROUPS_CLAIM, jwtAttributes.getGroups());
     }
 
     // Add a private UUID claim for uniqueness
     builder.claim(KNOX_ID_CLAIM, String.valueOf(UUID.randomUUID()));
 
-    builder.claim(MANAGED_TOKEN_CLAIM, String.valueOf(managed));
+    builder.claim(MANAGED_TOKEN_CLAIM, String.valueOf(jwtAttributes.isManaged()));
     claims = builder.build();
 
     jwt = new SignedJWT(header, claims);
   }
 
-  private String getClaimValue(String[] claims, int index) {
-    return claims == null || claims.length <= index ? null : claims[index];
-  }
-
   @Override
   public String getHeader() {
     JWSHeader header = jwt.getHeader();
@@ -245,7 +217,7 @@ public class JWTToken implements JWT {
 
   @Override
   public String getExpires() {
-    Date expires = getExpiresDate();
+      Date expires = getExpiresDate();
     if (expires != null) {
       return String.valueOf(expires.getTime());
     }
diff --git a/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java b/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java
index d022cb705..ff0df7af4 100644
--- a/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java
+++ b/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/token/impl/JWTTokenTest.java
@@ -17,6 +17,8 @@
  */
 package org.apache.knox.gateway.services.security.token.impl;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -34,6 +36,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
+import org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -73,14 +76,7 @@ public class JWTTokenTest {
     final String KID = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
     final String JKU = "https://localhost:8443/gateway/token/knoxtoken/api/v1/jwks.json";
     final String ALGO = "RS256";
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = KID;
-    claims[5] = JKU;
-    JWT token = new JWTToken(ALGO, claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(singletonList("https://login.example.com")).setKid(KID).setUserName("john.doe@example.com").setJku(JKU).setAlgorithm(ALGO).build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -93,14 +89,7 @@ public class JWTTokenTest {
 
   @Test
   public void testPrivateUUIDClaim() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWT token = new JWTToken("RS256", claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(singletonList("https://login.example.com")).setUserName("john.doe@example.com").setAlgorithm("RS256").build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -114,17 +103,10 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenCreationWithAudienceListSingle() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = null;
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
     List<String> audiences = new ArrayList<>();
     audiences.add("https://login.example.com");
 
-    JWT token = new JWTToken("RS256", claims, audiences);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setUserName("john.doe@example.com").setAudiences(audiences).setAlgorithm("RS256").setManaged(false).build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -134,18 +116,11 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenCreationWithAudienceListMultiple() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = null;
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
     List<String> audiences = new ArrayList<>();
     audiences.add("https://login.example.com");
     audiences.add("KNOXSSO");
 
-    JWT token = new JWTToken("RS256", claims, audiences);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setUserName("john.doe@example.com").setAudiences(audiences).setManaged(false).setAlgorithm("RS256").build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -155,19 +130,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenCreationWithAudienceListCombined() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "LJM";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    ArrayList<String> audiences = new ArrayList<>();
-    audiences.add("https://login.example.com");
-    audiences.add("KNOXSSO");
-
-    JWTToken token = new JWTToken("RS256", claims, audiences);
-
+    JWTToken token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(asList("https://login.example.com", "KNOXSSO", "LJM")).setUserName("john.doe@example.com").setManaged(false).setAlgorithm("RS256").build());
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
     assertEquals("https://login.example.com", token.getAudience());
@@ -176,17 +139,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenCreationWithNullAudienceList() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = null;
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    List<String> audiences = null;
-
-    JWT token = new JWTToken("RS256", claims, audiences);
-
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setUserName("john.doe@example.com").setAudiences((List<String>)null).setAlgorithm("RS256").setManaged(false).build());
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
     assertNull(token.getAudience());
@@ -195,14 +148,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenCreationRS512() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWTToken token = new JWTToken(JWSAlgorithm.RS512.getName(), claims);
+    JWTToken token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(singletonList("https://login.example.com")).setUserName("john.doe@example.com").setAlgorithm(JWSAlgorithm.RS512.getName()).build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -212,14 +158,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenSignature() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWT token = new JWTToken("RS256", claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(singletonList("https://login.example.com")).setUserName("john.doe@example.com").setAlgorithm("RS256").build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -237,14 +176,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenSignatureRS512() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWT token = new JWTToken(JWSAlgorithm.RS512.getName(), claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAudiences(singletonList("https://login.example.com")).setUserName("john.doe@example.com").setAlgorithm(JWSAlgorithm.RS512.getName()).build());
 
     assertEquals("KNOXSSO", token.getIssuer());
     assertEquals("john.doe@example.com", token.getSubject());
@@ -263,14 +195,7 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenExpiry() throws Exception {
-    String[] claims = new String[6];
-    claims[0] = "KNOXSSO";
-    claims[1] = "john.doe@example.com";
-    claims[2] = "https://login.example.com";
-    claims[3] = Long.toString( ( System.currentTimeMillis()/1000 ) + 300);
-    claims[4] = "E0LDZulQ0XE_otJ5aoQtQu-RnXv8hU-M9U4dD7vDioA";
-    claims[5] = null;
-    JWT token = new JWTToken("RS256", claims);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAlgorithm("RS256").build());
 
     assertNotNull(token.getExpires());
     assertNotNull(token.getExpiresDate());
@@ -294,11 +219,11 @@ public class JWTTokenTest {
 
   @Test
   public void testTokenType() throws Exception {
-    JWT token = new JWTToken("RS256", null, null, false, null);
+    JWT token = new JWTToken(new JWTokenAttributesBuilder().setAlgorithm("RS256").build());
     assertNull(token.getType());
 
     final String tokenType = "at+jwt";
-    token = new JWTToken("RS256", null, null, false, tokenType);
+    token = new JWTToken(new JWTokenAttributesBuilder().setAlgorithm("RS256").setType(tokenType).build());
     assertEquals(token.getType(), new JOSEObjectType(tokenType));
   }
 }