Pregunta

Necesito conectar Apache Axis 1.4 a un servicio web que utiliza la autenticación NTLM para restringir el acceso a sus operaciones. Estoy esperando para usar Samba JCIFS para manejar el protocolo de enlace NTLM.

He encontrado

http://hc.apache.org/httpcomponents-client/ntlm.html

lo que me da direcciones fantásticas para la forma de cablear HttpClient 4.0 con JCIFS.

El problema es que, Eje quiere usar cliente HTTP 3.0 y los dos apis un aspecto muy diferente.

Hay 2 posibilidades que puedo ver

  1. Escribir un objeto para el eje que le permite conectarlo a HttpClient 4.
  2. Averiguar cómo cablear HttpClient 3,0 con Samba JCIFS.

Número 1. parece no trivial, pero posible Número 2. No puedo encontrar ningún mensajes de ánimo en la web que describe cómo hacer esto.

Mi pregunta es: ¿alguien ha conectado con éxito JCIFS samba con HttpClient 3.0? Alguien ha creado un objeto Eje HttpSender que trabaja con HttpClient 4?

¿Hay alguna alternativa mejor que no he considerado?

¿Fue útil?

Solución

Por último tener una solución a esto.

El problema

Apache Axis utiliza HTTPClient Apache que proporciona su propia implementación de NTLM.
Sin embargo, esta aplicación es incompleta; sólo es compatible con la autenticación LM primitiva.
El sistema se necesita para conectarse a insiste en la autenticación NTLM más reciente.

Por lo tanto, mi servicio web estaba fallando para autenticar cuando se utiliza el cliente de Apache HTTP con NTLM.

En realidad, esto luego entra en un bucle infinito como el HTTPClient nunca dejará de intentar y fallar para autentificar.

La solución

JCIFS es totalmente compatible con todas las 3 versiones del protocolo de enlace NTLM.
Tengo org.apache.commons.httpclient.auth.NTLM de copiar y pegar en mi propia clase (se ha declarado como 'final' con el fin de derrotar a la herencia)

entonces he sobrescrito el método

public String getType3Message(
        String user, String password, String host, String domain,
        byte[] nonce) throws AuthenticationException

para construir una instancia de jcifs.ntlmssp.Type3Message y utilizar este objeto para devolver un Type3Message que tiene la autenticación NTLM generado correctamente.

Entonces necesitaba crear mi propia instancia de org.apache.commons.httpclient.auth.AuthScheme para hacer uso de esta nueva implementación NTLM. llamada org.apache.commons.httpclient.auth.AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, MyNewAuthScheme.class)

poner en marcha mi talón de WS punto final.

Y funciona !!!

Otros consejos

Gracias yo mucho Ben, un buen trabajo. Para mi solución que necesito 2 mejoras, en función de sus clases.

1) clase JcifsNtlmScheme

La interfaz ha cambiado en JCIFS (utilizo versión 1.3.14). Se requiere la bandera de NTLM, no estoy muy seguro, pero 0x82 funciona para mí.

int flags = Type3Message.NTLMSSP_NEGOTIATE_OEM | Type3Message.NTLMSSP_NEGOTIATE_LM_KEY;

Type3Message msg3 =
        new Type3Message(msg2, ntcredentials.getPassword(),
        ntcredentials.getDomain(), ntcredentials.getUserName(), ntcredentials.getHost(), flags);

2) NtlmJcifsCredentials clase

 DefaultHttpParams.setHttpParamsFactory(paramFact);

Esto funciona muy bien para la primera conexión. Parece que es un ajuste global. Probablemente no sea muy seguro para subprocesos. Necesito las credenciales de base de conexiones. Así que dejé esta clase y se inserta el built-in Authenticator directamente después de la creación del talón de servicio web:

jcifs.Config.setProperty("jcifs.encoding", "ASCII");
AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, JcifsNtlmScheme.class);

Authenticator authenticator = new Authenticator();
List<String> authScheme = new ArrayList<String>();
authScheme.add(Authenticator.NTLM);
authScheme.add(Authenticator.BASIC);
authenticator.setAuthSchemes(authScheme);
authenticator.setUsername(myusername);
authenticator.setPassword(mypassword);
authenticator.setHost(servername);
authenticator.setDomain(domain);        
exService._getServiceClient().getOptions().setProperty(HTTPConstants.AUTHENTICATE, authenticator);       
exService._getServiceClient().getOptions().setProperty(HTTPConstants.CHUNKED, Boolean.FALSE);
exService._getServiceClient().getOptions().setProperty(HTTPConstants.REUSE_HTTP_CLIENT, Boolean.TRUE);

