簡體   English   中英

在Linux上使用Java對Active Directory進行身份驗證

[英]Authenticating against Active Directory with Java on Linux

我有一個簡單的任務,使用Java對Active Directory進行身份驗證。 只是驗證憑據而不是其他任何內容。 假設我的域名為“fun.xyz.tld”,OU路徑未知,用戶名/密碼為testu / testp。

我知道有一些Java庫可以簡化這項任務,但我沒有成功實現它們。 我發現的大多數示例都是針對LDAP的,而不是特定的Active Directory。 發出LDAP請求意味着在其中發送OU路徑,這是我沒有的。 此外,發出LDAP請求的應用程序應該已綁定到Active Directory以便訪問它...不安全,因為憑據必須存儲在某處可被發現的位置。 如果可能的話,我希望測試綁定測試憑據 - 這意味着帳戶有效。

最后,如果可能的話,有沒有辦法使這種認證機制加密? 我知道AD使用Kerberos,但不確定Java的LDAP方法是否可以。

有沒有人有一個工作代碼的例子? 謝謝。

有3種身份驗證協議可用於在Linux或任何其他平台上執行Java和Active Directory之間的身份驗證(這些協議不僅僅針對HTTP服務):

  1. Kerberos - Kerberos提供單點登錄(SSO)和委派,但Web服務器也需要SPNEGO支持才能通過IE接受SSO。

  2. NTLM - NTLM通過IE(以及其他瀏覽器,如果它們已正確配置)支持SSO。

  3. LDAP - LDAP綁定可用於簡單地驗證帳戶名和密碼。

還有一種稱為“ADFS”的東西,它為使用調用Windows SSP的SAML的網站提供SSO,因此在實踐中它基本上是使用上述其他協議之一的迂回方式。

每個協議都有它的優點,但根據經驗,為了獲得最大的兼容性,你通常應該嘗試“像Windows一樣”。 那么Windows做什么?

首先,兩台Windows機器之間的身份驗證支持Kerberos,因為服務器不需要與DC通信,客戶端可以緩存Kerberos票證,從而減少DC上的負載(並且因為Kerberos支持委派)。

但是,如果身份驗證方都沒有域帳戶,或者客戶端無法與DC通信,則需要NTLM。 因此,Kerberos和NTLM不是互斥的,並且Kerberos不會廢棄NTLM。 事實上,在某些方面,NTLM比Kerberos更好。 請注意,當同時提到Kerberos和NTLM時,我還要提到SPENGO和集成Windows身份驗證(IWA)。 IWA是一個簡單的術語,基本上意味着Kerberos或NTLM或SPNEGO協商Kerberos或NTLM。

使用LDAP綁定作為驗證憑據的方法效率不高,需要SSL。 但直到最近,實施Kerberos和NTLM一直很困難,因此使用LDAP作為生成班次認證服務仍然存在。 但在這一點上通常應該避免。 LDAP是信息目錄,而不是身份驗證服務。 將它用於預期目的。

那么如何在Java中以及在Web應用程序的上下文中實現Kerberos或NTLM呢?

像Quest Software和Centrify這樣的大公司都有專門提到Java的解決方案。 我無法對這些進行評論,因為它們是公司范圍內的“身份管理解決方案”,所以,通過查看其網站上的營銷活動,很難確切地說明正在使用哪些協議以及如何使用。 您需要與他們聯系以獲取詳細信息。

在Java中實現Kerberos並不是非常困難,因為標准Java庫通過org.ietf.gssapi類支持Kerberos。 然而,直到最近還存在一個主要障礙 - IE不發送原始Kerberos令牌,它發送SPNEGO令牌。 但是使用Java 6,SPNEGO已經實現。 從理論上講,您應該能夠編寫一些可以驗證IE客戶端的GSSAPI代碼。 但我沒試過。 多年來,Sun實施的Kerberos一直是一個錯誤的喜劇,所以根據Sun在這個領域的記錄,我不會對他們的SPENGO實施做出任何承諾,直到你掌握了這只鳥。

