17

I want to use Windows NTLM authentication in my Java application to authenticate intranet users transparently. The users should not notice any authentication if using their browsers (single sign-on).

I've found a few libs with NTLM support, but don't know which one to use:

Any suggestions where to start?

deamon
  • 89,107
  • 111
  • 320
  • 448
  • Also be aware that in using NTLM for authentication, active attackers can [authenticate their own session](http://static.usenix.org/event/sec10/tech/slides/geer.pdf) using a valid user's negotiation with the server. – Michael Feb 23 '13 at 10:43

5 Answers5

10

Out of the above list, only ntlmv2-auth and Jespa support NTLMv2. Jespa is workable but commercial. ntlmv2-auth I haven't tried but it's based on the code from Liferay, which I've seen working before.

'ntlm-authentication-in-java' is only NTLMv1, which is old, insecure, and works in a dwindling number of environments as people upgrade to newer Windows versions. JCIFS used to have an NTLMv1 HTTP auth filter, but it was removed in later versions, as the way it was implemented amounts to a man-in-the-middle attack on the insecure protocol. (The same appears to be true of 'ntlm-authentication-in-java'.)

The 'spnego' project is Kerberos not NTLM. If you want to replicate full IWA as IIS does it, you'd need to support both NTLMv2 and Kerberos ('NTLM' auth, 'Negotiate' auth, NTLMSSP-in-SPNego auth and NTLM-masquerading-as-Negotiate auth).

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 1
    Waffle is also a very good option for WIndows servers only, been using for 3 years, thousands of logins of day at multiple sites no problem, supports v2 – Tony BenBrahim Jan 24 '14 at 08:28
  • 1
    https://jcifs.samba.org/src/docs/faq.html#ntlmv2 >Q: Does jCIFS support NTLMv2? >A: Yes. As of 1.3.0, JCIFS fully supports NTLMv2 and uses it by default.` `Note: The NTLM HTTP SSO Filter that used to be included with JCIFS cannot support NTLMv2.` – Nikhil Feb 22 '17 at 19:00
4

Luigi Dragone's script is really old and seems to always fail.

HttpURLConnection can work with NTLM if you add library jcifs, this example works with latest jcifs-1.3.18 :

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.impl.auth.NTLMEngineException;

public class TestNTLMConnection {
    public static void main(String[] args) throws UnknownHostException, IOException, NTLMEngineException {
        // Method 1 : authentication in URL
        jcifs.Config.registerSmbURLHandler();
        URL urlRequest = new URL("http://domain%5Cuser:pass@127.0.0.1/");

        // or Method 2 : authentication via System.setProperty()
        // System.setProperty("http.auth.ntlm.domain", "domain");
        // System.setProperty("jcifs.smb.client.domain", "domain");
        // System.setProperty("jcifs.smb.client.username", "user");
        // System.setProperty("jcifs.smb.client.password", "pass");
        // Not verified // System.setProperty("jcifs.netbios.hostname", "host");
        // System.setProperty("java.protocol.handler.pkgs", "jcifs");
        // URL urlRequest = new URL("http://127.0.0.1:8180/simulate_get.php");

        HttpURLConnection conn = (HttpURLConnection) urlRequest.openConnection();

        StringBuilder response = new StringBuilder();

        try {
            InputStream stream = conn.getInputStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(stream));

            String str = "";
            while ((str = in.readLine()) != null) {
                response.append(str);
            }
            in.close();   

            System.out.println(response);
        } catch(IOException err) {
            System.out.println(err);
        } finally {
            Map<String, String> msgResponse = new HashMap<String, String>();

            for (int i = 0;; i++) {
                String headerName = conn.getHeaderFieldKey(i);
                String headerValue = conn.getHeaderField(i);
                if (headerName == null && headerValue == null) {
                    break;
                }
                msgResponse.put(headerName == null ? "Method" : headerName, headerValue);
            }

            System.out.println(msgResponse);
        }
    }
}

And if you are curious about the content of each handshake, you can find another example using jcifs and Socket on this thread.

Community
  • 1
  • 1
