* Added HTTP parameters to define the order of preference for supported auth schemes for target host and proxy authentication

* Added SPNEGO auth policy

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@818601 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2009-09-24 19:35:03 +00:00
parent 9cbf76568f
commit af4b944189
8 changed files with 195 additions and 49 deletions

View File

@ -1,6 +1,10 @@
Changes since 4.0 Changes since 4.0
------------------- -------------------
* Added parameters to define the order of preference for supported auth
schemes for target host and proxy authentication.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-875] DefaultClientConnectionOperator#openConnection doesn't * [HTTPCLIENT-875] DefaultClientConnectionOperator#openConnection doesn't
update the connection state if the connection socket changed after update the connection state if the connection socket changed after
the call to SocketFactory#connectSocket(). the call to SocketFactory#connectSocket().

View File

@ -27,6 +27,8 @@
package org.apache.http.auth.params; package org.apache.http.auth.params;
import org.apache.http.auth.AuthScheme;
/** /**
* Parameter names for HTTP authentication classes. * Parameter names for HTTP authentication classes.
* *
@ -42,4 +44,26 @@ public interface AuthPNames {
*/ */
public static final String CREDENTIAL_CHARSET = "http.auth.credential-charset"; public static final String CREDENTIAL_CHARSET = "http.auth.credential-charset";
/**
* Defines the order of preference for supported {@link AuthScheme}s when
* authenticating with the target host.
* <p>
* This parameter expects a value of type {@link java.util.Collection}. The
* collection is expected to contain {@link String} instances representing
* a name of an authentication scheme as returned by
* {@link AuthScheme#getSchemeName()}.
*/
public static final String TARGET_AUTH_PREF = "http.auth.target-scheme-pref";
/**
* Defines the order of preference for supported {@link AuthScheme}s when
* authenticating with the proxy host.
* <p>
* This parameter expects a value of type {@link java.util.Collection}. The
* collection is expected to contain {@link String} instances representing
* a name of an authentication scheme as returned by
* {@link AuthScheme#getSchemeName()}.
*/
public static final String PROXY_AUTH_PREF = "http.auth.proxy-scheme-pref";
} }

View File

@ -59,4 +59,11 @@ public final class AuthPolicy {
*/ */
public static final String BASIC = "Basic"; public static final String BASIC = "Basic";
/**
* SPNEGO/Kerberos Authentication scheme.
*
* @since 4.1
*/
public static final String SPNEGO = "negotiate";
} }

View File

