401:Apache 轴客户端(java)使用 NTLM 身份验证技术调用 web 服务(.net)时发生未经授权的异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17855960/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
401: Unauthorized Exception occurred with an apache axis client(java) to invoke a webservice(.net) with an NTLM Authentication Technique
提问by Imran Khan
I am calling a web service written in .net located remotely running under IIS Server.
我正在调用一个用 .net 编写的 Web 服务,该服务位于远程运行在 IIS 服务器下。
I created its respective stub using apache axis 1.4
with eclipse IDE and created a respective web service client. This is just a test client in actual it will be my web application going to call this web service.
我使用apache axis 1.4
Eclipse IDE创建了各自的存根,并创建了各自的 Web 服务客户端。这实际上只是一个测试客户端,它将是我的 Web 应用程序将调用此 Web 服务。
We have kept its two different endpoint for keeping security credential enable/disable.
我们保留了两个不同的端点,用于保持安全凭证的启用/禁用。
- "ip:port/pigeon/pigeon.svc;" // authentication disabled
"ip:port/pwa/pigeon.svc; // authentiction enabled
So now when I am using endpoint no (1) I am able to call web service and gets things done, but since I want to apply security credential mandatory so when I use endpoint no (2) I am getting below exception
- "ip:port/pigeon/pigeon.svc;" // 身份验证被禁用
"ip:port/pwa/pigeon.svc; // 认证开启
所以现在当我使用端点号 (1) 时,我可以调用 Web 服务并完成任务,但是由于我想强制应用安全凭证,所以当我使用端点号 (2) 时,我遇到了异常
(401)Unauthorized
(401)Unauthorized AxisFault
faultCode: {http://xml.apache.org/axis/}HTTP
faultSubcode:
faultString: (401)Unauthorized
faultActor:
faultNode:
faultDetail: {}:return code: 401
(401)未经授权
(401)未经授权AxisFault
的faultcode:{ http://xml.apache.org/axis/} HTTP
faultSubcode:
faultString:(401)未经授权
faultActor:
faultNode:
faultDetail:{}:返回代码:401
I want to pass credential which are in this format :
1) domain\username
2) password
我想传递以下格式的凭据:
1) 域\用户名
2) 密码
I tried adding suggestion of other post over here which says set the respective before call method in stub but I am getting the same above mentioned exception.
我尝试在此处添加其他帖子的建议,其中说在存根中设置相应的 before call 方法,但我遇到了与上述相同的异常。
(mystub)._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, domain + "\" + username);
(mystub)._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, password);
(mystub)._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, domain + "\" + username);
(mystub)._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, 密码);
Hhowever with some search now I am able to do NTML authentication with java stand alone program invoking my remote .net web service by doing this :
但是,现在通过一些搜索,我可以使用 java 独立程序调用我的远程 .net Web 服务来执行 NTML 身份验证,方法如下:
public static void main(String[] args) throws Exception {
String urlStr = “http://example.com/root/action.dll?p1=value1″;
String domain = “”; // May also be referred as realm
String userName = “CHANGE_ME”;
String password = “CHANGE_ME”;
String responseText = getAuthenticatedResponse(urlStr, domain, userName, password);
System.out.println(”response: ” + responseText);
}
private static String getAuthenticatedResponse(final String urlStr, final String domain, final String userName, final String password) throws IOException {
StringBuilder response = new StringBuilder();
Authenticator.setDefault(new Authenticator() {
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(domain + “\” + userName, password.toCharArray());
}
});
URL urlRequest = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) urlRequest.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod(”GET”);
InputStream stream = conn.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String str = “”;
while ((str = in.readLine()) != null) {
response.append(str);
}
in.close();
return response.toString();
}
But I am not able to do so with my axis client as stub are generated with wsdl provided by .net web service in my web service client. I tried changing @stub level before invoke() call by modifying according to above demo but it throws same unauthorized exception.
但是我不能用我的轴客户端这样做,因为存根是用我的 web 服务客户端中的 .net web 服务提供的 wsdl 生成的。我尝试通过根据上面的演示进行修改来在 invoke() 调用之前更改 @stub 级别,但它引发了相同的未授权异常。
This is just fyi/all that remote IIS server using NTLM authentication technique.
这仅供参考/所有使用 NTLM 身份验证技术的远程 IIS 服务器。
Help expected on windows authentication using java in order to pass security credential to IIS.
使用 java 进行 Windows 身份验证的帮助,以便将安全凭证传递给 IIS。
[Note : my axis client(java) passes domain\user with password ,this is configured with IIS server on the otherside properly]
[注意:我的轴客户端(java)通过密码传递域\用户,这是在另一端正确配置了IIS服务器]
回答by David García González
The problem is that Axis 1.4 does not implement correctly the NTLM V2 protocol.
问题在于 Axis 1.4 没有正确实现 NTLM V2 协议。
I experienced the problem with Sharepoint 2010 web services. I had a client working perfectly with Sharepoint 2007 running on Windows 2003 server. Then, I tested this client with Sharepoint 2010 web services running on Windows 2008 R2 server and they stop working. The error was:
我遇到了 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)
Searching in google, the problem was that Windows 2003 is using NTLM V1 protocol as default, while Windows 2008 R2 was using NTLM V2 as default.
在谷歌搜索,问题是Windows 2003默认使用NTLM V1协议,而Windows 2008 R2默认使用NTLM V2。
I found the solution and the problem explained perfectly in the following url:
我找到了解决方案,问题在以下网址中得到了完美的解释:
http://devsac.blogspot.com.es/2010/10/supoprt-for-ntlmv2-with-apache.html
http://devsac.blogspot.com.es/2010/10/supoprt-for-ntlmv2-with-apache.html
The solution is creating the following class to resolve the HttpClient 3.x:
解决方案是创建以下类来解析 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());
}
}
}
}
Then it was Register the new JCIFS_NTLMScheme class as the replacement for NTLMScheme by using the following command:
然后使用以下命令将新的 JCIFS_NTLMScheme 类注册为 NTLMScheme 的替代品:
AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, org.xyz.JCIFS_NTLMScheme.class);