0

我搜索了如何为我的 Java Web 应用程序提供基于活动目录/LDAP 的登录。在这个论坛上也是大约。100+票,看不懂,或者和我目标不一样。我试了好几种配置。

使用以下代码,我得到了最好的结果,但仍然没有完全工作。

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;


@SuppressWarnings("restriction")
public class LdapAuth
{
private final static String ldapURI            = "ldap://XXX.XXX.XXX.X:389";  //LDAP Server IP
private final static String contextFactory     = "com.sun.jndi.ldap.LdapCtxFactory";
private static String[]     requiredAttributes = {"cn", "givenName", "sn", "displayName", "userPrincipalName",
    "sAMAccountName", "objectSid", "userAccountControl"};

private static String[] ADSearchPaths =
    {
        "CN=Users"
    };


public static void authenticateUserAndGetInfo(final String user, final String password) throws Exception
{
    try
    {

        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String      fullPath    = classLoader.getResource("/META-INF/krb5.conf").getPath();
        System.out.println(fullPath);

        System.setProperty("sun.security.krb5.debug", "true");
        System.setProperty("sun.security.spnego.debug", "true");

        System.setProperty("java.security.krb5.conf", fullPath);
        System.setProperty("sun.security.krb5.principal", user);
        System.setProperty("sun.security.krb5.PrincipalName", user + "@MY.DOMAIN");
        System.setProperty("sun.security.krb5.Credentials", password);
        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");

        final Hashtable<String, String> env = new Hashtable<>();

        env.put(Context.INITIAL_CONTEXT_FACTORY, LdapAuth.contextFactory);

        env.put(Context.REFERRAL, "ignore");
        env.put(Context.PROVIDER_URL, LdapAuth.ldapURI);
        env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
        env.put(Context.SECURITY_PRINCIPAL, user);
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.REFERRAL, "ignore");
        env.put(Context.AUTHORITATIVE, "false");

        final DirContext ctx = new InitialDirContext(env);

        String filter = "(sAMAccountName=" + user + ")"; // default for search filter username

        if(user.contains("@")) // if user name is a email then
        {
            // String parts[] = user.split("\\@");
            // use different filter for email
            filter = "(userPrincipalName=" + user + ")";
        }

        final SearchControls ctrl = new SearchControls();
        ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE);
        ctrl.setReturningAttributes(LdapAuth.requiredAttributes);

        NamingEnumeration userInfo = null;

        Integer i = 0;
        do
        {
            userInfo = ctx.search(LdapAuth.ADSearchPaths[i], filter, ctrl);
            i++;
        }
        while(!userInfo.hasMore() && i < LdapAuth.ADSearchPaths.length);

        if(userInfo.hasMore())
        {
            final SearchResult UserDetails = (SearchResult)userInfo.next();
            final Attributes   userAttr    = UserDetails.getAttributes();
            System.out.println("adEmail = " + userAttr.get("userPrincipalName").get(0).toString());
            System.out.println("adFirstName = " + userAttr.get("givenName").get(0).toString());
            System.out.println("adLastName = " + userAttr.get("sn").get(0).toString());
            System.out.println("name = " + userAttr.get("cn").get(0).toString());
            System.out.println("AdFullName = " + userAttr.get("cn").get(0).toString());
        }

        userInfo.close();

    }
    catch(final javax.naming.AuthenticationException e)
    {
        e.printStackTrace();
    }

}
}

作为 AD 控制器,我使用了 Synology AD 包。我尝试了简单的身份验证,但这不起作用。

我收到一个错误:

javax.naming.AuthenticationNotSupportedException: [LDAP: error code 8 - BindSimple: Transport encryption required.]
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(Unknown Source)
...

附加信息。使用客户端控制台上的 klist,我为我的用户获得了两张票。我可以通过 AD-Controller 上的 ssh 从客户端控制台登录成功

在更改和设置 System.setProperty .... 6 行和设置而不是简单之后:

env.put(Context.SECURITY_AUTHENTICATION, "GSSSAPI");

我得到了以下输出,并在控制台上询问了 Kerberos 用户 ID。

>>>KinitOptions cache name is C:\Users\myuser\krb5cc_myuser
>> Acquire default native Credentials
default etypes for default_tkt_enctypes: 23.
>>> Found no TGT's in LSA
Kerberos-Password für myUserId: mySecretPassword
         ########################################
>>> KdcAccessibility: reset
default etypes for default_tkt_enctypes: 23.
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000, number of retries =3, #bytes=126
>>> KDCCommunication: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000,Attempt =1, #bytes=126
>>>DEBUG: TCPClient reading 248 bytes
>>> KrbKdcReq send: #bytes read=248
>>>Pre-Authentication Data:
 PA-DATA type = 2
 PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
 PA-DATA type = 16

