簡體   English   中英

在 Java 應用程序中使用 NTLM 身份驗證

[英]Using NTLM authentication in Java applications

我想在我的 Java 應用程序中使用 Windows NTLM 身份驗證來透明地對 Intranet 用戶進行身份驗證。 如果使用他們的瀏覽器(單點登錄),用戶應該不會注意到任何身份驗證。

我找到了一些支持 NTLM 的庫,但不知道該使用哪一個:

任何建議從哪里開始?

在上面的列表中,只有 ntlmv2-auth 和 Jespa 支持 NTLMv2。 Jespa 可行但商業化。 ntlmv2-auth 我沒試過,但它基於 Liferay 的代碼,我以前見過它工作。

'ntlm-authentication-in-java' 只是 NTLMv1,它是舊的、不安全的,並且隨着人們升級到更新的 Windows 版本,它在越來越少的環境中工作。 JCIFS 曾經有一個 NTLMv1 HTTP 身份驗證過濾器,但它在以后的版本中被刪除,因為它的實現方式相當於對不安全協議的中間人攻擊。 ('ntlm-authentication-in-java' 似乎也是如此。)

“spnego”項目是 Kerberos 而不是 NTLM。 如果您想像 IIS 那樣復制完整的 IWA,您需要同時支持 NTLMv2 和 Kerberos(“NTLM”身份驗證、“協商”身份驗證、NTLMSSP-in-SPNego 身份驗證和 NTLM-masquerading-as-Negotiate 身份驗證)。

Luigi Dragone 的劇本真的很舊,似乎總是失敗。

如果添加庫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);
        }
    }
}

如果你對每次握手的內容感到好奇,你可以在這個線程上找到另一個使用 jcifs 和 Socket 的例子。

最近不得不在工作中實現這一點,因此這里是使用 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();
        }

    }
}

需要的依賴是: spring-weborg.apache.httpcomponents

ps:輸入不帶域的用戶名很重要,否則不起作用。 就像您的域是 companyName/username 通常人們只是輸入整個內容作為用戶名,您應該做的是分別輸入它們,其中 domain="companyName" 和 username="username"

相對於您提供的列表,我會選擇JCIFS 圖書館很成熟,他們的文檔很好。 最重要的是,他們有相當定期的發布,最后一個是 2011 年 11 月。

Personal Experience :與我嘗試過的其他工具(spnego 和 ntmv2auth)相比,上手相當容易

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

問:jCIFS 是否支持 NTLMv2?
答:是的。 從 1.3.0 開始,JCIFS 完全支持 NTLMv2 並默認使用它。

注意:曾經包含在 JCIFS 中的 NTLM HTTP SSO 過濾器不支持 NTLMv2。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM