'401: Unauthorized Exception occurred with an apache axis client(java) to invoke a webservice(.net) with an NTLM Authentication Technique

I am calling a web service written in .net located remotely running under IIS Server.

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.

We have kept its two different endpoint for keeping security credential enable/disable.

  1. "ip:port/pigeon/pigeon.svc;" // authentication disabled
  2. "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

(401)Unauthorized
(401)Unauthorized AxisFault
faultCode: {http://xml.apache.org/axis/}HTTP
faultSubcode:
faultString: (401)Unauthorized
faultActor:
faultNode:
faultDetail: {}:return code: 401

I want to pass credential which are in this format :
1) domain\username
2) password

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.

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

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 :

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.

This is just fyi/all that remote IIS server using NTLM authentication technique.

Help expected on windows authentication using java in order to pass security credential to IIS.

[Note : my axis client(java) passes domain\user with password ,this is configured with IIS server on the otherside properly]



Solution 1:[1]

The problem is that Axis 1.4 does not implement correctly the NTLM V2 protocol.

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:

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.

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

The solution is creating the following class to resolve the 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:

AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, org.xyz.JCIFS_NTLMScheme.class);

Thanks to Sachin's Tech Place

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David García González