En respuesta al comentario de Sergey ...

Tengo dos clases en mi solución. Un régimen de autorización como esto


import java.io.IOException;

import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.auth.AuthChallengeParser;
import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.auth.InvalidCredentialsException;
import org.apache.commons.httpclient.auth.MalformedChallengeException;
import org.apache.commons.httpclient.auth.NTLMScheme;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * AuthScheme that delegates the work of reading and writing NTLM messages to
 * the JCIFS implementation
 * 
 * directly inspired by org.apache.commons.httpclient.auth.NTLMScheme
 * 
 *
 * This software is based upon voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 */
public class JcifsNtlmScheme implements AuthScheme
{
    /** Log object for this class. */
    private static final Log LOG = LogFactory.getLog(NTLMScheme.class);

    /** NTLM challenge string. */
    private String ntlmchallenge = null;

    private static final int UNINITIATED = 0;
    private static final int INITIATED = 1;
    private static final int TYPE1_MSG_GENERATED = 2;
    private static final int TYPE2_MSG_RECEIVED = 3;
    private static final int TYPE3_MSG_GENERATED = 4;
    private static final int FAILED = Integer.MAX_VALUE;

    /** Authentication process state */
    private int state;

    /**
     * Default constructor for the NTLM authentication scheme.
     * 
     * @since 3.0
     */
    public JcifsNtlmScheme()
    {
        super();
        this.state = UNINITIATED;
    }

    /**
     * Constructor for the NTLM authentication scheme.
     * 
     * @param challenge
     *            The authentication challenge
     * 
     * @throws MalformedChallengeException
     *             is thrown if the authentication challenge is malformed
     */
    public JcifsNtlmScheme(final String challenge)
            throws MalformedChallengeException
    {
        super();
        processChallenge(challenge);
    }

    /**
     * Processes the NTLM challenge.
     * 
     * @param challenge
     *            the challenge string
     * 
     * @throws MalformedChallengeException
     *             is thrown if the authentication challenge is malformed
     * 
     * @since 3.0
     */
    public void processChallenge(final String challenge)
            throws MalformedChallengeException
    {
        String s = AuthChallengeParser.extractScheme(challenge);
        if (!s.equalsIgnoreCase(getSchemeName()))
        {
            throw new MalformedChallengeException("Invalid NTLM challenge: "
                    + challenge);
        }
        int i = challenge.indexOf(' ');
        if (i != -1)
        {
            s = challenge.substring(i, challenge.length());
            this.ntlmchallenge = s.trim();
            this.state = TYPE2_MSG_RECEIVED;
        }
        else
        {
            this.ntlmchallenge = "";
            if (this.state == UNINITIATED)
            {
                this.state = INITIATED;
            }
            else
            {
                this.state = FAILED;
            }
        }
    }

    /**
     * Tests if the NTLM authentication process has been completed.
     * 
     * @return true if Basic authorization has been processed,
     *         false otherwise.
     * 
     * @since 3.0
     */
    public boolean isComplete()
    {
        return this.state == TYPE3_MSG_GENERATED || this.state == FAILED;
    }

    /**
     * Returns textual designation of the NTLM authentication scheme.
     * 
     * @return ntlm
     */
    public String getSchemeName()
    {
        return "ntlm";
    }

    /**
     * The concept of an authentication realm is not supported by the NTLM
     * authentication scheme. Always returns null.
     * 
     * @return null
     */
    public String getRealm()
    {
        return null;
    }

