HTTPCLIENT-1873: Config option for Kerberos delegation

This commit is contained in:
Oleg Kalnichevski 2017-10-23 11:16:16 +02:00
parent d054442cdf
commit a403075948
10 changed files with 202 additions and 48 deletions

View File

@ -29,10 +29,12 @@ package org.apache.hc.client5.testing.sync;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.AuthSchemes; import org.apache.hc.client5.http.config.AuthSchemes;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
@ -94,7 +96,7 @@ public class TestSPNegoScheme extends LocalServerTestBase {
GSSContext context = Mockito.mock(GSSContext.class); GSSContext context = Mockito.mock(GSSContext.class);
NegotiateSchemeWithMockGssManager() throws Exception { NegotiateSchemeWithMockGssManager() throws Exception {
super(true); super(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
Mockito.when(context.initSecContext( Mockito.when(context.initSecContext(
ArgumentMatchers.<byte[]>any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())) ArgumentMatchers.<byte[]>any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
.thenReturn("12345678".getBytes()); .thenReturn("12345678".getBytes());

View File

@ -0,0 +1,158 @@
/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.auth;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
/**
* Immutable class encapsulating Kerberos configuration options.
*
* @since 4.6
*/
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class KerberosConfig implements Cloneable {
public enum Option {
DEFAULT,
ENABLE,
DISABLE
}
public static final KerberosConfig DEFAULT = new Builder().build();
private final Option stripPort;
private final Option useCanonicalHostname;
private final Option requestDelegCreds;
/**
* Intended for CDI compatibility
*/
protected KerberosConfig() {
this(Option.DEFAULT, Option.DEFAULT, Option.DEFAULT);
}
KerberosConfig(
final Option stripPort,
final Option useCanonicalHostname,
final Option requestDelegCreds) {
super();
this.stripPort = stripPort;
this.useCanonicalHostname = useCanonicalHostname;
this.requestDelegCreds = requestDelegCreds;
}
public Option getStripPort() {
return stripPort;
}
public Option getUseCanonicalHostname() {
return useCanonicalHostname;
}
public Option getRequestDelegCreds() {
return requestDelegCreds;
}
@Override
protected KerberosConfig clone() throws CloneNotSupportedException {
return (KerberosConfig) super.clone();
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append("stripPort=").append(stripPort);
builder.append(", useCanonicalHostname=").append(useCanonicalHostname);
builder.append(", requestDelegCreds=").append(requestDelegCreds);
builder.append("]");
return builder.toString();
}
public static KerberosConfig.Builder custom() {
return new Builder();
}
public static KerberosConfig.Builder copy(final KerberosConfig config) {
return new Builder()
.setStripPort(config.getStripPort())
.setUseCanonicalHostname(config.getUseCanonicalHostname())
.setRequestDelegCreds(config.getRequestDelegCreds());
}
public static class Builder {
private Option stripPort;
private Option useCanonicalHostname;
private Option requestDelegCreds;
Builder() {
super();
this.stripPort = Option.DEFAULT;
this.useCanonicalHostname = Option.DEFAULT;
this.requestDelegCreds = Option.DEFAULT;
}
public Builder setStripPort(final Option stripPort) {
this.stripPort = stripPort;
return this;
}
public Builder setStripPort(final boolean stripPort) {
this.stripPort = stripPort ? Option.ENABLE : Option.DISABLE;
return this;
}
public Builder setUseCanonicalHostname(final Option useCanonicalHostname) {
this.useCanonicalHostname = useCanonicalHostname;
return this;
}
public Builder setUseCanonicalHostname(final boolean useCanonicalHostname) {
this.useCanonicalHostname = useCanonicalHostname ? Option.ENABLE : Option.DISABLE;
return this;
}
public Builder setRequestDelegCreds(final Option requestDelegCreds) {
this.requestDelegCreds = requestDelegCreds;
return this;
}
public KerberosConfig build() {
return new KerberosConfig(
stripPort,
useCanonicalHostname,
requestDelegCreds);
}
}
}

View File

@ -47,6 +47,7 @@ import org.apache.hc.client5.http.UserTokenHandler;
import org.apache.hc.client5.http.async.AsyncExecChainHandler; import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.client5.http.config.AuthSchemes; import org.apache.hc.client5.http.config.AuthSchemes;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.cookie.BasicCookieStore;
@ -963,8 +964,8 @@ public class HttpAsyncClientBuilder {
.register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.CREDSSP, new CredSspSchemeFactory()) .register(AuthSchemes.CREDSSP, new CredSspSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory()) .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.build(); .build();
} }
Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry; Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry;

View File

@ -40,6 +40,7 @@ import org.apache.hc.client5.http.auth.AuthenticationException;
import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.InvalidCredentialsException; import org.apache.hc.client5.http.auth.InvalidCredentialsException;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.client5.http.auth.KerberosCredentials; import org.apache.hc.client5.http.auth.KerberosCredentials;
import org.apache.hc.client5.http.auth.MalformedChallengeException; import org.apache.hc.client5.http.auth.MalformedChallengeException;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
@ -69,9 +70,8 @@ public abstract class GGSSchemeBase implements AuthScheme {
private final Logger log = LogManager.getLogger(getClass()); private final Logger log = LogManager.getLogger(getClass());
private final KerberosConfig config;
private final DnsResolver dnsResolver; private final DnsResolver dnsResolver;
private final boolean stripPort;
private final boolean useCanonicalHostname;
/** Authentication process state */ /** Authentication process state */
private State state; private State state;
@ -79,23 +79,19 @@ public abstract class GGSSchemeBase implements AuthScheme {
private String challenge; private String challenge;
private byte[] token; private byte[] token;
GGSSchemeBase( GGSSchemeBase(final KerberosConfig config, final DnsResolver dnsResolver) {
final DnsResolver dnsResolver,
final boolean stripPort,
final boolean useCanonicalHostname) {
super(); super();
this.config = config != null ? config : KerberosConfig.DEFAULT;
this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE; this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
this.stripPort = stripPort;
this.useCanonicalHostname = useCanonicalHostname;
this.state = State.UNINITIATED; this.state = State.UNINITIATED;
} }
GGSSchemeBase(final boolean stripPort) { GGSSchemeBase(final KerberosConfig config) {
this(null, stripPort, true); this(config, SystemDefaultDnsResolver.INSTANCE);
} }
GGSSchemeBase() { GGSSchemeBase() {
this(null, true, true); this(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
} }
@Override @Override
@ -152,6 +148,9 @@ public abstract class GGSSchemeBase implements AuthScheme {
final GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, gssCredential, final GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, gssCredential,
GSSContext.DEFAULT_LIFETIME); GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true); gssContext.requestMutualAuth(true);
if (config.getRequestDelegCreds() != KerberosConfig.Option.DEFAULT) {
gssContext.requestCredDeleg(config.getRequestDelegCreds() == KerberosConfig.Option.ENABLE);
}
return gssContext; return gssContext;
} }
/** /**
@ -204,13 +203,13 @@ public abstract class GGSSchemeBase implements AuthScheme {
try { try {
final String authServer; final String authServer;
String hostname = host.getHostName(); String hostname = host.getHostName();
if (this.useCanonicalHostname){ if (config.getUseCanonicalHostname() != KerberosConfig.Option.DISABLE){
try { try {
hostname = dnsResolver.resolveCanonicalHostname(host.getHostName()); hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
} catch (final UnknownHostException ignore){ } catch (final UnknownHostException ignore){
} }
} }
if (this.stripPort) { if (config.getStripPort() != KerberosConfig.Option.DISABLE) {
authServer = hostname; authServer = hostname;
} else { } else {
authServer = hostname + ":" + host.getPort(); authServer = hostname + ":" + host.getPort();

View File

@ -27,6 +27,7 @@
package org.apache.hc.client5.http.impl.auth; package org.apache.hc.client5.http.impl.auth;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid; import org.ietf.jgss.Oid;
@ -40,14 +41,10 @@ public class KerberosScheme extends GGSSchemeBase {
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
/** /**
* @since 4.4 * @since 5.0
*/ */
public KerberosScheme(final DnsResolver dnsResolver, final boolean stripPort, final boolean useCanonicalHostname) { public KerberosScheme(final KerberosConfig config, final DnsResolver dnsResolver) {
super(dnsResolver, stripPort, useCanonicalHostname); super(config, dnsResolver);
}
public KerberosScheme(final boolean stripPort) {
super(stripPort);
} }
public KerberosScheme() { public KerberosScheme() {

View File

@ -29,6 +29,7 @@ package org.apache.hc.client5.http.impl.auth;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
@ -42,23 +43,21 @@ import org.apache.hc.core5.http.protocol.HttpContext;
@Contract(threading = ThreadingBehavior.IMMUTABLE) @Contract(threading = ThreadingBehavior.IMMUTABLE)
public class KerberosSchemeFactory implements AuthSchemeProvider { public class KerberosSchemeFactory implements AuthSchemeProvider {
private final KerberosConfig config;
private final DnsResolver dnsResolver; private final DnsResolver dnsResolver;
private final boolean stripPort;
private final boolean useCanonicalHostname;
/** /**
* @since 4.4 * @since 5.0
*/ */
public KerberosSchemeFactory(final DnsResolver dnsResolver, final boolean stripPort, final boolean useCanonicalHostname) { public KerberosSchemeFactory(final KerberosConfig config, final DnsResolver dnsResolver) {
super(); super();
this.config = config;
this.dnsResolver = dnsResolver; this.dnsResolver = dnsResolver;
this.stripPort = stripPort;
this.useCanonicalHostname = useCanonicalHostname;
} }
@Override @Override
public AuthScheme create(final HttpContext context) { public AuthScheme create(final HttpContext context) {
return new KerberosScheme(this.dnsResolver, this.stripPort, this.useCanonicalHostname); return new KerberosScheme(this.config, this.dnsResolver);
} }
} }

View File

@ -27,6 +27,7 @@
package org.apache.hc.client5.http.impl.auth; package org.apache.hc.client5.http.impl.auth;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid; import org.ietf.jgss.Oid;
@ -41,14 +42,10 @@ public class SPNegoScheme extends GGSSchemeBase {
private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
/** /**
* @since 4.4 * @since 5.0
*/ */
public SPNegoScheme(final DnsResolver dnsResolver, final boolean stripPort, final boolean useCanonicalHostname) { public SPNegoScheme(final KerberosConfig config, final DnsResolver dnsResolver) {
super(dnsResolver, stripPort, useCanonicalHostname); super(config, dnsResolver);
}
public SPNegoScheme(final boolean stripPort) {
super(stripPort);
} }
public SPNegoScheme() { public SPNegoScheme() {

View File

@ -29,6 +29,7 @@ package org.apache.hc.client5.http.impl.auth;
import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
@ -42,23 +43,21 @@ import org.apache.hc.core5.http.protocol.HttpContext;
@Contract(threading = ThreadingBehavior.IMMUTABLE) @Contract(threading = ThreadingBehavior.IMMUTABLE)
public class SPNegoSchemeFactory implements AuthSchemeProvider { public class SPNegoSchemeFactory implements AuthSchemeProvider {
private final KerberosConfig config;
private final DnsResolver dnsResolver; private final DnsResolver dnsResolver;
private final boolean stripPort;
private final boolean useCanonicalHostname;
/** /**
* @since 4.4 * @since 5.0
*/ */
public SPNegoSchemeFactory(final DnsResolver dnsResolver, final boolean stripPort, final boolean useCanonicalHostname) { public SPNegoSchemeFactory(final KerberosConfig config, final DnsResolver dnsResolver) {
super(); super();
this.config = config;
this.dnsResolver = dnsResolver; this.dnsResolver = dnsResolver;
this.stripPort = stripPort;
this.useCanonicalHostname = useCanonicalHostname;
} }
@Override @Override
public AuthScheme create(final HttpContext context) { public AuthScheme create(final HttpContext context) {
return new SPNegoScheme(this.dnsResolver, this.stripPort, this.useCanonicalHostname); return new SPNegoScheme(this.config, this.dnsResolver);
} }
} }

View File

@ -47,6 +47,7 @@ import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.UserTokenHandler; import org.apache.hc.client5.http.UserTokenHandler;
import org.apache.hc.client5.http.auth.AuthSchemeProvider; import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.client5.http.classic.BackoffManager; import org.apache.hc.client5.http.classic.BackoffManager;
import org.apache.hc.client5.http.classic.ConnectionBackoffStrategy; import org.apache.hc.client5.http.classic.ConnectionBackoffStrategy;
import org.apache.hc.client5.http.classic.ExecChainHandler; import org.apache.hc.client5.http.classic.ExecChainHandler;
@ -952,8 +953,8 @@ public class HttpClientBuilder {
.register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.CREDSSP, new CredSspSchemeFactory()) .register(AuthSchemes.CREDSSP, new CredSspSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory()) .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.build(); .build();
} }
Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry; Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry;

View File

@ -40,6 +40,7 @@ import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.ChallengeType; import org.apache.hc.client5.http.auth.ChallengeType;
import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.KerberosConfig;
import org.apache.hc.client5.http.config.AuthSchemes; import org.apache.hc.client5.http.config.AuthSchemes;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy; import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
@ -117,8 +118,8 @@ public class ProxyClient {
.register(AuthSchemes.BASIC, new BasicSchemeFactory()) .register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory()) .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
.build(); .build();
this.reuseStrategy = new DefaultConnectionReuseStrategy(); this.reuseStrategy = new DefaultConnectionReuseStrategy();
} }