在 java 中使用 gss/kerberos 身份验证进行抢先身份验证

preemptive authentication using gss/kerberos authentication in java

据我所知,当前的 java.net.URL 握手(对于 GSS/Kerberos 身份验证模式)总是需要 401 作为第一站操作,如果我们知道客户端和服务器,这会有点低效将使用 GSS/Kerberos,对吗?有谁知道 java 世界是否可以使用抢先式身份验证(您可以像 python 一个 https://github.com/requests/requests-kerberos#preemptive-authentication 那样预先出示令牌)?

快速 google 指向 https://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html 但先发制人的例子似乎仅适用于基本方案。

谢谢!

经过大量调查,在默认的 Hotspot java 实现中似乎无法使用抢先式 kerberos 身份验证。 Apache 的 http-components 也无法提供帮助。

但是,默认实现确实能够仅在负载可能很大时发送 headers,如 Expect Header and 100-Continue response section. To enable this, we need to use the fixed length streaming mode (or other similar means). But as noted in the javadoc 中所述,无法自动处理身份验证和重定向 - 我们又回来了到原来的问题。

我遇到了同样的问题并得出了与您相同的结论 - Oracle JRE HttpUrlConnection 和 Apache HTTP 组件都不支持抢先式 SPNEGO 身份验证。我没有检查其他 HTTP 客户端,但几乎可以肯定它应该是相同的。

我开始研究可与任何 HTTP 客户端一起使用的替代 Spnego 客户端 - 它称为 Kerb4J

你可以这样使用它:

SpnegoClient spnegoClient = SpnegoClient.loginWithKeyTab("clientPrincipal", "C:/kerberos/clientPrincipal.keytab");
URL url = new URL("http://kerberized.service/helloworld");
URLConnection urlConnection = url.openConnection();
HttpURLConnection huc = (HttpURLConnection) urlConnection;

SpnegoContext context = spnegoClient.createContext(url);

huc.setRequestProperty("Authorization", context.createTokenAsAuthroizationHeader());

// Optional mutual authentication step
String challenge = huc.getHeaderField("WWW-Authenticate").substring("Negotiate ".length());
byte[] decode = Base64.getDecoder().decode(challenge);
context.processMutualAuthorization(decode, 0, decode.length);

以下示例显示了如何使用 login.conf 中的自定义条目执行抢先式 spnego 登录。这完全绕过了 AuthScheme 的东西,并完成了生成 "authorization" header.

的所有工作
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import java.io.File;
import java.net.InetAddress;
import java.security.PrivilegedExceptionAction;
import java.util.Base64;

public class AsyncHttpSpnego {

  public static final String SPNEGO_OID = "1.3.6.1.5.5.2";
  private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";

  public static void main(String[] args) throws Exception {

    InetAddress inetAddress = InetAddress.getLocalHost();

    String host = inetAddress.getHostName().toUpperCase();

    System.setProperty("java.security.krb5.conf", new File(host + "-krb5.ini").getCanonicalPath());
    System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
    System.setProperty("java.security.auth.login.config", new File(host + "-login.conf").getCanonicalPath());
    LoginContext lc = new LoginContext("anotherentry");
    lc.login();
    byte[] token = new byte[0];

    token = getAuthToken(host, lc, token);

    String authorizationHeader = "Negotiate" + " " + Base64.getEncoder().encodeToString(token);

    System.out.println("Next Authorization header: " + authorizationHeader);

    CloseableHttpClient closeableHttpClient = HttpClients.createMinimal();
    HttpGet httpget = new HttpGet("http://" + host + ":81/nick.txt");
    httpget.setHeader("Authorization", authorizationHeader);
    CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpget);
    try {
      System.out.println(IOUtils.toString(closeableHttpResponse.getEntity().getContent()));
    } finally {
      closeableHttpResponse.close();
    }
  }

  private static byte[] getAuthToken(String host, LoginContext lc, byte[] inToken) throws GSSException, java.security.PrivilegedActionException {
    Oid negotiationOid = new Oid(SPNEGO_OID);

    GSSManager manager = GSSManager.getInstance();
    final PrivilegedExceptionAction<GSSCredential> action = () -> manager.createCredential(null,
        GSSCredential.INDEFINITE_LIFETIME, negotiationOid, GSSCredential.INITIATE_AND_ACCEPT);

    boolean tryKerberos = false;
    GSSContext gssContext = null;
    try {
      GSSName serverName = manager.createName("HTTP@" + host, GSSName.NT_HOSTBASED_SERVICE);
      gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, Subject.doAs(lc.getSubject(), action),
          GSSContext.DEFAULT_LIFETIME);
      gssContext.requestMutualAuth(true);
      gssContext.requestCredDeleg(true);
    } catch (GSSException ex) {
      if (ex.getMajor() == GSSException.BAD_MECH) {
        System.out.println("GSSException BAD_MECH, retry with Kerberos MECH");
        tryKerberos = true;
      } else {
        throw ex;
      }
    }
    if (tryKerberos) {
      Oid kerbOid = new Oid(KERBEROS_OID);
      GSSName serverName = manager.createName("HTTP@" + host, GSSName.NT_HOSTBASED_SERVICE);
      gssContext = manager.createContext(serverName.canonicalize(kerbOid), kerbOid, Subject.doAs(lc.getSubject(), action),
          GSSContext.DEFAULT_LIFETIME);
      gssContext.requestMutualAuth(true);
      gssContext.requestCredDeleg(true);
    }

    return gssContext.initSecContext(inToken, 0, inToken.length);
  }
}