對於NTLM,有一個名為JCIFS的免費OSS項目,它具有NTLM HTTP身份驗證Servlet過濾器。 但是,它使用中間人方法來驗證使用不能與NTLMv2一起使用的SMB服務器的憑證(這正逐漸成為必需的域安全策略)。 出於這個原因和其他原因,計划刪除JCIFS的HTTP過濾器部分。 請注意,有許多衍生產品使用JCIFS來實現相同的技術。 因此,如果您看到聲稱支持NTLM SSO的其他項目,請檢查細則。

使用Active Directory驗證NTLM憑據的唯一正確方法是使用帶有安全通道的NETLOGON上的NetrLogonSamLogon DCERPC調用。 Java中是否存在這樣的事情? 是。 這里是:

http://www.ioplex.com/jespa.html

Jespa是一個100%Java NTLM實現,支持NTLMv2,NTLMv1,完整的完整性和機密性選項以及前面提到的NETLOGON憑證驗證。 它包括HTTP SSO過濾器,JAAS LoginModule,HTTP客戶端,SASL客戶端和服務器(帶有JNDI綁定),用於創建自定義NTLM服務的通用“安全提供程序”等。

麥克風

以下是我根據此博客中的示例匯總的代碼: LINK和此來源: LINK

import com.sun.jndi.ldap.LdapCtxFactory;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.AuthenticationException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;

class App2 {

    public static void main(String[] args) {

        if (args.length != 4 && args.length != 2) {
            System.out.println("Purpose: authenticate user against Active Directory and list group membership.");
            System.out.println("Usage: App2 <username> <password> <domain> <server>");
            System.out.println("Short usage: App2 <username> <password>");
            System.out.println("(short usage assumes 'xyz.tld' as domain and 'abc' as server)");
            System.exit(1);
        }

        String domainName;
        String serverName;

        if (args.length == 4) {
            domainName = args[2];
            serverName = args[3];
        } else {
            domainName = "xyz.tld";
            serverName = "abc";
        }

        String username = args[0];
        String password = args[1];

        System.out
                .println("Authenticating " + username + "@" + domainName + " through " + serverName + "." + domainName);

        // bind by using the specified username/password
        Hashtable props = new Hashtable();
        String principalName = username + "@" + domainName;
        props.put(Context.SECURITY_PRINCIPAL, principalName);
        props.put(Context.SECURITY_CREDENTIALS, password);
        DirContext context;

        try {
            context = LdapCtxFactory.getLdapCtxInstance("ldap://" + serverName + "." + domainName + '/', props);
            System.out.println("Authentication succeeded!");

            // locate this user's record
            SearchControls controls = new SearchControls();
            controls.setSearchScope(SUBTREE_SCOPE);
            NamingEnumeration<SearchResult> renum = context.search(toDC(domainName),
                    "(& (userPrincipalName=" + principalName + ")(objectClass=user))", controls);
            if (!renum.hasMore()) {
                System.out.println("Cannot locate user information for " + username);
                System.exit(1);
            }
            SearchResult result = renum.next();

            List<String> groups = new ArrayList<String>();
            Attribute memberOf = result.getAttributes().get("memberOf");
            if (memberOf != null) {// null if this user belongs to no group at all
                for (int i = 0; i < memberOf.size(); i++) {
                    Attributes atts = context.getAttributes(memberOf.get(i).toString(), new String[] { "CN" });
                    Attribute att = atts.get("CN");
                    groups.add(att.get().toString());
                }
            }

            context.close();

            System.out.println();
            System.out.println("User belongs to: ");
            Iterator ig = groups.iterator();
            while (ig.hasNext()) {
                System.out.println("   " + ig.next());
            }

        } catch (AuthenticationException a) {
            System.out.println("Authentication failed: " + a);
            System.exit(1);
        } catch (NamingException e) {
            System.out.println("Failed to bind to LDAP / get account information: " + e);
            System.exit(1);
        }
    }

