Как я могу заставить jcifs хорошо работать с осью Apache?
Вопрос
Мне нужно подключить Apache Axis 1.4 к веб-сервису, который использует аутентификацию NTLM, чтобы ограничить доступ к его операциям.Я планирую использовать Samba Jcifs для обработки рукопожатия NTLM.
я нашел
http://hc.apache.org/httpcomComponents-client/ntlm.html
который дает мне фантастические указания о том, как подключить HttpClient 4.0 с помощью jcifs.
Проблема в том, что Axis хочет использовать Http Client 3.0, а два API выглядят совершенно по-разному.
Есть 2 возможности, которые я вижу
- Напишите объект для Axis, который позволит ему подключаться к HttpClient 4.
- Выясните, как подключить HttpClient 3.0 к Samba Jcifs.
Номер 1.Выглядит нетривиально, но возможное число 2.Я не могу найти в Интернете обнадеживающих сообщений, описывающих, как это сделать.
Мой вопрос:Кто-нибудь успешно подключил samba jcifs к HttpClient 3.0?Кто-нибудь уже создал объект Axis HttpSender, который работает с HttpClient 4?
Есть ли лучшая альтернатива, которую я не рассмотрел?
Решение
Наконец-то есть решение этой проблемы.
Проблема
Apache Axis использует Apache HTTPClient
который обеспечивает собственную реализацию NTLM.
Однако эта реализация является неполной;он поддерживает только примитивную аутентификацию LM.
Система, к которой мне нужно подключиться, настаивает на более новой аутентификации NTLM.
Поэтому моему веб-сервису не удалось пройти аутентификацию при использовании HTTP-клиента Apache с NTLM.
Фактически это затем входит в бесконечный цикл, поскольку HTTPClient
никогда не перестанет пытаться и не сможет пройти аутентификацию.
Решение
jcifs полностью поддерживает все три версии подтверждения 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 улучшения, основанные на ваших классах.
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);
Это отлично работает для первого подключения.Похоже, это глобальная ситуация.Вероятно, это не совсем потокобезопасно.Мне нужны учетные данные для базы соединений.Поэтому я удалил этот класс и вставил встроенный аутентификатор сразу после создания заглушки веб-сервиса:
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);
В ответ на комментарий Сергея...
В моем решении есть два класса.Такая схема авторизации
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.
*
* * Для аутентификации NTLM нет действительных параметров, поэтому этот метод * всегда возвращает 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;
}
}
И класс для регистрации схемы авторизации, вот так
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 может легко привести к блокировке вашей учетной записи из Windows.
У меня это работает, но я еще НЕ реализовал поддержку прокси-сервера в HTTP.http://www.magsoft.nl/share/Axis2%20patch.zipВсе банки, которые я использую, находятся в каталоге lib проекта.Существуют некоторые требования к пути к классам.Сначала патч.jar Axis2 HTTPClient4 должен находиться над jar-файлами осей.Более того, 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());
}
}
Этот 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;
и без особых изменений кода он работает отлично.Спасибо!