    /**
     * Unsupported.
     */
    public String getID()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the authentication parameter with the given name, if available.
     * 
     * 

* There are no valid parameters for NTLM authentication so this method * always returns null. *

* * @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; } /** * Returns true. NTLM authentication scheme is connection based. * * @return true. * * @since 3.0 */ public boolean isConnectionBased() { return true; } /** * Unsupported. */ public static String authenticate( final NTCredentials credentials, final String challenge) throws AuthenticationException { throw new UnsupportedOperationException(); } /** * Unsupported. */ public static String authenticate( final NTCredentials credentials, final String challenge, String charset) throws AuthenticationException { throw new UnsupportedOperationException(); } /** * Unsupported. */ public String authenticate( Credentials credentials, String method, String uri) throws AuthenticationException { throw new UnsupportedOperationException(); } /** * Produces NTLM authorization string for the given set of * {@link Credentials}. * * @param credentials * The set of credentials to be used for athentication * @param method * The method being authenticated * * @throws InvalidCredentialsException * if authentication credentials are not valid or not applicable * for this authentication scheme * @throws AuthenticationException * if authorization string cannot be generated due to an * authentication failure * * @return an NTLM authorization string * * @since 3.0 */ public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { LOG.trace("enter NTLMScheme.authenticate(Credentials, HttpMethod)"); if (this.state == UNINITIATED) { throw new IllegalStateException( "NTLM authentication process has not been initiated"); } NTCredentials ntcredentials = null; try { ntcredentials = (NTCredentials) credentials; } catch (ClassCastException e) { throw new InvalidCredentialsException( "Credentials cannot be used for NTLM authentication: " + credentials.getClass().getName()); } byte[] msgBytes = null; String response = null; if (this.state == INITIATED) { Type1Message msg = new Type1Message(); // @see http://davenport.sourceforge.net/ntlm.html#theType1Message // dont' support Unicode // negotiate OEM // request authentication realm in Type2 response // not signed // not encrypted // not authenticated // no lan manager key // negotiate NTLM msg.setFlags(0x5206); msg.setSuppliedWorkstation(ntcredentials.getHost()); msg.setSuppliedDomain(ntcredentials.getDomain()); msgBytes = msg.toByteArray(); this.state = TYPE1_MSG_GENERATED; } else if (this.state == TYPE2_MSG_RECEIVED) { byte[] msg2Bytes = Base64.decodeBase64(EncodingUtil.getBytes( this.ntlmchallenge, method.getParams().getCredentialCharset())); try { Type2Message msg2 = new Type2Message(msg2Bytes); Type3Message msg3 = new Type3Message(msg2, ntcredentials.getPassword(), ntcredentials.getDomain(), ntcredentials .getUserName(), ntcredentials.getHost()); msgBytes = msg3.toByteArray(); } catch (IOException ex) { throw new AuthenticationException( "unable to parse Type2Message", ex); } this.state = TYPE3_MSG_GENERATED; } else { throw new RuntimeException("failed to authenticate"); } response = EncodingUtil.getAsciiString(Base64.encodeBase64(msgBytes)); return "NTLM " + response; } }

Y una clase para registrar el régimen de autorización, como este


import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
import org.apache.commons.httpclient.auth.CredentialsProvider;
import org.apache.commons.httpclient.params.DefaultHttpParams;
import org.apache.commons.httpclient.params.DefaultHttpParamsFactory;
import org.apache.commons.httpclient.params.HttpParams;

/**
 * registers NTLM authentication for apache axis
 * 
 */
public class NtlmJcifsCredentials
{
    public static void register(String password)
    {
        final String username = System.getProperty("user.name");
        final String computername = System.getenv("COMPUTERNAME");
        final String userDomain = System.getenv("USERDOMAIN");
        register(username, password, computername, userDomain);
    }
    public static void register(String username, String password, String userDomain)
    {
        final String computername = System.getenv("COMPUTERNAME");
        register(username, password, computername, userDomain);
    }

    public static void register(
            String username, String password, String computername, String domain)
    {
        final NTCredentials ntCred =
                new NTCredentials(username, password, computername, domain);

        final CredentialsProvider ntlmCredProvider = new CredentialsProvider()
        {
            public Credentials getCredentials(
                    AuthScheme scheme, String host, int port, boolean proxy)
                    throws CredentialsNotAvailableException
            {
                return ntCred;
            }
        };
        final DefaultHttpParamsFactory paramFact =
                new DefaultHttpParamsFactory()
                {
                    @Override
                    protected HttpParams createParams()
                    {
                        HttpParams htp = super.createParams();
                        htp.setParameter(
                                CredentialsProvider.PROVIDER,
                                ntlmCredProvider);
                        return htp;
                    }
                };
        DefaultHttpParams.setHttpParamsFactory(paramFact);

        // we want all our jcifs encoding to be ascii
        jcifs.Config.setProperty("jcifs.encoding", "ASCII");

        // our jcifs implemented NTLM is required for MDW's authentication
        AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, JcifsNtlmScheme.class);
    }
}

En tiempo de ejecución que llamo

 NtlmJcifsCredentials.register(username, password, domain) 

construyo mi talón de punto final y simplemente funciona. Como efecto secundario beneficioso esto simplemente tirar una excepción si si no puede autenticar - el defecto de clase Apache Commons seguirá intentando conectar infinitamente -. Que en el caso de NTLM puede conducir fácilmente a su cuenta sea bloqueada de las ventanas

Tengo que trabajar, pero aún no implementó el soporte de servidor proxy en el HTTP. http://www.magsoft.nl/share/Axis2%20patch.zip Todos los frascos que uso están en el directorio lib del proyecto. Hay algunos requisitos de trayectoria clase. En primer lugar el Axis2 HTTPClient4 patch.jar tiene que estar por encima de los frascos de eje. Por otra parte, commons-httpclient-3.1.jar tiene que estar todavía en la ruta de clases, pero después de los httpclient-4 frascos.

Así es como he implementado el cliente:


Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
HttpParams httpParms = new BasicHttpParams();
ClientConnectionManager connManager = new ThreadSafeClientConnManager(httpParms, sr);
DefaultHttpClient httpclient = new DefaultHttpClient(connManager, httpParms);
httpclient.getAuthSchemes().register(HttpTransportProperties.Authenticator.NTLM, new NTLMSchemeFactory());
httpclient.getCredentialsProvider().setCredentials(new AuthScope(host, -1), new NTCredentials(user, pass, host, domain));

sps = new SharepointServiceStub(addr.toString());

List authScheme = new ArrayList();
authScheme.add(HttpTransportProperties.Authenticator.NTLM);
HttpTransportProperties.Authenticator auth = new HttpTransportProperties.Authenticator();
auth.setHost(host);
auth.setDomain(domain);
auth.setUsername(user);
auth.setPassword(pass);
auth.setAuthSchemes(authScheme);
Options options = sps._getServiceClient().getOptions();
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.REUSE_HTTP_CLIENT, true);
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.CACHED_HTTP_CLIENT, httpclient);
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, auth);
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, Boolean.TRUE);
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.CONNECTION_TIMEOUT, 900000); // 15 minutes
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.SO_TIMEOUT, 180000); // 3 minutes

