问题是 Axis 1.4 没有正确实现 NTLM V2 协议。
我遇到了 Sharepoint 2010 Web 服务的问题。我有一个客户端与在 Windows 2003 服务器上运行的 Sharepoint 2007 完美配合。然后,我用在 Windows 2008 R2 服务器上运行的 Sharepoint 2010 Web 服务测试了这个客户端,它们停止工作。错误是:
Caused by: (401)Unauthorized
at org.apache.axis.transport.http.CommonsHTTPSender.invoke(CommonsHTTPSender.java:218)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
at org.apache.axis.client.AxisClient.invoke(AxisClient.java:165)
at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
at org.apache.axis.client.Call.invoke(Call.java:2767)
at org.apache.axis.client.Call.invoke(Call.java:2443)
at org.apache.axis.client.Call.invoke(Call.java:2366)
at org.apache.axis.client.Call.invoke(Call.java:1812)
在google中搜索,问题是Windows 2003默认使用NTLM V1协议,而Windows 2008 R2默认使用NTLM V2。
我在以下网址中找到了解决方案和问题的完美解释:
http://devsac.blogspot.com.es/2010/10/supoprt-for-ntlmv2-with-apache.html
解决方案是创建以下类来解析 HttpClient 3.x:
public class JCIFS_NTLMScheme implements AuthScheme {
private static AppLogger logger = new AppLogger(HTTPHelper.class.getName());
/** 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;
public JCIFS_NTLMScheme() throws AuthenticationException {
// Check if JCIFS is present. If not present, do not proceed.
try {
Class.forName("jcifs.ntlmssp.NtlmMessage",false,this.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
throw new AuthenticationException("Unable to proceed as JCIFS library is not found.");
}
}
public String authenticate(Credentials credentials, HttpMethod method)
throws AuthenticationException {
logger.doLog(AppLogger.FINEST,
"Enter JCIFS_NTLMScheme.authenticate(Credentials, HttpMethod)",
null);
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());
}
NTLM ntlm = new NTLM();
ntlm.setCredentialCharset(method.getParams().getCredentialCharset());
String response = null;
if (this.state == INITIATED || this.state == FAILED) {
response = ntlm.generateType1Msg(ntcredentials.getHost(),
ntcredentials.getDomain());
this.state = TYPE1_MSG_GENERATED;
} else {
response = ntlm.generateType3Msg(ntcredentials.getUserName(),
ntcredentials.getPassword(), ntcredentials.getHost(),
ntcredentials.getDomain(), this.ntlmchallenge);
this.state = TYPE3_MSG_GENERATED;
}
return "NTLM " + response;
}
public String authenticate(Credentials credentials, String method,
String uri) throws AuthenticationException {
throw new RuntimeException(
"Not implemented as it is deprecated anyway in Httpclient 3.x");
}
public String getID() {
throw new RuntimeException(
"Not implemented as it is deprecated anyway in Httpclient 3.x");
}
/**
* Returns the authentication parameter with the given name, if available.
*
* <p>
* There are no valid parameters for NTLM 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 NTLM
* authentication scheme. Always returns <code>null</code>.
*
* @return <code>null</code>
*/
public String getRealm() {
return null;
}
/**
* Returns textual designation of the NTLM authentication scheme.
*
* @return <code>ntlm</code>
*/
public String getSchemeName() {
return "ntlm";
}
/**
* Tests if the NTLM authentication process has been completed.
*
* @return <tt>true</tt> if Basic authorization has been processed,
* <tt>false</tt> otherwise.
*
* @since 3.0
*/
public boolean isComplete() {
return this.state == TYPE3_MSG_GENERATED || this.state == FAILED;
}
/**
* Returns <tt>true</tt>. NTLM authentication scheme is connection based.
*
* @return <tt>true</tt>.
*
* @since 3.0
*/
public boolean isConnectionBased() {
return true;
}
/**
* 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;
}
}
}
private class NTLM {
/** Character encoding */
public static final String DEFAULT_CHARSET = "ASCII";
/**
* The character was used by 3.x's NTLM to encode the username and
* password. Apparently, this is not needed in when passing username,
* password from NTCredentials to the JCIFS library
*/
private String credentialCharset = DEFAULT_CHARSET;
void setCredentialCharset(String credentialCharset) {
this.credentialCharset = credentialCharset;
}
private String generateType1Msg(String host, String domain) {
jcifs.ntlmssp.Type1Message t1m = new jcifs.ntlmssp.Type1Message(jcifs.ntlmssp.Type1Message.getDefaultFlags(),
domain, host);
return jcifs.util.Base64.encode(t1m.toByteArray());
}
private String generateType3Msg(String username, String password, String host,
String domain, String challenge) {
jcifs.ntlmssp.Type2Message t2m;
try {
t2m = new jcifs.ntlmssp.Type2Message(jcifs.util.Base64.decode(challenge));
} catch (IOException e) {
throw new RuntimeException("Invalid Type2 message", e);
}
jcifs.ntlmssp.Type3Message t3m = new jcifs.ntlmssp.Type3Message(t2m, password, domain,
username, host, 0);
return jcifs.util.Base64.encode(t3m.toByteArray());
}
}
}
然后使用以下命令注册新的 JCIFS_NTLMScheme 类作为 NTLMScheme 的替代品:
AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, org.xyz.JCIFS_NTLMScheme.class);
感谢 Sachin 的 Tech Place