diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java index ceec927e3e..9bcebc37b1 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java @@ -293,10 +293,10 @@ public Void run() throws Exception { GSSManager gssManager = GSSManager.getInstance(); String servicePrincipal = KerberosUtil.getServicePrincipal("HTTP", KerberosAuthenticator.this.url.getHost()); - Oid oid = KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL"); + Oid oid = KerberosUtil.NT_GSS_KRB5_PRINCIPAL_OID; GSSName serviceName = gssManager.createName(servicePrincipal, oid); - oid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID"); + oid = KerberosUtil.GSS_KRB5_MECH_OID; gssContext = gssManager.createContext(serviceName, oid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestCredDeleg(true); diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java index e0ee227c61..887548ba85 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java @@ -28,31 +28,20 @@ import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.Configuration; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; +import javax.security.auth.kerberos.KeyTab; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; -import java.net.InetAddress; +import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; -import com.google.common.collect.HashMultimap; - -import static org.apache.hadoop.util.PlatformName.IBM_JAVA; - /** * The {@link KerberosAuthenticationHandler} implements the Kerberos SPNEGO * authentication mechanism for HTTP. @@ -76,60 +65,6 @@ public class KerberosAuthenticationHandler implements AuthenticationHandler { public static final Logger LOG = LoggerFactory.getLogger( KerberosAuthenticationHandler.class); - /** - * Kerberos context configuration for the JDK GSS library. - */ - private static class KerberosConfiguration extends Configuration { - private String keytab; - private String principal; - - public KerberosConfiguration(String keytab, String principal) { - this.keytab = keytab; - this.principal = principal; - } - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - Map options = new HashMap(); - if (IBM_JAVA) { - options.put("useKeytab", - keytab.startsWith("file://") ? keytab : "file://" + keytab); - options.put("principal", principal); - options.put("credsType", "acceptor"); - } else { - options.put("keyTab", keytab); - options.put("principal", principal); - options.put("useKeyTab", "true"); - options.put("storeKey", "true"); - options.put("doNotPrompt", "true"); - options.put("useTicketCache", "true"); - options.put("renewTGT", "true"); - options.put("isInitiator", "false"); - } - options.put("refreshKrb5Config", "true"); - String ticketCache = System.getenv("KRB5CCNAME"); - if (ticketCache != null) { - if (IBM_JAVA) { - options.put("useDefaultCcache", "true"); - // The first value searched when "useDefaultCcache" is used. - System.setProperty("KRB5CCNAME", ticketCache); - options.put("renewTGT", "true"); - options.put("credsType", "both"); - } else { - options.put("ticketCache", ticketCache); - } - } - if (LOG.isDebugEnabled()) { - options.put("debug", "true"); - } - - return new AppConfigurationEntry[]{ - new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(), - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, - options), }; - } - } - /** * Constant that identifies the authentication mechanism. */ @@ -157,43 +92,6 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) { private String keytab; private GSSManager gssManager; private Subject serverSubject = new Subject(); - private List loginContexts = new ArrayList(); - /** - * HADOOP-10158 added support of running HTTP with multiple SPNs - * but implicit requirements is that they must come from the SAME local realm. - * - * This is a regression for use cases where HTTP service needs to run with - * with SPN from foreign realm, which is not supported after HADOOP-10158. - * - * HADOOP-13565 brings back support of SPNs from foreign realms - * without dependency on specific Kerberos domain_realm mapping mechanism. - * - * There are several reasons for not using native Kerberos domain_realm - * mapping: - * 1. As commented in KerberosUtil#getDomainRealm(), JDK's - * domain_realm mapping routines are private to the security.krb5 - * package. As a result, KerberosUtil#getDomainRealm() always return local - * realm. - * - * 2. Server krb5.conf is not the only place that contains the domain_realm - * mapping in real deployment. Based on MIT KDC document here: - * https://web.mit.edu/kerberos/krb5-1.13/doc/admin/realm_config.html, the - * Kerberos domain_realm mapping can be implemented in one of the three - * mechanisms: - * 1) Server host-based krb5.conf on HTTP server - * 2) KDC-based krb5.conf on KDC server - * 3) DNS-based with TXT record with _kerberos prefix to the hostname. - * - * We choose to maintain domain_realm mapping based on HTTP principals - * from keytab. The mapping is built at login time with HTTP principals - * key-ed by server name and is used later to - * looked up SPNs based on server name from request for authentication. - * The multi-map implementation allows SPNs of same server from - * different realms. - * - */ - private HashMultimap serverPrincipalMap = - HashMultimap.create(); /** * Creates a Kerberos SPNEGO authentication handler with the default @@ -236,7 +134,8 @@ public void init(Properties config) throws ServletException { if (keytab == null || keytab.trim().length() == 0) { throw new ServletException("Keytab not defined in configuration"); } - if (!new File(keytab).exists()) { + File keytabFile = new File(keytab); + if (!keytabFile.exists()) { throw new ServletException("Keytab does not exist: " + keytab); } @@ -252,39 +151,19 @@ public void init(Properties config) throws ServletException { } else { spnegoPrincipals = new String[]{principal}; } - + KeyTab keytabInstance = KeyTab.getInstance(keytabFile); + serverSubject.getPrivateCredentials().add(keytabInstance); + for (String spnegoPrincipal : spnegoPrincipals) { + Principal krbPrincipal = new KerberosPrincipal(spnegoPrincipal); + LOG.info("Using keytab {}, for principal {}", + keytab, krbPrincipal); + serverSubject.getPrincipals().add(krbPrincipal); + } String nameRules = config.getProperty(NAME_RULES, null); if (nameRules != null) { KerberosName.setRules(nameRules); } - - for (String spnegoPrincipal : spnegoPrincipals) { - LOG.info("Login using keytab {}, for principal {}", - keytab, spnegoPrincipal); - final KerberosConfiguration kerberosConfiguration = - new KerberosConfiguration(keytab, spnegoPrincipal); - final LoginContext loginContext = - new LoginContext("", serverSubject, null, kerberosConfiguration); - try { - loginContext.login(); - } catch (LoginException le) { - LOG.warn("Failed to login as [{}]", spnegoPrincipal, le); - throw new AuthenticationException(le); - } - loginContexts.add(loginContext); - KerberosName kerbName = new KerberosName(spnegoPrincipal); - if (kerbName.getHostName() != null - && kerbName.getServiceName() != null - && kerbName.getServiceName().equals("HTTP")) { - boolean added = serverPrincipalMap.put(kerbName.getHostName(), - spnegoPrincipal); - LOG.info("Map server: {} to principal: [{}], added = {}", - kerbName.getHostName(), spnegoPrincipal, added); - } else { - LOG.warn("HTTP principal: [{}] is invalid for SPNEGO!", - spnegoPrincipal); - } - } + try { gssManager = Subject.doAs(serverSubject, new PrivilegedExceptionAction() { @@ -310,14 +189,6 @@ public GSSManager run() throws Exception { public void destroy() { keytab = null; serverSubject = null; - for (LoginContext loginContext : loginContexts) { - try { - loginContext.logout(); - } catch (LoginException ex) { - LOG.warn(ex.getMessage(), ex); - } - } - loginContexts.clear(); } /** @@ -409,40 +280,20 @@ public AuthenticationToken authenticate(HttpServletRequest request, KerberosAuthenticator.NEGOTIATE.length()).trim(); final Base64 base64 = new Base64(0); final byte[] clientToken = base64.decode(authorization); - final String serverName = InetAddress.getByName(request.getServerName()) - .getCanonicalHostName(); try { + final String serverPrincipal = + KerberosUtil.getTokenServerName(clientToken); + if (!serverPrincipal.startsWith("HTTP/")) { + throw new IllegalArgumentException( + "Invalid server principal " + serverPrincipal + + "decoded from client request"); + } token = Subject.doAs(serverSubject, new PrivilegedExceptionAction() { - private Set serverPrincipals = - serverPrincipalMap.get(serverName); @Override public AuthenticationToken run() throws Exception { - if (LOG.isTraceEnabled()) { - LOG.trace("SPNEGO with server principals: {} for {}", - serverPrincipals.toString(), serverName); - } - AuthenticationToken token = null; - Exception lastException = null; - for (String serverPrincipal : serverPrincipals) { - try { - token = runWithPrincipal(serverPrincipal, clientToken, - base64, response); - } catch (Exception ex) { - lastException = ex; - LOG.trace("Auth {} failed with {}", serverPrincipal, ex); - } finally { - if (token != null) { - LOG.trace("Auth {} successfully", serverPrincipal); - break; - } - } - } - if (token != null) { - return token; - } else { - throw new AuthenticationException(lastException); - } + return runWithPrincipal(serverPrincipal, clientToken, + base64, response); } }); } catch (PrivilegedActionException ex) { @@ -451,6 +302,8 @@ public AuthenticationToken run() throws Exception { } else { throw new AuthenticationException(ex.getException()); } + } catch (Exception ex) { + throw new AuthenticationException(ex); } } return token; @@ -458,8 +311,7 @@ public AuthenticationToken run() throws Exception { private AuthenticationToken runWithPrincipal(String serverPrincipal, byte[] clientToken, Base64 base64, HttpServletResponse response) throws - IOException, AuthenticationException, ClassNotFoundException, - GSSException, IllegalAccessException, NoSuchFieldException { + IOException, GSSException { GSSContext gssContext = null; GSSCredential gssCreds = null; AuthenticationToken token = null; @@ -467,11 +319,11 @@ private AuthenticationToken runWithPrincipal(String serverPrincipal, LOG.trace("SPNEGO initiated with server principal [{}]", serverPrincipal); gssCreds = this.gssManager.createCredential( this.gssManager.createName(serverPrincipal, - KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL")), + KerberosUtil.NT_GSS_KRB5_PRINCIPAL_OID), GSSCredential.INDEFINITE_LIFETIME, new Oid[]{ - KerberosUtil.getOidInstance("GSS_SPNEGO_MECH_OID"), - KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID")}, + KerberosUtil.GSS_SPNEGO_MECH_OID, + KerberosUtil.GSS_KRB5_MECH_OID }, GSSCredential.ACCEPT_ONLY); gssContext = this.gssManager.createContext(gssCreds); byte[] serverToken = gssContext.acceptSecContext(clientToken, 0, diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java index 6d33c2d86e..c0110455cc 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java @@ -21,15 +21,20 @@ import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.IllegalCharsetNameException; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.NoSuchElementException; import java.util.Set; import java.util.regex.Pattern; @@ -50,7 +55,24 @@ public static String getKrb5LoginModuleName() { ? "com.ibm.security.auth.module.Krb5LoginModule" : "com.sun.security.auth.module.Krb5LoginModule"; } - + + public static final Oid GSS_SPNEGO_MECH_OID = + getNumericOidInstance("1.3.6.1.5.5.2"); + public static final Oid GSS_KRB5_MECH_OID = + getNumericOidInstance("1.2.840.113554.1.2.2"); + public static final Oid NT_GSS_KRB5_PRINCIPAL_OID = + getNumericOidInstance("1.2.840.113554.1.2.2.1"); + + // numeric oids will never generate a GSSException for a malformed oid. + // use to initialize statics. + private static Oid getNumericOidInstance(String oidName) { + try { + return new Oid(oidName); + } catch (GSSException ex) { + throw new IllegalArgumentException(ex); + } + } + public static Oid getOidInstance(String oidName) throws ClassNotFoundException, GSSException, NoSuchFieldException, IllegalAccessException { @@ -255,4 +277,178 @@ public static boolean hasKerberosKeyTab(Subject subject) { public static boolean hasKerberosTicket(Subject subject) { return !subject.getPrivateCredentials(KerberosTicket.class).isEmpty(); } + + /** + * Extract the TGS server principal from the given gssapi kerberos or spnego + * wrapped token. + * @param rawToken bytes of the gss token + * @return String of server principal + * @throws IllegalArgumentException if token is undecodable + */ + public static String getTokenServerName(byte[] rawToken) { + // subsequent comments include only relevant portions of the kerberos + // DER encoding that will be extracted. + DER token = new DER(rawToken); + // InitialContextToken ::= [APPLICATION 0] IMPLICIT SEQUENCE { + // mech OID + // mech-token (NegotiationToken or InnerContextToken) + // } + DER oid = token.next(); + if (oid.equals(DER.SPNEGO_MECH_OID)) { + // NegotiationToken ::= CHOICE { + // neg-token-init[0] NegTokenInit + // } + // NegTokenInit ::= SEQUENCE { + // mech-token[2] InitialContextToken + // } + token = token.next().get(0xa0, 0x30, 0xa2, 0x04).next(); + oid = token.next(); + } + if (!oid.equals(DER.KRB5_MECH_OID)) { + throw new IllegalArgumentException("Malformed gss token"); + } + // InnerContextToken ::= { + // token-id[1] + // AP-REQ + // } + if (token.next().getTag() != 1) { + throw new IllegalArgumentException("Not an AP-REQ token"); + } + // AP-REQ ::= [APPLICATION 14] SEQUENCE { + // ticket[3] Ticket + // } + DER ticket = token.next().get(0x6e, 0x30, 0xa3, 0x61, 0x30); + // Ticket ::= [APPLICATION 1] SEQUENCE { + // realm[1] String + // sname[2] PrincipalName + // } + // PrincipalName ::= SEQUENCE { + // name-string[1] SEQUENCE OF String + // } + String realm = ticket.get(0xa1, 0x1b).getAsString(); + DER names = ticket.get(0xa2, 0x30, 0xa1, 0x30); + StringBuilder sb = new StringBuilder(); + while (names.hasNext()) { + if (sb.length() > 0) { + sb.append('/'); + } + sb.append(names.next().getAsString()); + } + return sb.append('@').append(realm).toString(); + } + + // basic ASN.1 DER decoder to traverse encoded byte arrays. + private static class DER implements Iterator { + static final DER SPNEGO_MECH_OID = getDER(GSS_SPNEGO_MECH_OID); + static final DER KRB5_MECH_OID = getDER(GSS_KRB5_MECH_OID); + + private static DER getDER(Oid oid) { + try { + return new DER(oid.getDER()); + } catch (GSSException ex) { + // won't happen. a proper OID is encodable. + throw new IllegalArgumentException(ex); + } + } + + private final int tag; + private final ByteBuffer bb; + + DER(byte[] buf) { + this(ByteBuffer.wrap(buf)); + } + + DER(ByteBuffer srcbb) { + tag = srcbb.get() & 0xff; + int length = readLength(srcbb); + bb = srcbb.slice(); + bb.limit(length); + srcbb.position(srcbb.position() + length); + } + + int getTag() { + return tag; + } + + // standard ASN.1 encoding. + private static int readLength(ByteBuffer bb) { + int length = bb.get(); + if ((length & (byte)0x80) != 0) { + int varlength = length & 0x7f; + length = 0; + for (int i=0; i < varlength; i++) { + length = (length << 8) | (bb.get() & 0xff); + } + } + return length; + } + + DER choose(int subtag) { + while (hasNext()) { + DER der = next(); + if (der.getTag() == subtag) { + return der; + } + } + return null; + } + + DER get(int... tags) { + DER der = this; + for (int i=0; i < tags.length; i++) { + int expectedTag = tags[i]; + // lookup for exact match, else scan if it's sequenced. + if (der.getTag() != expectedTag) { + der = der.hasNext() ? der.choose(expectedTag) : null; + } + if (der == null) { + StringBuilder sb = new StringBuilder("Tag not found:"); + for (int ii=0; ii <= i; ii++) { + sb.append(" 0x").append(Integer.toHexString(tags[ii])); + } + throw new IllegalStateException(sb.toString()); + } + } + return der; + } + + String getAsString() { + try { + return new String(bb.array(), bb.arrayOffset() + bb.position(), + bb.remaining(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalCharsetNameException("UTF-8"); // won't happen. + } + } + + @Override + public int hashCode() { + return 31 * tag + bb.hashCode(); + } + + @Override + public boolean equals(Object o) { + return (o instanceof DER) && + tag == ((DER)o).tag && bb.equals(((DER)o).bb); + } + + @Override + public boolean hasNext() { + // it's a sequence or an embedded octet. + return ((tag & 0x30) != 0 || tag == 0x04) && bb.hasRemaining(); + } + + @Override + public DER next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return new DER(bb); + } + + @Override + public String toString() { + return "[tag=0x"+Integer.toHexString(tag)+" bb="+bb+"]"; + } + } } diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestMultiSchemeAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestMultiSchemeAuthenticationHandler.java index 8f9668a862..735cb43ef3 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestMultiSchemeAuthenticationHandler.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestMultiSchemeAuthenticationHandler.java @@ -182,7 +182,7 @@ public void testRequestWithInvalidKerberosAuthorization() throws Exception { } catch (AuthenticationException ex) { // Expected } catch (Exception ex) { - Assert.fail(); + Assert.fail("Wrong exception :"+ex); } } diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java index 63df9eae94..9999eb2546 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java @@ -16,11 +16,14 @@ */ package org.apache.hadoop.security.authentication.util; +import static org.junit.Assert.assertEquals; + import java.io.File; import java.io.IOException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; @@ -178,4 +181,74 @@ private void createKeyTab(String fileName, String[] principalNames) keytab.addKeytabEntries(lstEntries); keytab.store(new File(testKeytab)); } + + @Test + public void testServicePrincipalDecode() throws Exception { + // test decoding standard krb5 tokens and spnego wrapped tokens + // for principals with the default realm, and a non-default realm. + String krb5Default = + "YIIB2AYJKoZIhvcSAQICAQBuggHHMIIBw6ADAgEFoQMCAQ6iBwMFACAAAACj" + + "gethgegwgeWgAwIBBaENGwtFWEFNUExFLkNPTaIcMBqgAwIBAKETMBEbBEhU" + + "VFAbCWxvY2FsaG9zdKOBsDCBraADAgERoQMCAQGigaAEgZ23QsT1+16T23ni" + + "JI1uFRU0FN13hhPSLAl4+oAqpV5s1Z6E+G2VKGx2+rUF21utOdlwUK/J5CKF" + + "HxM4zfNsmzRFhdk5moJW6AWHuRqGJ9hrZgTxA2vOBIn/tju+n/vJVEcUvW0f" + + "DiPfjPIPFOlc7V9GlWvZFyr5NMJSFwspKJXYh/FSNpSVTecfGskjded9TZzR" + + "2tOVzgpjFvAu/DETpIG/MIG8oAMCARGigbQEgbGWnbKlV1oo7/gzT4hi/Q41" + + "ff2luDnSxADEmo6M8LC42scsYMLNgU4iLJhuf4YLb7ueh790HrbB6Kdes71/" + + "gSBiLI2/mn3BqNE43gt94dQ8VFBix4nJCsYnuORYxLJjRSJE+3ImJNsSjqaf" + + "GRI0sp9w3hc4IVm8afb3Ggm6PgRIyyGNdTzK/p03v+zA01MJh3htuOgLKUOV" + + "z002pHnGzu/purZ5mOyaQT12vHxJ2T+Cwi8="; + + String krb5Other = + "YIIB2AYJKoZIhvcSAQICAQBuggHHMIIBw6ADAgEFoQMCAQ6iBwMFACAAAACj" + + "gethgegwgeWgAwIBBaENGwtBQkNERUZHLk9SR6IcMBqgAwIBAKETMBEbBEhU" + + "VFAbCW90aGVyaG9zdKOBsDCBraADAgERoQMCAQGigaAEgZ23QsT1+16T23ni" + + "JI1uFRU0FN13hhPSLAl4+oAqpV5s1Z6E+G2VKGx2+rUF21utOdlwUK/J5CKF" + + "HxM4zfNsmzRFhdk5moJW6AWHuRqGJ9hrZgTxA2vOBIn/tju+n/vJVEcUvW0f" + + "DiPfjPIPFOlc7V9GlWvZFyr5NMJSFwspKJXYh/FSNpSVTecfGskjded9TZzR" + + "2tOVzgpjFvAu/DETpIG/MIG8oAMCARGigbQEgbGWnbKlV1oo7/gzT4hi/Q41" + + "ff2luDnSxADEmo6M8LC42scsYMLNgU4iLJhuf4YLb7ueh790HrbB6Kdes71/" + + "gSBiLI2/mn3BqNE43gt94dQ8VFBix4nJCsYnuORYxLJjRSJE+3ImJNsSjqaf" + + "GRI0sp9w3hc4IVm8afb3Ggm6PgRIyyGNdTzK/p03v+zA01MJh3htuOgLKUOV" + + "z002pHnGzu/purZ5mOyaQT12vHxJ2T+Cwi8K"; + + String spnegoDefault = + "YIICCQYGKwYBBQUCoIIB/TCCAfmgDTALBgkqhkiG9xIBAgKhBAMCAXaiggHg" + + "BIIB3GCCAdgGCSqGSIb3EgECAgEAboIBxzCCAcOgAwIBBaEDAgEOogcDBQAg" + + "AAAAo4HrYYHoMIHloAMCAQWhDRsLRVhBTVBMRS5DT02iHDAaoAMCAQChEzAR" + + "GwRIVFRQGwlsb2NhbGhvc3SjgbAwga2gAwIBEaEDAgEBooGgBIGdBWbzvV1R" + + "Iqb7WuPIW3RTkFtwjU9P/oFAbujGPd8h/qkCszroNdvHhUkPntuOqhFBntMo" + + "bilgTqNEdDUGvBbfkJaRklNGqT/IAOUV6tlGpBUCXquR5UdPzPpUvGZiVRUu" + + "FGH5DGGHvYF1CwXPp2l1Jq373vSLQ1kBl6TXl+aKLsZYhVUjKvE7Auippclb" + + "hv/GGGex/TcjNH48k47OQaSBvzCBvKADAgERooG0BIGxeChp3TMVtWbCdFGo" + + "YL+35r2762j+OEwZRfcj4xCK7j0mUTcxLtyVGxyY9Ax+ljl5gTwzRhXcJq0T" + + "TjiQwKJckeZ837mXQAURbfJpFc3VLAXGfNkMFCR7ZkWpGA1Vzc3PeUNczn2D" + + "Lpu8sme55HFFQDi/0akW6Lwv/iCrpwIkZPyZPjaEmwLVALu4E8m0Ka3fJkPV" + + "GAhamg9OQpuREIK0pCk3ZSHhJz8qMwduzRZHc4vN"; + + String spnegoOther = + "YIICCQYGKwYBBQUCoIIB/TCCAfmgDTALBgkqhkiG9xIBAgKhBAMCAXaiggHg" + + "BIIB3GCCAdgGCSqGSIb3EgECAgEAboIBxzCCAcOgAwIBBaEDAgEOogcDBQAg" + + "AAAAo4HrYYHoMIHloAMCAQWhDRsLQUJDREVGRy5PUkeiHDAaoAMCAQChEzAR" + + "GwRIVFRQGwlvdGhlcmhvc3SjgbAwga2gAwIBEaEDAgEBooGgBIGdBWbzvV1R" + + "Iqb7WuPIW3RTkFtwjU9P/oFAbujGPd8h/qkCszroNdvHhUkPntuOqhFBntMo" + + "bilgTqNEdDUGvBbfkJaRklNGqT/IAOUV6tlGpBUCXquR5UdPzPpUvGZiVRUu" + + "FGH5DGGHvYF1CwXPp2l1Jq373vSLQ1kBl6TXl+aKLsZYhVUjKvE7Auippclb" + + "hv/GGGex/TcjNH48k47OQaSBvzCBvKADAgERooG0BIGxeChp3TMVtWbCdFGo" + + "YL+35r2762j+OEwZRfcj4xCK7j0mUTcxLtyVGxyY9Ax+ljl5gTwzRhXcJq0T" + + "TjiQwKJckeZ837mXQAURbfJpFc3VLAXGfNkMFCR7ZkWpGA1Vzc3PeUNczn2D" + + "Lpu8sme55HFFQDi/0akW6Lwv/iCrpwIkZPyZPjaEmwLVALu4E8m0Ka3fJkPV" + + "GAhamg9OQpuREIK0pCk3ZSHhJz8qMwduzRZHc4vNCg=="; + + + assertEquals("HTTP/localhost@EXAMPLE.COM", getPrincipal(krb5Default)); + assertEquals("HTTP/otherhost@ABCDEFG.ORG", getPrincipal(krb5Other)); + assertEquals("HTTP/localhost@EXAMPLE.COM", getPrincipal(spnegoDefault)); + assertEquals("HTTP/otherhost@ABCDEFG.ORG", getPrincipal(spnegoOther)); + } + + private static String getPrincipal(String token) { + return KerberosUtil.getTokenServerName( + Base64.getDecoder().decode(token)); + } } \ No newline at end of file