>>>Pre-Authentication Data:
 PA-DATA type = 15

>>>Pre-Authentication Data:
 PA-DATA type = 11
 PA-ETYPE-INFO etype = 23, salt = null

>>>Pre-Authentication Data:
 PA-DATA type = 19
 PA-ETYPE-INFO2 etype = 23, salt = null, s2kparams = null

>>> KdcAccessibility: remove XXX.XXX.XXX.X:88
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
 sTime is Sat Mar 07 16:49:32 CET 2020 1583596172000
 suSec is 184771
 error code is 25
 error Message is Additional pre-authentication required
 cname is myUserId@my.domain
 sname is krbrAdmin/my.domain@MY.DOMAIN
 eData provided.
 msgType is 30
>>>Pre-Authentication Data:
 PA-DATA type = 2
 PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
 PA-DATA type = 16

>>>Pre-Authentication Data:
 PA-DATA type = 15

>>>Pre-Authentication Data:
 PA-DATA type = 11
 PA-ETYPE-INFO etype = 23, salt = null

>>>Pre-Authentication Data:
 PA-DATA type = 19
 PA-ETYPE-INFO2 etype = 23, salt = null, s2kparams = null

KRBError received: Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ
KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ
default etypes for default_tkt_enctypes: 23.
default etypes for default_tkt_enctypes: 23.
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000, number of retries =3, #bytes=206
>>> KDCCommunication: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000,Attempt =1, #bytes=206
>>>DEBUG: TCPClient reading 1186 bytes
>>> KrbKdcReq send: #bytes read=1186
>>> KdcAccessibility: remove XXX.XXX.XXX.X:88
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbAsRep cons in KrbAsReq.getReply hri4wa1
Found ticket for myUserId@MY.DOMAIN to go to krbrAdmin/my.domain@MY.DOMAIN expiring on Sun Mar 08 02:49:32 CET 2020
Entered Krb5Context.initSecContext with state=STATE_NEW
Service ticket not found in the subject
>>> Credentials acquireServiceCreds: same realm
default etypes for default_tgs_enctypes: 23.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbKdcReq send: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000, number of retries =3, #bytes=1225
>>> KDCCommunication: kdc=XXX.XXX.XXX.X TCP:88, timeout=30000,Attempt =1, #bytes=1225
>>>DEBUG: TCPClient reading 107 bytes
>>> KrbKdcReq send: #bytes read=107
>>> KdcAccessibility: remove XXX.XXX.XXX.X:88
>>> KDCRep: init() encoding tag is 126 req type is 13

在此输出之后,我收到以下错误:

java.lang.IllegalArgumentException: Empty nameStrings not allowed
    at sun.security.krb5.PrincipalName.validateNameStrings(Unknown Source)

我试图弄清楚如何防止这种情况:也没有成功也设置

    System.setProperty("sun.security.krb5.PrincipalName", user + "@MY.DOMAIN");

仍然没有工作。

还有其他信息,我的服务器支持:

{supportedsaslmechanisms=supportedSASLMechanisms: GSS-SPNEGO、GSSAPI、NTLM}

我觉得让它运行的问题不止一个。我也不确定的一个问题是,是否需要以任何方式配置我的 tomcat?

4

3 回答 3

2

我已经设法用 oauth/azure 广告解决了这个问题。它不是 LDAP - 但无论如何它可能很有趣。看看:https ://github.com/xware-gmbh/SeicentoBilling

于 2020-03-25T09:42:45.297 回答
1

这是通过java创建用户的代码

