javasharepointntlmapache-wink

How to use NTLM authentication with Apache Wink client


I'm trying to use wink-client v1.4 to communicate with a Sharepoint RESTful web service. I have created a simple Java SE Maven project that can do this task on Windows using a BasicAuthSecurityHandler. However, this same project doesn't work on Mac OS X. I receive a 401 HTTP status code on the Mac. Wink is somehow using my NTLM credentials when being run from Windows. I'm using JDK 7 for both platforms.

How can I use NTLM authentication with Apache Wink client?

public String getSharepointInfo() {
    spUser = "user";
    spPassword = "password";
    spUri = "https://someSharepointURL/";

    ClientConfig clientConfig = new ClientConfig();

    Application app = new Application() {
        public Set<Class<?>> getClasses() {
            Set<Class<?>> classes = new HashSet<Class<?>>();
            classes.add(WinkMOXyJsonProvider.class);
            return classes;
        }
    };
    clientConfig.applications(app);
    BasicAuthSecurityHandler basicAuthSecurityHandler = new BasicAuthSecurityHandler();
    basicAuthSecurityHandler.setUserName(spUser);
    basicAuthSecurityHandler.setPassword(spPassword);
    clientConfig.handlers(basicAuthSecurityHandler);
    RestClient client = new RestClient(clientConfig);
    Resource resource = client.resource(spUri);

    ClientResponse response = resource.accept("*/*").get();

    String blah = response.getEntity(String.class);
    System.out.println("The response is " + blah);
    return blah.toString();
}

Solution

  • I've figured it out.

    My ultimate goal was to create a simple test case that I could port to WebSphere Application Server v8.0. The Apache Wink client can't handle NTLM authentication on its own. You have to use a separate Http client to handle NTLM authentication. I chose Apache Http Cient v4.0.1, since that buggy version is packaged in WAS v8.0. It's a huge pain to override that provided version too. That's why I didn't choose a more recent, better version of Apache HttpClient.

    So, here's how you get Apache Http Client v4.0.1 to handle NTLM authentication: Use the following dependencies...

        <dependency>
            <groupId>jcifs</groupId>
            <artifactId>jcifs</artifactId>
            <version>1.3.17</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.apache.wink</groupId>
            <artifactId>wink-client</artifactId>
            <version>1.4</version>
        </dependency>
    

    I'm using com.ibm.ws.prereq.jaxrs.jar contained in WAS v8.0 to get Apache Http Client v4.0.1. That's installed in my Maven repo, and I specify that as a dependency to get Http Client v4.0.1.

    Follow the steps here.

    Now, Wink comes into play:

    public int attemptWinkHttpClienGET() {
        ClientResponse response = null;
        try {
            String spUri = "https://some-sharepoint-url/listdata.svc/";
    
            StringBuilder sb = new StringBuilder();
            sb.append(spUri).append("UserInformationList").toString();
    
            DefaultHttpClient httpClient = new DefaultHttpClient();
            httpClient.getAuthSchemes().register("ntlm",new JCIFSNTLMSchemeFactory());
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            NTCredentials ntcred = new NTCredentials("username_here", "password_here", InetAddress.getLocalHost().getHostName(), "domain_here");
            credsProvider.setCredentials(new AuthScope("base_url_here_sans_https://", 443, AuthScope.ANY_REALM, "NTLM"), ntcred);
            httpClient.setCredentialsProvider(credsProvider);
    
            org.apache.wink.client.ClientConfig httpClientConfig = new org.apache.wink.client.ApacheHttpClientConfig(httpClient);
            Application app = new Application() {
                public Set<Class<?>> getClasses() {
                    Set<Class<?>> classes = new HashSet<Class<?>>();
                    classes.add(WinkMOXyJsonProvider.class);
                    return classes;
                }
            };
            httpClientConfig.applications(app);
            RestClient client = new RestClient(httpClientConfig);
            Resource resource = client.resource(sb.toString());
            response = resource.accept(MediaType.APPLICATION_JSON_TYPE).get();
            UserInformationListResponse blah = response.getEntity(UserInformationListResponse.class);
            Results[] results = blah.getD().getResults();
            for (Results result : results) {
                System.out.println("User Name: " + result.getFirstName() + " " + result.getLastName());
            }
            System.out.println("The response is " + response.getStatusCode());
            response.consumeContent();
        } catch (UnknownHostException ex) {
            Logger.getLogger(HttpTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    
        return response.getStatusCode();
    }
    

    Now, the final bit. I use MOXy as my JAXB implementation. I had some issues getting it to work even though I was registering it in my app variable. I was seeing some Jackson-related errors. Apache HttpClient v4.0.1 is apparently using Jackons under the hood as a default. Here's what I did to overcome that problem.

    I added the following dependencies:

        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-moxy</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.4.0-rc2</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.13</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-xc</artifactId>
            <version>1.9.13</version>
        </dependency>
    

    Here's WinkMOXyJsonProvider.java

    import javax.ws.rs.Consumes;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.ext.Provider;
    import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
    
    @Provider
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public class WinkMOXyJsonProvider extends MOXyJsonProvider {
    
    }
    

    I observed the String result returned from Sharepoint and then created a bunch of MOXy POJO's mimicking the JSON object hierarchy.