@ -89,8 +89,9 @@ public interface ClientContext {
public static final String PROXY_AUTH_STATE = "http.auth.proxy-scope"; public static final String PROXY_AUTH_STATE = "http.auth.proxy-scope";
/** /**
* RESERVED. DO NOT USE!!! * @deprecated do not use
*/ */
@Deprecated
public static final String AUTH_SCHEME_PREF = "http.auth.scheme-pref"; public static final String AUTH_SCHEME_PREF = "http.auth.scheme-pref";
/** /**

View File

@ -35,18 +35,18 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.apache.http.annotation.Immutable;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.http.FormattedHeader; import org.apache.http.FormattedHeader;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AuthScheme; import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeRegistry; import org.apache.http.auth.AuthSchemeRegistry;
import org.apache.http.auth.AuthenticationException; import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.client.AuthenticationHandler; import org.apache.http.client.AuthenticationHandler;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.protocol.ClientContext; import org.apache.http.client.protocol.ClientContext;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
@ -64,9 +64,10 @@ public abstract class AbstractAuthenticationHandler implements AuthenticationHan
private static final List<String> DEFAULT_SCHEME_PRIORITY = private static final List<String> DEFAULT_SCHEME_PRIORITY =
Collections.unmodifiableList(Arrays.asList(new String[] { Collections.unmodifiableList(Arrays.asList(new String[] {
"ntlm", AuthPolicy.SPNEGO,
"digest", AuthPolicy.NTLM,
"basic" AuthPolicy.DIGEST,
AuthPolicy.BASIC
})); }));
public AbstractAuthenticationHandler() { public AbstractAuthenticationHandler() {
@ -106,11 +107,31 @@ public abstract class AbstractAuthenticationHandler implements AuthenticationHan
return map; return map;
} }
/**
* Returns default list of auth scheme names in their order of preference.
*
* @return list of auth scheme names
*/
protected List<String> getAuthPreferences() { protected List<String> getAuthPreferences() {
return DEFAULT_SCHEME_PRIORITY; return DEFAULT_SCHEME_PRIORITY;
} }
public AuthScheme selectScheme( /**
* Returns default list of auth scheme names in their order of preference
* based on the HTTP response and the current execution context.
*
* @param response HTTP response.
* @param context HTTP execution context.
*
* @since 4.1
*/
protected List<String> getAuthPreferences(
final HttpResponse response,
final HttpContext context) {
return getAuthPreferences();
}
public AuthScheme selectScheme(
final Map<String, Header> challenges, final Map<String, Header> challenges,
final HttpResponse response, final HttpResponse response,
final HttpContext context) throws AuthenticationException { final HttpContext context) throws AuthenticationException {
@ -121,11 +142,9 @@ public abstract class AbstractAuthenticationHandler implements AuthenticationHan
throw new IllegalStateException("AuthScheme registry not set in HTTP context"); throw new IllegalStateException("AuthScheme registry not set in HTTP context");
} }
@SuppressWarnings("unchecked") Collection<String> authPrefs = getAuthPreferences(response, context);
Collection<String> authPrefs = (Collection<String>) context.getAttribute(
ClientContext.AUTH_SCHEME_PREF);
if (authPrefs == null) { if (authPrefs == null) {
authPrefs = getAuthPreferences(); authPrefs = DEFAULT_SCHEME_PRIORITY;
} }
if (this.log.isDebugEnabled()) { if (this.log.isDebugEnabled()) {

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.client; package org.apache.http.impl.client;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.http.annotation.Immutable; import org.apache.http.annotation.Immutable;
@ -36,6 +37,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.auth.AUTH; import org.apache.http.auth.AUTH;
import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.params.AuthPNames;
import org.apache.http.client.AuthenticationHandler; import org.apache.http.client.AuthenticationHandler;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
@ -72,4 +74,18 @@ public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHan
return parseChallenges(headers); return parseChallenges(headers);
} }
@Override
protected List<String> getAuthPreferences(
final HttpResponse response,
final HttpContext context) {
@SuppressWarnings("unchecked")
List<String> authpref = (List<String>) response.getParams().getParameter(
AuthPNames.PROXY_AUTH_PREF);
if (authpref != null) {
return authpref;
} else {
return super.getAuthPreferences(response, context);
}
}
} }

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.client; package org.apache.http.impl.client;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.http.annotation.Immutable; import org.apache.http.annotation.Immutable;
@ -36,6 +37,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.auth.AUTH; import org.apache.http.auth.AUTH;
import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.params.AuthPNames;
import org.apache.http.client.AuthenticationHandler; import org.apache.http.client.AuthenticationHandler;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
@ -72,4 +74,19 @@ public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHa
return parseChallenges(headers); return parseChallenges(headers);
} }
@Override
protected List<String> getAuthPreferences(
final HttpResponse response,
final HttpContext context) {
@SuppressWarnings("unchecked")
List<String> authpref = (List<String>) response.getParams().getParameter(
AuthPNames.TARGET_AUTH_PREF);
if (authpref != null) {
return authpref;
} else {
return super.getAuthPreferences(response, context);
}
}
} }

View File

@ -103,8 +103,8 @@ pwd
those applications that do not want the overhead of full transport security those applications that do not want the overhead of full transport security
through TLS/SSL encryption.</para> through TLS/SSL encryption.</para>
</formalpara> </formalpara>
<listitem>
</listitem> </listitem>
<listitem>
<formalpara> <formalpara>
<title>NTLM:</title> <title>NTLM:</title>
<para>NTLM is a proprietary authentication scheme developed by Microsoft and <para>NTLM is a proprietary authentication scheme developed by Microsoft and
@ -130,8 +130,8 @@ pwd
If this parameter is not set HttpClient will handle authentication If this parameter is not set HttpClient will handle authentication
automatically.</para> automatically.</para>
</formalpara> </formalpara>
<listitem>
</listitem> </listitem>
<listitem>
<formalpara> <formalpara>
<title>'http.auth.credential-charset':</title> <title>'http.auth.credential-charset':</title>
<para>defines the charset to be used when encoding user credentials. This <para>defines the charset to be used when encoding user credentials. This
@ -139,7 +139,41 @@ pwd
this parameter is not set <literal>US-ASCII</literal> will be used.</para> this parameter is not set <literal>US-ASCII</literal> will be used.</para>
</formalpara> </formalpara>
</listitem> </listitem>
<listitem>
<formalpara>
<title>'http.auth.target-scheme-pref':</title>
<para>Defines the order of preference for supported
<interfacename>AuthScheme</interfacename>s when authenticating with the
target host. This parameter expects a value of type
<interface>java.util.Collection</interface>. The collection is expected
to contain <classname>java.lang.String</classname> instances representing a
name of an authentication scheme as returned by
<methodname>AuthScheme#getSchemeName()</methodname></para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>'http.auth.proxy-scheme-pref':</title>
<para>Defines the order of preference for supported
<interfacename>AuthScheme</interfacename>s when authenticating with the
proxy host. This parameter expects a value of type
<interface>java.util.Collection</interface>. The collection is expected
to contain <classname>java.lang.String</classname> instances representing a
name of an authentication scheme as returned by
<methodname>AuthScheme#getSchemeName()</methodname></para>
</formalpara>
</listitem>
</itemizedlist> </itemizedlist>
<para>For example, one can force HttpClient to use a different order of preference for
authentication schemes</para>
<programlisting><![CDATA[
DefaultHttpClient httpclient = new DefaultHttpClient(ccm, params);
// Choose BASIC over DIGEST for proxy authentication
List<String> authpref = new ArrayList<String>();
authpref.add(AuthPolicy.BASIC);
authpref.add(AuthPolicy.DIGEST);
httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
]]></programlisting>
</section> </section>
<section> <section>
<title>Authentication scheme registry</title> <title>Authentication scheme registry</title>
@ -332,44 +366,59 @@ httpclient.addRequestInterceptor(preemptiveAuth, 0);
<section> <section>
<title><literal>SPNEGO</literal>/Kerberos Authentication</title> <title><literal>SPNEGO</literal>/Kerberos Authentication</title>
<para><literal>SPNEGO</literal> (<emphasis>S</emphasis>imple and <emphasis>P</emphasis>rotected <literal>GSSAPI</literal> <para><literal>SPNEGO</literal> (<emphasis>S</emphasis>imple and
<emphasis>Nego</emphasis>tiation Mechanism) is designed to allow for authentication <emphasis>P</emphasis>rotected <literal>GSSAPI</literal>
to services when neither end knows what the other can use/provide. It is most <emphasis>Nego</emphasis>tiation Mechanism) is designed to allow for authentication to
commonly used to do Kerberos authentication. It can wrap other mechanisms, however services when neither end knows what the other can use/provide. It is most commonly used
the current version in HTTPClient is designed solely with Kerberos in mind. The image below shows a simple authentication process. to do Kerberos authentication. It can wrap other mechanisms, however the current version
</para> in HTTPClient is designed solely with Kerberos in mind. The image below shows a simple
authentication process. </para>
<mediaobject> <mediaobject>
<imageobject> <imageobject>
<imagedata fileref=".//images/SPNEGO_cropped.png" /> <imagedata fileref=".//images/SPNEGO_cropped.png"/>
</imageobject> </imageobject>
</mediaobject> </mediaobject>
<sidebar> <sidebar>
<para>1. Client Web Browser does HTTP GET for resource.</para> <para>1. Client Web Browser does HTTP GET for resource.</para>
<para>2. Web server returns HTTP 401 status and a header: &quot;WWW-Authenticate: Negotiate</para> <para>2. Web server returns HTTP 401 status and a header: &quot;WWW-Authenticate:
<para>3. Client generates a NegTokenInit, base64 encodes it, and resubmits the GET with an Authorization header: &quot;Authorization: Negotiate &lt;base64 encoding&gt;&quot;.</para> Negotiate</para>
<para>4. Server decodes the NegTokenInit, extracts the supported MechTypes (only Kerberos V5 in our case), ensures it is one of the expected ones, and then extracts the MechToken (Kerberos Token) and authenticates it.</para> <para>3. Client generates a NegTokenInit, base64 encodes it, and resubmits the GET with
<para>4a. If more processing is required another HTTP 401 is returned to the client with more data in the the WWW-Authenticate header. Client takes the info and generates another token passing this back in the Authorization header.... until complete.</para> an Authorization header: &quot;Authorization: Negotiate &lt;base64
<para>5. When the client has been authenticated the Web server should return the HTTP 200 status, a final WWW-Authenticate header and the page content.</para> encoding&gt;&quot;.</para>
<para>4. Server decodes the NegTokenInit, extracts the supported MechTypes (only
Kerberos V5 in our case), ensures it is one of the expected ones, and then extracts
the MechToken (Kerberos Token) and authenticates it.</para>
<para>4a. If more processing is required another HTTP 401 is returned to the client with
more data in the the WWW-Authenticate header. Client takes the info and generates
another token passing this back in the Authorization header.... until
complete.</para>
<para>5. When the client has been authenticated the Web server should return the HTTP
200 status, a final WWW-Authenticate header and the page content.</para>
</sidebar> </sidebar>
<section> <section>
<title>HTTPClient Implementation</title> <title>HTTPClient Implementation</title>
<para>Supports Sun Java versions 1.5 and up.</para> <para>Supports Sun Java versions 1.5 and up.</para>
<para>The JRE provides the supporting classes to do nearly all the kerberos and SPNEGO token handling. This means that a lot of the setup <para>The JRE provides the supporting classes to do nearly all the kerberos and SPNEGO
is for the GSS classes. The NegotiateScheme is a simple class to handle marshalling the tokens and reading and writing the correct headers.</para> token handling. This means that a lot of the setup is for the GSS classes. The
NegotiateScheme is a simple class to handle marshalling the tokens and reading and
writing the correct headers.</para>
</section> </section>
<section> <section>
<title>Usage.</title> <title>Usage.</title>
<para>The best way to start is to grab the KerberosHttpClient.java file in examples and try and get it to work. <para>The best way to start is to grab the KerberosHttpClient.java file in examples and
There are a lot of issues that can happen but if lucky it'll work without too much problem. It should also try and get it to work. There are a lot of issues that can happen but if lucky it'll
provide some output to debug with.</para> work without too much problem. It should also provide some output to debug
with.</para>
<para>In windows it should default to using the logged in credentials, this <para>In windows it should default to using the logged in credentials, this can be
can be overridden by using 'kinit' e.g. <literal>$JAVA_HOME\bin\kinit testuser@AD.EXAMPLE.NET</literal>, which is very overridden by using 'kinit' e.g. <literal>$JAVA_HOME\bin\kinit
helpful for testing and debugging issues. Remove the cache file created to revert back to the windows testuser@AD.EXAMPLE.NET</literal>, which is very helpful for testing and
Kerberos cache.</para> debugging issues. Remove the cache file created to revert back to the windows
<para>Make sure to list domain_realms in the krb5.conf file. This is a major source of problems.</para> Kerberos cache.</para>
<para>Make sure to list domain_realms in the krb5.conf file. This is a major source of
problems.</para>
</section> </section>
<section> <section>
<title>NegotiateSchemeFactory Class</title> <title>NegotiateSchemeFactory Class</title>
@ -379,35 +428,44 @@ Kerberos cache.</para>
<section> <section>
<title>setStripPort(boolean)</title> <title>setStripPort(boolean)</title>
<para>Strips the port off service names e.g. HTTP/webserver.ad.example.net:8080 -> HTTP/webserver.ad.example.net</para> <para>Strips the port off service names e.g. HTTP/webserver.ad.example.net:8080 ->
HTTP/webserver.ad.example.net</para>
<para>Found it useful when using JbossNegotiation.</para> <para>Found it useful when using JbossNegotiation.</para>
</section> </section>
<section> <section>
<title> setSpnegoCreate(boolean)</title> <title> setSpnegoCreate(boolean)</title>
<para>If using Java 1.5 or a Kerberos ticket an attempt will be made to wrap it up into a SPNEGO token. Again for JbossNegotiation. II7 accepts plain Kerberos tickets.</para> <para>If using Java 1.5 or a Kerberos ticket an attempt will be made to wrap it up into
a SPNEGO token. Again for JbossNegotiation. II7 accepts plain Kerberos
tickets.</para>
</section> </section>
<section> <section>
<title>setSpengoGenerator(SpnegoTokenGenerator)</title> <title>setSpengoGenerator(SpnegoTokenGenerator)</title>
<para>Inject a custom SpnegoTokenGenerator class to do the Kerberos to SPNEGO token wrapping. BouncySpnegoTokenGenerator example <para>Inject a custom SpnegoTokenGenerator class to do the Kerberos to SPNEGO token
is provided. This requires the BouncyCastle libs <ulink url="http://www.bouncycastle.org/java.html">"http://www.bouncycastle.org/java.html"</ulink> wrapping. BouncySpnegoTokenGenerator example is provided. This requires the
</para> BouncyCastle libs <ulink url="http://www.bouncycastle.org/java.html"
>"http://www.bouncycastle.org/java.html"</ulink>
</para>
</section> </section>
<section> <section>
<title>GSS/Java Kerberos Setup.</title> <title>GSS/Java Kerberos Setup.</title>
<para>This documentation assumes you are using windows but much of the informationapplies to Unix as well.</para> <para>This documentation assumes you are using windows but much of the
<para>The org.ietf.jgss classes have lots of possible configuration parameters, informationapplies to Unix as well.</para>
mainly in the krb5.conf/krb5.ini file. Some more info on the format at <para>The org.ietf.jgss classes have lots of possible configuration parameters, mainly
<ulink url="http://web.mit.edu/kerberos/krb5-1.4/krb5-1.4.1/doc/krb5-admin/krb5.conf.html">http://web.mit.edu/kerberos/krb5-1.4/krb5-1.4.1/doc/krb5-admin/krb5.conf.html</ulink>.</para> in the krb5.conf/krb5.ini file. Some more info on the format at <ulink
url="http://web.mit.edu/kerberos/krb5-1.4/krb5-1.4.1/doc/krb5-admin/krb5.conf.html"
>http://web.mit.edu/kerberos/krb5-1.4/krb5-1.4.1/doc/krb5-admin/krb5.conf.html</ulink>.</para>
</section> </section>
<section> <section>
<title>login.conf file</title> <title>login.conf file</title>
<para>The following configuration is a basic setup that works in Windows XP against both IIS7 and JbossNegotiate modules.</para> <para>The following configuration is a basic setup that works in Windows XP against both
<para>The system property that can be use to point to the login.conf file is <emphasis>java.security.auth.login.config</emphasis>.</para> IIS7 and JbossNegotiate modules.</para>
<para>The system property that can be use to point to the login.conf file is
<emphasis>java.security.auth.login.config</emphasis>.</para>
<para>Sample usage...</para> <para>Sample usage...</para>
<programlisting>System.setProperty("java.security.auth.login.config", "login.conf");</programlisting> <programlisting>System.setProperty("java.security.auth.login.config", "login.conf");</programlisting>
@ -432,7 +490,7 @@ com.sun.security.jgss.accept {
<para>If unspecified the system default will be used. Override with...</para> <para>If unspecified the system default will be used. Override with...</para>
<programlisting>System.setProperty("java.security.krb5.conf", "krb5.conf");</programlisting> <programlisting>System.setProperty("java.security.krb5.conf", "krb5.conf");</programlisting>
<para>Example file contents...</para> <para>Example file contents...</para>
<programlisting><![CDATA[ <programlisting><![CDATA[
[libdefaults] [libdefaults]
default_realm = AD.EXAMPLE.NET default_realm = AD.EXAMPLE.NET