duplicate and rename mutual SPNEGO related classes
This commit is contained in:
parent
66dca67f9a
commit
a23b9f84cd
|
@ -49,7 +49,7 @@ import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
||||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||||
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
|
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
|
||||||
import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
|
import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
|
||||||
import org.apache.hc.client5.http.impl.auth.SPNegoScheme;
|
import org.apache.hc.client5.http.impl.auth.MutualSpnegoScheme;
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||||
import org.apache.hc.client5.http.utils.Base64;
|
import org.apache.hc.client5.http.utils.Base64;
|
||||||
import org.apache.hc.client5.testing.extension.sync.ClientProtocolLevel;
|
import org.apache.hc.client5.testing.extension.sync.ClientProtocolLevel;
|
||||||
|
@ -80,9 +80,9 @@ import org.mockito.Mockito;
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SPNegoScheme}.
|
* Tests for {@link SPNegoScheme}.
|
||||||
*/
|
*/
|
||||||
public class TestSPNegoScheme extends AbstractIntegrationTestBase {
|
public class TestMutualSpnegoScheme extends AbstractIntegrationTestBase {
|
||||||
|
|
||||||
protected TestSPNegoScheme() {
|
protected TestMutualSpnegoScheme() {
|
||||||
super(URIScheme.HTTP, ClientProtocolLevel.STANDARD);
|
super(URIScheme.HTTP, ClientProtocolLevel.STANDARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ public class TestSPNegoScheme extends AbstractIntegrationTestBase {
|
||||||
* Kerberos configuration.
|
* Kerberos configuration.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static class NegotiateSchemeWithMockGssManager extends SPNegoScheme {
|
private static class NegotiateSchemeWithMockGssManager extends MutualSpnegoScheme {
|
||||||
|
|
||||||
final GSSManager manager = Mockito.mock(GSSManager.class);
|
final GSSManager manager = Mockito.mock(GSSManager.class);
|
||||||
final GSSName name = Mockito.mock(GSSName.class);
|
final GSSName name = Mockito.mock(GSSName.class);
|
||||||
|
@ -218,7 +218,7 @@ public class TestSPNegoScheme extends AbstractIntegrationTestBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MutualNegotiateSchemeWithMockGssManager extends SPNegoScheme {
|
private static class MutualNegotiateSchemeWithMockGssManager extends MutualSpnegoScheme {
|
||||||
|
|
||||||
final GSSManager manager = Mockito.mock(GSSManager.class);
|
final GSSManager manager = Mockito.mock(GSSManager.class);
|
||||||
final GSSName name = Mockito.mock(GSSName.class);
|
final GSSName name = Mockito.mock(GSSName.class);
|
|
@ -32,14 +32,14 @@ import java.security.Principal;
|
||||||
import org.apache.hc.client5.http.DnsResolver;
|
import org.apache.hc.client5.http.DnsResolver;
|
||||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||||
import org.apache.hc.client5.http.auth.AuthChallenge;
|
import org.apache.hc.client5.http.auth.AuthChallenge;
|
||||||
import org.apache.hc.client5.http.auth.AuthScheme2;
|
import org.apache.hc.client5.http.auth.AuthScheme;
|
||||||
import org.apache.hc.client5.http.auth.AuthScope;
|
import org.apache.hc.client5.http.auth.AuthScope;
|
||||||
import org.apache.hc.client5.http.auth.AuthenticationException;
|
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.MalformedChallengeException;
|
||||||
import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
||||||
import org.apache.hc.client5.http.auth.KerberosConfig;
|
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||||
import org.apache.hc.client5.http.utils.Base64;
|
import org.apache.hc.client5.http.utils.Base64;
|
||||||
import org.apache.hc.core5.http.HttpHost;
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
|
@ -60,51 +60,45 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*
|
*
|
||||||
|
* @deprecated Do not use. This class implements functionality for the old deprecated non mutual
|
||||||
|
* authentication capable {@link SPNegoScheme} and {@link KerberosScheme} classes.
|
||||||
|
* The new mutual authentication capable implementation is {@link MutualGSSSchemeBase}.
|
||||||
*/
|
*/
|
||||||
// FIXME The class name looks like a Typo. Rename in 6.0 ?
|
@Deprecated
|
||||||
public abstract class GGSSchemeBase implements AuthScheme2 {
|
public abstract class GGSSchemeBase implements AuthScheme {
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
UNINITIATED,
|
UNINITIATED,
|
||||||
TOKEN_READY,
|
CHALLENGE_RECEIVED,
|
||||||
TOKEN_SENT,
|
TOKEN_GENERATED,
|
||||||
SUCCEEDED,
|
|
||||||
FAILED,
|
FAILED,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GGSSchemeBase.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GGSSchemeBase.class);
|
||||||
private static final String NO_TOKEN = "";
|
private static final String NO_TOKEN = "";
|
||||||
private static final String KERBEROS_SCHEME = "HTTP";
|
private static final String KERBEROS_SCHEME = "HTTP";
|
||||||
|
private final org.apache.hc.client5.http.auth.KerberosConfig config;
|
||||||
// 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.
|
|
||||||
private static final int MAX_GSS_CHALLENGES = 3;
|
|
||||||
private final KerberosConfig config;
|
|
||||||
private final DnsResolver dnsResolver;
|
private final DnsResolver dnsResolver;
|
||||||
private final boolean mutualAuth;
|
|
||||||
private int challengesLeft = MAX_GSS_CHALLENGES;
|
|
||||||
|
|
||||||
/** Authentication process state */
|
/** Authentication process state */
|
||||||
private State state;
|
private State state;
|
||||||
private GSSCredential gssCredential;
|
private GSSCredential gssCredential;
|
||||||
private GSSContext gssContext;
|
|
||||||
private String challenge;
|
private String challenge;
|
||||||
private byte[] queuedToken = new byte[0];
|
private byte[] token;
|
||||||
|
|
||||||
GGSSchemeBase(final KerberosConfig config, final DnsResolver dnsResolver) {
|
GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config, final DnsResolver dnsResolver) {
|
||||||
super();
|
super();
|
||||||
this.config = config != null ? config : KerberosConfig.DEFAULT;
|
this.config = config != null ? config : org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT;
|
||||||
this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
|
this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
|
||||||
this.mutualAuth = config.getRequestMutualAuth() == KerberosConfig.Option.ENABLE;
|
|
||||||
this.state = State.UNINITIATED;
|
this.state = State.UNINITIATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
GGSSchemeBase(final KerberosConfig config) {
|
GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config) {
|
||||||
this(config, SystemDefaultDnsResolver.INSTANCE);
|
this(config, SystemDefaultDnsResolver.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
GGSSchemeBase() {
|
GGSSchemeBase() {
|
||||||
this(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
|
this(org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,115 +106,24 @@ public abstract class GGSSchemeBase implements AuthScheme2 {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
@Override
|
||||||
public void processChallenge(
|
public void processChallenge(
|
||||||
final HttpHost host,
|
|
||||||
final AuthChallenge authChallenge,
|
final AuthChallenge authChallenge,
|
||||||
final HttpContext context,
|
final HttpContext context) throws MalformedChallengeException {
|
||||||
final boolean challenged) throws AuthenticationException {
|
Args.notNull(authChallenge, "AuthChallenge");
|
||||||
|
|
||||||
if (challengesLeft-- <= 0 ) {
|
this.challenge = authChallenge.getValue() != null ? authChallenge.getValue() : NO_TOKEN;
|
||||||
|
|
||||||
|
if (state == State.UNINITIATED) {
|
||||||
|
token = Base64.decodeBase64(challenge.getBytes());
|
||||||
|
state = State.CHALLENGE_RECEIVED;
|
||||||
|
} else {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
||||||
final String exchangeId = clientContext.getExchangeId();
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
LOG.debug("{} GSS error: too many challenges received. Infinite loop ?", exchangeId);
|
LOG.debug("{} Authentication already attempted", exchangeId);
|
||||||
}
|
}
|
||||||
// TODO: Should we throw an exception ? There is a test for this behaviour.
|
|
||||||
state = State.FAILED;
|
state = State.FAILED;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final byte[] challengeToken = Base64.decodeBase64(authChallenge == null ? null : authChallenge.getValue());
|
|
||||||
|
|
||||||
final String gssHostname;
|
|
||||||
String hostname = host.getHostName();
|
|
||||||
if (config.getUseCanonicalHostname() != KerberosConfig.Option.DISABLE) {
|
|
||||||
try {
|
|
||||||
hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
|
|
||||||
} catch (final UnknownHostException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (config.getStripPort() != KerberosConfig.Option.DISABLE) {
|
|
||||||
gssHostname = hostname;
|
|
||||||
} else {
|
|
||||||
gssHostname = hostname + ":" + host.getPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
|
||||||
final String exchangeId = clientContext.getExchangeId();
|
|
||||||
LOG.debug("{} GSS init {}", exchangeId, gssHostname);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
queuedToken = generateToken(challengeToken, KERBEROS_SCHEME, gssHostname);
|
|
||||||
switch (state) {
|
|
||||||
case UNINITIATED:
|
|
||||||
if (challenge != NO_TOKEN) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
// TODO Should we fail ? That would break existing tests that send a token
|
|
||||||
// in the first response, which is against the RFC.
|
|
||||||
}
|
|
||||||
state = State.TOKEN_READY;
|
|
||||||
break;
|
|
||||||
case TOKEN_SENT:
|
|
||||||
if (challenged) {
|
|
||||||
state = State.TOKEN_READY;
|
|
||||||
} else if (mutualAuth) {
|
|
||||||
// We should have received a valid mutualAuth token
|
|
||||||
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(
|
|
||||||
"requireMutualAuth is set but GSSContext is not established");
|
|
||||||
} else if (!gssContext.getMutualAuthState()) {
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
final HttpClientContext clientContext =
|
|
||||||
HttpClientContext.cast(context);
|
|
||||||
final String exchangeId = clientContext.getExchangeId();
|
|
||||||
LOG.debug("{} requireMutualAuth is set but GSSAUthContext does not have"
|
|
||||||
+ " mutualAuthState set", exchangeId);
|
|
||||||
}
|
|
||||||
state = State.FAILED;
|
|
||||||
throw new AuthenticationException(
|
|
||||||
"requireMutualAuth is set but GSSContext mutualAuthState is not set");
|
|
||||||
} else {
|
|
||||||
state = State.SUCCEEDED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state = State.FAILED;
|
|
||||||
throw new IllegalStateException("Illegal state: " + state);
|
|
||||||
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +135,11 @@ public abstract class GGSSchemeBase implements AuthScheme2 {
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
protected byte[] generateGSSToken(
|
protected byte[] generateGSSToken(
|
||||||
final byte[] input, final Oid oid, final String gssServiceName, final String gssHostname) throws GSSException {
|
final byte[] input, final Oid oid, final String serviceName, final String authServer) throws GSSException {
|
||||||
final GSSManager manager = getManager();
|
final GSSManager manager = getManager();
|
||||||
final GSSName peerName = manager.createName(gssServiceName + "@" + gssHostname, GSSName.NT_HOSTBASED_SERVICE);
|
final GSSName serverName = manager.createName(serviceName + "@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
|
||||||
|
|
||||||
if (gssContext == null) {
|
final GSSContext gssContext = createGSSContext(manager, oid, serverName, gssCredential);
|
||||||
gssContext = createGSSContext(manager, oid, peerName, gssCredential);
|
|
||||||
}
|
|
||||||
if (input != null) {
|
if (input != null) {
|
||||||
return gssContext.initSecContext(input, 0, input.length);
|
return gssContext.initSecContext(input, 0, input.length);
|
||||||
}
|
}
|
||||||
|
@ -251,35 +152,24 @@ public abstract class GGSSchemeBase implements AuthScheme2 {
|
||||||
protected GSSContext createGSSContext(
|
protected GSSContext createGSSContext(
|
||||||
final GSSManager manager,
|
final GSSManager manager,
|
||||||
final Oid oid,
|
final Oid oid,
|
||||||
final GSSName peerName,
|
final GSSName serverName,
|
||||||
final GSSCredential gssCredential) throws GSSException {
|
final GSSCredential gssCredential) throws GSSException {
|
||||||
final GSSContext gssContext = manager.createContext(peerName.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) {
|
if (config.getRequestDelegCreds() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DEFAULT) {
|
||||||
gssContext.requestCredDeleg(config.getRequestDelegCreds() == KerberosConfig.Option.ENABLE);
|
gssContext.requestCredDeleg(config.getRequestDelegCreds() == org.apache.hc.client5.http.auth.KerberosConfig.Option.ENABLE);
|
||||||
}
|
|
||||||
if (config.getRequestMutualAuth() != KerberosConfig.Option.DEFAULT) {
|
|
||||||
gssContext.requestMutualAuth(config.getRequestMutualAuth() == KerberosConfig.Option.ENABLE);
|
|
||||||
}
|
}
|
||||||
return gssContext;
|
return gssContext;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
protected abstract byte[] generateToken(byte[] input, String gssServiceName, String gssHostname) throws GSSException;
|
protected abstract byte[] generateToken(byte[] input, String serviceName, String authServer) throws GSSException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isChallengeComplete() {
|
public boolean isChallengeComplete() {
|
||||||
// For the mutual authentication response, this is should technically return true.
|
return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
|
||||||
// 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 && mutualAuth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -306,8 +196,6 @@ public abstract class GGSSchemeBase implements AuthScheme2 {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the queued token and update the state.
|
|
||||||
// All token processing is done in processChallenge()
|
|
||||||
@Override
|
@Override
|
||||||
public String generateAuthResponse(
|
public String generateAuthResponse(
|
||||||
final HttpHost host,
|
final HttpHost host,
|
||||||
|
@ -320,16 +208,53 @@ public abstract class GGSSchemeBase implements AuthScheme2 {
|
||||||
throw new AuthenticationException(getName() + " authentication has not been initiated");
|
throw new AuthenticationException(getName() + " authentication has not been initiated");
|
||||||
case FAILED:
|
case FAILED:
|
||||||
throw new AuthenticationException(getName() + " authentication has failed");
|
throw new AuthenticationException(getName() + " authentication has failed");
|
||||||
case SUCCEEDED:
|
case CHALLENGE_RECEIVED:
|
||||||
return null;
|
try {
|
||||||
case TOKEN_READY:
|
final String authServer;
|
||||||
state = State.TOKEN_SENT;
|
String hostname = host.getHostName();
|
||||||
|
if (config.getUseCanonicalHostname() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE) {
|
||||||
|
try {
|
||||||
|
hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
|
||||||
|
} catch (final UnknownHostException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.getStripPort() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE) {
|
||||||
|
authServer = hostname;
|
||||||
|
} else {
|
||||||
|
authServer = hostname + ":" + host.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
||||||
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
|
LOG.debug("{} init {}", exchangeId, authServer);
|
||||||
|
}
|
||||||
|
token = generateToken(token, KERBEROS_SCHEME, authServer);
|
||||||
|
state = State.TOKEN_GENERATED;
|
||||||
|
} 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());
|
||||||
|
}
|
||||||
|
case TOKEN_GENERATED:
|
||||||
final Base64 codec = new Base64(0);
|
final Base64 codec = new Base64(0);
|
||||||
final String tokenstr = new String(codec.encode(queuedToken));
|
final String tokenstr = new String(codec.encode(token));
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
||||||
final String exchangeId = clientContext.getExchangeId();
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
LOG.debug("{} Sending GSS response '{}' back to the auth server", exchangeId, tokenstr);
|
LOG.debug("{} Sending response '{}' back to the auth server", exchangeId, tokenstr);
|
||||||
}
|
}
|
||||||
return StandardAuthScheme.SPNEGO + " " + tokenstr;
|
return StandardAuthScheme.SPNEGO + " " + tokenstr;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -41,8 +41,10 @@ import org.ietf.jgss.Oid;
|
||||||
*
|
*
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*
|
*
|
||||||
* @deprecated Do not use. Consider using Spengo, Basic or Bearer authentication with TLS instead.
|
* @deprecated Do not use. The Kerberos authentication scheme was never standardised.
|
||||||
|
* Use {@link MutualSpnegoScheme} or some other scheme instead.
|
||||||
*
|
*
|
||||||
|
* @see MutualSpnegoScheme
|
||||||
* @see BasicScheme
|
* @see BasicScheme
|
||||||
* @see BearerScheme
|
* @see BearerScheme
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -45,9 +45,10 @@ import org.apache.hc.core5.http.protocol.HttpContext;
|
||||||
*
|
*
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*
|
*
|
||||||
* @deprecated Do not use. The GGS based experimental authentication schemes are no longer
|
* @deprecated Do not use. The Kerberos authentication scheme was never standardised.
|
||||||
* supported. Consider using Basic or Bearer authentication with TLS instead.
|
* Use {@link MutualSpnegoScheme} or some other scheme instead.
|
||||||
*
|
*
|
||||||
|
* @see MutualSpnegoSchemeFactory
|
||||||
* @see BasicSchemeFactory
|
* @see BasicSchemeFactory
|
||||||
* @see BearerSchemeFactory
|
* @see BearerSchemeFactory
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,348 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.impl.auth;
|
||||||
|
|
||||||
|
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.AuthScheme2;
|
||||||
|
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.KerberosConfig;
|
||||||
|
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 GGSScheme} class, which was deprecated in 5.3.
|
||||||
|
*
|
||||||
|
* @since 5.5
|
||||||
|
*
|
||||||
|
* @see GGSSchemeBase
|
||||||
|
*/
|
||||||
|
public abstract class MutualGssSchemeBase implements AuthScheme2 {
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
UNINITIATED,
|
||||||
|
TOKEN_READY,
|
||||||
|
TOKEN_SENT,
|
||||||
|
SUCCEEDED,
|
||||||
|
FAILED,
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MutualGssSchemeBase.class);
|
||||||
|
private static final String NO_TOKEN = "";
|
||||||
|
private static final String KERBEROS_SCHEME = "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.
|
||||||
|
private static final int MAX_GSS_CHALLENGES = 3;
|
||||||
|
private final KerberosConfig config;
|
||||||
|
private final DnsResolver dnsResolver;
|
||||||
|
private final boolean mutualAuth;
|
||||||
|
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];
|
||||||
|
|
||||||
|
MutualGssSchemeBase(final KerberosConfig config, final DnsResolver dnsResolver) {
|
||||||
|
super();
|
||||||
|
this.config = config != null ? config : KerberosConfig.DEFAULT;
|
||||||
|
this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
|
||||||
|
this.mutualAuth = config.getRequestMutualAuth() == KerberosConfig.Option.ENABLE;
|
||||||
|
this.state = State.UNINITIATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutualGssSchemeBase(final KerberosConfig config) {
|
||||||
|
this(config, SystemDefaultDnsResolver.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
MutualGssSchemeBase() {
|
||||||
|
this(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRealm() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 AuthChallenge authChallenge,
|
||||||
|
final HttpContext context,
|
||||||
|
final boolean challenged) throws AuthenticationException {
|
||||||
|
|
||||||
|
if (challengesLeft-- <= 0 ) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
||||||
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
|
LOG.debug("{} GSS error: too many challenges received. Infinite loop ?", exchangeId);
|
||||||
|
}
|
||||||
|
// TODO: Should we throw an exception ? There is a test for this behaviour.
|
||||||
|
state = State.FAILED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] challengeToken = Base64.decodeBase64(authChallenge == null ? null : authChallenge.getValue());
|
||||||
|
|
||||||
|
final String gssHostname;
|
||||||
|
String hostname = host.getHostName();
|
||||||
|
if (config.getUseCanonicalHostname() != KerberosConfig.Option.DISABLE) {
|
||||||
|
try {
|
||||||
|
hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
|
||||||
|
} catch (final UnknownHostException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.getStripPort() != KerberosConfig.Option.DISABLE) {
|
||||||
|
gssHostname = hostname;
|
||||||
|
} else {
|
||||||
|
gssHostname = hostname + ":" + host.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
final HttpClientContext clientContext = HttpClientContext.cast(context);
|
||||||
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
|
LOG.debug("{} GSS init {}", exchangeId, gssHostname);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
queuedToken = generateToken(challengeToken, KERBEROS_SCHEME, gssHostname);
|
||||||
|
switch (state) {
|
||||||
|
case UNINITIATED:
|
||||||
|
if (challenge != NO_TOKEN) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
// TODO Should we fail ? That would break existing tests that send a token
|
||||||
|
// in the first response, which is against the RFC.
|
||||||
|
}
|
||||||
|
state = State.TOKEN_READY;
|
||||||
|
break;
|
||||||
|
case TOKEN_SENT:
|
||||||
|
if (challenged) {
|
||||||
|
state = State.TOKEN_READY;
|
||||||
|
} else if (mutualAuth) {
|
||||||
|
// We should have received a valid mutualAuth token
|
||||||
|
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(
|
||||||
|
"requireMutualAuth is set but GSSContext is not established");
|
||||||
|
} else if (!gssContext.getMutualAuthState()) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
final HttpClientContext clientContext =
|
||||||
|
HttpClientContext.cast(context);
|
||||||
|
final String exchangeId = clientContext.getExchangeId();
|
||||||
|
LOG.debug("{} requireMutualAuth is set but GSSAUthContext does not have"
|
||||||
|
+ " mutualAuthState set", exchangeId);
|
||||||
|
}
|
||||||
|
state = State.FAILED;
|
||||||
|
throw new AuthenticationException(
|
||||||
|
"requireMutualAuth is set but GSSContext mutualAuthState is not set");
|
||||||
|
} else {
|
||||||
|
state = State.SUCCEEDED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state = State.FAILED;
|
||||||
|
throw new IllegalStateException("Illegal state: " + state);
|
||||||
|
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
final GSSContext gssContext = manager.createContext(peerName.canonicalize(oid), oid, gssCredential,
|
||||||
|
GSSContext.DEFAULT_LIFETIME);
|
||||||
|
gssContext.requestMutualAuth(true);
|
||||||
|
if (config.getRequestDelegCreds() != KerberosConfig.Option.DEFAULT) {
|
||||||
|
gssContext.requestCredDeleg(config.getRequestDelegCreds() == KerberosConfig.Option.ENABLE);
|
||||||
|
}
|
||||||
|
if (config.getRequestMutualAuth() != KerberosConfig.Option.DEFAULT) {
|
||||||
|
gssContext.requestMutualAuth(config.getRequestMutualAuth() == KerberosConfig.Option.ENABLE);
|
||||||
|
}
|
||||||
|
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 && mutualAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResponseReady(
|
||||||
|
final HttpHost host,
|
||||||
|
final CredentialsProvider credentialsProvider,
|
||||||
|
final HttpContext context) throws AuthenticationException {
|
||||||
|
|
||||||
|
Args.notNull(host, "Auth host");
|
||||||
|
Args.notNull(credentialsProvider, "CredentialsProvider");
|
||||||
|
|
||||||
|
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();
|
||||||
|
} else {
|
||||||
|
this.gssCredential = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.impl.auth;
|
||||||
|
|
||||||
|
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.core5.annotation.Experimental;
|
||||||
|
import org.ietf.jgss.GSSException;
|
||||||
|
import org.ietf.jgss.Oid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
|
||||||
|
* scheme.
|
||||||
|
* <p>
|
||||||
|
* This is the new mutual authentication capable Scheme which replaces the old deprecated non mutual
|
||||||
|
* authentication capable {@link SPNegoScheme}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that this scheme is not enabled by default. To use it, you need create a custom
|
||||||
|
* {@link AuthenticationStrategy} and a custom {@link AuthSchemeFactory} {@link Registry},
|
||||||
|
* and set them on the HttpClientBuilder.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* private static class SpnegoAuthenticationStrategy extends DefaultAuthenticationStrategy {
|
||||||
|
* private static final List<String> SPNEGO_SCHEME_PRIORITY =
|
||||||
|
* Collections.unmodifiableList(
|
||||||
|
* Arrays.asList(StandardAuthScheme.SPNEGO
|
||||||
|
* // Add other Schemes as needed
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @Override
|
||||||
|
* protected final List<String> getSchemePriority() {
|
||||||
|
* return SPNEGO_SCHEME_PRIORITY;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* AuthenticationStrategy mutualStrategy = new SpnegoAuthenticationStrategy();
|
||||||
|
*
|
||||||
|
* AuthSchemeFactory mutualFactory = new MutualSpnegoSchemeFactory();
|
||||||
|
* Registry<AuthSchemeFactory> mutualSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
|
||||||
|
* .register(StandardAuthScheme.SPNEGO, mutualFactory)
|
||||||
|
* //register other schemes as needed
|
||||||
|
* .build();
|
||||||
|
*
|
||||||
|
* CloseableHttpClient mutualClient = HttpClientBuilder.create()
|
||||||
|
* .setTargetAuthenticationStrategy(mutualStrategy);
|
||||||
|
* .setDefaultAuthSchemeRegistry(mutualSchemeRegistry);
|
||||||
|
* .build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
@Experimental
|
||||||
|
public class MutualSpnegoScheme extends MutualGssSchemeBase {
|
||||||
|
|
||||||
|
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public MutualSpnegoScheme(final org.apache.hc.client5.http.auth.KerberosConfig config, final DnsResolver dnsResolver) {
|
||||||
|
super(config, dnsResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutualSpnegoScheme() {
|
||||||
|
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, new Oid(SPNEGO_OID), gssServiceName, gssHostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnectionBased() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* ====================================================================
|
||||||
|
* 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.impl.auth;
|
||||||
|
|
||||||
|
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 MutualSpnegoScheme} instances.
|
||||||
|
* <p>
|
||||||
|
* This replaces the old deprecated {@link SPNegoSchemeFactory}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @since 5.5
|
||||||
|
*
|
||||||
|
* @see SPNegoSchemeFactory
|
||||||
|
*/
|
||||||
|
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||||
|
@Experimental
|
||||||
|
public class MutualSpnegoSchemeFactory implements AuthSchemeFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance for the default configuration.
|
||||||
|
*/
|
||||||
|
public static final MutualSpnegoSchemeFactory DEFAULT = new MutualSpnegoSchemeFactory(org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT,
|
||||||
|
SystemDefaultDnsResolver.INSTANCE);
|
||||||
|
|
||||||
|
private final org.apache.hc.client5.http.auth.KerberosConfig config;
|
||||||
|
private final DnsResolver dnsResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public MutualSpnegoSchemeFactory(final org.apache.hc.client5.http.auth.KerberosConfig config, final DnsResolver dnsResolver) {
|
||||||
|
super();
|
||||||
|
this.config = config;
|
||||||
|
this.dnsResolver = dnsResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthScheme create(final HttpContext context) {
|
||||||
|
return new MutualSpnegoScheme(this.config, this.dnsResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -36,12 +36,19 @@ import org.ietf.jgss.Oid;
|
||||||
* SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
|
* SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
|
||||||
* scheme.
|
* scheme.
|
||||||
* <p>
|
* <p>
|
||||||
* Please note this class is considered experimental and may be discontinued or removed
|
* This class implements the old deprecated non mutual authentication capable SPNEGO implementation.
|
||||||
* in the future.
|
* Use {@link MutualSpnegoScheme} instead.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link MutualSpnegoScheme} or some other auth scheme instead.
|
||||||
|
*
|
||||||
|
* @see MutualSpnegoScheme
|
||||||
|
* @see BasicScheme
|
||||||
|
* @see BearerScheme
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@Experimental
|
@Experimental
|
||||||
public class SPNegoScheme extends GGSSchemeBase {
|
public class SPNegoScheme extends GGSSchemeBase {
|
||||||
|
|
||||||
|
@ -64,8 +71,8 @@ public class SPNegoScheme extends GGSSchemeBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] generateToken(final byte[] input, final String gssServiceName, final String gssHostname) throws GSSException {
|
protected byte[] generateToken(final byte[] input, final String serviceName, final String authServer) throws GSSException {
|
||||||
return generateGSSToken(input, new Oid(SPNEGO_OID), gssServiceName, gssHostname);
|
return generateGSSToken(input, new Oid(SPNEGO_OID), serviceName, authServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -39,15 +39,15 @@ import org.apache.hc.core5.http.protocol.HttpContext;
|
||||||
* {@link AuthSchemeFactory} implementation that creates and initializes
|
* {@link AuthSchemeFactory} implementation that creates and initializes
|
||||||
* {@link SPNegoScheme} instances.
|
* {@link SPNegoScheme} instances.
|
||||||
* <p>
|
* <p>
|
||||||
* Please note this class is considered experimental and may be discontinued or removed
|
* This factory creates the old deprecated non mutual authentication capable SPNEGO implementation.
|
||||||
* in the future.
|
* Use {@link MutualSpnegoAuthFactory} instead.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*
|
*
|
||||||
* @deprecated Do not use. The GGS based experimental authentication schemes are no longer
|
* @deprecated Use {@link MutualSpnegoAuthFactory} or some other auth scheme instead.
|
||||||
* supported. Consider using Basic or Bearer authentication with TLS instead.
|
|
||||||
*
|
*
|
||||||
|
* @see MutualSpnegoAuthFactory
|
||||||
* @see BasicSchemeFactory
|
* @see BasicSchemeFactory
|
||||||
* @see BearerSchemeFactory
|
* @see BearerSchemeFactory
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue