You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2009/09/13 15:21:14 UTC

svn commit: r814311 [1/2] - in /httpcomponents/httpclient/trunk: contrib/org/apache/http/contrib/auth/ contrib/org/apache/http/contrib/conn/ httpclient/src/examples/org/apache/http/examples/client/ httpclient/src/main/java/org/apache/http/impl/auth/ sr...

Author: olegk
Date: Sun Sep 13 13:21:13 2009
New Revision: 814311

URL: http://svn.apache.org/viewvc?rev=814311&view=rev
Log:
HTTPCLIENT-523: SPNEGO auth scheme 
Contributed by Matthew Stevenson <mavricknzwork at yahoo.com>

Added:
    httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/
    httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java   (with props)
    httpcomponents/httpclient/trunk/src/docbkx/resources/images/SPNEGO.svg   (with props)
Removed:
    httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/conn/
Modified:
    httpcomponents/httpclient/trunk/src/docbkx/authentication.xml

Added: httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java?rev=814311&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java (added)
+++ httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java Sun Sep 13 13:21:13 2009
@@ -0,0 +1,105 @@
+package org.apache.http.contrib.auth;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.impl.auth.SpnegoTokenGenerator;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.util.ASN1Dump;
+
+/**
+ * Takes Kerberos ticket and wraps into a SPNEGO token. Leaving some optional fields out.
+ * Uses bouncycastle libs need a bcprov.jar
+ */
+public class BouncySpnegoTokenGenerator implements SpnegoTokenGenerator {
+    private static final Log LOG = LogFactory.getLog(BouncySpnegoTokenGenerator.class);
+    
+    private DERObjectIdentifier spnegoOid = new DERObjectIdentifier("1.3.6.1.5.5.2");
+    private DERObjectIdentifier kerbOid = new DERObjectIdentifier("1.2.840.113554.1.2.2");
+
+    public byte [] generateSpnegoDERObject(byte [] kerbTicket) throws IOException {
+        LOG.debug("enter: generateSpnegoDERObject(byte [] kerbTicket)");
+        DEROctetString ourKerberosTicket = new DEROctetString(kerbTicket);
+        
+        DERSequence kerbOidSeq = new DERSequence(kerbOid);
+        DERTaggedObject tagged0 = new DERTaggedObject(0, kerbOidSeq);
+//        DERBitString bits = new DERBitString(new byte[]{0x01});
+//        DERTaggedObject tagged1 = new DERTaggedObject(1, bits);
+        DERTaggedObject tagged2 = new DERTaggedObject(2, ourKerberosTicket);
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(tagged0);
+//        v.add(tagged1);
+        v.add(tagged2);
+        DERSequence seq = new DERSequence(v);
+        DERTaggedObject taggedSpnego = new DERTaggedObject(0, seq);
+        
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ASN1OutputStream asn1Out = new ASN1OutputStream(out);
+
+        ASN1Object spnegoOIDASN1 =  (ASN1Object) spnegoOid.toASN1Object();
+        ASN1Object taggedSpnegoASN1    =    (ASN1Object) taggedSpnego.toASN1Object();
+        
+        int length = spnegoOIDASN1.getDEREncoded().length + taggedSpnegoASN1.getDEREncoded().length;
+        byte [] lenBytes = writeLength(length);
+        byte[] appWrap = new byte[lenBytes.length + 1];
+        
+        appWrap[0] = 0x60;
+        for(int i=1; i < appWrap.length; i++){
+            appWrap[i] = lenBytes[i-1];
+        }
+        
+        asn1Out.write(appWrap);
+        asn1Out.writeObject(spnegoOid.toASN1Object());
+        asn1Out.writeObject(taggedSpnego.toASN1Object());
+
+        byte[] app = out.toByteArray();
+        ASN1InputStream in = new ASN1InputStream(app);
+
+        if( LOG.isDebugEnabled() ){
+            dumpToken(app);
+        }
+        
+        return in.readObject().getDEREncoded();
+    }
+
+    private byte [] writeLength(int length) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        if (length > 127) {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0) {
+                size++;
+            }
+            
+            out.write((byte) (size | 0x80));
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8) {
+                out.write((byte) (length >> i));
+            }
+        } else {
+            out.write((byte) length);
+        }
+        return out.toByteArray();
+    }
+    
+    protected void dumpToken(byte [] token) throws IOException{
+        int skip = 12;
+        byte [] manipBytes = new byte[token.length - skip];
+        for(int i=skip; i < token.length; i++){
+            manipBytes[i-skip] = token[i];
+        }
+        ASN1InputStream ourSpnego = new ASN1InputStream( manipBytes );
+        System.out.println(ASN1Dump.dumpAsString( ourSpnego.readObject() ) );
+    }
+    
+}

Propchange: httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/contrib/org/apache/http/contrib/auth/BouncySpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java?rev=814311&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java Sun Sep 13 13:21:13 2009
@@ -0,0 +1,218 @@
+/*
+ * ====================================================================
+ *
+ *  Copyright 2002-2009 The Apache Software Foundation
+ *
+ *  Licensed 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.http.examples.client;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthSchemeRegistry;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.impl.auth.NegotiateSchemeFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Kerberos auth example.
+ * <p>
+ * <b>krb5.conf</b>
+ * <pre>
+ * [libdefaults]
+ *     default_realm = AD.EXAMPLE.NET
+ *     udp_preference_limit = 1
+ * [realms]
+ *     AD.EXAMPLE.NET = {
+ *         kdc = AD.EXAMPLE.NET
+ *     }
+ *     DEV.EXAMPLE.NET = {
+ *         kdc = DEV.EXAMPLE.NET
+ *     }
+ * [domain_realms]
+ * .ad.example.net = AD.EXAMPLE.NET
+ * ad.example.net = AD.EXAMPLE.NET
+ * .dev.example.net = DEV.EXAMPLE.NET
+ * dev.example.net = DEV.EXAMPLE.NET
+ * gb.dev.example.net = DEV.EXAMPLE.NET
+ * .gb.dev.example.net = DEV.EXAMPLE.NET
+ * </pre>
+ * <b>login.conf</b>
+ * <pre>
+ *com.sun.security.jgss.login {
+ *   com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true debug=true;
+ *};
+ *
+ *com.sun.security.jgss.initiate {
+ *   com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true debug=true;
+ *};
+ *
+ *com.sun.security.jgss.accept {
+ *   com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true debug=true;
+ *};
+ * </pre>
+ */
+public class ClientKerberosAuthentication {
+    private static final Log LOG = LogFactory.getLog(ClientKerberosAuthentication.class);
+    private static String kerbHttpHost = "";
+    
+    public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException {
+        System.setProperty("java.security.auth.login.config", "login.conf");
+        System.setProperty("java.security.krb5.conf", "krb5.conf");
+        System.setProperty("sun.security.krb5.debug", "true");
+        System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
+        
+        if( args.length > 0 )
+            kerbHttpHost = args[0];
+        
+        /*        Below is helpful on windows.
+
+         Solution 2: You need to update the Windows registry to disable this new feature. The registry key allowtgtsessionkey should be added--and set correctly--to allow session keys to be sent in the Kerberos Ticket-Granting Ticket.
+
+         On the Windows Server 2003 and Windows 2000 SP4, here is the required registry setting:
+
+             HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
+             Value Name: allowtgtsessionkey
+             Value Type: REG_DWORD
+             Value: 0x01 
+
+         Here is the location of the registry setting on Windows XP SP2:
+
+             HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\
+             Value Name: allowtgtsessionkey
+             Value Type: REG_DWORD
+             Value: 0x01
+         */
+
+        DefaultHttpClient httpclient = new DefaultHttpClient();
+
+        AuthSchemeRegistry authSchemeRegistry = httpclient.getAuthSchemes();
+        authSchemeRegistry.unregister("basic");
+        authSchemeRegistry.unregister("digest");
+        authSchemeRegistry.unregister("NTLM");
+        
+        NegotiateSchemeFactory negotiateFact = new NegotiateSchemeFactory();
+        negotiateFact.setStripPort(false);
+        negotiateFact.setSpnegoCreate(false);
+//        negotiateFact.setSpengoGenerator(new BouncySpnegoTokenGenerator());
+        
+        authSchemeRegistry.register("Negotiate", negotiateFact);
+        //        authSchemeRegistry.register("NTLM", new NTLMSchemeFactory());
+        //        authSchemeRegistry.register("Basic", new BasicSchemeFactory());
+        httpclient.setAuthSchemes(authSchemeRegistry);
+
+        Credentials use_jaas_creds = new Credentials() {
+            // @Override
+            public String getPassword() {
+                return null;
+            }
+            // @Override
+            public Principal getUserPrincipal() {
+                return null;
+            }
+        };
+
+        httpclient.getCredentialsProvider().setCredentials(
+                new AuthScope(null, -1, null),
+                use_jaas_creds);
+
+        HttpUriRequest request = new HttpGet(kerbHttpHost);
+        HttpResponse response = null;
+        HttpEntity entity = null;
+
+        // ResponseHandler<String> responseHandler = new BasicResponseHandler();
+        String responseBody = null;
+        /* note the we use the 2 parameter execute call. */
+        /* also keepalives should be implemented, either set on server or code in client */
+        try{
+            // responseBody = httpclient.execute(request,  responseHandler, createHttpContext(httpclient));
+            response = httpclient.execute(request, createHttpContext(httpclient));
+            entity = response.getEntity();
+        } catch ( Exception ex){
+            LOG.debug(ex.getMessage(), ex);
+        }
+
+        System.out.println("----------------------------------------");
+        System.out.println(responseBody);
+        System.out.println("----------------------------------------");
+        if (entity != null) {
+            System.out.println("Response content length: " + entity.getContentLength());
+            entity.writeTo(System.out);
+        }
+        if (entity != null) {
+            entity.consumeContent();
+        }
+    }
+
+    /**
+     * createHttpContext - This is a copy of DefaultHttpClient method
+     * createHttpContext with "negotiate" added to AUTH_SCHEME_PREF to allow for 
+     * Kerberos authentication. Could also extend DefaultHttpClient overriding the
+     * default createHttpContext.
+     * 
+     * @param httpclient - our Httpclient
+     * @return HttpContext
+     */
+    static HttpContext createHttpContext(DefaultHttpClient httpclient){
+        HttpContext context = new BasicHttpContext();
+        context.setAttribute(
+                ClientContext.AUTHSCHEME_REGISTRY, 
+                httpclient.getAuthSchemes());
+        context.setAttribute(
+                ClientContext.AUTH_SCHEME_PREF, 
+                Collections.unmodifiableList( Arrays.asList(new String[] {
+                        "negotiate",
+                        "ntlm",
+                        "digest",
+                        "basic" 
+                }))
+        );
+        context.setAttribute(
+                ClientContext.COOKIESPEC_REGISTRY, 
+                httpclient.getCookieSpecs());
+        context.setAttribute(
+                ClientContext.COOKIE_STORE, 
+                httpclient.getCookieStore());
+        context.setAttribute(
+                ClientContext.CREDS_PROVIDER, 
+                httpclient.getCredentialsProvider());
+        return context;
+    }
+    
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java?rev=814311&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java Sun Sep 13 13:21:13 2009
@@ -0,0 +1,349 @@
+/*
+ * ====================================================================
+ *
+ *  Copyright 2002-2009 The Apache Software Foundation
+ *
+ *  Licensed 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.http.impl.auth;
+
+import java.io.IOException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.InvalidCredentialsException;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.message.BasicHeader;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class NegotiateScheme implements AuthScheme {
+    
+    private static final int UNINITIATED         = 0;
+    private static final int INITIATED           = 1;
+    private static final int NEGOTIATING         = 3;
+    private static final int ESTABLISHED         = 4;
+    private static final int FAILED              = Integer.MAX_VALUE;
+    private static final String SPNEGO_OID        = "1.3.6.1.5.5.2";
+    private static final String KERBEROS_OID        = "1.2.840.113554.1.2.2";
+    
+    private final Log log;
+    
+    /* stripPort removes ports from the generated Service Name.
+     * Helpful if you don't/can't have all SN:port combo's in AD/Directory.
+     * Probably a debatable addition.
+    */
+    private boolean stripPort = false;
+    /* spnegoCreate is used to generate an SPNEGO wrapper around
+     * for JDKs < 1.6.
+     */
+    private boolean spnegoCreate = false;
+    
+    private SpnegoTokenGenerator spengoGenerator = null;
+    
+    private GSSContext context = null;
+
+    /** Authentication process state */
+    private int state;
+
+    /** base64 decoded challenge **/
+    byte[] token = new byte[0];
+    
+    private Oid negotiationOid = null;
+    
+    /**
+     * Default constructor for the Negotiate authentication scheme.
+     * 
+     */
+    public NegotiateScheme() {
+        super();
+        log = LogFactory.getLog(getClass());
+        state = UNINITIATED;
+    }
+
+    /**
+     * Init GSSContext for negotiation.
+     * 
+     * @param server servername only (e.g: radar.it.su.se)
+     */
+    protected void init(String server) throws GSSException {
+        if (log.isDebugEnabled()) {
+            log.debug("init " + server);
+        }
+        /* 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 = GSSManager.getInstance();
+            GSSName serverName = manager.createName("HTTP/"+server, null); 
+            context = manager.createContext(
+                    serverName.canonicalize(negotiationOid), negotiationOid, null,
+                    GSSContext.DEFAULT_LIFETIME);
+            context.requestMutualAuth(true); 
+            context.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 = GSSManager.getInstance();
+            GSSName serverName = manager.createName("HTTP/"+server, null); 
+            context = manager.createContext(
+                    serverName.canonicalize(negotiationOid), negotiationOid, null,
+                    GSSContext.DEFAULT_LIFETIME);
+            context.requestMutualAuth(true); 
+            context.requestCredDeleg(true);
+        }
+        state = INITIATED;
+    }
+
+    /**
+     * Tests if the Negotiate authentication process has been completed.
+     * 
+     * @return <tt>true</tt> if authorization has been processed,
+     *   <tt>false</tt> otherwise.
+     * 
+     */
+    public boolean isComplete() {
+        return this.state == ESTABLISHED || this.state == FAILED;
+    }
+
+    /**
+     * Returns textual designation of the Negotiate authentication scheme.
+     * 
+     * @return <code>Negotiate</code>
+     */
+    public String getSchemeName() {
+        return "Negotiate";
+    }
+
+    /**
+     * Produces Negotiate authorization Header based on token created by 
+     * processChallenge.
+     * 
+     * @param credentials Never used be the Negotiate scheme but must be provided to 
+     * satisfy common-httpclient API. Credentials from JAAS will be used instead.
+     * @param request The request being authenticated
+     * 
+     * @throws AuthenticationException if authorisation string cannot 
+     *   be generated due to an authentication failure
+     * 
+     * @return an Negotiate authorisation Header
+     */
+
+    public Header authenticate(Credentials credentials,
+            HttpRequest request) throws AuthenticationException {
+        if (state == UNINITIATED) {
+            throw new IllegalStateException(
+                    "Negotiation authentication process has not been initiated");
+        }
+
+        try {
+
+            if (context==null) {
+                if (isStripPort()) {
+                    init( (request.getLastHeader("Host")).getValue().replaceAll(":[0-9]+$", "") );
+                } else {
+                    init( (request.getLastHeader("Host")).getValue());
+                }
+            }
+            
+            // HTTP 1.1 issue:
+            // Mutual auth will never complete to do 200 instead of 401 in 
+            // return from server. "state" will never reach ESTABLISHED
+            // but it works anyway
+
+            token = context.initSecContext(token, 0, token.length);
+            
+            /* 
+             * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish?
+             * seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token.
+             */
+            if(isSpnegoCreate() && negotiationOid.toString().equals(KERBEROS_OID)
+                    && spengoGenerator != null )
+                token = spengoGenerator.generateSpnegoDERObject(token);
+
+            if (log.isDebugEnabled()) {
+                log.info("got token, sending " + token.length + " bytes to server");
+            }
+        } catch (GSSException gsse) {
+            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 = FAILED;
+            throw new AuthenticationException(ex.getMessage());
+        }
+        return new BasicHeader("Authorization", "Negotiate " + 
+                new String(new Base64().encode(token)) );
+    }
+
+
+    /**
+     * Returns the authentication parameter with the given name, if available.
+     * 
+     * <p>There are no valid parameters for Negotiate authentication so this 
+     * method always returns <tt>null</tt>.</p>
+     * 
+     * @param name The name of the parameter to be returned
+     * 
+     * @return the parameter with the given name
+     */
+    public String getParameter(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Parameter name may not be null"); 
+        }
+        return null;
+    }
+
+    /**
+     * The concept of an authentication realm is not supported by the Negotiate 
+     * authentication scheme. Always returns <code>null</code>.
+     * 
+     * @return <code>null</code>
+     */
+    public String getRealm() {
+        return null;
+    }
+
+    /**
+     * Returns <tt>true</tt>. 
+     * Negotiate authentication scheme is connection based.
+     * 
+     * @return <tt>true</tt>.
+     */
+    public boolean isConnectionBased() {
+        return true;
+    }
+
+    /**
+     * Processes the Negotiate challenge.
+     *  
+     */
+    public void processChallenge(Header header) throws MalformedChallengeException {
+        if (log.isDebugEnabled()) {
+            log.debug("Challenge header: " + header);
+        }
+        String challenge = header.getValue();
+        
+        if (challenge.startsWith("Negotiate")) {
+            if(isComplete() == false)
+                state = NEGOTIATING;
+
+            if (challenge.startsWith("Negotiate ")){
+                token = new Base64().decode(challenge.substring(10).getBytes());
+                if (log.isDebugEnabled()) {
+                    log.debug("challenge = " + challenge.substring(10));
+                }
+            } else {
+                token = new byte[0];
+            }
+        }
+    }
+
+    public boolean isStripPort() {
+        return stripPort;
+    }
+
+    /**
+     * Strips the port off the Kerberos service name e.g. HTTP/webserver.ad.net:8080 -> HTTP/webserver.ad.net
+     * @param stripport
+     */
+    public void setStripPort(boolean stripport) {
+        if (stripport){
+            log.debug("Will strip ports off Service Names e.g. HTTP/server:8080 -> HTTP/server");
+        } else{
+            log.debug("Will NOT strip ports off Service Names e.g. HTTP/server:8080 -> HTTP/server");
+        }
+        stripPort = stripport;
+    }
+
+    /**
+     * Sould an attempt be made to wrap Kerberos ticket up as an SPNEGO token.
+     * Use only with Java <= 1.5
+     * @return
+     */
+    public boolean isSpnegoCreate() {
+        return spnegoCreate;
+    }
+
+    /**
+     * Set to true if an attempt should be made to wrap Kerberos ticket up as an SPNEGO token.
+     * Use only with Java <= 1.5
+     * @param spnegocreate - set to true do attempt SPNEGO wrapping 
+     */
+    public void setSpnegoCreate(boolean spnegocreate) {
+        spnegoCreate = spnegocreate;
+    }
+
+    /**
+     * Inject the class to be used to generate an SPNEGO token from a Kerberos ticket.
+     * Use only with Java <= 1.5 , tested against Jboss Negotiate.
+     * @param spengoGenerator - An SpnegoTokenGenerator implementation Class
+     */
+    public void setSpengoGenerator(SpnegoTokenGenerator SpengoGenerator) {
+        this.spengoGenerator = SpengoGenerator;
+    }
+    
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java?rev=814311&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java Sun Sep 13 13:21:13 2009
@@ -0,0 +1,74 @@
+/*
+ * ====================================================================
+ *
+ *  Copyright 2002-2009 The Apache Software Foundation
+ *
+ *  Licensed 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.http.impl.auth;
+
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.params.HttpParams;
+
+public class NegotiateSchemeFactory implements AuthSchemeFactory {
+    
+    private boolean stripPort = false; // strip port off kerb name
+    private boolean spnegoCreate = false; // generate an SPNEGO wrapper for JDKs < 1.6.
+    private SpnegoTokenGenerator spengoGenerator = null;
+    
+    public AuthScheme newInstance(final HttpParams params) {
+        NegotiateScheme negotiateScheme = new NegotiateScheme();
+        negotiateScheme.setStripPort(stripPort);
+        negotiateScheme.setSpnegoCreate(spnegoCreate);
+        negotiateScheme.setSpengoGenerator(spengoGenerator);
+        return new NegotiateScheme();
+    }
+
+    public NegotiateSchemeFactory(){
+        super();
+    }
+    
+    public void setStripPort(boolean stripPort) {
+        this.stripPort = stripPort;
+    }
+
+    public boolean isStripPort() {
+        return stripPort;
+    }
+
+    public void setSpnegoCreate(boolean spnegoCreate) {
+        this.spnegoCreate = spnegoCreate;
+    }
+
+    public boolean isSpnegoCreate() {
+        return spnegoCreate;
+    }
+
+    public void setSpengoGenerator(SpnegoTokenGenerator spengoGenerator) {
+        this.spengoGenerator = spengoGenerator;
+    }
+
+    public SpnegoTokenGenerator getSpengoGenerator() {
+        return spengoGenerator;
+    }
+    
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java?rev=814311&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java Sun Sep 13 13:21:13 2009
@@ -0,0 +1,12 @@
+package org.apache.http.impl.auth;
+
+import java.io.IOException;
+
+/**
+ * Implementations should take an Kerberos ticket and transform into a SPNEGO token.
+ */
+public interface SpnegoTokenGenerator {
+    
+    byte [] generateSpnegoDERObject(byte [] kerberosTicket) throws IOException;
+    
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpclient/trunk/src/docbkx/authentication.xml
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/src/docbkx/authentication.xml?rev=814311&r1=814310&r2=814311&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/src/docbkx/authentication.xml (original)
+++ httpcomponents/httpclient/trunk/src/docbkx/authentication.xml Sun Sep 13 13:21:13 2009
@@ -315,4 +315,150 @@
 httpclient.addRequestInterceptor(preemptiveAuth, 0);
 ]]></programlisting>
     </section>
+
+    <section>
+        <title><literal>SPNEGO</literal>/Kerberos Authentication</title>
+        <para><literal>SPNEGO</literal> (<emphasis>S</emphasis>imple and <emphasis>P</emphasis>rotected <literal>GSSAPI</literal>
+<emphasis>Nego</emphasis>tiation Mechanism) is designed to allow for authentication
+to services when neither end knows what the other can use/provide. It is most
+commonly used to do Kerberos authentication. It can wrap other mechanisms, however
+the current version in HTTPClient is designed solely with Kerberos in mind. The image below shows a simple authentication process.
+        </para>
+        <mediaobject>
+            <imageobject>
+                <imagedata fileref=".//images/SPNEGO_cropped.png" />
+            </imageobject>
+        </mediaobject>
+      	<sidebar>
+        <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>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>
+        <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>
+        <section>
+            <title>HTTPClient Implementation</title>
+
+            <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
+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>
+            <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.
+There are a lot of issues that can happen but if lucky it'll 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
+can be overridden by using 'kinit' e.g. <literal>$JAVA_HOME\bin\kinit testuser@AD.EXAMPLE.NET</literal>, which is very
+helpful for testing and debugging issues. Remove the cache file created to revert back to the windows
+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>
+            <title>NegotiateSchemeFactory Class</title>
+
+            <para>NegotiateScheme has a few class properties that can be set.</para>
+        </section>
+        <section>
+            <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>Found it useful when using JbossNegotiation.</para>
+        </section>
+        <section>
+            <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>
+        </section>
+        <section>
+            <title>setSpengoGenerator(SpnegoTokenGenerator)</title>
+
+            <para>Inject a custom SpnegoTokenGenerator class to do the Kerberos to SPNEGO token wrapping. BouncySpnegoTokenGenerator example
+is provided. This requires the BouncyCastle libs <ulink url="http://www.bouncycastle.org/java.html">"http://www.bouncycastle.org/java.html"</ulink>
+</para>
+        </section>
+        <section>
+            <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>The org.ietf.jgss classes have lots of possible configuration parameters,
+mainly 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>
+            <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 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>
+            <programlisting>System.setProperty("java.security.auth.login.config", "login.conf");</programlisting>
+
+            <para>Example file contents...</para>
+            <programlisting><![CDATA[
+com.sun.security.jgss.login {
+  com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true;
+};
+
+com.sun.security.jgss.initiate {
+  com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true;
+};
+
+com.sun.security.jgss.accept {
+  com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=true;
+};
+]]>
+</programlisting>
+        </section>
+        <section>
+            <title>krb5.conf/krb5.ini file</title>
+            <para>If unspecified the system default will be used. Override with...</para>
+            <programlisting>System.setProperty("java.security.krb5.conf", "krb5.conf");</programlisting>
+            <para>Example file contents...</para>
+            
+            <programlisting><![CDATA[
+[libdefaults]
+    default_realm = AD.EXAMPLE.NET
+    udp_preference_limit = 1
+[realms]
+    AD.EXAMPLE.NET = {
+        kdc = KDC.AD.EXAMPLE.NET
+    }
+[domain_realms]
+.ad.example.net=AD.EXAMPLE.NET
+ad.example.net=AD.EXAMPLE.NET
+]]>
+</programlisting>
+        </section>
+        <section>
+            <title>Windows Specific help</title>
+            <para>To allow windows to use the current users tickets do ...</para>
+            <para>System.setProperty("javax.security.auth.useSubjectCredsOnly","false");</para>
+            <para>and ...</para>
+            <programlisting><![CDATA[
+Solution 2: You need to update the Windows registry to disable this new feature.
+The registry key allowtgtsessionkey should be added and set correctly to allow
+session keys to be sent in the Kerberos Ticket-Granting Ticket.
+
+On the Windows Server 2003 and Windows 2000 SP4, here is the required registry setting:
+
+HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
+Value Name: allowtgtsessionkey
+Value Type: REG_DWORD
+Value: 0x01
+
+Here is the location of the registry setting on Windows XP SP2:
+
+HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\
+Value Name: allowtgtsessionkey
+Value Type: REG_DWORD
+Value: 0x01
+]]>
+</programlisting>
+        </section>
+    </section>
+
 </chapter>