    private static String toDC(String domainName) {
        StringBuilder buf = new StringBuilder();
        for (String token : domainName.split("\\.")) {
            if (token.length() == 0)
                continue; // defensive check
            if (buf.length() > 0)
                buf.append(",");
            buf.append("DC=").append(token);
        }
        return buf.toString();
    }

}

我剛剛完成了一個使用AD和Java的項目。 我們使用了Spring ldapTemplate。

AD符合LDAP標准(差不多),我認為您的任務沒有任何問題。 我的意思是它是AD或任何其他LDAP服務器,如果你只想連接它並不重要。

我想看看: Spring LDAP

他們也有例子。

至於加密,我們使用SSL連接(因此它是LDAPS)。 必須在SSL端口/協議上配置AD。

但首先,請確保您可以通過LDAP IDE正確連接到AD。 我使用Apache Directory Studio ,它非常酷,它是用Java編寫的。 這就是我所需要的一切。 出於測試目的,您還可以安裝Apache Directory Server

正如ioplex和其他人所說,有很多選擇。 要使用LDAP(以及Novell LDAP API)進行身份驗證,我使用了以下內容:


LDAPConnection connection = new LDAPConnection( new LDAPJSSEStartTLSFactory() );
connection.connect(hostname, port);
connection.startTLS();
connection.bind(LDAPConnection.LDAP_V3, username+"@"+domain, password.getBytes());

作為“特殊功能”,Active Directory允許LDAP綁定“user @ domain”而不使用帳戶的可分辨名稱。 此代碼使用StartTLS在連接上啟用TLS加密; 另一種選擇是LDAP over SSL, 我的 AD服務器不支持。

真正的訣竅是找到服務器和主機; 官方的方法是使用DNS SRV(服務)記錄查找來定位一組候選主機,然后執行基於UDP的LDAP“ping”(以特定的Microsoft格式)來定位正確的服務器。 如果您有興趣,我已經發布了一些關於我在該領域的冒險和發現之旅的博客文章

如果你想做基於Kerberos的用戶名/密碼驗證,你正在看另一個魚的水壺; 它可以使用Java GSS-API代碼,但我不確定它是否執行驗證身份驗證的最后一步。 (執行驗證的代碼可以聯系AD服務器以檢查用戶名和密碼,這會導致為用戶授予票證,但為了確保AD服務器沒有被模擬,它還需要嘗試獲取票證用戶自己,這有點復雜。)

如果要進行基於Kerberos的單點登錄,假設您的用戶已對域進行身份驗證,那么您也可以使用Java GSS-API代碼執行此操作。 我會發布一個代碼示例,但我仍然需要將我可怕的原型變成適合人眼的東西。 查看SpringSource的一些代碼以獲得一些靈感。

如果你正在尋找NTLM(我被理解為不太安全)或其他東西,那么,祝你好運。

你只是驗證憑證嗎? 在這種情況下,你可以做普通的kerberos而不是麻煩LDAP

如果你想要做的就是使用Kerberos對AD進行身份驗證,那么一個簡單的http://spnego.sourceforge.net/HelloKDC.java程序應該這樣做。

看看該項目的“飛行前”文檔,該文檔討論了HelloKDC.java程序。

沒有SSL的ldap身份驗證是不安全的,任何人都可以查看用戶憑據,因為ldap客戶端在ldap綁定操作期間傳輸usernamae和密碼所以始終使用ldaps協議。 source: Ldap authentication Java Spring Security with Example中的活動目錄

我建議你看一下oVirt項目的adbroker包。 它使用Spring-Ldap和Krb5 JAAS登錄模塊(使用GSSAPI),以便使用Kerberos對Ldap服務器(Active-Directory,ipa,rhds,Tivoli-DS)進行身份驗證。 在engine \\ backend \\ manager \\ modules \\ bll \\ src \\ main \\ java \\ org \\ ovirt \\ engine \\ core \\ bll \\ adbroker中查找代碼

您可以使用git克隆存儲庫或使用gerrit鏈接進行瀏覽

暫無
暫無

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

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