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

时间:2017-07-20 20:48:10

标签: java authentication apache-httpclient-4.x

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

快速谷歌指向https://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html,但先发制人的例子似乎仅针对基本方案。

谢谢!

3 个答案:

答案 0 :(得分:2)

我遇到了同样的问题并得出了与您相同的结论 - Oracle JRE HttpUrlConnection和Apache HTTP Components都不支持抢占式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);

答案 1 :(得分:0)

经过大量调查,看起来默认的Hotspot java实现中没有抢占式kerberos身份验证。来自Apache的http组件也无法提供帮助。

但是,默认实现确实能够仅在有效负载可能很大时发送标头,如Expect Header and 100-Continue response部分所述。要启用此功能,我们需要使用the fixed length streaming modeor other similar means)。但正如javadoc中所述,身份验证和重定向无法自动处理 - 我们再次回到原来的问题。

答案 2 :(得分:0)

以下示例显示了如何使用在login.conf中的自定义条目进行抢先spnego登录。这完全绕过了AuthScheme的工作,并完成了生成“授权”标头的所有工作。

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);
  }
}