ron190
  • 1,032
  • 1
  • 17
  • 29
  • Can you make that URL part a little more clear, I mean how to pass the username and password – Sony Apr 16 '16 at 06:15
  • 1
    %5C is backslash. e.g h-t-t-p://CORP\username:password@127.0.0.1/ – ron190 Apr 16 '16 at 15:02
  • The first method gave me problems with complex urls, but commented out Method2 works well using Java 1.7 and jcifs 1.3.17 against IIS on Windows Server 2012. – rwheadon Sep 22 '16 at 14:46
4

Had to recently implement this at work hence here is updated solution with Spring's RestTemplate:

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

public class Runner {
    
    public static void main(String[] args) {
        var credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new NTCredentials("username", "password", "", "someDomain"));
        try (var client = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .build();) {
            var requestFactory = new HttpComponentsClientHttpRequestFactory();
            requestFactory.setHttpClient(client);
            RestTemplate restTemplate = new RestTemplate(requestFactory);
            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity("url", new HttpEntity<>("yourDtoObject"), String.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

dependencies needed are: spring-web and org.apache.httpcomponents

ps: it is important to enter username without domain otherwise it doesn't work. As in if your domain is companyName/username often people just enter that whole thing as username and what you should do is enter them separately where domain="companyName" and username="username"

J Asgarov
  • 2,526
  • 1
  • 8
  • 18
  • Is this solution is available for the spring-webclient too? – Vivek Jain Nov 21 '22 at 11:58
  • You would need to configure credentials differently for webclient, probably somewhere as an authorization header, I never had to do it so I don't have an example, but what I would do is grab this example, run it, look in debugger what kind of headers end up getting created and try to recreate same authorization related headers manually for spring webclient - but that is just how I would go about solving it, actual implementation is up to you – J Asgarov Nov 21 '22 at 12:14
1

Ref: https://jcifs.samba.org/src/docs/faq.html#ntlmv2

Q: Does jCIFS support NTLMv2?
A: Yes. As of 1.3.0, JCIFS fully supports NTLMv2 and uses it by default.

Note: The NTLM HTTP SSO Filter that used to be included with JCIFS cannot support NTLMv2.

Nikhil
  • 570
  • 5
  • 12
0

Relatively from the list you gave,I would go with JCIFS. The library is mature , and their documentation is good. To top it off they had fairly regular releases , and the last one being Nov 2011.

Personal Experience : it was fairly easy to get started when compared to others i have tried (spnego and ntmv2auth)

Sudhakar
  • 4,823
  • 2
  • 35
  • 42
  • 1
    JCIFS does not support NTLMv2 (ie Windows 7/8), unless explicitely enabled on client machines via Global Policy or a registry change. – Tony BenBrahim Jan 24 '14 at 08:29
  • Could you clarify what did you mean by the explicit setup in Windows 7/8? – Miroslav Ligas Apr 25 '14 at 08:25
  • 1
    @MiroslavLigas. I think he means that Win 7/8 no longer use NTLMv1 since it is deprecated and considered open to exploits. Now if JCIFS only supports NTLMv1 then you'll need to force your Win 7/8 desktops to allow use of NTLMv1 (which is done by a change to Win Registry) in order for such desktops to work with JCIFS. In most companies the administrator will never allow such a change. For all practical purposes NTLMv1 is dead! – peterh Jun 09 '14 at 09:41
  • "JCIFS uses cryptography including RC4 128 (for NTLMv2) and AES 256 (for Kerberos) for authentication, digital signatures and encryption. Products that use cryptography and which are exported from the U.S. to other countries are supposed to obtain an export classification." is what I found on the first page of http://jcifs.samba.org ... and the entry is from 2009, so I call $hit on Tony's assumption. I have never tested it though ... – thecarpy Aug 13 '14 at 12:28
  • @thecarpy - JCIFS uses NTLMv2 for the CIFS client. The HTTP bits in JCIFS never supported NTLMv2 and never will. All HTTP stuff in JCIFS is deprecated and will be removed. – squarewav Oct 30 '14 at 21:29
  • @squarewav : You are right. JCIFS uses NTLMv2 not in HTTP. Ref: https://jcifs.samba.org/src/docs/faq.html#ntlmv2 – Nikhil Feb 22 '17 at 18:58