Sin embargo, para que esto funcione, necesitará las siguientes clases de árboles: NTLMSchemeFactory.java


package ...;

import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.impl.auth.NTLMScheme;
import org.apache.http.params.HttpParams;

public class NTLMSchemeFactory implements AuthSchemeFactory
{

    public NTLMSchemeFactory()
    {
    }

    public AuthScheme newInstance(final HttpParams params)
    {
        return new NTLMScheme(new JCIFSEngine());
    }

}

JCIFSScheme.java


package ...;

import org.apache.http.impl.auth.NTLMScheme;



public class JCIFSScheme extends NTLMScheme
{
    public JCIFSScheme()
    {
        super(new JCIFSEngine());
    }
}

JCIFSEngine.java


package ...;

import java.io.IOException;

import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;

import org.apache.http.impl.auth.NTLMEngine;
import org.apache.http.impl.auth.NTLMEngineException;

public class JCIFSEngine implements NTLMEngine
{
    public String generateType1Msg(String domain, String workstation) throws NTLMEngineException
    {

        Type1Message t1m = new Type1Message(Type1Message.getDefaultFlags(), domain, workstation);
        return Base64.encode(t1m.toByteArray());
    }


    public String generateType3Msg(String username, String password, String domain, String workstation, String challenge)
            throws NTLMEngineException
    {
        Type2Message t2m;
        try
        {
            t2m = new Type2Message(Base64.decode(challenge));
        } catch (IOException ex)
        {
            throw new NTLMEngineException("Invalid Type2 message", ex);
        }
        Type3Message t3m = new Type3Message(t2m, password, domain, username, workstation, 0);
        return Base64.encode(t3m.toByteArray());
    }

}

Axis2Patch.zip ha sido un verdadero salvavidas. Esto es lo que hice:

Axis2Patch Compilado con beta1 httpclient4.1 que tiene NTLMv2 construir en. Importada que en mi proyecto e importados httpclient4.1beta1 también.

Me cambié de las importaciones como esto:

import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.auth.NTLMScheme;
//import org.apache.commons.httpclient.NTCredentials;
//import org.apache.commons.httpclient.auth.AuthPolicy;

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.params.AuthPolicy;

y sin demasiado cambio de código que funciona perfectamente. Gracias!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top