access logfiles, transactions will be
# logged therein and *not* in this file.
#
- CustomLog /proc/self/fd/1 common
+ CustomLog /proc/1/fd/1 common
#
# If you prefer a logfile with access, agent, and referer information
@@ -595,3 +596,15 @@ SSLRandomSeed connect builtin
Require valid-user
+
+
+
+
+ AuthType GSSAPI
+ AuthName "GSSAPI Single Sign On Login"
+ GssapiCredStore keytab:/keytabs/HTTP.keytab
+ GssapiAcceptorName HTTP
+ Require valid-user
+
+
+
diff --git a/httpclient5-testing/src/test/resources/docker/kdc/krb5.conf b/httpclient5-testing/src/test/resources/docker/kdc/krb5.conf
new file mode 100644
index 0000000000..a9ce6acc50
--- /dev/null
+++ b/httpclient5-testing/src/test/resources/docker/kdc/krb5.conf
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==========================================================================
+#
+[libdefaults]
+ default_realm = EXAMPLE.ORG
+ forwardable = true
+ udp_preference_limit = 1
+
+
+[realms]
+ EXAMPLE.ORG = {
+ kdc = test-kdc
+ }
+
+[domain_realm]
+ .example.org = EXAMPLE.ORG
+ example.org = EXAMPLE.ORG
+
+[logging]
+ kdc = FILE:/var/log/kerberos/krb5kdc.log
+ admin_server = FILE:/var/log/kerberos/kadmin.log
+ default = FILE:/var/log/kerberos/krb5lib.log
diff --git a/httpclient5-testing/src/test/resources/docker/kdc/start.sh b/httpclient5-testing/src/test/resources/docker/kdc/start.sh
new file mode 100644
index 0000000000..3156d67ff8
--- /dev/null
+++ b/httpclient5-testing/src/test/resources/docker/kdc/start.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==========================================================================
+#
+# The default image has no init
+kdb5_util -P unsafe create -s
+echo Kerberos DB created
+krb5kdc
+echo KDC started
+useradd testclient
+echo testclient:testclient | chpasswd
+kadmin.local addprinc -pw HTTP HTTP/localhost@EXAMPLE.ORG
+kadmin.local addprinc -pw testclient testclient@EXAMPLE.ORG
+kadmin.local addprinc -pw testpwclient testpwclient@EXAMPLE.ORG
+rm -f /keytabs/testclient.keytab
+rm -f /keytabs/HTTP.keytab
+kadmin.local ktadd -k /keytabs/testclient.keytab testclient@EXAMPLE.ORG
+kadmin.local ktadd -k /keytabs/HTTP.keytab HTTP/localhost@EXAMPLE.ORG
+chmod 666 /keytabs/testclient.keytab
+chmod 666 /keytabs/HTTP.keytab
+echo keytabs written
+sleep 3600
+
diff --git a/httpclient5-testing/src/test/resources/docker/squid/squid.conf b/httpclient5-testing/src/test/resources/docker/squid/squid.conf
index 0f476df6c3..f1fdb56b67 100644
--- a/httpclient5-testing/src/test/resources/docker/squid/squid.conf
+++ b/httpclient5-testing/src/test/resources/docker/squid/squid.conf
@@ -17,12 +17,18 @@
http_port 8888
http_port 8889
+debug_options ALL,1
+
coredump_dir /var/spool/squid
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/htpasswd
auth_param basic children 5
auth_param basic realm test-proxy
+auth_param negotiate program /usr/lib/squid/negotiate_kerberos_auth -k /keytabs/HTTP.keytab -s HTTP/localhost -d -i
+auth_param negotiate children 5
+auth_param negotiate keep_alive on
+
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
@@ -71,4 +77,4 @@ refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
-refresh_pattern . 0 20% 4320
\ No newline at end of file
+refresh_pattern . 0 20% 4320
diff --git a/httpclient5-testing/src/test/resources/log4j2-debug.xml.template b/httpclient5-testing/src/test/resources/log4j2-debug.xml.template
index 3386294619..8d540913a7 100644
--- a/httpclient5-testing/src/test/resources/log4j2-debug.xml.template
+++ b/httpclient5-testing/src/test/resources/log4j2-debug.xml.template
@@ -25,6 +25,8 @@
+
+
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosConfig.java
index 508eeb9b0e..6b405bbe5d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosConfig.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosConfig.java
@@ -35,9 +35,10 @@
*
* @since 4.6
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
- *
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme
+ * @see org.apache.hc.client5.http.auth.gss.GssConfig
*/
@Deprecated
@Contract(threading = ThreadingBehavior.IMMUTABLE)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosCredentials.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosCredentials.java
index 92bab8d4f3..beb5fe7ed1 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosCredentials.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/KerberosCredentials.java
@@ -26,9 +26,7 @@
*/
package org.apache.hc.client5.http.auth;
-import java.io.Serializable;
-import java.security.Principal;
-
+import org.apache.hc.client5.http.auth.gss.GssCredentials;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.ietf.jgss.GSSCredential;
@@ -38,42 +36,23 @@
*
* @since 4.4
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
+ * The original KerberosCredentials class has been renamed to
+ * org.apache.hc.client5.http.auth.gss.GssCredentials.
*
- * @see UsernamePasswordCredentials
- * @see BearerToken
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported.
+ * Use org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme, or consider using Basic or Bearer
+ * authentication with TLS instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme
+ * @see org.apache.hc.client5.http.auth.gss.GssConfig
+ * @see org.apache.hc.client5.http.auth.gss.GssCredentials
*/
@Deprecated
@Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class KerberosCredentials implements Credentials, Serializable {
-
- private static final long serialVersionUID = 487421613855550713L;
-
- /** GSSCredential */
- private final GSSCredential gssCredential;
+public class KerberosCredentials extends GssCredentials {
- /**
- * Constructor with GSSCredential argument
- *
- * @param gssCredential
- */
public KerberosCredentials(final GSSCredential gssCredential) {
- this.gssCredential = gssCredential;
- }
-
- public GSSCredential getGSSCredential() {
- return gssCredential;
- }
-
- @Override
- public Principal getUserPrincipal() {
- return null;
- }
-
- @Override
- public char[] getPassword() {
- return null;
+ super(gssCredential);
}
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
index 4d7d34b7ea..7ccb6e9c88 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/StandardAuthScheme.java
@@ -67,16 +67,15 @@ private StandardAuthScheme() {
/**
* SPNEGO authentication scheme as defined in RFC 4559 and RFC 4178.
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
+ * Use {@link org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme} instead of the old
+ * deprecated {@link org.apache.hc.client5.http.impl.auth.SPNegoScheme}
*/
- @Deprecated
public static final String SPNEGO = "Negotiate";
/**
* Kerberos authentication scheme as defined in RFC 4120.
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
* supported. Consider using Basic or Bearer authentication with TLS instead.
*/
@Deprecated
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssConfig.java
new file mode 100644
index 0000000000..2f8907f732
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssConfig.java
@@ -0,0 +1,204 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.http.auth.gss;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+
+/**
+ * Immutable class encapsulating GSS configuration options for the new mutual auth capable
+ * {@link SpnegoScheme}.
+ *
+ * Unlike the deprecated {@link KerberosConfig}, this class uses explicit defaults, and
+ * primitive booleans.
+ *
+ * Compared to {@link KerberosConfig} stripPort has been changed to addPort, and the default is now
+ * false (same effect). The default for useCanonicalHostname has been changed to false from true.
+ *
+ * @since 5.5
+ *
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class GssConfig implements Cloneable {
+
+
+ public static final GssConfig DEFAULT = new Builder().build();
+ public static final GssConfig LEGACY =
+ new Builder().setIgnoreIncompleteSecurityContext(true).setRequireMutualAuth(false).build();
+
+ private final boolean addPort;
+ private final boolean useCanonicalHostname;
+ private final boolean requestMutualAuth;
+ private final boolean requireMutualAuth;
+ private final boolean requestDelegCreds;
+ private final boolean ignoreIncompleteSecurityContext;
+
+ /**
+ * Intended for CDI compatibility
+ */
+ protected GssConfig() {
+ this(false, false, true, true, false, false);
+ }
+
+ GssConfig(
+ final boolean addPort,
+ final boolean useCanonicalHostname,
+ final boolean requestMutualAuth,
+ final boolean requireMutualAuth,
+ final boolean requestDelegCreds,
+ final boolean ignoreIncompleteSecurityContext) {
+ super();
+ this.addPort = addPort;
+ this.useCanonicalHostname = useCanonicalHostname;
+ this.requestMutualAuth = requestMutualAuth;
+ this.requireMutualAuth = requireMutualAuth;
+ this.requestDelegCreds = requestDelegCreds;
+ this.ignoreIncompleteSecurityContext = ignoreIncompleteSecurityContext;
+ }
+
+ public boolean isAddPort() {
+ return addPort;
+ }
+
+ public boolean isUseCanonicalHostname() {
+ return useCanonicalHostname;
+ }
+
+ public boolean isRequestDelegCreds() {
+ return requestDelegCreds;
+ }
+
+ public boolean isRequestMutualAuth() {
+ return requestMutualAuth;
+ }
+
+ public boolean isRequireMutualAuth() {
+ return requireMutualAuth;
+ }
+
+ public boolean isIgnoreIncompleteSecurityContext() {
+ return ignoreIncompleteSecurityContext;
+ }
+
+ @Override
+ protected GssConfig clone() throws CloneNotSupportedException {
+ return (GssConfig) super.clone();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ builder.append("addPort=").append(addPort);
+ builder.append(", useCanonicalHostname=").append(useCanonicalHostname);
+ builder.append(", requestDelegCreds=").append(requestDelegCreds);
+ builder.append(", requestMutualAuth=").append(requestMutualAuth);
+ builder.append(", requireMutualAuth=").append(requireMutualAuth);
+ builder.append(", ignoreIncompleteSecurityContext=").append(ignoreIncompleteSecurityContext);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static GssConfig.Builder custom() {
+ return new Builder();
+ }
+
+ public static GssConfig.Builder copy(final GssConfig config) {
+ return new Builder()
+ .setAddPort(config.isAddPort())
+ .setUseCanonicalHostname(config.isUseCanonicalHostname())
+ .setRequestDelegCreds(config.isRequestDelegCreds())
+ .setRequireMutualAuth(config.isRequireMutualAuth())
+ .setRequestMutualAuth(config.isRequestMutualAuth())
+ .setIgnoreIncompleteSecurityContext(config.isIgnoreIncompleteSecurityContext());
+ }
+
+ public static class Builder {
+
+ private boolean addPort = false;
+ private boolean useCanonicalHostname = false;
+ private boolean requestMutualAuth = true;
+ private boolean requireMutualAuth = true;
+ private boolean requestDelegCreds = false;
+ private boolean ignoreIncompleteSecurityContext = false;
+
+
+ Builder() {
+ super();
+ }
+
+ public Builder setAddPort(final boolean addPort) {
+ this.addPort = addPort;
+ return this;
+ }
+
+ public Builder setUseCanonicalHostname(final boolean useCanonicalHostname) {
+ this.useCanonicalHostname = useCanonicalHostname;
+ return this;
+ }
+
+ public Builder setRequestMutualAuth(final boolean requestMutualAuth) {
+ this.requestMutualAuth = requestMutualAuth;
+ return this;
+ }
+
+ public Builder setRequireMutualAuth(final boolean requireMutualAuth) {
+ this.requireMutualAuth = requireMutualAuth;
+ return this;
+ }
+
+ public Builder setRequestDelegCreds(final boolean requuestDelegCreds) {
+ this.requestDelegCreds = requuestDelegCreds;
+ return this;
+ }
+
+ public Builder setIgnoreIncompleteSecurityContext(final boolean ignoreIncompleteSecurityContext) {
+ this.ignoreIncompleteSecurityContext = ignoreIncompleteSecurityContext;
+ return this;
+ }
+
+ public GssConfig build() {
+ if (requireMutualAuth && ignoreIncompleteSecurityContext) {
+ throw new IllegalArgumentException("If requireMutualAuth is set then ignoreIncompleteSecurityContext must not be set");
+ }
+ if (requireMutualAuth && !requestMutualAuth) {
+ throw new IllegalArgumentException("If requireMutualAuth is set then requestMutualAuth must also be set");
+ }
+ return new GssConfig(
+ addPort,
+ useCanonicalHostname,
+ requestMutualAuth,
+ requireMutualAuth,
+ requestDelegCreds,
+ ignoreIncompleteSecurityContext
+ );
+ }
+
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssCredentials.java b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssCredentials.java
new file mode 100644
index 0000000000..2a118a2829
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/auth/gss/GssCredentials.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.auth.gss;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.ietf.jgss.GSSCredential;
+
+/**
+ * Kerberos specific {@link Credentials} representation based on {@link GSSCredential}.
+ *
+ * @since 5.5
+ *
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class GssCredentials implements Credentials, Serializable {
+
+ private static final long serialVersionUID = 487421613855550713L;
+
+ /** GSSCredential */
+ private final GSSCredential gssCredential;
+
+ /**
+ * Constructor with GSSCredential argument
+ *
+ * @param gssCredential
+ */
+ public GssCredentials(final GSSCredential gssCredential) {
+ this.gssCredential = gssCredential;
+ }
+
+ public GSSCredential getGSSCredential() {
+ return gssCredential;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ // TODO obtain from gssCredential
+ return null;
+ }
+
+ @Override
+ public char[] getPassword() {
+ return null;
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
index 0d8c504627..1b35f8cb34 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
@@ -334,26 +334,25 @@ private boolean needAuthentication(
}
}
+ boolean targetNeedsAuth = false;
+ boolean proxyNeedsAuth = false;
if (targetAuthRequested || targetMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
+ targetNeedsAuth = authenticator.handleResponse(target, ChallengeType.TARGET, response,
targetAuthStrategy, targetAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
}
-
- return updated;
}
if (proxyAuthRequested || proxyMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
+ proxyNeedsAuth = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
proxyAuthStrategy, proxyAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
}
-
- return updated;
}
+ return targetNeedsAuth || proxyNeedsAuth;
}
return false;
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
index 8d38ec8e75..b02d718346 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
@@ -121,8 +121,7 @@ public boolean isChallenged(
}
/**
- * Determines whether the given response represents an authentication challenge, without
- * changing the {@link AuthExchange} state.
+ * Determines whether the response is 401/407 response depending to the challengeType
*
* @param challengeType the challenge type (target or proxy).
* @param response the response message head.
@@ -364,7 +363,7 @@ public boolean handleResponse(
/**
* Generates a response to the authentication challenge based on the actual {@link AuthExchange} state
- * and adds it to the given {@link HttpRequest} message .
+ * and adds it to the given {@link HttpRequest} message.
*
* @param host the hostname of the opposite endpoint.
* @param challengeType the challenge type (target or proxy).
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
index 773746b612..833381883a 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
@@ -61,7 +61,11 @@
* @since 4.2
*
* @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
+ * supported. Use org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme, or consider using Basic or
+ * Bearer authentication with TLS instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme
+ * @see BasicScheme
+ * @see BearerScheme
*/
@Deprecated
public abstract class GGSSchemeBase implements AuthScheme {
@@ -138,6 +142,7 @@ protected byte[] generateGSSToken(
final GSSManager manager = getManager();
final GSSName serverName = manager.createName(serviceName + "@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
+
final GSSContext gssContext = createGSSContext(manager, oid, serverName, gssCredential);
if (input != null) {
return gssContext.initSecContext(input, 0, input.length);
@@ -182,8 +187,8 @@ public boolean isResponseReady(
final Credentials credentials = credentialsProvider.getCredentials(
new AuthScope(host, null, getName()), context);
- if (credentials instanceof org.apache.hc.client5.http.auth.KerberosCredentials) {
- this.gssCredential = ((org.apache.hc.client5.http.auth.KerberosCredentials) credentials).getGSSCredential();
+ if (credentials instanceof org.apache.hc.client5.http.auth.gss.GssCredentials) {
+ this.gssCredential = ((org.apache.hc.client5.http.auth.gss.GssCredentials) credentials).getGSSCredential();
} else {
this.gssCredential = null;
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosScheme.java
index 656f29633a..deb8316179 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosScheme.java
@@ -41,9 +41,11 @@
*
* @since 4.2
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
- *
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported. Use org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme, or consider using Basic or
+ * Bearer authentication with TLS
+ * instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme
* @see BasicScheme
* @see BearerScheme
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosSchemeFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosSchemeFactory.java
index 25930f0997..161f12629c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosSchemeFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/KerberosSchemeFactory.java
@@ -45,9 +45,10 @@
*
* @since 4.2
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
- *
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported. Use org.apache.hc.client5.http.impl.auth.gss.SpnegoSchemeFactory, or consider using
+ * Basic or Bearer authentication with TLS instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoSchemeFactory
* @see BasicSchemeFactory
* @see BearerSchemeFactory
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoScheme.java
index 7971ff935d..2516588dc3 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoScheme.java
@@ -42,9 +42,10 @@
*
* @since 4.2
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
- *
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported. Use org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme, or consider using Basic or
+ * Bearer authentication with TLS instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoScheme
* @see BasicScheme
* @see BearerScheme
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoSchemeFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoSchemeFactory.java
index 14d8528c5e..d45d9dfe1a 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoSchemeFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SPNegoSchemeFactory.java
@@ -45,9 +45,10 @@
*
* @since 4.2
*
- * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
- * supported. Consider using Basic or Bearer authentication with TLS instead.
- *
+ * @deprecated Do not use. The old GGS based experimental authentication schemes are no longer
+ * supported. Use org.apache.hc.client5.http.impl.auth.gss.SpnegoSchemeFactory, or consider using
+ * Basic or Bearer authentication with TLS instead.
+ * @see org.apache.hc.client5.http.impl.auth.gss.SpnegoSchemeFactory
* @see BasicSchemeFactory
* @see BearerSchemeFactory
*/
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/GssSchemeBase.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/GssSchemeBase.java
new file mode 100644
index 0000000000..095b6ec858
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/GssSchemeBase.java
@@ -0,0 +1,416 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.auth.gss;
+
+import java.net.UnknownHostException;
+import java.security.Principal;
+
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.auth.AuthChallenge;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.AuthenticationException;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.InvalidCredentialsException;
+import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.auth.gss.GssConfig;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.utils.Base64;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Common behaviour for the new mutual authentication capable {@code GSS} based authentication
+ * schemes.
+ *
+ * This class is derived from the old {@link org.apache.hc.client5.http.impl.auth.GGSSchemeBase}
+ * class, which was deprecated in 5.3.
+ *
+ * @since 5.5
+ *
+ * @see GGSSchemeBase
+ */
+public abstract class GssSchemeBase implements AuthScheme {
+
+ enum State {
+ UNINITIATED,
+ TOKEN_READY,
+ TOKEN_SENT,
+ SUCCEEDED,
+ FAILED,
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(GssSchemeBase.class);
+ private static final String PEER_SERVICE_NAME = "HTTP";
+
+ // The GSS spec does not specify how long the conversation can be. This should be plenty.
+ // Realistically, we get one initial token, then one maybe one more for mutual authentication.
+ // TODO In the future this might need to be configurable with the upcoming IAKerb support
+ private static final int MAX_GSS_CHALLENGES = 3;
+ private final GssConfig config;
+ private final DnsResolver dnsResolver;
+ private final boolean requireMutualAuth;
+ private final boolean ignoreIncompleteSecurityContext;
+ private int challengesLeft = MAX_GSS_CHALLENGES;
+
+ /** Authentication process state */
+ private State state;
+ private GSSCredential gssCredential;
+ private GSSContext gssContext;
+ private String challenge;
+ private byte[] queuedToken = new byte[0];
+
+ GssSchemeBase(final GssConfig config, final DnsResolver dnsResolver) {
+ super();
+ this.config = config != null ? config : GssConfig.DEFAULT;
+ this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
+ this.requireMutualAuth = config.isRequireMutualAuth();
+ this.ignoreIncompleteSecurityContext = config.isIgnoreIncompleteSecurityContext();
+ this.state = State.UNINITIATED;
+ }
+
+ private void dispose() {
+ // remove sensitive information from memory
+ // cleaning up the credential is the caller's job
+ try {
+ if (gssContext != null) {
+ gssContext.dispose();
+ }
+ } catch (final Exception e) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn("Exception caught while calling gssContext.dispose()", e);
+ }
+ }
+ }
+
+ GssSchemeBase(final GssConfig config) {
+ this(config, SystemDefaultDnsResolver.INSTANCE);
+ }
+
+ GssSchemeBase() {
+ this(GssConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
+ }
+
+ @Override
+ public String getRealm() {
+ return null;
+ }
+
+ // Required by AuthScheme for backwards compatibility
+ @Override
+ public void processChallenge(final AuthChallenge authChallenge,
+ final HttpContext context ) {
+ // If this gets called, then AuthScheme was changed in an incompatible way
+ throw new UnsupportedOperationException();
+ }
+
+ // The AuthScheme API maps awkwardly to GSSAPI, where proccessChallange and generateAuthResponse
+ // map to the same single method call. Hence the generated token is only stored in this method.
+ @Override
+ public void processChallenge(
+ final HttpHost host,
+ final boolean challenged,
+ final AuthChallenge authChallenge,
+ final HttpContext context
+ ) throws AuthenticationException {
+
+ if (challengesLeft-- <= 0 ) {
+ if (LOG.isWarnEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.warn("{} GSS error: too many challenges received. Infinite loop ?", exchangeId);
+ }
+ state = State.FAILED;
+ return;
+ }
+
+ final byte[] challengeToken = (authChallenge == null) ? null : Base64.decodeBase64(authChallenge.getValue());
+
+ final String gssHostname;
+ String hostname = host.getHostName();
+ if (config.isUseCanonicalHostname()) {
+ try {
+ hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
+ } catch (final UnknownHostException ignore) {
+ if (LOG.isWarnEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.warn("{} Could not canonicalize hostname {}, using as is.", exchangeId, host.getHostName());
+ }
+ }
+ }
+ if (config.isAddPort()) {
+ gssHostname = hostname + ":" + host.getPort();
+ } else {
+ gssHostname = hostname;
+ }
+
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} GSS init {}", exchangeId, gssHostname);
+ }
+ try {
+ switch (state) {
+ case UNINITIATED:
+ setGssCredential(HttpClientContext.cast(context).getCredentialsProvider(), host, context);
+ if (challengeToken == null) {
+ queuedToken = generateToken(challengeToken, PEER_SERVICE_NAME, gssHostname);
+ state = State.TOKEN_READY;
+ } else {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} Internal GSS error: token received when none was sent yet: {}", exchangeId, challengeToken);
+ }
+ state = State.FAILED;
+ }
+ break;
+ case TOKEN_SENT:
+ if (challengeToken == null) {
+ if (!challenged && ignoreIncompleteSecurityContext) {
+ // Got a Non 401/407 code without a challenge. Old non RFC compliant server.
+ if (LOG.isWarnEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.warn("{} GSS Context is not established, but continuing because GssConfig.ignoreIncompleteSecurityContext is true.", exchangeId);
+ }
+ state = State.SUCCEEDED;
+ break;
+ } else {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} Did not receive required challenge.",
+ exchangeId);
+ }
+ state = State.FAILED;
+ throw new AuthenticationException(
+ "Did not receive required challenge.");
+ }
+ }
+ queuedToken = generateToken(challengeToken, PEER_SERVICE_NAME, gssHostname);
+ if (challenged) {
+ state = State.TOKEN_READY;
+ } else if (!gssContext.isEstablished()) {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} GSSContext is not established.", exchangeId);
+ }
+ state = State.FAILED;
+ // TODO should we have specific exception(s) for these ?
+ throw new AuthenticationException(
+ "GSSContext is not established.");
+ } else if (!gssContext.getMutualAuthState()) {
+ if (requireMutualAuth) {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} requireMutualAuth is true but GSSContext mutualAuthState is false",
+ exchangeId);
+ }
+ state = State.FAILED;
+ throw new AuthenticationException(
+ "requireMutualAuth is true but GSSContext mutualAuthState is false");
+ } else {
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} GSSContext MutualAuthState is false, but continuing because GssConfig.requireMutualAuth is false.",
+ exchangeId);
+ }
+ state = State.SUCCEEDED;
+ }
+ } else {
+ state = State.SUCCEEDED;
+ }
+ break;
+ default:
+ final State prevState = state;
+ state = State.FAILED;
+ throw new IllegalStateException("Illegal state: " + prevState);
+ }
+ } catch (final GSSException gsse) {
+ state = State.FAILED;
+ if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
+ || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ }
+ if (gsse.getMajor() == GSSException.NO_CRED) {
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ }
+ if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
+ || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
+ || gsse.getMajor() == GSSException.OLD_TOKEN) {
+ throw new AuthenticationException(gsse.getMessage(), gsse);
+ }
+ // other error
+ throw new AuthenticationException(gsse.getMessage(), gsse);
+ } finally {
+ if ((state == State.FAILED || state == State.SUCCEEDED) && gssContext != null) {
+ dispose();
+ }
+ }
+ }
+
+ protected GSSManager getManager() {
+ return GSSManager.getInstance();
+ }
+
+ /**
+ * @since 4.4
+ */
+ protected byte[] generateGSSToken(
+ final byte[] input, final Oid oid, final String gssServiceName, final String gssHostname) throws GSSException {
+ final GSSManager manager = getManager();
+ final GSSName peerName = manager.createName(gssServiceName + "@" + gssHostname, GSSName.NT_HOSTBASED_SERVICE);
+
+ if (gssContext == null) {
+ gssContext = createGSSContext(manager, oid, peerName, gssCredential);
+ }
+ if (input != null) {
+ return gssContext.initSecContext(input, 0, input.length);
+ }
+ return gssContext.initSecContext(new byte[] {}, 0, 0);
+ }
+
+ /**
+ * @since 5.0
+ */
+ protected GSSContext createGSSContext(
+ final GSSManager manager,
+ final Oid oid,
+ final GSSName peerName,
+ final GSSCredential gssCredential) throws GSSException {
+ LOG.error("XXXX", new Exception("XXXX"));
+ (new Exception("XXXX")).printStackTrace();
+ final GSSContext gssContext = manager.createContext(peerName.canonicalize(oid), oid, gssCredential,
+ GSSContext.DEFAULT_LIFETIME);
+ gssContext.requestMutualAuth(config.isRequestMutualAuth());
+ gssContext.requestCredDeleg(config.isRequestDelegCreds());
+ return gssContext;
+ }
+
+ /**
+ * @since 4.4
+ */
+ protected abstract byte[] generateToken(byte[] input, String gssServiceName, String gssHostname) throws GSSException;
+
+ @Override
+ public boolean isChallengeComplete() {
+ // For the mutual authentication response, this is should technically return true.
+ // However, the HttpAuthenticator immediately fails the authentication
+ // process if we return true, so we only return true here if the authentication has failed.
+ return this.state == State.FAILED;
+ }
+
+ @Override
+ public boolean isChallengeExpected() {
+ return state == State.TOKEN_SENT;
+ }
+
+ @Override
+ public boolean isResponseReady(
+ final HttpHost host,
+ final CredentialsProvider credentialsProvider,
+ final HttpContext context) throws AuthenticationException {
+
+ Args.notNull(host, "Auth host");
+ Args.notNull(credentialsProvider, "CredentialsProvider");
+
+ return true;
+ }
+
+ protected void setGssCredential(final CredentialsProvider credentialsProvider,
+ final HttpHost host,
+ final HttpContext context) {
+ final Credentials credentials =
+ credentialsProvider.getCredentials(new AuthScope(host, null, getName()), context);
+ if (credentials instanceof org.apache.hc.client5.http.auth.gss.GssCredentials) {
+ this.gssCredential =
+ ((org.apache.hc.client5.http.auth.gss.GssCredentials) credentials)
+ .getGSSCredential();
+ } else {
+ this.gssCredential = null;
+ }
+ }
+
+ @Override
+ public Principal getPrincipal() {
+ return null;
+ }
+
+ // Format the queued token and update the state.
+ // All token processing is done in processChallenge()
+ @Override
+ public String generateAuthResponse(
+ final HttpHost host,
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
+ Args.notNull(host, "HTTP host");
+ Args.notNull(request, "HTTP request");
+ switch (state) {
+ case UNINITIATED:
+ throw new AuthenticationException(getName() + " authentication has not been initiated");
+ case FAILED:
+ throw new AuthenticationException(getName() + " authentication has failed");
+ case SUCCEEDED:
+ return null;
+ case TOKEN_READY:
+ state = State.TOKEN_SENT;
+ final Base64 codec = new Base64(0);
+ final String tokenstr = new String(codec.encode(queuedToken));
+ if (LOG.isDebugEnabled()) {
+ final HttpClientContext clientContext = HttpClientContext.cast(context);
+ final String exchangeId = clientContext.getExchangeId();
+ LOG.debug("{} Sending GSS response '{}' back to the auth server", exchangeId, tokenstr);
+ }
+ return StandardAuthScheme.SPNEGO + " " + tokenstr;
+ default:
+ throw new IllegalStateException("Illegal state: " + state);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getName() + "{" + this.state + " " + challenge + '}';
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoScheme.java
new file mode 100644
index 0000000000..d6808e9649
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoScheme.java
@@ -0,0 +1,121 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.auth.gss;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.auth.StandardAuthScheme;
+import org.apache.hc.client5.http.impl.auth.SPNegoScheme;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
+/**
+ * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
+ * scheme.
+ *
+ * This is the new mutual authentication capable Scheme which replaces the old deprecated
+ * {@link SPNegoScheme}
+ *
+ *
+ *
+ * Note that this scheme is not enabled by default. To use it, you need create a custom
+ * {@link AuthenticationStrategy} and a custom
+ * {@link org.apache.hc.client5.http.auth.AuthSchemeFactory}
+ * {@link org.apache.hc.core5.http.config.Registry},
+ * and set them on the HttpClientBuilder.
+ *
+ *
+ *
+ * {@code
+ * private static class SpnegoAuthenticationStrategy extends DefaultAuthenticationStrategy {
+ * private static final List SPNEGO_SCHEME_PRIORITY =
+ * Collections.unmodifiableList(
+ * Arrays.asList(StandardAuthScheme.SPNEGO
+ * // Add other Schemes as needed
+ * );
+ *
+ * protected final List getSchemePriority() {
+ * return SPNEGO_SCHEME_PRIORITY;
+ * }
+ * }
+ *
+ * AuthenticationStrategy spnegoStrategy = new SpnegoAuthenticationStrategy();
+ *
+ * AuthSchemeFactory spnegoFactory = new SpnegoSchemeFactory();
+ * Registry mutualSchemeRegistry = RegistryBuilder.create()
+ * .register(StandardAuthScheme.SPNEGO, spnegoFactory)
+ * //register other schemes as needed
+ * .build();
+ *
+ * CloseableHttpClient mutualClient = HttpClientBuilder.create()
+ * .setTargetAuthenticationStrategy(spnegoStrategy);
+ * .setDefaultAuthSchemeRegistry(spnegoSchemeRegistry);
+ * .build();
+ * }
+ *
+ *
+ * @since 5.5
+ */
+public class SpnegoScheme extends GssSchemeBase {
+
+ private static final String SPNEGO_OID_STRING = "1.3.6.1.5.5.2";
+ private static final Oid SPNEGO_OID;
+ static {
+ try {
+ SPNEGO_OID = new Oid(SPNEGO_OID_STRING);
+ } catch (final GSSException e) {
+ throw new IllegalStateException("Failed to create OID for SPNEGO mechanism", e);
+ }
+ }
+
+ /**
+ * @since 5.0
+ */
+ public SpnegoScheme(final org.apache.hc.client5.http.auth.gss.GssConfig config, final DnsResolver dnsResolver) {
+ super(config, dnsResolver);
+ }
+
+ public SpnegoScheme() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return StandardAuthScheme.SPNEGO;
+ }
+
+ @Override
+ protected byte[] generateToken(final byte[] input, final String gssServiceName, final String gssHostname) throws GSSException {
+ return generateGSSToken(input, SPNEGO_OID, gssServiceName, gssHostname);
+ }
+
+ @Override
+ public boolean isConnectionBased() {
+ return false;
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoSchemeFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoSchemeFactory.java
new file mode 100644
index 0000000000..1247d11afc
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/gss/SpnegoSchemeFactory.java
@@ -0,0 +1,79 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl.auth.gss;
+
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeFactory;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Experimental;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * {@link AuthSchemeFactory} implementation that creates and initialises
+ * {@link SpnegoScheme} instances.
+ *
+ * This replaces the old deprecated {@link org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory}
+ *
+ *
+ * @since 5.5
+ *
+ * @see SPNegoSchemeFactory
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+@Experimental
+public class SpnegoSchemeFactory implements AuthSchemeFactory {
+
+ /**
+ * Singleton instance for the default configuration.
+ */
+ public static final SpnegoSchemeFactory DEFAULT = new SpnegoSchemeFactory(org.apache.hc.client5.http.auth.gss.GssConfig.DEFAULT,
+ SystemDefaultDnsResolver.INSTANCE);
+
+ public static final SpnegoSchemeFactory LEGACY = new SpnegoSchemeFactory(org.apache.hc.client5.http.auth.gss.GssConfig.LEGACY,
+ SystemDefaultDnsResolver.INSTANCE);
+
+ private final org.apache.hc.client5.http.auth.gss.GssConfig config;
+ private final DnsResolver dnsResolver;
+
+ /**
+ * @since 5.5
+ */
+ public SpnegoSchemeFactory(final org.apache.hc.client5.http.auth.gss.GssConfig config, final DnsResolver dnsResolver) {
+ super();
+ this.config = config;
+ this.dnsResolver = dnsResolver;
+ }
+
+ @Override
+ public AuthScheme create(final HttpContext context) {
+ return new SpnegoScheme(this.config, this.dnsResolver);
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
index fe3d35281f..b927dbc798 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
@@ -295,26 +295,25 @@ private boolean needAuthentication(
}
}
+ boolean targetNeedsAuth = false;
+ boolean proxyNeedsAuth = false;
if (targetAuthRequested || targetMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
+ targetNeedsAuth = authenticator.handleResponse(target, ChallengeType.TARGET, response,
targetAuthStrategy, targetAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
}
-
- return updated;
}
if (proxyAuthRequested || proxyMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
+ proxyNeedsAuth = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
proxyAuthStrategy, proxyAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
}
-
- return updated;
}
+ return targetNeedsAuth || proxyNeedsAuth;
}
return false;
}
diff --git a/pom.xml b/pom.xml
index cc21e55cc1..8eac0d3ea0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
1.21.3
2.10.1
5.3
- javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer,jdk.net.ExtendedSocketOptions,jdk.net.Sockets
+ javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer,jdk.net.ExtendedSocketOptions,jdk.net.Sockets,java.lang.invoke.MethodHandle
1.27.1
1.5.7-4
1.15.2
@@ -336,6 +336,8 @@
com.github.siom79.japicmp
japicmp-maven-plugin
+
+ true
${project.groupId}
@@ -404,6 +406,8 @@
${hc.surefire.version}
slow
+ 25
+true