简体   繁体   English

在 Java 应用程序中使用 NTLM 身份验证

[英]Using NTLM authentication in Java applications

I want to use Windows NTLM authentication in my Java application to authenticate intranet users transparently.我想在我的 Java 应用程序中使用 Windows NTLM 身份验证来透明地对 Intranet 用户进行身份验证。 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:我找到了一些支持 NTLM 的库,但不知道该使用哪一个:

Any suggestions where to start?任何建议从哪里开始?

Out of the above list, only ntlmv2-auth and Jespa support NTLMv2.在上面的列表中,只有 ntlmv2-auth 和 Jespa 支持 NTLMv2。 Jespa is workable but commercial. Jespa 可行但商业化。 ntlmv2-auth I haven't tried but it's based on the code from Liferay, which I've seen working before. ntlmv2-auth 我没试过,但它基于 Liferay 的代码,我以前见过它工作。

'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. 'ntlm-authentication-in-java' 只是 NTLMv1,它是旧的、不安全的,并且随着人们升级到更新的 Windows 版本,它在越来越少的环境中工作。 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. JCIFS 曾经有一个 NTLMv1 HTTP 身份验证过滤器,但它在以后的版本中被删除,因为它的实现方式相当于对不安全协议的中间人攻击。 (The same appears to be true of 'ntlm-authentication-in-java'.) ('ntlm-authentication-in-java' 似乎也是如此。)

The 'spnego' project is Kerberos not NTLM. “spnego”项目是 Kerberos 而不是 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).如果您想像 IIS 那样复制完整的 IWA,您需要同时支持 NTLMv2 和 Kerberos(“NTLM”身份验证、“协商”身份验证、NTLMSSP-in-SPNego 身份验证和 NTLM-masquerading-as-Negotiate 身份验证)。

Luigi Dragone's script is really old and seems to always fail. Luigi Dragone 的剧本真的很旧,似乎总是失败。

HttpURLConnection can work with NTLM if you add library jcifs , this example works with latest jcifs-1.3.18 :如果添加库jcifs HttpURLConnection 可以与 NTLM一起使用,此示例适用于最新的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 .如果你对每次握手的内容感到好奇,你可以在这个线程上找到另一个使用 jcifs 和 Socket 的例子。

Had to recently implement this at work hence here is updated solution with Spring's RestTemplate:最近不得不在工作中实现这一点,因此这里是使用 Spring 的 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需要的依赖是: spring-weborg.apache.httpcomponents

ps: it is important to enter username without domain otherwise it doesn't work. ps:输入不带域的用户名很重要,否则不起作用。 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"就像您的域是 companyName/username 通常人们只是输入整个内容作为用户名,您应该做的是分别输入它们,其中 domain="companyName" 和 username="username"

Relatively from the list you gave,I would go with JCIFS .相对于您提供的列表,我会选择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.最重要的是,他们有相当定期的发布,最后一个是 2011 年 11 月。

Personal Experience : it was fairly easy to get started when compared to others i have tried (spnego and ntmv2auth) Personal Experience :与我尝试过的其他工具(spnego 和 ntmv2auth)相比,上手相当容易

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

Q: Does jCIFS support NTLMv2?问:jCIFS 是否支持 NTLMv2?
A: Yes.答:是的。 As of 1.3.0, JCIFS fully supports NTLMv2 and uses it by default.从 1.3.0 开始,JCIFS 完全支持 NTLMv2 并默认使用它。

Note: The NTLM HTTP SSO Filter that used to be included with JCIFS cannot support NTLMv2.注意:曾经包含在 JCIFS 中的 NTLM HTTP SSO 过滤器不支持 NTLMv2。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM