在 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);
}
}
据我所知,当前的 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);
}
}