private void addUser(final DirContext ctx, final LDAPPojo ldapPojo)
{

    this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
        + "::LDAPCreateUserUtility::addUser::");
    try
    {
        
        final Attributes                            attributes = new BasicAttributes();
        final javax.naming.directory.BasicAttribute objClasses = new BasicAttribute("objectClass");
        
        // WARNING: DO NOT TOUCH
        objClasses.add("inetOrgPerson");
        objClasses.add("posixAccount");
        objClasses.add("top");
        attributes.put(objClasses);
        
        // Define User attributes
        attributes.put(ConfigProperties.LDAP_KEY_LOGIN_SHELL, ldapPojo.getLoginShell());
        attributes.put(ConfigProperties.LDAP_KEY_GIVEN_NAME, ldapPojo.getFirstName());
        attributes.put(ConfigProperties.LDAP_KEY_HOME_DIRECTORY, ldapPojo.getHomeDirectory());
        attributes.put(ConfigProperties.LDAP_KEY_UID, ldapPojo.getUid());
        attributes.put(ConfigProperties.LDAP_KEY_UID_NUMBER, ldapPojo.getUidNumber());
        attributes.put(ConfigProperties.LDAP_KEY_GID_NUMBER, ldapPojo.getGidNumber());
        attributes.put(ConfigProperties.LDAP_KEY_SN, ldapPojo.getSn());
        attributes.put(ConfigProperties.LDAP_KEY_CN, ldapPojo.getCn());
        attributes.put(ConfigProperties.LDAP_KEY_PASSWORD, ldapPojo.getPassword());
        
        this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::addUser::Sub Context: " + ldapPojo.getSubContext());
        ctx.createSubcontext(ldapPojo.getSubContext(), attributes);
        
        this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::addUser::success");
        ldapPojo.setReturnFlag(ResourceProperty.configBundle.getString("RETURN_TRUE_FLAG"));
    }
    catch(final Exception e)
    {
        this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::addUser::Exception: " + e.getStackTrace(), e);
        this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::addUser::exception_for_user_LDAP >> " + ldapPojo.getUid());
        ldapPojo.setReturnFlag(ResourceProperty.messagesBundle.getString("LDAP_USER_CREATION_ERROR_MSG"));
    }
    finally
    {
        try
        {
            this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
                + "::LDAPCreateUserUtility::addUser::pstmt closed");
        }
        catch(final Exception ignore)
        {
            this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
                + "::LDAPCreateUserUtility::addUser::Exception: " + ignore.getStackTrace(), ignore);
            ldapPojo.setReturnFlag(ResourceProperty.messagesBundle.getString("LDAP_USER_CREATION_ERROR_MSG"));
        }
    }
}
public void createLDAPUser(final LDAPPojo ldapPojo)
{
    this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
        + "::LDAPCreateUserUtility::createLDAPUser::");
    try
    {
        final Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, ConfigProperties.INITIAL_CONTEXT_FACTORY);
        env.put(Context.PROVIDER_URL, this.contextSetPropertiesUtilityPOJO.getLDAP_PROVIDER_URL());
        env.put(Context.SECURITY_PRINCIPAL, ConfigProperties.SECURITY_PRINCIPAL);
        env.put(Context.SECURITY_AUTHENTICATION, ConfigProperties.SECURITY_AUTHENTICATION);
        env.put(Context.SECURITY_PRINCIPAL, this.contextSetPropertiesUtilityPOJO.getLDAP_ROOT_LOGIN_USER());
        env.put(Context.SECURITY_CREDENTIALS, this.contextSetPropertiesUtilityPOJO.getLDAP_ROOT_LOGIN_PASSWORD());
        this.addUser((new InitialDirContext(env)), ldapPojo);
        
    }
    catch(final Exception exception)
    {
        this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::addUser::Exception: " + exception.getStackTrace());
    }
}
于 2020-07-10T14:48:02.500 回答
1

这是 OpenLDAP 登录的工作代码

public void checkUserCredential(final LDAPPojo ldapPojo)
{
    this.contextSetPropertiesUtilityPOJO =
        this.contextSetPropertiesUtility.ContextSetProperties(this.contextSetPropertiesUtilityPOJO);
    this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
        + "::LDAPCreateUserUtility::checkUserCredential::");
    
    this.log.info(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
        + "::LDAPCreateUserUtility::checkUserCredential::get LDAP Provider URL: "
        + this.contextSetPropertiesUtilityPOJO.getLDAP_PROVIDER_URL());
    
    try
    {
        final Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, ConfigProperties.INITIAL_CONTEXT_FACTORY);
        env.put(Context.PROVIDER_URL, this.contextSetPropertiesUtilityPOJO.getLDAP_PROVIDER_URL());
        env.put(Context.SECURITY_PRINCIPAL, ConfigProperties.SECURITY_PRINCIPAL);
        env.put(Context.SECURITY_AUTHENTICATION, ConfigProperties.SECURITY_AUTHENTICATION);
        env.put(Context.SECURITY_PRINCIPAL, ldapPojo.getUserName());
        env.put(Context.SECURITY_CREDENTIALS, ldapPojo.getPassword());
        
        new InitialDirContext(env).close();
        ldapPojo.setReturnFlag(ResourceProperty.errorCodeBundle.getString("PASSWORD_MATCHES_CODE"));
    }
    catch(final NamingException e)
    {
        this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::checkUserCredential::Exception: " + e.getStackTrace(), e);
        ldapPojo.setReturnFlag(ResourceProperty.messagesBundle.getString("PASSWORD_DOES_NOT_MATCH_MSG"));
    }
    catch(final Exception ex)
    {
        this.log.error(UI.getCurrent().getSession().getAttribute(ConfigProperties.SESSION_KEY)
            + "::LDAPCreateUserUtility::checkUserCredential::Exception: " + ex.getStackTrace(), ex);
        ldapPojo.setReturnFlag(ResourceProperty.messagesBundle.getString("PASSWORD_DOES_NOT_MATCH_MSG"));
    }
于 2020-07-10T14:44:47.143 回答