Как я могу заставить jcifs хорошо играть с осью apache - PullRequest
2 голосов
/ 27 мая 2009

Мне нужно подключить Apache Axis 1.4 к веб-сервису, который использует проверку подлинности NTLM для ограничения доступа к своим операциям. Я ожидаю использовать Samba Jcifs для обработки рукопожатия NTLM.

Я нашел

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

, который дает мне фантастические указания о том, как подключить HttpClient 4.0 с jcifs.

Проблема в том, что Axis хочет использовать Http Client 3.0, и эти два API выглядят очень по-разному.

Есть 2 варианта, которые я вижу

  1. Напишите объект для Axis, который позволит ему подключаться к HttpClient 4.
  2. Узнайте, как подключить HttpClient 3.0 к Samba Jcifs.

Номер 1. выглядит нетривиально, но возможно Номер 2. Я не могу найти какие-либо обнадеживающие сообщения в Интернете, описывающие, как это сделать.

У меня вопрос: кто-нибудь успешно подключал samba jcifs с HttpClient 3.0? Кто-нибудь уже создал объект Axis HttpSender, который работает с HttpClient 4?

Есть ли лучшая альтернатива, которую я не рассматривал?

Ответы [ 5 ]

7 голосов
/ 04 сентября 2009

Наконец-то есть решение этой проблемы.

Проблема

Apache Axis использует Apache HTTPClient, который обеспечивает собственную реализацию NTLM.
Однако эта реализация является неполной; он поддерживает только примитивную аутентификацию LM.
Система, к которой мне нужно подключиться, настаивает на более поздней аутентификации NTLM.

Поэтому мой веб-сервис не смог пройти аутентификацию при использовании HTTP-клиента Apache с NTLM.

Затем он фактически входит в бесконечный цикл, поскольку HTTPClient никогда не прекратит попытки и не сможет пройти аутентификацию.

Решение

jcifs полностью поддерживает все 3 версии рукопожатия NTLM.
Я скопировал и вставил org.apache.commons.httpclient.auth.NTLM в свой собственный класс (он объявлен как «окончательный», чтобы победить наследство)

Затем я переписал метод

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

для создания экземпляра jcifs.ntlmssp.Type3Message и используйте этот объект для возврата Type3Message, в котором правильно сгенерирована аутентификация NTML.

Затем мне нужно было создать свой собственный экземпляр org.apache.commons.httpclient.auth.AuthScheme использовать эту новую реализацию NTLM. вызов org.apache.commons.httpclient.auth.AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, MyNewAuthScheme.class)

запустите мою заглушку конечной точки WS.

И это работает !!!

2 голосов
/ 06 апреля 2010

Большое спасибо, Бен, хорошая работа. Для моего решения мне нужно 2 улучшения, основанные на ваших классах.

1) класс JcifsNtlmScheme

Интерфейс изменился в jcifs (я использую версию 1.3.14). Требуется флаг NTLM, я не совсем уверен, но у меня работает 0x82.

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

 DefaultHttpParams.setHttpParamsFactory(paramFact);

Это прекрасно работает для первого соединения. Кажется, это глобальная обстановка. Это, вероятно, не очень потокобезопасный. Мне нужны учетные данные на базе соединений. Поэтому я удалил этот класс и вставил встроенный Authenticator сразу после создания заглушки веб-сервиса:

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);
1 голос
/ 28 января 2011

Этот Axis2Patch.zip был настоящим спасателем. Вот что я сделал:

Скомпилированный Axis2Patch с httpclient4.1 beta1 со встроенной NTLMv2 Импортировал это в мой проект и импортировал httpclient4.1beta1.

Я изменил свой импорт следующим образом:

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;

и без слишком большого изменения кода работает отлично. Спасибо!

1 голос
/ 09 марта 2010

У меня это работает, но я НЕ реализовал поддержку прокси-сервера в HTTP. http://www.magsoft.nl/share/Axis2%20patch.zip Все банки, которые я использую, находятся в каталоге lib проекта. Есть некоторые требования к классам. Сначала файл Axis2 HTTPClient4 patch.jar должен находиться над осью jars. Кроме того, commons-httpclient-3.1.jar должен оставаться в пути к классам, но после jar httpclient-4.

Вот как я реализовал клиент:


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

Но для этого вам понадобятся следующие классы дерева: 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());
    }

}
1 голос
/ 13 октября 2009

В ответ на комментарий Сергея ...

У меня есть два предложения в моем решении. Схема авторизации, подобная этой


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 <code>org.apache.commons.httpclient.auth.NTLMScheme</code>
 * 
 *
 * 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 <code>ntlm</code>
     */
    public String getSchemeName()
    {
        return "ntlm";
    }

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

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

    /**
     * Returns the authentication parameter with the given name, if available.
     * 
     * <p>
     * There are no valid parameters for NTLM authentication so this method
     * always returns null.
     * </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;
    }

    /**
     * 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;
    }
}

И класс для регистрации схемы авторизации, вот так


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);
    }
}

Во время выполнения я звоню

 NtlmJcifsCredentials.register(username, password, domain) 

Я создаю свою заглушку конечной точки, и она просто работает. В качестве полезного побочного эффекта это просто вызовет исключение, если в случае невозможности аутентификации класс по умолчанию Apache Commons будет пытаться бесконечно подключаться, что в случае NTLM может легко привести к блокировке вашей учетной записи из окон. *

...