Better state handling in NegotiateScheme#authenticate

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1181015 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2011-10-10 15:17:26 +00:00
parent 90b6853111
commit 8f46fbacd8
1 changed files with 109 additions and 104 deletions

View File

@ -152,124 +152,129 @@ public class NegotiateScheme extends AuthSchemeBase {
if (request == null) {
throw new IllegalArgumentException("HTTP request may not be null");
}
if (state != State.CHALLENGE_RECEIVED) {
throw new IllegalStateException(
"Negotiation authentication process has not been initiated");
}
try {
String key = null;
if (isProxy()) {
key = ExecutionContext.HTTP_PROXY_HOST;
} else {
key = ExecutionContext.HTTP_TARGET_HOST;
}
HttpHost host = (HttpHost) context.getAttribute(key);
if (host == null) {
throw new AuthenticationException("Authentication host is not set " +
"in the execution context");
}
String authServer;
if (!this.stripPort && host.getPort() > 0) {
authServer = host.toHostString();
} else {
authServer = host.getHostName();
}
if (log.isDebugEnabled()) {
log.debug("init " + authServer);
}
/* Using the SPNEGO OID is the correct method.
* Kerberos v5 works for IIS but not JBoss. Unwrapping
* the initial token when using SPNEGO OID looks like what is
* described here...
*
* http://msdn.microsoft.com/en-us/library/ms995330.aspx
*
* Another helpful URL...
*
* http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html
*
* Unfortunately SPNEGO is JRE >=1.6.
*/
/** Try SPNEGO by default, fall back to Kerberos later if error */
negotiationOid = new Oid(SPNEGO_OID);
boolean tryKerberos = false;
switch (state) {
case UNINITIATED:
throw new AuthenticationException("SPNEGO authentication has not been initiated");
case FAILED:
throw new AuthenticationException("SPNEGO authentication has failed");
case CHALLENGE_RECEIVED:
try {
GSSManager manager = getManager();
GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
} catch (GSSException ex){
// BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
// Rethrow any other exception.
if (ex.getMajor() == GSSException.BAD_MECH ){
log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
tryKerberos = true;
String key = null;
if (isProxy()) {
key = ExecutionContext.HTTP_PROXY_HOST;
} else {
throw ex;
key = ExecutionContext.HTTP_TARGET_HOST;
}
HttpHost host = (HttpHost) context.getAttribute(key);
if (host == null) {
throw new AuthenticationException("Authentication host is not set " +
"in the execution context");
}
String authServer;
if (!this.stripPort && host.getPort() > 0) {
authServer = host.toHostString();
} else {
authServer = host.getHostName();
}
}
if (tryKerberos){
/* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/
log.debug("Using Kerberos MECH " + KERBEROS_OID);
negotiationOid = new Oid(KERBEROS_OID);
GSSManager manager = getManager();
GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
}
if (token == null) {
token = new byte[0];
}
token = gssContext.initSecContext(token, 0, token.length);
if (token == null) {
if (log.isDebugEnabled()) {
log.debug("init " + authServer);
}
/* Using the SPNEGO OID is the correct method.
* Kerberos v5 works for IIS but not JBoss. Unwrapping
* the initial token when using SPNEGO OID looks like what is
* described here...
*
* http://msdn.microsoft.com/en-us/library/ms995330.aspx
*
* Another helpful URL...
*
* http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html
*
* Unfortunately SPNEGO is JRE >=1.6.
*/
/** Try SPNEGO by default, fall back to Kerberos later if error */
negotiationOid = new Oid(SPNEGO_OID);
boolean tryKerberos = false;
try {
GSSManager manager = getManager();
GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
} catch (GSSException ex){
// BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
// Rethrow any other exception.
if (ex.getMajor() == GSSException.BAD_MECH ){
log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
tryKerberos = true;
} else {
throw ex;
}
}
if (tryKerberos){
/* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/
log.debug("Using Kerberos MECH " + KERBEROS_OID);
negotiationOid = new Oid(KERBEROS_OID);
GSSManager manager = getManager();
GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
gssContext = manager.createContext(
serverName.canonicalize(negotiationOid), negotiationOid, null,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestMutualAuth(true);
gssContext.requestCredDeleg(true);
}
if (token == null) {
token = new byte[0];
}
token = gssContext.initSecContext(token, 0, token.length);
if (token == null) {
state = State.FAILED;
throw new AuthenticationException("GSS security context initialization failed");
}
/*
* IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish?
* seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token.
*/
if (spengoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) {
token = spengoGenerator.generateSpnegoDERObject(token);
}
state = State.TOKEN_GENERATED;
} catch (GSSException gsse) {
state = State.FAILED;
throw new AuthenticationException("GSS security context initialization 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());
} catch (IOException ex){
state = State.FAILED;
throw new AuthenticationException(ex.getMessage());
}
/*
* IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish?
* seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token.
*/
if (spengoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) {
token = spengoGenerator.generateSpnegoDERObject(token);
}
state = State.TOKEN_GENERATED;
case TOKEN_GENERATED:
String tokenstr = new String(Base64.encodeBase64(token, false));
if (log.isDebugEnabled()) {
log.debug("Sending response '" + tokenstr + "' back to the auth server");
}
return new BasicHeader("Authorization", "Negotiate " + tokenstr);
} catch (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());
} catch (IOException ex){
state = State.FAILED;
throw new AuthenticationException(ex.getMessage());
default:
throw new IllegalStateException("Illegal state: " + state);
}
}
/**
* Returns the authentication parameter with the given name, if available.
*