java - 401 : Unauthorized Exception occurred with an apache axis client(java) to invoke a webservice(. 网络)使用 NTLM 身份验证技术

标签 java web-services webservice-client ntlm axis

我正在调用一个在 IIS 服务器下远程运行的 .net 中编写的 Web 服务。

我使用 apache axis 1.4 和 eclipse IDE 创建了各自的 stub ,并创建了各自的 web 服务客户端。这实际上只是一个测试客户端,我的 Web 应用程序将调用此 Web 服务。

我们保留了其两个不同的端点以保持安全凭证启用/禁用。

  1. “ip:port/pigeon/pigeon.svc;”//身份验证已禁用
  2. "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

我想传递这种格式的凭据:
1) 域\用户名
2)密码

我尝试在此处添加其他帖子的建议,其中说在 stub 中设置相应的调用前方法,但我得到了与上述相同的异常。

(mystub)._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, domain + "\" + username);
(mystub)._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, password);

然而,通过一些搜索,现在我可以通过调用我的远程 .net Web 服务的 java 独立程序进行 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();
}

但我无法使用我的 Axis 客户端执行此操作,因为 stub 是使用我的 Web 服务客户端中的 .net Web 服务提供的 wsdl 生成的。我尝试根据上面的演示通过修改在 invoke() 调用之前更改 @stub 级别,但它会抛出相同的未经授权的异常。

这只是 fyi/所有使用 NTLM 身份验证技术的远程 IIS 服务器。

希望在使用 java 的 Windows 身份验证上提供帮助,以便将安全凭证传递给 IIS。

[注意:我的 axis 客户端 (java) 通过域\用户和密码,这是在另一端正确配置的 IIS 服务器]

最佳答案

问题是 Axis 1.4 没有正确实现 NTLM V2 协议(protocol)。

我遇到了 Sharepoint 2010 网络服务的问题。我有一个客户端可以与在 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)

在谷歌搜索,问题是Windows 2003默认使用NTLM V1协议(protocol),而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);

Thanks to Sachin's Tech Place

关于java - 401 : Unauthorized Exception occurred with an apache axis client(java) to invoke a webservice(. 网络)使用 NTLM 身份验证技术,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17855960/

相关文章:

ios - 临时存储图像并通过网络服务接收 URL

Java Jersey 服务器未获取 POST 参数

java - Spring Security 和 Web 服务 session

java - Stax处理指令

java - 使用Java获取域名

获取 SOAP 响应时出现 javax.xml.bind.UnmarshalException

c++ - 用于 C++ 的通用 WebService (SOAP) 客户端库

java - Apache HTTP 客户端 400 错误

java - 文字冒险游戏,添加时间和游戏结局

java - Netbeans 未检测到 JDK 安装