java连接AD(Microsoft Active Directory)模拟用户登录认证

在这里插入图片描述

一、背景

亲测可用,之前搜索了很多博客,啥样的都有,就是不介绍报错以及配置用处,根本不懂照抄那些配置是干啥的,稀里糊涂的按照博客搭完也跑不起来,因此记录这个。

项目背景:公司项目当前采用http协议+shiro+mysql的登录认证方式,而现在想支持ldap协议认证登录然后能够访问自己公司的项目网站。

举例说明:假设我们公司有自己的门户网站,现在我们收购了一家公司,他们数据库采用ldap存储用户数据,那么为了他们账户能登陆我们公司项目所以需要集成,而不是再把他们的账户重新在mysql再创建一遍,万一人家有1W个账户呢,不累死了且也不现实啊。

需要安装AD+kerberos,且ldap和kerberos安装在同一台服务器上,当前版本如下:

  • windows server 2016
  • 服务器IP:10.110.25.51

我公司电脑室windows10的,我是VietualBox安装的windows server 2016,类似安装个虚拟机,然后去安装AD。

二、使用流程:

前提条件
需要先搭建LDAP环境(AD或者Openldap)情况下,再进行如下操作比如点击“测试连接”按钮查看是否能通。

参考文章:https://docs.vmware.com/en/VMware-NSX-Advanced-Load-Balancer/30.1/Administration-Guide/GUID-B2D6E88F-4718-447A-A8CA-77B9C9594078.html

页面分2页

  • 第一页LDAP Auth Profile:进行TA账户配置,你也可以理解为管理元账户配置,配置更细层的参数
  • 第二页LDAP Availability Test:进行账户测试,判断是否允许访问项目,只有条件符合且用户所属组在有效组及子组范围内才允许访问项目。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 单击“选择文件”,上传LDAP公钥文件。
    LDAP公钥用于加密通信,验证数字签名。
  2. TA账户配置说明,本配置使用管理员绑定方式,支持用户过滤器和组过滤器设置。
  • Settings
    • Domain name:配置AD域服务器的域名。
    • LDAP Connection Security Mode:选择LDAP连接的安全模式。
    • Port:使用LDAPS端口636。
    • Base DN:搜索的起始点,通常为LDAP目录结构中的根节点。
    • Admin Bind DN:用于绑定到LDAP目录的管理员用户DN,用于身份验证。
    • Admin Bind Password:管理员绑定密码,与Admin Bind DN对应,用于验证管理员身份。
  • User
    • User Search DN :定义从哪个DN开始搜索LDAP目录中的用户。
    • User Search Scope:LDAP用户搜索范围,决定从用户搜索DN开始的搜索广度。
      可以设置为:
      • Base(基本):仅搜索指定的DN
      • One Level(单层):搜索指定DN直接下级的对象
      • Subtree(子树):搜索指定DN及其所有子级对象
    • User ID Attribute:唯一标识单个用户记录的登录属性。
      允许格式包括:
      1)简单属性值:sAMAccountName
      2)属性过滤器:sAMAccountName=或者(sAMAccountName=)
      3)复杂过滤器: (&(objectCategory=person)(sAMAccountName=*))
      4)多属性用逗号分割:sAMAccountName,cn
  • Group
    • Group Search DN :定义从哪个DN开始搜索LDAP目录中的组。
    • Group Search Scope:LDAP组搜索范围,决定从组搜索DN开始的搜索广度。
      可以设置为:
      • Base(基本):仅搜索指定的DN
      • One Level(单层):搜索指定DN直接下级的对象
      • Subtree(子树):搜索指定DN及其所有子级对象
    • Group Filter:精确搜索特定组条目的过滤条件。支持使用LDAP查询语法来定义过滤条件,以便只返回符合条件的组。
      允许格式包括:
      1)属性过滤器:objectClass=或者(objectClass=)
      2)复杂过滤器:(|(objectClass=group)(objectClass=organizationalUnit))
    • Group Member Attribute:是一个多值属性,用于存储组成员DN的多值属性。该属性记录了属于该组的用户DN列表。
      允许格式包括:
      1)简单属性值:member
      2)多属性用逗号分割:member,cn
    • Group Access UNM:配置具有网管访问权限的组名,支持多个组名的配置,请使用逗号“,”分隔每个组名。
  1. 输入LDAP服务的用户名、登录密码,单击“测试”,判断当前网管是否连通LDAP服务器。
  • ldap账户名:配置用于测试连接的账户名,在测试过程中, UNM不会在系统中创建测试账户。
  • ldap账户密码:配置用于测试连接的账户密码。
    在“测试”过程中,UNM不会保存测试账户信息。
  1. 如果弹窗提示成功,则证明已连通LDAP服务器;如果提示其他错误请及时修改配置参数。
    在这里插入图片描述

三、代码

LdapLoginRequest请求实体类

package com.hero.lte.ems.security.entity;

import java.util.Date;

public class LdapLoginRequest {
    private String username;

    private String password;

    private String principal;

    /**
     * kerberos keytab文件的绝对路径
     */
    private String keytabFilePath;

    /**
     * ldap服务器标识,只能为1或2
     */
    private String ldapFlag;
    /**
     * 域名
     */
    private String domainName;

    //An LDAP Admin Bind DN (Distinguished Name) is a unique identifier used to authenticate an administrative user to an LDAP server
    public String baseDn;
    //used to authenticate the administrative user to an LDAP server
    public String adminBindDn;
    //used to authenticate the administrative user to an LDAP server
    public String adminBindPassword;
    //LDAP User Search DN is the root of search for a given user in the LDAP directory
    public String userSearchDn;
    //LDAP User Search Scope determines the extent of search for the user starting from user search DN.[Base: searches only the specified DN, One Level: searches objects directly below the specified DN, Subtree: searches the specified DN and all its child objects]
    public int userSearchScope;
    //LDAP User ID Attribute is the login attribute that uniquely identifies a single user record
    public String userIdAttribute;
    //LDAP Group Search DN is the root of search for a given group in the LDAP directory
    public String groupSearchDn;
    //Define the LDAP Group Search Scope using filters for the group starting from the group search DN.[Base: searches only the specified DN, One Level: searches objects directly below the specified DN, Subtree: searches the specified DN and all its child objects]
    public int groupSearchScope;
    //LDAP Group Filter is a search filter used to focus the search results to specific group entries within the directory
    public String groupFilter;
    //LDAP Group Member Attribute is a multi-valued attribute that can store the DNs of the users who are members of the group. It is used for retrieving data about group members and managing group membership
    public String groupMemberAttribute;
    //创建时间
    private Date createTime;
    //更新时间
    private Date updateTime;
    //LDAP Connection Security Mode,both LDAP and LDAPS are included
    public int ldapConnectionSecurityMode;
    //Use LDAP port 389 and LDAPS port 636
    public int port;
    //Enable Ignore Referrals for the group search to skip referrals links that connect to another LDAP server. This option is deselected by default
    public int ignoreReferrals;
    //Enable this option if the group member entries have full DNS instead of just user ID attributes
    public int enableFullDnForGroupMemberAttribute;
    //Configure the group name that has the UNM access permission. Multiple group names can be configured. Use commas (,) to separate each group name
    public String validGroup;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPrincipal() {
        return principal;
    }

    public void setPrincipal(String principal) {
        this.principal = principal;
    }

    public String getKeytabFilePath() {
        return keytabFilePath;
    }

    public void setKeytabFilePath(String keytabFilePath) {
        this.keytabFilePath = keytabFilePath;
    }

    public String getLdapFlag() {
        return ldapFlag;
    }

    public void setLdapFlag(String ldapFlag) {
        this.ldapFlag = ldapFlag;
    }

    public String getDomainName() {
        return domainName;
    }

    public void setDomainName(String domainName) {
        this.domainName = domainName;
    }

    public String getBaseDn() {
        return baseDn;
    }

    public void setBaseDn(String baseDn) {
        this.baseDn = baseDn;
    }

    public String getAdminBindDn() {
        return adminBindDn;
    }

    public void setAdminBindDn(String adminBindDn) {
        this.adminBindDn = adminBindDn;
    }

    public String getAdminBindPassword() {
        return adminBindPassword;
    }

    public void setAdminBindPassword(String adminBindPassword) {
        this.adminBindPassword = adminBindPassword;
    }

    public String getUserSearchDn() {
        return userSearchDn;
    }

    public void setUserSearchDn(String userSearchDn) {
        this.userSearchDn = userSearchDn;
    }

    public int getUserSearchScope() {
        return userSearchScope;
    }

    public void setUserSearchScope(int userSearchScope) {
        this.userSearchScope = userSearchScope;
    }

    public String getUserIdAttribute() {
        return userIdAttribute;
    }

    public void setUserIdAttribute(String userIdAttribute) {
        this.userIdAttribute = userIdAttribute;
    }

    public String getGroupSearchDn() {
        return groupSearchDn;
    }

    public void setGroupSearchDn(String groupSearchDn) {
        this.groupSearchDn = groupSearchDn;
    }

    public int getGroupSearchScope() {
        return groupSearchScope;
    }

    public void setGroupSearchScope(int groupSearchScope) {
        this.groupSearchScope = groupSearchScope;
    }

    public String getGroupFilter() {
        return groupFilter;
    }

    public void setGroupFilter(String groupFilter) {
        this.groupFilter = groupFilter;
    }

    public String getGroupMemberAttribute() {
        return groupMemberAttribute;
    }

    public void setGroupMemberAttribute(String groupMemberAttribute) {
        this.groupMemberAttribute = groupMemberAttribute;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public int getLdapConnectionSecurityMode() {
        return ldapConnectionSecurityMode;
    }

    public void setLdapConnectionSecurityMode(int ldapConnectionSecurityMode) {
        this.ldapConnectionSecurityMode = ldapConnectionSecurityMode;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getIgnoreReferrals() {
        return ignoreReferrals;
    }

    public void setIgnoreReferrals(int ignoreReferrals) {
        this.ignoreReferrals = ignoreReferrals;
    }

    public int getEnableFullDnForGroupMemberAttribute() {
        return enableFullDnForGroupMemberAttribute;
    }

    public void setEnableFullDnForGroupMemberAttribute(int enableFullDnForGroupMemberAttribute) {
        this.enableFullDnForGroupMemberAttribute = enableFullDnForGroupMemberAttribute;
    }

    public String getValidGroup() {
        return validGroup;
    }

    public void setValidGroup(String validGroup) {
        this.validGroup = validGroup;
    }

    @Override
    public String toString() {
        return "LdapLoginRequest{" +
                "username='" + username + '\'' +
                ", principal='" + principal + '\'' +
                ", keytabFilePath='" + keytabFilePath + '\'' +
                ", ldapFlag='" + ldapFlag + '\'' +
                ", domainName='" + domainName + '\'' +
                ", baseDn='" + baseDn + '\'' +
                ", adminBindDn='" + adminBindDn + '\'' +
                ", userSearchDn='" + userSearchDn + '\'' +
                ", userSearchScope=" + userSearchScope +
                ", userIdAttribute='" + userIdAttribute + '\'' +
                ", groupSearchDn='" + groupSearchDn + '\'' +
                ", groupSearchScope=" + groupSearchScope +
                ", groupFilter='" + groupFilter + '\'' +
                ", groupMemberAttribute='" + groupMemberAttribute + '\'' +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                ", ldapConnectionSecurityMode=" + ldapConnectionSecurityMode +
                ", port=" + port +
                ", ignoreReferrals=" + ignoreReferrals +
                ", enableFullDnForGroupMemberAttribute=" + enableFullDnForGroupMemberAttribute +
                ", validGroup='" + validGroup + '\'' +
                '}';
    }
}

LdapService实现类

package com.hero.lte.ems.security.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hero.lte.ems.configuration.CommonMessageAccessor;
import com.hero.lte.ems.framework.enums.LteResultCodeEnum;
import com.hero.lte.ems.framework.exception.LteException;
import com.hero.lte.ems.framework.resp.ResultSet;
import com.hero.lte.ems.security.constant.LdapConstants;
import com.hero.lte.ems.security.entity.LdapLoginRequest;
import com.hero.lte.ems.security.entity.TreeNode;
import com.hero.lte.ems.security.service.ILdapService;
import com.hero.lte.ems.sysmanager.service.ISysNameService;
import com.sun.security.auth.module.Krb5LoginModule;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.security.krb5.Config;
import sun.security.krb5.KrbException;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.PortUnreachableException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class LdapService implements ILdapService {

    private static final Logger log = LoggerFactory.getLogger(LdapService.class);

    private static final String SAMACCOUNTNAME_KEY = "sAMAccountName";
    private static final String GROUP_KEY = "memberOf";
    private static final String MEMBER_KEY = "member";
    private static final String NAME_KEY = "name";
    private static final String KEYSTORE_FILE = "C:\\Users\\211145187\\Desktop\\fsdownload\\ldap_keystore.jks";
    private static final String KEYSTORE_PASSWORD = "ldap@1993";
    private static final String KEYSTORE_TYPE = "JKS";
    @Autowired
    private ISysNameService sysNameService;

    @Override
    public ResultSet login(LdapLoginRequest request) {
        log.info("ldap login request:{}", request);
        String configPath = LdapConstants.LDAP_SERVER1_CONFIG_PATH;
        File keystoreFile = new File(configPath + File.separator + LdapConstants.KEYSTORE_FILE_NAME);
        if (keystoreFile.exists()) {
            try {
                setCertificate(configPath + File.separator + LdapConstants.KEYSTORE_FILE_NAME);
            } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException |
                     KeyManagementException e) {
                return new ResultSet(new LteException(LteResultCodeEnum.SSL_CERTIFICATE_ERROR.getErrorCode()));
            }
        } else {
            return new ResultSet(new LteException(LteResultCodeEnum.CERTIFICATE_FILE_NOT_EXIST.getErrorCode()));
        }
        ResultSet resultSet = null;
        if (StringUtils.isNotBlank(request.getKeytabFilePath())) {
            File keytabFile = new File(request.getKeytabFilePath());
            if (keytabFile.exists()) {
                resultSet = loginWithKeytab(request, configPath);
                log.info("resultSet from loginWithKeytab:{}", resultSet);
            } else {
                log.warn("keytab file does not exit");
            }
        }
        if (resultSet != null && LteResultCodeEnum.SUCCESS.name().equals(resultSet.getErrorCode())) {
            return resultSet;
        } else {
            return loginWithPassword(request, configPath);
        }
    }

    private static class KerberosCallbackHandler implements javax.security.auth.callback.CallbackHandler {
        private final String username;
        private final String password;

        public KerberosCallbackHandler(String username, String password) {
            this.username = username;
            this.password = password;
        }

        @Override
        public void handle(Callback[] callbacks) throws UnsupportedCallbackException, IOException {
            for (Callback callback : callbacks) {
                if (callback instanceof TextOutputCallback) {
                    // display the message according to the specified type
                    TextOutputCallback toc = (TextOutputCallback) callback;
                    switch (toc.getMessageType()) {
                        case TextOutputCallback.INFORMATION:
                            log.info(toc.getMessage());
                            break;
                        case TextOutputCallback.ERROR:
                            log.error("ERROR: {}", toc.getMessage());
                            break;
                        case TextOutputCallback.WARNING:
                            log.warn("WARNING: {}", toc.getMessage());
                            break;
                        default:
                            throw new IOException("Unsupported message type: " + toc.getMessageType());
                    }
                } else if (callback instanceof NameCallback) {
                    ((NameCallback) callback).setName(username);
                } else if (callback instanceof PasswordCallback) {
                    ((PasswordCallback) callback).setPassword(password.toCharArray());
                } else {
                    throw new UnsupportedCallbackException(callback, "Unrecognized Callback");
                }
            }
        }
    }

    private void refreshConfig() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> configClass = Config.class;
        Field instanceField = configClass.getDeclaredField("instance");
        instanceField.setAccessible(true);
        instanceField.set(null, null);
        Method refreshMethod = configClass.getDeclaredMethod("refresh");
        refreshMethod.setAccessible(true);
        refreshMethod.invoke(null);
    }

    private ResultSet loginWithPassword(LdapLoginRequest request, String configPath) {
        try {
            sun.security.krb5.Config.refresh();
//            refreshConfig();
        } catch (KrbException e) {
            log.error("refresh config error", e);
        }
        System.setProperty("java.security.krb5.conf", configPath + File.separator + LdapConstants.KRB5_CONF_FILE_NAME);
        System.setProperty("sun.security.krb5.debug", "true");

        LoginContext loginContext = null;
        try {
            loginContext = new LoginContext("com.sun.security.jgss.krb5.initiate", null, new KerberosCallbackHandler(request.getUsername(), request.getPassword()),
                    new Configuration() {
                        @Override
                        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
                            Map<String, String> options = new HashMap<>();
                            options.put("useKeyTab", "false");
                            options.put("useTicketCache", "false");
                            options.put("storeKey", "true");
                            options.put("refreshKrb5Config", "true");
                            return new AppConfigurationEntry[]{
                                    new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
                                            AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)
                            };
                        }
                    });
            loginContext.login();
            Subject subject = loginContext.getSubject();
            return checkTicket(subject, request);
        } catch (LoginException e) {
            log.error("login error: ", e);
            return handleException(e);
        } finally {
            try {
                if (loginContext != null) {
                    loginContext.logout();
                }
            } catch (LoginException e) {
                log.error("logout error: ", e);
            }
        }
    }

    private ResultSet loginWithKeytab(LdapLoginRequest request, String configPath) {
        try {
            sun.security.krb5.Config.refresh();
//            refreshConfig();
        } catch (KrbException e) {
            log.error("refresh config error", e);
        }
        if (StringUtils.isBlank(request.getKeytabFilePath())) {
            return new ResultSet(new LteException(LteResultCodeEnum.ILLEGAL_PARAM.getErrorCode()));
        }
        File keytabFile = new File(request.getKeytabFilePath());
        if (!keytabFile.exists()) {
            return new ResultSet(new LteException(LteResultCodeEnum.KEYTAB_FILE_NOT_EXIST.getErrorCode()));
        }
        System.setProperty("java.security.krb5.conf", configPath + File.separator + LdapConstants.KRB5_CONF_FILE_NAME);
        System.setProperty("sun.security.krb5.debug", "true");
        Map<String, String> options = new Hashtable<>();
        options.put("useKeyTab", "true");
        options.put("keyTab", request.getKeytabFilePath());
        options.put("storeKey", "true");
        options.put("principal", request.getPrincipal());
        options.put("refreshKrb5Config", "true");
        options.put("useTicketCache", "false");
        options.put("debug", "true");
        LoginContext loginContext = null;
        try {
            Configuration jaasConfig = new Configuration() {
                @Override
                public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
                    return new AppConfigurationEntry[]{
                            new AppConfigurationEntry(Krb5LoginModule.class.getName(),
                                    AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)
                    };
                }
            };
            loginContext = new LoginContext("com.sun.security.jgss.krb5.initiate", null, null, jaasConfig);
            loginContext.login();
            Subject subject = loginContext.getSubject();
            return checkTicket(subject, request);
        } catch (LoginException e) {
            log.error("login error: ", e);
            return handleException(e);
        } finally {
            try {
                assert loginContext != null;
                loginContext.logout();
            } catch (LoginException e) {
                log.error("logout error: ", e);
            }
        }
    }

    private ResultSet checkTicket(Subject subject, LdapLoginRequest request) {
        log.debug("subject: {},request:{}", subject, request);
        if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty()) {
            log.info("kerberos authentication failed");
            return new ResultSet(new LteException(LteResultCodeEnum.KERBEROS_AUTHENTICATION_FAILED.getErrorCode()));
        } else {
            log.info("kerberos authentication succeeded");
            return Subject.doAs(subject, (PrivilegedAction<ResultSet>) () -> {
                LdapContext ldapContext = simpleBind(request);
                if (ldapContext == null) {
                    return new ResultSet(new LteException(LteResultCodeEnum.SIMPLE_BIND_FAILED.getErrorCode()));
                }
                boolean result;
                Map<String, List<Object>> userInfo = new HashMap<>();
                List<Map<String, Object>> groupList = null;
                try {
                    if (org.springframework.util.StringUtils.isEmpty(request.getValidGroup())) {
                        result = true;
                    } else {
                        //                    String searchBase = getSearchBase(request);
                        userInfo = getUserInfo(ldapContext, request, request.getUsername());
                        result = checkGroup(userInfo, request);
                        if (!result) {
                            groupList = getGroupInfo(ldapContext, request, request.getUsername());
                            Map<String, TreeNode> tree = buildTree(groupList);
                            String[] validGroups = request.getValidGroup().split(",");
                            if (userInfo.size() > 0 && userInfo.containsKey(GROUP_KEY)) {
                                List<Object> memberOfList = userInfo.get(GROUP_KEY);
                                for (Object userGroup : memberOfList) {
                                    for (int i = 0; i < validGroups.length; i++) {
                                        result = isMemberOf(tree, userGroup.toString(), validGroups[i]);
                                        if (result) {
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                } catch (LteException e) {
                    String errorMessage = CommonMessageAccessor.getMessage(e.getErrorCode(), null);
                    log.error("ldap operation errorCode: {}, errorMessage:{}", e.getErrorCode(), errorMessage);
                    return new ResultSet(new LteException(e.getErrorCode()));
                } catch (NamingException e) {
                    log.error("ldap operation error: ", e);
                    return new ResultSet(new LteException(LteResultCodeEnum.FAIL_GET_USER_INFO.getErrorCode()));
                }
                if (!result) {
                    log.warn("user not in valid group");
                    return new ResultSet(new LteException(LteResultCodeEnum.NOT_IN_VALID_GROUP.getErrorCode()));
                }
                JSONObject response = new JSONObject();
                response.put("messageOf", userInfo.get(GROUP_KEY));
                ResultSet resultSet = new ResultSet(response);
                JSONObject jsonObject = (JSONObject) JSON.toJSON(resultSet);
                log.info("resultSet:{}", jsonObject);
                return resultSet;
            });
        }
    }

    public static Map<String, TreeNode> buildTree(List<Map<String, Object>> data) {
        Map<String, TreeNode> nodeMap = new HashMap<>();

        // Create nodes
        for (Map<String, Object> entry : data) {
            String name = (String) entry.get("name");
            nodeMap.put(name, new TreeNode(name));
        }

        // Set parent-child relationships
        for (Map<String, Object> entry : data) {
            String name = (String) entry.get("name");
            String parentName = (String) entry.get("memberOf");
            TreeNode node = nodeMap.get(name);

            if (parentName != null) {
                TreeNode parent = nodeMap.get(parentName);
                if (parent != null) {
                    node.setParent(parent);
                    parent.getChildren().add(node);
                }
            }
        }
        return nodeMap;
    }
    public static boolean isMemberOf(Map<String, TreeNode> tree, String userGroup, String validGroup) {
        TreeNode userNode = tree.get(userGroup);
        TreeNode validNode = tree.get(validGroup);

        if (userNode == null || validNode == null) {
            return false;
        }

        // Check if userGroup is validGroup
        if (userGroup.equals(validGroup)) {
            return true;
        }

        // Check if userGroup is a descendant of validGroup
        return isDescendant(userNode, validNode);
    }

    private static boolean isDescendant(TreeNode node, TreeNode potentialAncestor) {
        while (node != null) {
            if (node.equals(potentialAncestor)) {
                return true;
            }
            node = node.getParent();
        }
        return false;
    }

    private ResultSet handleException(Exception e) {
        if (e.getCause() != null && e.getCause() instanceof KrbException) {
            log.warn("catch KrbException");
            KrbException krbException = (KrbException) e.getCause();
            int returnCode = krbException.returnCode();
            log.info("returnCode:{}", returnCode);
            switch (returnCode) {
                case 6:
                    log.warn("account does not exist");
                    return new ResultSet(new LteException(LteResultCodeEnum.NO_EXIST_ACCOUNT.getErrorCode()));
                case 24:
                    log.warn("incorrect password");
                    return new ResultSet(new LteException(LteResultCodeEnum.INCORRECT_PASSWORD.getErrorCode()));
                default:
                    return new ResultSet(new LteException(LteResultCodeEnum.KERBEROS_AUTHENTICATION_FAILED.getErrorCode()));
            }
        } else if (e.getCause() != null && e.getCause() instanceof PortUnreachableException) {
            log.warn("catch PortUnreachableException");
            return new ResultSet(new LteException(LteResultCodeEnum.PORT_UNREACHABLE.getErrorCode()));
        } else if (e.getCause() != null && e.getCause() instanceof SocketTimeoutException) {
            log.warn("catch SocketTimeoutException");
            return new ResultSet(new LteException(LteResultCodeEnum.SOCKET_TIMEOUT.getErrorCode()));
        } else if (e.getCause() != null && e.getCause() instanceof UnknownHostException) {
            log.info("unknown host");
            return new ResultSet(new LteException(LteResultCodeEnum.UNKNOWN_HOST.getErrorCode()));
        }
        return new ResultSet(new LteException(LteResultCodeEnum.KERBEROS_AUTHENTICATION_FAILED.getErrorCode()));
    }

    private void setCertificate(String keyStorePath) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException {
        try (FileInputStream fis = new FileInputStream(keyStorePath)) {
            KeyStore keyStore = KeyStore.getInstance(LdapConstants.KEYSTORE_TYPE);
            keyStore.load(fis, new String(Base64.getDecoder().decode(LdapConstants.KEYSTORE_PASSWORD)).toCharArray());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            SSLContext.setDefault(sslContext);
        }
    }

    private LdapContext simpleBind(LdapLoginRequest request) {
        if (StringUtils.isBlank(request.getDomainName())) {
            log.error("domainName is null");
            throw new LteException(LteResultCodeEnum.DOMAIN_NAME_ISNULL.getErrorCode());
        }
        Hashtable<String, String> env = getEnvHashtable(request);
        LdapContext ctx;
        try {
            ctx = new InitialLdapContext(env, null);
            log.info("LDAPS simple bind success");
        } catch (NamingException e) {
            log.error("simple bind error: ", e);
            return null;
        }
        return ctx;
    }

    private boolean checkGroup(Map<String, List<Object>> userInfo, LdapLoginRequest request) {
        try {
            if (userInfo == null || userInfo.isEmpty()) {
                throw new LteException(LteResultCodeEnum.FAIL_GET_USER_INFO.getErrorCode());
            }
            if (userInfo.get(GROUP_KEY) == null) {
                throw new LteException(LteResultCodeEnum.FAIL_GET_GROUP_INFO.getErrorCode());
            }
            List<Object> memberOfList = userInfo.get(GROUP_KEY);
            String validGroup = request.getValidGroup();
            log.info("memberOf: {},validGroup: {}", memberOfList, validGroup);
            if (StringUtils.isBlank(validGroup)) {
                return false;
            }
            List<String> validGroupList = Arrays.asList(validGroup.split(","));
            return validGroupList.stream().anyMatch(memberOfList::contains);
        } catch (Exception e) {
            log.error("performLdapOperation error: ", e);
            return false;
        }
    }

    private String getSearchBase(LdapLoginRequest request) {
        String domainName = request.getDomainName();
        String[] domainNameArray = domainName.split("\\.");
        StringBuilder searchBase = new StringBuilder();
        for (String str : domainNameArray) {
            searchBase.append("DC=").append(str).append(",");
        }
        searchBase = new StringBuilder(searchBase.substring(0, searchBase.length() - 1));
        return searchBase.toString();
    }

    private Hashtable<String, String> getEnvHashtable(LdapLoginRequest config) {
        String ldapConnectionSecurityMode = "";
        if (config.getLdapConnectionSecurityMode() == 0) {
            ldapConnectionSecurityMode = "ldaps";
        } else {
            ldapConnectionSecurityMode = "ldap";
        }
        String ldapURL = ldapConnectionSecurityMode + "://" + config.getDomainName() + ":" + config.getPort();
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, ldapURL);
        String adminBindDn = config.getAdminBindDn();
        if (adminBindDn.contains("cn=")) {
            env.put(Context.SECURITY_PRINCIPAL, adminBindDn);
        } else {
            String securityPrincipal = adminBindDn + "@" + config.getDomainName();
            log.info("securityPrincipal:{}", securityPrincipal);
            env.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
        }
        env.put(Context.SECURITY_CREDENTIALS, config.getAdminBindPassword());
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        return env;
    }

    public Map<String, List<Object>> getUserInfo(DirContext ctx, LdapLoginRequest request, String userAccount) throws NamingException {
        String userBaseDN = request.getBaseDn();
        if (!org.springframework.util.StringUtils.isEmpty(request.getUserSearchDn())) {
            userBaseDN = request.getUserSearchDn();
        }
        StringBuilder searchFilter = new StringBuilder();
        if (request.getUserIdAttribute().contains("&") || request.getUserIdAttribute().contains("|")) {
            searchFilter.append(request.getUserIdAttribute());
        } else if (request.getUserIdAttribute().contains("(") && request.getUserIdAttribute().contains(")")) {
            if (request.getUserIdAttribute().contains("=")) {
                searchFilter.append("(&(objectCategory=person)(objectClass=user)").append(request.getUserIdAttribute()).append(")");
            } else {
                String userIdAttribute = request.getUserIdAttribute().replace(")", "");
                searchFilter.append("(&(objectCategory=person)(objectClass=user)").append(userIdAttribute).append("=*").append("))");
            }
        } else if (!request.getUserIdAttribute().contains("(") && !request.getUserIdAttribute().contains(")")) {
            if (request.getUserIdAttribute().contains("=")) {
                searchFilter.append("(&(objectCategory=person)(objectClass=user)(").append(request.getUserIdAttribute()).append("))");
            } else if (request.getUserIdAttribute().contains(",")) {
                String[] split = request.getUserIdAttribute().split(",");
                searchFilter.append("(&(objectCategory=person)(objectClass=user)");
                for (String userIdAttribute : split) {
                    searchFilter.append("(").append(userIdAttribute).append("=*)");
                }
                searchFilter.append(")");
            } else {
                searchFilter.append("(&(objectCategory=person)(objectClass=user)(").append(request.getUserIdAttribute()).append("=*").append("))");
            }
        }

        SearchControls searchControls = new SearchControls();
        if (request.getUserSearchScope() == 0) {
            searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
        } else if (request.getUserSearchScope() == 1) {
            searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
        } else {
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        }
        Map<String, List<Object>> userInfo = new HashMap<>();
        log.info("-getUserInfo-userBaseDN:{},searchFilter:{},searchControls:{}", userBaseDN, searchFilter.toString(), searchControls.getSearchScope());
        NamingEnumeration<SearchResult> answers = ctx.search(userBaseDN, searchFilter.toString(), searchControls);
        while (answers.hasMoreElements()) {
            SearchResult sr = answers.next();
            Attributes attrs = sr.getAttributes();
            //获取的用户列表中是否含有和登录用户名相同的用户对象
            if (attrs != null) {
                try {
                    Attribute samAccountNameAttr = attrs.get("sAMAccountName");
                    if (samAccountNameAttr != null && samAccountNameAttr.size() > 0) {
                        String samAccountName = samAccountNameAttr.get(0).toString();
                        if (userAccount.equals(samAccountName)) {
                            for (NamingEnumeration<? extends Attribute> ne = attrs.getAll(); ne.hasMore(); ) {
                                Attribute attr = ne.next();
                                for (int i = 0; i < attr.size(); i++) {
                                    String id = attr.getID();
                                    Object value = attr.get(i);
                                    log.debug("id:{},value:{}", id, value);
                                    List<Object> list;
                                    if (userInfo.containsKey(id)) {
                                        list = userInfo.get(id);
                                    } else {
                                        list = new ArrayList<>();
                                    }
                                    if (GROUP_KEY.equals(id)) {
                                        String valueT = String.valueOf(value);
                                        extractOUValues(valueT, list);
                                        log.info("user belong to OU:{}", list);
                                        value = valueT.replaceAll("CN=", "").split(",")[0];
                                        log.debug("id:{},after group info intercepted value:{}", id, value);
                                    }
                                    list.add(value);
                                    userInfo.put(id, list);
                                }
                            }
                        }
                    }
                } catch (NamingException e) {
                    log.error("NamingException: ", e);
                }
            }
        }
        return userInfo;
    }

    public static List<Object> extractOUValues(String dn, List<Object> ouValues) {
        // 定义正则表达式,用于匹配 OU 属性及其值
        String ouPattern = "OU=([^,]+)";
        // 创建 Pattern 和 Matcher 对象
        Pattern pattern = Pattern.compile(ouPattern);
        Matcher matcher = pattern.matcher(dn);
        // 查找所有匹配的 OU 值并添加到列表
        while (matcher.find()) {
            if (!ouValues.contains(matcher.group(1))) {
                ouValues.add(matcher.group(1));
            }
        }
        return ouValues;
    }

    public List<Map<String, Object>> getGroupInfo(DirContext ctx, LdapLoginRequest request, String userAccount) throws NamingException {
        List<Map<String, Object>> groupList = new ArrayList<>();
        Map<String, Object> map = null;
        List<Object> memberList = null;
        String name = "";
        String userBaseDN = request.getBaseDn();
        if (!org.springframework.util.StringUtils.isEmpty(request.getGroupSearchDn())) {
            userBaseDN = request.getGroupSearchDn();
        }
        StringBuilder searchFilter = new StringBuilder();
        if (request.getGroupFilter().contains("&") || request.getGroupFilter().contains("|")) {
            searchFilter.append(request.getGroupFilter());
        } else if (request.getGroupFilter().contains("(") && request.getGroupFilter().contains(")")) {
            if (request.getGroupFilter().contains("=")) {
                searchFilter.append("(&").append(request.getGroupFilter());
            } else {
                String groupIdAttribute = request.getGroupFilter().replace(")", "");
                searchFilter.append("(&").append(groupIdAttribute).append("=group").append(")");
            }
        } else if (!request.getGroupFilter().contains("(") && !request.getGroupFilter().contains(")")) {
            if (request.getGroupFilter().contains("=")) {
                searchFilter.append("(&(").append(request.getGroupFilter()).append(")");
            } else {
                searchFilter.append("(&(").append(request.getGroupFilter()).append("=group").append(")");
            }
        }
        int count = 0;
        // 统计左括号的数量
        for (char c : searchFilter.toString().toCharArray()) {
            if (c == '(') {
                count++;
            }
        }
        if (count >= 3) {
            if (searchFilter.length() > 1) {
                searchFilter = new StringBuilder(searchFilter.substring(0, searchFilter.length() - 1));
            }
        }
        if (request.getGroupMemberAttribute().contains("&") || request.getGroupMemberAttribute().contains("|")) {
            searchFilter.append(request.getGroupMemberAttribute()).append(")");
        } else if (request.getGroupMemberAttribute().contains("(") && request.getGroupMemberAttribute().contains(")")) {
            if (request.getGroupMemberAttribute().contains("=")) {
                searchFilter.append(request.getGroupMemberAttribute()).append(")");
            } else {
                String groupMemberAttribute = request.getGroupMemberAttribute().replace(")", "");
                searchFilter.append(groupMemberAttribute).append("=*").append("))");
            }
        } else if (!request.getGroupMemberAttribute().contains("(") && !request.getGroupMemberAttribute().contains(")")) {
            if (request.getGroupMemberAttribute().contains("=")) {
                searchFilter.append("(").append(request.getGroupMemberAttribute()).append("))");
            } else if (request.getGroupMemberAttribute().contains(",")) {
                String[] split = request.getGroupMemberAttribute().split(",");
                for (String userIdAttribute : split) {
                    searchFilter.append("(").append(userIdAttribute).append("=*)");
                }
                searchFilter.append(")");
            } else {
                searchFilter.append("(").append(request.getGroupMemberAttribute()).append("=*").append("))");
            }
        }

        SearchControls searchControls = new SearchControls();
        if (request.getGroupSearchScope() == 0) {
            searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
        } else if (request.getGroupSearchScope() == 1) {
            searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
        } else {
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        }

        log.info("-getGroupInfo-userBaseDN:{},searchFilter:{},searchControls:{}", userBaseDN, searchFilter.toString(), searchControls.getSearchScope());
        NamingEnumeration<SearchResult> answers = ctx.search(userBaseDN, searchFilter.toString(), searchControls);
        // 用于存储组的层级结构
        while (answers.hasMoreElements()) {
            SearchResult sr = answers.next();
            Attributes attrs = sr.getAttributes();
            if (attrs != null) {
                try {
                    map = new HashMap<>();
                    memberList = new ArrayList<>();
                    log.info("---------------------------");
                    for (NamingEnumeration<? extends Attribute> ne = attrs.getAll(); ne.hasMore(); ) {
                        Attribute attr = ne.next();
                        for (int i = 0; i < attr.size(); i++) {
                            String id = attr.getID();
                            Object value = attr.get(i);
                            log.debug("id:{},value:{}", id, value);
                            if (GROUP_KEY.equals(id)) {
                                String valueT = String.valueOf(value);
                                value = valueT.replaceAll("CN=", "").split(",")[0];
                                log.debug("id:{},memberOf value:{}", id, value);
                                map.put(GROUP_KEY, value);
                            } else if (MEMBER_KEY.equals(id)) {
                                String valueT = String.valueOf(value);
                                value = valueT.replaceAll("CN=", "").split(",")[0];
                                log.debug("id:{},member value:{}", id, value);
                                memberList.add(value);
                            } else if (NAME_KEY.equals(id)) {
                                name = String.valueOf(value);
                                log.debug("id:{},name value:{}", id, value);
                                map.put(NAME_KEY, name);
                            }
                        }
                    }
                    map.put(MEMBER_KEY, memberList);
                    groupList.add(map);
                } catch (NamingException e) {
                    log.error("NamingException: ", e);
                }
            }
        }
        return groupList;
    }

    private static void setCertificate() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException {
        try (FileInputStream fis = new FileInputStream(KEYSTORE_FILE)) {
            KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
//            keyStore.load(fis, new String(Base64.getDecoder().decode(KEYSTORE_PASSWORD)).toCharArray());
            keyStore.load(fis, KEYSTORE_PASSWORD.toCharArray());
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            SSLContext sslContext = SSLContext.getInstance("TLS");



            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            SSLContext.setDefault(sslContext);
        }
    }

    public static void main(String[] args) {
        try {
            setCertificate();
        } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException |
                KeyManagementException e) {
            log.error("setCertificate error: ", e);
        }

        String ldapUrl = "ldaps://WIN-NNK9AORQHND.testldap.cat.com:636";
        String userBaseDN = "dc=testldap,dc=cat,dc=com";
        String searchFilter = "(|(objectClass=group)(objectClass=organizationalUnit))";
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_PRINCIPAL, "Administrator@testldap.cat.com");
        env.put(Context.SECURITY_CREDENTIALS, "Bingo@1993");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        LdapContext ctx = null;
        try {
            ctx = new InitialLdapContext(env, null);
            log.info("Successfully authenticated and connected to LDAP over SSL");
            List<Map<String, Object>> groupList = new ArrayList<>();
            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration<SearchResult> answers = ctx.search(userBaseDN, searchFilter, searchControls);
            Map<String, Object> map = null;
            List<Object> memberList = null;
            String name = "";
            while (answers.hasMoreElements()) {
                SearchResult sr = answers.next();
                Attributes attrs = sr.getAttributes();
                if (attrs != null) {
                    try {
                        map = new HashMap<>();
                        memberList = new ArrayList<>();
                        log.info("---------------------------");
                        for (NamingEnumeration<? extends Attribute> ne = attrs.getAll(); ne.hasMore(); ) {
                            Attribute attr = ne.next();
                            for (int i = 0; i < attr.size(); i++) {
                                String id = attr.getID();
                                Object value = attr.get(i);
//                                System.out.println("id:" + id + ",value:" + value);
                                if (GROUP_KEY.equals(id)) {
                                    String valueT = String.valueOf(value);
                                    value = valueT.replaceAll("CN=", "").split(",")[0];
//                                    System.out.println("id:" + id + ",memberOf value:" + value);
                                    map.put(GROUP_KEY, value);
                                } else if (MEMBER_KEY.equals(id)) {
                                    String valueT = String.valueOf(value);
                                    value = valueT.replaceAll("CN=", "").split(",")[0];
//                                    System.out.println("id:" + id + ",member value:" + value);
                                    memberList.add(value);
                                } else if (NAME_KEY.equals(id)) {
                                    name = String.valueOf(value);
//                                    System.out.println("id:" + id + ",name value:" + value);
                                    map.put(NAME_KEY, name);
                                }
                            }
                        }
                        map.put(MEMBER_KEY, memberList);
                        groupList.add(map);
                    } catch (NamingException e) {
                        log.error("NamingException: ", e);
                    }
                }
            }
            System.out.println(groupList.size());
            for (Map<String, Object> item : groupList) {
                System.out.println("item:" + item);
            }

            Map<String, TreeNode> tree = buildTree(groupList);
        } catch (Exception e) {
            log.error("Failed to connect to LDAP over SSL", e);
        } finally {
            try {
                if (ctx != null) {
                    ctx.close();
                }
            } catch (NamingException e) {
                log.error("Failed to close context", e);
            }
        }
    }

    private static void createGroup(LdapContext ctx, String groupName, String ouPath) {
        try {
            Attributes attrs = new BasicAttributes(true);
            Attribute objClass = new BasicAttribute("objectClass");
            objClass.add("top");
            objClass.add("group");
            attrs.put(objClass);

            attrs.put("cn", groupName);
            attrs.put("sAMAccountName", groupName);

            ctx.createSubcontext("cn=" + groupName + "," + ouPath, attrs);
            log.info("Group created successfully");
        } catch (Exception e) {
            log.error("Failed to create group", e);
        }
    }

    private static void addUserToGroup(LdapContext ctx, String username, String groupDN) {
        try {
            ModificationItem[] mods = new ModificationItem[1];
            Attribute memberAttr = new BasicAttribute("member", "cn=" + username + ",ou=Moscow,DC=ms-dcs,DC=bee,DC=vimpelcom,DC=ru");
            mods[0] = new ModificationItem(LdapContext.ADD_ATTRIBUTE, memberAttr);
            ctx.modifyAttributes(groupDN, mods);
            log.info("User added to group successfully");
        } catch (Exception e) {
            log.error("Failed to add user to group", e);
        }
    }

    private static void createUser(LdapContext ctx, String username, String password, String ouPath) {
        try {
            Attributes attrs = new BasicAttributes(true);
            Attribute objClass = new BasicAttribute("objectClass");
            objClass.add("top");
            objClass.add("person");
            objClass.add("organizationalPerson");
            objClass.add("user");
            attrs.put(objClass);

            attrs.put("cn", username);
            attrs.put("sAMAccountName", username);
            attrs.put("userPrincipalName", username + "@ms-dcs.bee.vimpelcom.ru");
            attrs.put("userPassword", password);

            ctx.createSubcontext("cn=" + username + "," + ouPath, attrs);
            log.info("User created successfully");
        } catch (Exception e) {
            log.error("Failed to create user", e);
        }
    }
}

Msg

package com.example.ldaptest2.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

/**
 * Msg
 *
 * @author:
 * @date: 2020-06-18
 */
@AllArgsConstructor
@Getter
public enum Msg {

    /**
     * 请求类型错误
     */
    SUCCESS(200, "success"), FAILED(20001, "Operation failed"),
    EXCUTE_ERROR(16001, "Service invocation exception, please try again later"),
    UNKNOW_ERROR(16002, "unknown error"),
    PARAM_ERROR(16003, "request param error"),
    DATA_SAVE_FAIL(16004, "data save fail"),
    ID_IS_NOT_EXIST(16005, "id isn't exist"),
    QUERY_RESULT_IS_EMPTY(16006, "request result is null"),
    NAME_IS_EXIST(16007, "name already exist"),
    ID_IS_NOT_EXIST_DATA(16008, "There is no data for the current ID"),
    DATASOURCE_NOT_EXIST(16009, "datasource not exist"),
    JDBC_EXCEPTION(16010, "jdbc connection or search exception"),
    TASK_HAS_BEEN_STOPPED(16011, "task has been stopped");

    private int code;
    private String msg;

    private static final Map<Integer, Msg> MAP = new HashMap<>();

    static {
        for (Msg msgEnum : Msg.values()) {
            MAP.put(msgEnum.code, msgEnum);
        }
    }

    /**
     * 通过code获取枚举
     *
     * @param code
     *            code
     * @return 枚举
     */
    public static Msg getByCode(int code) {
        return MAP.get(code);
    }
}

ADTest测试类

补充说明

部分补充说明:

  • 说明点1:认证方式分2种:一种是用户名+密码登录认证;另一种是用户名+keytab登录认证;keytab文件有有效期的,正常服务间通信才采用keytab认证登录;如果是首页登录认证则推荐使用用户名+密码登录。不推荐keytab认证方式,因为keytab导出时进包含指定的用户,如果你们公司后续又有新人来了,则必须重新制作keytab,否则新人无法登陆项目。
  • 说明点2:AD用户名testuser26,密码Bingo@1993,用户主体testuser26@TESTLDAP.CAT.COM,因为搭建AD环境时生成keytab采用的是主用主体,所以采用用户主体进行登录认证
  • 说明点3:LDAP公钥证书必须上传后缀名为.jks的文件,比如名称为ldap_keystore.jks,在搭建AD环境时生成的导出的其实是xxx.cer的后缀文件,但实际使用的必须是xxx.jks的文件,所以需要输入命令生成。我们采用的是,页面只能上传.cer文件,然后我后端代码执行命令生成.jks文件。
keytool -import -alias ad-server-cert -file /home/ems/ems_file/ldap/ldapServerConfig/ldap_keystore.cer -keystore /home/ems/ems_file/ldap/ldapServerConfig/ldap_keystore.jks -storepass ldap@1993
  • 说明点4:用户主体由用户名+@大写域名方式拼接而成
  • 说明点5:登录认证还需要加载配置文件krb5.conf,改配置文件内容如下,这些其实是安装kerberos时候使用的文件,具体可看我的文章了解每一项意义,Centos7.9安装kerberos
[libdefaults]
    default_realm = TESTLDAP.CAT.COM
    forwardable = true
    kdc_timeout = 3000
    max_retries = 2

[realms]
    TESTLDAP.CAT.COM = {
        kdc = testldap.cat.com
        admin_server = testldap.cat.com
    }

[domain_realm]
    .testldap.cat.com = TESTLDAP.CAT.COM
    testldap.cat.com = TESTLDAP.CAT.COM

  • 说明点6:认证方式解答,之所以“keytab认证方式”失败后再次调用“用户名+密码认证方式”,是因为keytab文件是需要持续更新的,假设你们单位有100人,而你上传的keytab里面只包含50人而未及时更新keytab文件,那么也需要保证所有ldap账户都能登录认证使用项目。
采用先判断keytab文件是否上传
	如果上传了,先采用“keytab认证方式”
		认证成功,则放行访问公司项目首页
		认证失败,再调用“用户名+密码认证方式”
			认证成功,则放行访问公司项目首页
			认证失败,则弹窗报错
	如果未上传,则采用“用户名+密码认证方式”
		认证成功,则放行访问公司项目首页
			先判断Group Access UNM是否配置
				未配置:则执行放行通过(未配置则ldap所有账户都允许访问项目)
				已配置:先查询用户信息,判断用户所属组在有效组内
						如果用户所属组就在有效组内则允许访问
						不在有效组内,则查询组信息,判断用户组是否是有效组的子组下
							是:则允许访问
							不是:则不允许访问
						
		认证失败,则弹窗报错

说明点7:想看keytab文件里用户主体都叫啥可以把文件上传任意一台linux环境,然后执行命令

klist -ke  <xx/xxx/ldap.keytab>

在这里插入图片描述
说明点8:代码中的KEYSTORE_PASSWORD 是加密后的,是为了安全考虑,尽量不要使用明文,万一人家监控到你的代码参数,就泄露了。

//kerberos的认证命令密码:明文是ldap@1993
public static final String KEYSTORE_PASSWORD = "bGRhcEAxOTkz";

四、认证结果

测试类kerberos认证代码

public static void main(String[] args) {
try {
            sun.security.krb5.Config.refresh();
//            refreshConfig();
        } catch (KrbException e) {
            log.error("refresh config error", e);
        }
        System.setProperty("java.security.krb5.conf", configPath + File.separator + LdapConstants.KRB5_CONF_FILE_NAME);
        System.setProperty("sun.security.krb5.debug", "true");

        LoginContext loginContext = null;
        try {
            loginContext = new LoginContext("com.sun.security.jgss.krb5.initiate", null, new KerberosCallbackHandler(request.getUsername(), request.getPassword()),
                    new Configuration() {
                        @Override
                        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
                            Map<String, String> options = new HashMap<>();
                            options.put("useKeyTab", "false");
                            options.put("useTicketCache", "false");
                            options.put("storeKey", "true");
                            options.put("refreshKrb5Config", "true");
                            return new AppConfigurationEntry[]{
                                    new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
                                            AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)
                            };
                        }
                    });
            loginContext.login();
            Subject subject = loginContext.getSubject();
            System.out.println(subject);
        } catch (LoginException e) {
            log.error("login error: ", e);
        } finally {
            try {
                if (loginContext != null) {
                    loginContext.logout();
                }
            } catch (LoginException e) {
                log.error("logout error: ", e);
            }
        }
}

认证成功

E:\Java8\jdk1.8.0_192\bin\java.exe "-javaagent:E:\JetBrains\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=10827:E:\JetBrains\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath E:\Java8\jdk1.8.0_192\jre\lib\charsets.jar;E:\Java8\jdk1.8.0_192\jre\lib\deploy.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\dnsns.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\jaccess.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\localedata.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\nashorn.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\sunec.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;E:\Java8\jdk1.8.0_192\jre\lib\ext\zipfs.jar;E:\Java8\jdk1.8.0_192\jre\lib\javaws.jar;E:\Java8\jdk1.8.0_192\jre\lib\jce.jar;E:\Java8\jdk1.8.0_192\jre\lib\jfr.jar;E:\Java8\jdk1.8.0_192\jre\lib\jfxswt.jar;E:\Java8\jdk1.8.0_192\jre\lib\jsse.jar;E:\Java8\jdk1.8.0_192\jre\lib\management-agent.jar;E:\Java8\jdk1.8.0_192\jre\lib\plugin.jar;E:\Java8\jdk1.8.0_192\jre\lib\resources.jar;E:\Java8\jdk1.8.0_192\jre\lib\rt.jar;G:\WorkSpace\ldap-test2\target\test-classes;G:\WorkSpace\ldap-test2\target\classes;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-web\1.4.7.RELEASE\spring-boot-starter-web-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter\1.4.7.RELEASE\spring-boot-starter-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot\1.4.7.RELEASE\spring-boot-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-autoconfigure\1.4.7.RELEASE\spring-boot-autoconfigure-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-logging\1.4.7.RELEASE\spring-boot-starter-logging-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;E:\apache-maven-3.6.3\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;E:\apache-maven-3.6.3\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;E:\apache-maven-3.6.3\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;E:\apache-maven-3.6.3\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-tomcat\1.4.7.RELEASE\spring-boot-starter-tomcat-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.15\tomcat-embed-core-8.5.15.jar;E:\apache-maven-3.6.3\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.15\tomcat-embed-el-8.5.15.jar;E:\apache-maven-3.6.3\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.15\tomcat-embed-websocket-8.5.15.jar;E:\apache-maven-3.6.3\repository\org\hibernate\hibernate-validator\5.2.5.Final\hibernate-validator-5.2.5.Final.jar;E:\apache-maven-3.6.3\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;E:\apache-maven-3.6.3\repository\org\jboss\logging\jboss-logging\3.3.1.Final\jboss-logging-3.3.1.Final.jar;E:\apache-maven-3.6.3\repository\com\fasterxml\classmate\1.3.3\classmate-1.3.3.jar;E:\apache-maven-3.6.3\repository\com\fasterxml\jackson\core\jackson-databind\2.8.8\jackson-databind-2.8.8.jar;E:\apache-maven-3.6.3\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.8\jackson-annotations-2.8.8.jar;E:\apache-maven-3.6.3\repository\com\fasterxml\jackson\core\jackson-core\2.8.8\jackson-core-2.8.8.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-web\4.3.9.RELEASE\spring-web-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-webmvc\4.3.9.RELEASE\spring-webmvc-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;E:\apache-maven-3.6.3\repository\junit\junit\4.13\junit-4.13.jar;E:\apache-maven-3.6.3\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-test\1.4.7.RELEASE\spring-boot-starter-test-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-test\1.4.7.RELEASE\spring-boot-test-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-test-autoconfigure\1.4.7.RELEASE\spring-boot-test-autoconfigure-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\com\jayway\jsonpath\json-path\2.2.0\json-path-2.2.0.jar;E:\apache-maven-3.6.3\repository\net\minidev\json-smart\2.2.1\json-smart-2.2.1.jar;E:\apache-maven-3.6.3\repository\net\minidev\accessors-smart\1.1\accessors-smart-1.1.jar;E:\apache-maven-3.6.3\repository\org\ow2\asm\asm\5.0.3\asm-5.0.3.jar;E:\apache-maven-3.6.3\repository\org\assertj\assertj-core\2.5.0\assertj-core-2.5.0.jar;E:\apache-maven-3.6.3\repository\org\mockito\mockito-core\1.10.19\mockito-core-1.10.19.jar;E:\apache-maven-3.6.3\repository\org\objenesis\objenesis\2.1\objenesis-2.1.jar;E:\apache-maven-3.6.3\repository\org\hamcrest\hamcrest-library\1.3\hamcrest-library-1.3.jar;E:\apache-maven-3.6.3\repository\org\skyscreamer\jsonassert\1.3.0\jsonassert-1.3.0.jar;E:\apache-maven-3.6.3\repository\org\json\json\20140107\json-20140107.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-core\4.3.9.RELEASE\spring-core-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-test\4.3.9.RELEASE\spring-test-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-security\1.4.7.RELEASE\spring-boot-starter-security-1.4.7.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-aop\4.3.9.RELEASE\spring-aop-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\spring-security-config\4.1.4.RELEASE\spring-security-config-4.1.4.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\spring-security-web\4.1.4.RELEASE\spring-security-web-4.1.4.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\boot\spring-boot-starter-data-ldap\2.6.3\spring-boot-starter-data-ldap-2.6.3.jar;E:\apache-maven-3.6.3\repository\org\springframework\data\spring-data-ldap\2.6.3\spring-data-ldap-2.6.3.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-context\4.3.9.RELEASE\spring-context-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\data\spring-data-commons\1.12.11.RELEASE\spring-data-commons-1.12.11.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;E:\apache-maven-3.6.3\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;E:\apache-maven-3.6.3\repository\com\sun\ldapbp\1.0\ldapbp-1.0.jar;E:\apache-maven-3.6.3\repository\org\springframework\ldap\spring-ldap-core\2.4.0\spring-ldap-core-2.4.0.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-beans\4.3.9.RELEASE\spring-beans-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-tx\4.3.9.RELEASE\spring-tx-4.3.9.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\apache\directory\api\api-all\2.0.0\api-all-2.0.0.jar;E:\apache-maven-3.6.3\repository\org\apache\servicemix\bundles\org.apache.servicemix.bundles.xpp3\1.1.4c_7\org.apache.servicemix.bundles.xpp3-1.1.4c_7.jar;E:\apache-maven-3.6.3\repository\org\apache\servicemix\bundles\org.apache.servicemix.bundles.dom4j\2.1.1_1\org.apache.servicemix.bundles.dom4j-2.1.1_1.jar;E:\apache-maven-3.6.3\repository\org\apache\commons\commons-pool2\2.4.2\commons-pool2-2.4.2.jar;E:\apache-maven-3.6.3\repository\org\apache\mina\mina-core\2.1.3\mina-core-2.1.3.jar;E:\apache-maven-3.6.3\repository\org\apache\commons\commons-lang3\3.9\commons-lang3-3.9.jar;E:\apache-maven-3.6.3\repository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;E:\apache-maven-3.6.3\repository\org\apache\servicemix\bundles\org.apache.servicemix.bundles.antlr\2.7.7_5\org.apache.servicemix.bundles.antlr-2.7.7_5.jar;E:\apache-maven-3.6.3\repository\commons-codec\commons-codec\1.10\commons-codec-1.10.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\kerberos\spring-security-kerberos-core\1.0.1.RELEASE\spring-security-kerberos-core-1.0.1.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\kerberos\spring-security-kerberos-client\1.0.1.RELEASE\spring-security-kerberos-client-1.0.1.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\apache\httpcomponents\httpclient\4.5.3\httpclient-4.5.3.jar;E:\apache-maven-3.6.3\repository\org\apache\httpcomponents\httpcore\4.4.6\httpcore-4.4.6.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\kerberos\spring-security-kerberos-web\1.0.1.RELEASE\spring-security-kerberos-web-1.0.1.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\spring-security-ldap\4.2.3.RELEASE\spring-security-ldap-4.2.3.RELEASE.jar;E:\apache-maven-3.6.3\repository\org\springframework\security\spring-security-core\4.2.3.RELEASE\spring-security-core-4.2.3.RELEASE.jar;E:\apache-maven-3.6.3\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;E:\apache-maven-3.6.3\repository\org\springframework\spring-expression\4.3.9.RELEASE\spring-expression-4.3.9.RELEASE.jar com.example.ldaptest2.ADTest
Debug is  true storeKey true useTicketCache false useKeyTab true doNotPrompt false ticketCache is null isInitiator true KeyTab is C:\Users\211145187\Desktop\fsdownload\ldap.keytab refreshKrb5Config is true principal is testuser26@TESTLDAP.CAT.COM tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Refreshing Kerberos configuration
principal is testuser26@TESTLDAP.CAT.COM
Will use keytab
Commit Succeeded 

10:41:07.376 [main] INFO com.example.ldaptest2.ADTest - subject: 主体: 
	主用户: testuser26@TESTLDAP.CAT.COM
	专用身份证明: Ticket (hex) = 
0000: 61 82 04 52 30 82 04 4E   A0 03 02 01 05 A1 12 1B  a..R0..N........
0010: 10 54 45 53 54 4C 44 41   50 2E 43 41 54 2E 43 4F  .TESTLDAP.CAT.CO
0020: 4D A2 25 30 23 A0 03 02   01 02 A1 1C 30 1A 1B 06  M.%0#.......0...
0030: 6B 72 62 74 67 74 1B 10   54 45 53 54 4C 44 41 50  krbtgt..TESTLDAP
0040: 2E 43 41 54 2E 43 4F 4D   A3 82 04 0A 30 82 04 06  .CAT.COM....0...
0050: A0 03 02 01 12 A1 03 02   01 02 A2 82 03 F8 04 82  ................
0060: 03 F4 32 37 7A 5F 23 6B   5A CB 5D 22 91 95 E5 B8  ..27z_#kZ.]"....
0070: 31 07 0F B2 DA F9 F8 FE   8F B5 90 FE D1 AD 70 4A  1.............pJ
0080: 0B D9 8C 5C 4A 01 D7 54   B2 94 7B 46 E6 D2 C7 EA  ...\J..T...F....
0090: C2 B2 A1 A1 C6 70 2B EC   E5 A7 AB 4F 3A 80 A6 CB  .....p+....O:...
00A0: 37 1A 9C AF 19 7F 68 A4   65 9A 8D 53 AF 36 D9 F5  7.....h.e..S.6..
00B0: F2 F4 E0 50 0A EC F3 FA   13 77 44 50 C9 1F E5 31  ...P.....wDP...1
00C0: 64 A4 A4 33 34 D0 C9 B4   D0 C9 97 61 D8 A1 C6 D1  d..34......a....
00D0: 47 3E 03 AE CE E1 9B 91   80 06 22 63 61 37 98 D3  G>........"ca7..
00E0: 64 70 8D 03 0B D1 FD 23   3B B6 AF 08 03 82 8B D3  dp.....#;.......
00F0: 08 AE 9C FE DE 81 3F C4   37 18 B3 A6 F8 CC CC 8D  ......?.7.......
0100: EE E7 3B 26 23 58 E2 1D   91 CD 07 81 AE A4 88 13  ..;&#X..........
0110: F0 D6 28 9D 4D 56 F3 80   D2 9C 07 41 DE 62 2E 8F  ..(.MV.....A.b..
0120: F7 89 F8 70 1E B1 0A FE   20 0E 07 81 19 06 C8 71  ...p.... ......q
0130: 3B D5 6B 98 C7 24 52 A0   DF 0E D2 02 EB 52 32 EF  ;.k..$R......R2.
0140: 42 2F 14 31 BF 29 FB ED   CB 62 2A AD D5 EA BD FD  B/.1.)...b*.....
0150: A6 ED A6 7E EC 55 2F F7   4C CA D7 9D E7 17 A0 2B  .....U/.L......+
0160: 36 B2 46 CB 8D D9 2C 97   97 EA C7 CA 75 30 AA 3B  6.F...,.....u0.;
0170: DD 3F 37 2E 1E 73 34 08   66 26 BF 45 D1 7E D1 99  .?7..s4.f&.E....
0180: 56 0A 4A 81 7C 15 0A 83   EB BF B3 17 F0 9C 35 C8  V.J...........5.
0190: 9A 3A 98 FD CF B9 DF 2D   2A 84 32 8B 5E D3 92 41  .:.....-*.2.^..A
01A0: 00 DF 21 D2 4F 0A 81 7A   64 E2 50 9F 9D 89 EB F1  ..!.O..zd.P.....
01B0: B3 63 4B 49 59 E8 C3 AB   97 DD 4F FA 6C 68 B1 DD  .cKIY.....O.lh..
01C0: B9 E1 5D F0 E4 49 88 D3   00 11 4C 20 A8 54 AD CC  ..]..I....L .T..
01D0: BF 52 17 FC DB 91 26 F7   59 A5 5C 6D 26 C2 78 C2  .R....&.Y.\m&.x.
01E0: EA 9E 6D CB 92 47 F5 0B   8F 10 08 95 24 DD 91 49  ..m..G......$..I
01F0: 18 45 58 BE 10 95 7F 08   71 E0 81 2E 3F 94 72 99  .EX.....q...?.r.
0200: 2C 8D D1 A7 DA 80 12 95   6D A0 4E 4B CA D9 D6 54  ,.......m.NK...T
0210: 74 63 F0 BE 9B E1 1C 8C   9E C5 A0 4E CD A9 DD 82  tc.........N....
0220: C3 BD 8C E2 70 08 6F 1E   01 F5 8A 01 90 FF CA 0A  ....p.o.........
0230: 64 FB 03 C6 88 30 C9 65   CB 81 49 C1 5C 82 BB E6  d....0.e..I.\...
0240: 72 07 A9 D5 7A EC 31 1E   40 51 DB DD 21 D0 A9 80  r...z.1.@Q..!...
0250: DF 41 A8 D1 F6 94 7C 7D   CE 0D 9B 76 2F FD E6 13  .A.........v/...
0260: 81 04 51 04 2D 80 37 A6   75 51 CF 1D E9 D4 0F 22  ..Q.-.7.uQ....."
0270: E0 FB 02 08 76 96 35 25   45 CC A7 35 AB 8B 23 05  ....v.5%E..5..#.
0280: 80 1D EC B4 BD A8 A6 AB   E4 64 DC D7 40 8D 7C 4A  .........d..@..J
0290: FC 7A D6 83 02 E9 96 52   83 9A F1 7D 85 F4 66 A0  .z.....R......f.
02A0: 71 AB AB F2 CB 99 A7 25   69 C1 86 69 84 A2 E9 D7  q......%i..i....
02B0: 07 93 77 CA 3A 0F 16 36   4C 86 E4 F1 5E 21 A3 AD  ..w.:..6L...^!..
02C0: CD 02 DA A5 DC 30 88 6B   F9 59 24 E9 C1 5C D1 35  .....0.k.Y$..\.5
02D0: 7E 6D 8D E1 73 BF BE 36   26 7E 5B 89 57 6E FB 97  .m..s..6&.[.Wn..
02E0: AB 99 B1 2F B3 EC 01 79   B7 11 06 19 97 7F 1F 11  .../...y........
02F0: C7 8D 7A 03 28 75 73 FF   2C 9B F7 0F 6E CC 7E DD  ..z.(us.,...n...
0300: F2 E4 9C A2 E6 3C C6 AC   48 C7 4C 38 67 94 E5 DD  .....<..H.L8g...
0310: CC A9 97 3B 7B 27 58 ED   9F D1 F1 FB 63 1F D8 E8  ...;.'X.....c...
0320: 6C F2 73 8D C2 50 3E D9   79 40 E8 F5 C1 75 04 1B  l.s..P>.y@...u..
0330: 0E 40 37 6F AC CD 96 00   3B C9 AA D3 F1 D0 D2 3A  .@7o....;......:
0340: C2 7E 0D A1 CC 18 4E C6   D9 A8 6D DA D8 F4 84 6E  ......N...m....n
0350: A5 9B 35 D7 A2 64 27 3C   3B 93 A9 61 86 94 6B D6  ..5..d'<;..a..k.
0360: 5E 0D B6 31 78 1D A6 83   E1 84 C3 90 1B 33 E4 E1  ^..1x........3..
0370: 6D DF 13 C1 88 B8 7F B9   FD 01 3A 49 32 5D F0 00  m.........:I2]..
0380: D9 53 97 CE 6F B0 A3 C8   64 A2 57 32 1B F7 2C 0D  .S..o...d.W2..,.
0390: EC 0E 1E 06 52 14 EA 7F   47 25 E5 4C AC 95 8D 72  ....R...G%.L...r
03A0: 31 28 A4 A7 8B F4 00 11   AB A1 9D D9 B4 B4 A4 DB  1(..............
03B0: 2A 82 75 8F EA 8E 9E 44   3D D5 23 9E C5 86 8F 81  *.u....D=.#.....
03C0: B9 3C 01 95 31 25 ED 48   26 A5 4F 91 E5 C9 AF 87  .<..1%.H&.O.....
03D0: 0E 5C B6 71 9C 82 9D 02   FA 3E 6B B5 C2 1E FE F2  .\.q.....>k.....
03E0: 32 33 54 39 A9 8B EB F1   82 1A 7E 27 11 D4 FD 2E  23T9.......'....
03F0: 37 5D 03 3B 05 D3 09 51   0F 70 9A 43 E1 44 45 1F  7].;...Q.p.C.DE.
0400: 16 48 69 64 07 E7 9E F9   28 36 4E 6F B6 97 2A EF  .Hid....(6No..*.
0410: 22 7C 2E 62 82 03 3C C2   97 D2 D0 C9 04 D0 78 00  "..b..<.......x.
0420: B9 2B 66 CA 3C F5 D1 07   50 B3 2E 0D 4B 15 1A AD  .+f.<...P...K...
0430: B8 9F 4B 54 55 C1 BC 3C   5C 1A BF 63 A4 C4 65 93  ..KTU..<\..c..e.
0440: CA 4D 9E 2C 56 33 A1 43   7A 15 B8 26 6E 4D DD A2  .M.,V3.Cz..&nM..
0450: C6 BC 5B 48 72 C7                                  ..[Hr.

Client Principal = testuser26@TESTLDAP.CAT.COM
Server Principal = krbtgt/TESTLDAP.CAT.COM@TESTLDAP.CAT.COM
Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)=
0000: 76 A5 11 BD 7B B4 8B 9A   B4 7B 2D F8 F2 4D 28 62  v.........-..M(b
0010: 89 E4 5F 92 3E C3 61 C5   28 39 63 D2 96 D6 CA FA  .._.>.a.(9c.....


Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Fri Jul 05 10:41:07 CST 2024
Start Time = Fri Jul 05 10:41:07 CST 2024
End Time = Fri Jul 05 20:41:07 CST 2024
Renew Till = null
Client Addresses  Null 
	专用身份证明: C:\Users\211145187\Desktop\fsdownload\ldap.keytab for testuser26@TESTLDAP.CAT.COM

10:41:07.395 [main] INFO com.example.ldaptest2.ADTest - kerberos authentication succeeded
		[Krb5LoginModule]: Entering logout
		[Krb5LoginModule]: logged out Subject
10:41:07.398 [main] INFO com.example.ldaptest2.ADTest - loginWithKeytab login success!
10:41:07.398 [main] INFO com.example.ldaptest2.ADTest - response from loginWithKeytab:Response(code=200, msg=success, info=null)

Process finished with exit code 0

认证失败

10:42:38.883 [main] ERROR com.example.ldaptest2.ADTest - login error: 
10:42:38.884 [main] INFO com.example.ldaptest2.ADTest - returnCode:6
10:42:38.889 [main] INFO com.example.ldaptest2.ADTest - loginWithPassword login success!
10:42:38.889 [main] INFO com.example.ldaptest2.ADTest - response from loginWithPassword:Response(code=20001, msg=账号不存在, info=null)

测试ldap账户信息或者组信息查询代码

public static void main(String[] args) {
try {
            setCertificate();
        } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException |
                KeyManagementException e) {
            log.error("setCertificate error: ", e);
        }

        String ldapUrl = "ldaps://WIN-NNK9AORQHND.testldap.cat.com:636";
        String userBaseDN = "dc=testldap,dc=cat,dc=com";
        String searchFilter = "(|(objectClass=group)(objectClass=organizationalUnit))";
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_PRINCIPAL, "Administrator@testldap.cat.com");
        env.put(Context.SECURITY_CREDENTIALS, "Bingo@1993");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        LdapContext ctx = null;
        try {
            ctx = new InitialLdapContext(env, null);
            log.info("Successfully authenticated and connected to LDAP over SSL");
            List<Map<String, Object>> groupList = new ArrayList<>();
            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration<SearchResult> answers = ctx.search(userBaseDN, searchFilter, searchControls);
            Map<String, Object> map = null;
            List<Object> memberList = null;
            String name = "";
            while (answers.hasMoreElements()) {
                SearchResult sr = answers.next();
                Attributes attrs = sr.getAttributes();
                if (attrs != null) {
                    try {
                        map = new HashMap<>();
                        memberList = new ArrayList<>();
                        log.info("---------------------------");
                        for (NamingEnumeration<? extends Attribute> ne = attrs.getAll(); ne.hasMore(); ) {
                            Attribute attr = ne.next();
                            for (int i = 0; i < attr.size(); i++) {
                                String id = attr.getID();
                                Object value = attr.get(i);
//                                System.out.println("id:" + id + ",value:" + value);
                                if (GROUP_KEY.equals(id)) {
                                    String valueT = String.valueOf(value);
                                    value = valueT.replaceAll("CN=", "").split(",")[0];
//                                    System.out.println("id:" + id + ",memberOf value:" + value);
                                    map.put(GROUP_KEY, value);
                                } else if (MEMBER_KEY.equals(id)) {
                                    String valueT = String.valueOf(value);
                                    value = valueT.replaceAll("CN=", "").split(",")[0];
//                                    System.out.println("id:" + id + ",member value:" + value);
                                    memberList.add(value);
                                } else if (NAME_KEY.equals(id)) {
                                    name = String.valueOf(value);
//                                    System.out.println("id:" + id + ",name value:" + value);
                                    map.put(NAME_KEY, name);
                                }
                            }
                        }
                        map.put(MEMBER_KEY, memberList);
                        groupList.add(map);
                    } catch (NamingException e) {
                        log.error("NamingException: ", e);
                    }
                }
            }
            System.out.println(groupList.size());
            for (Map<String, Object> item : groupList) {
                System.out.println("item:" + item);
            }

            Map<String, TreeNode> tree = buildTree(groupList);
        } catch (Exception e) {
            log.error("Failed to connect to LDAP over SSL", e);
        } finally {
            try {
                if (ctx != null) {
                    ctx.close();
                }
            } catch (NamingException e) {
                log.error("Failed to close context", e);
            }
        }
}

认证成功

63
item:{name=Administrators, member=[Domain Admins, Enterprise Admins, Administrator]}
item:{name=Users, member=[Domain Users, S-1-5-11, S-1-5-4]}
item:{name=Guests, member=[Domain Guests, Guest]}
item:{name=Print Operators, member=[]}
item:{name=Backup Operators, member=[]}
item:{name=Replicator, member=[]}
item:{name=Remote Desktop Users, member=[]}
item:{name=Network Configuration Operators, member=[]}
item:{name=Performance Monitor Users, member=[]}
item:{name=Performance Log Users, member=[]}
item:{name=Distributed COM Users, member=[]}
item:{name=IIS_IUSRS, member=[]}
item:{name=Cryptographic Operators, member=[]}
item:{name=Event Log Readers, member=[]}
item:{name=Certificate Service DCOM Access, member=[S-1-5-11]}
item:{name=RDS Remote Access Servers, member=[]}
item:{name=RDS Endpoint Servers, member=[]}
item:{name=RDS Management Servers, member=[]}
item:{name=Hyper-V Administrators, member=[]}
item:{name=Access Control Assistance Operators, member=[]}
item:{name=Remote Management Users, member=[]}
item:{name=System Managed Accounts Group, member=[DefaultAccount]}
item:{name=Storage Replica Administrators, member=[]}
item:{name=Domain Computers, member=[]}
item:{name=Domain Controllers, member=[], memberOf=Denied RODC Password Replication Group}
item:{name=Schema Admins, member=[Administrator], memberOf=Denied RODC Password Replication Group}
item:{name=Enterprise Admins, member=[Administrator], memberOf=Administrators}
item:{name=Cert Publishers, member=[WIN-NNK9AORQHND], memberOf=Denied RODC Password Replication Group}
item:{name=Domain Admins, member=[Administrator], memberOf=Administrators}
item:{name=Domain Users, member=[], memberOf=Users}
item:{name=Domain Guests, member=[], memberOf=Guests}
item:{name=Group Policy Creator Owners, member=[Administrator], memberOf=Denied RODC Password Replication Group}
item:{name=RAS and IAS Servers, member=[]}
item:{name=Server Operators, member=[]}
item:{name=Account Operators, member=[]}
item:{name=Pre-Windows 2000 Compatible Access, member=[WIN-NNK9AORQHND, S-1-5-11]}
item:{name=Incoming Forest Trust Builders, member=[]}
item:{name=Windows Authorization Access Group, member=[S-1-5-9]}
item:{name=Terminal Server License Servers, member=[]}
item:{name=Allowed RODC Password Replication Group, member=[]}
item:{name=Denied RODC Password Replication Group, member=[Read-only Domain Controllers, Group Policy Creator Owners, Domain Admins, Cert Publishers, Enterprise Admins, Schema Admins, Domain Controllers, krbtgt]}
item:{name=Read-only Domain Controllers, member=[], memberOf=Denied RODC Password Replication Group}
item:{name=Enterprise Read-only Domain Controllers, member=[]}
item:{name=Cloneable Domain Controllers, member=[]}
item:{name=Protected Users, member=[]}
item:{name=Key Admins, member=[]}
item:{name=Enterprise Key Admins, member=[]}
item:{name=DnsAdmins, member=[]}
item:{name=DnsUpdateProxy, member=[]}
item:{name=APP_Hytera_MS, member=[testuser26]}
item:{name=GroupA, member=[]}
item:{name=APP_Hytera_MSB1, member=[APP_Hytera_MSB1-2, APP_Hytera_MSB1-1], memberOf=APP_Hytera_GroupB}
item:{name=APP_Hytera_MSB2, member=[testuserb2], memberOf=APP_Hytera_GroupB}
item:{name=APP_Hytera_MSB1-1, member=[testuserb1], memberOf=APP_Hytera_MSB1}
item:{name=APP_Hytera_MSB1-2, member=[testuserb1], memberOf=APP_Hytera_MSB1}
item:{name=APP_Hytera_GroupB, member=[APP_Hytera_MSB2, APP_Hytera_MSB1]}
item:{name=APP_Hytera_GroupC, member=[APP_Hytera_GroupD]}
item:{name=APP_Hytera_GroupD, member=[testuserb3], memberOf=APP_Hytera_GroupC}
item:{name=group1, member=[testuser10, group2]}
item:{name=group2, member=[testuser11], memberOf=group1}
item:{name=Domain Controllers, member=[]}
item:{name=OrgUnitA, member=[]}
item:{name=SubUnitB, member=[]}

Process finished with exit code 0

五、错误场景举例

错误场景1:javax.security.auth.login.LoginException: Receive timed out

完整错误:

2024.07.10 09:34:10.189 [qtp1478797373-35]ERROR com.hero.lte.ems.security.service.impl.LdapService  loginWithKeytab 201 - login error:  javax.security.auth.login.LoginException: Receive timed out
        at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:808) ~[?:1.8.0_121]
        at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_121]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_121]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_121]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_121]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_121]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_121]
        at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_121]
        at com.hero.lte.ems.security.service.impl.LdapService.loginWithKeytab(LdapService.java:197) [ems-eam-security-1.0-SNAPSHOT.jar:?]
        at com.hero.lte.ems.security.service.impl.LdapService.login(LdapService.java:71) [ems-eam-security-1.0-SNAPSHOT.jar:?]
        at com.hero.lte.ems.sysmanager.resources.SystemPersonalController.ldapServerConfigConnectionTest(SystemPersonalController.java:348) [ems-eam-sysmanager-resource-1.0-SNAPSHOT.jar:?]
        at com.hero.lte.ems.sysmanager.resources.SystemPersonalController$$FastClassBySpringCGLIB$$855fb4a7.invoke(<generated>) [ems-eam-sysmanager-resource-1.0-SNAPSHOT.jar:?]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) [spring-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:718) [spring-aop-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) [spring-aop-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) [spring-aop-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:654) [spring-aop-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at com.hero.lte.ems.sysmanager.resources.SystemPersonalController$$EnhancerBySpringCGLIB$$4d44a2ec.ldapServerConfigConnectionTest(<generated>) [ems-eam-sysmanager-resource-1.0-SNAPSHOT.jar:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_121]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_121]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_121]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_121]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:222) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:814) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:737) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) [javax.servlet-api-3.1.0.jar:3.1.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [javax.servlet-api-3.1.0.jar:3.1.0]
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123) [druid-1.1.6.jar:1.1.6]
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at com.hero.lte.ems.eam.server.config.OperateFilter.doFilter(OperateFilter.java:91) [ems-eam-server-1.0-SNAPSHOT.jar:?]
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) [shiro-core-1.4.0.jar:1.4.0]
        at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) [shiro-core-1.4.0.jar:1.4.0]
        at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387) [shiro-core-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) [shiro-web-1.4.0.jar:1.4.0]
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.4.0.jar:1.4.0]
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515) [jetty-servlet-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.Server.handle(Server.java:499) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257) [jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544) [jetty-io-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635) [jetty-util-9.2.16.v20160414.jar:9.2.16.v20160414]
        at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555) [jetty-util-9.2.16.v20160414.jar:9.2.16.v20160414]
        at java.lang.Thread.run(Thread.java:745) [?:1.8.0_121]
Caused by: java.net.SocketTimeoutException: Receive timed out
        at java.net.PlainDatagramSocketImpl.receive0(Native Method) ~[?:1.8.0_121]
        at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:143) ~[?:1.8.0_121]
        at java.net.DatagramSocket.receive(DatagramSocket.java:812) ~[?:1.8.0_121]
        at sun.security.krb5.internal.UDPClient.receive(NetClient.java:206) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm$KdcCommunication.run(KdcComm.java:411) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm$KdcCommunication.run(KdcComm.java:364) ~[?:1.8.0_121]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm.send(KdcComm.java:348) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm.sendIfPossible(KdcComm.java:253) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm.send(KdcComm.java:229) ~[?:1.8.0_121]
        at sun.security.krb5.KdcComm.send(KdcComm.java:200) ~[?:1.8.0_121]
        at sun.security.krb5.KrbAsReqBuilder.send(KrbAsReqBuilder.java:316) ~[?:1.8.0_121]
        at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361) ~[?:1.8.0_121]
        at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:776) ~[?:1.8.0_121]

错误原因:超时说明未连接通,原因可能有2,
排查原因1:看服务器能否ping通,比如我的域名叫testldap.cat.com,执行如下命令

# ping testldap.cat.com
或者
# ping 10.110.25.48

解决方案:如果不能ping通,请关闭windows server2016虚拟机的防火墙,同时在你的主机服务器配置hosts文件,配置域名,如果是linux服务器那么文件在服务器/etc/hosts

10.110.25.48 testldap.cat.com

排查原因2:如果能ping通,排查88端口是否被占用,因为kerberos的默认端口是88,执行如下命令,因为我们验证发现有的服务器部署就能通,而有的服务器不通,说明不通的那台服务器网络配置可能有点问题,或者说不同服务器之间网络配置不同导致的,只有telnet能通,才能正确访问ldap登录认证。

telnet 10.110.25.48 88

如果返回结果如下,则证明是通的
在这里插入图片描述

如果返回结果如下,证明是不通的
在这里插入图片描述

解决方案:目前我也不会修改网络,所以这里我不知道,如果会的人,可以解答下,我补充上。

错误场景2:java.net.UnknownHostException: testldap.cat.com

完整错误:

javax.naming.CommunicationException: testldap.cat.com:636
	at com.sun.jndi.ldap.Connection.<init>(Connection.java:228) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1615) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:210) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:153) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:83) ~[?:1.8.0_192]
	at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684) ~[?:1.8.0_192]
	at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313) ~[?:1.8.0_192]
	at javax.naming.InitialContext.init(InitialContext.java:244) ~[?:1.8.0_192]
	at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:154) ~[?:1.8.0_192]
	at com.hero.lte.ems.security.service.impl.LdapService.main(LdapService.java:745) [classes/:?]
Caused by: java.net.UnknownHostException: testldap.cat.com
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[?:1.8.0_192]
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[?:1.8.0_192]
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[?:1.8.0_192]
	at java.net.Socket.connect(Socket.java:589) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:666) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.<init>(SSLSocketImpl.java:426) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketFactoryImpl.createSocket(SSLSocketFactoryImpl.java:88) ~[?:1.8.0_192]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_192]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_192]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_192]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.Connection.createSocket(Connection.java:340) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.Connection.<init>(Connection.java:215) ~[?:1.8.0_192]
	... 13 more

错误原因:域名不认识

错误代码

String ldapUrl = "ldaps://testldap.cat.com:636";
String userBaseDN = "dc=testldap,dc=cat,dc=com";
String searchFilter = "(|(objectClass=group)(objectClass=organizationalUnit))";
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_PRINCIPAL, "Administrator@testldap.cat.com");
env.put(Context.SECURITY_CREDENTIALS, "Bingo@1993");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PROTOCOL, "ssl");
...

解决方案:在windows的C:\Windows\System32\drivers\etc\hosts文件中配置域名映射

10.110.25.51 testldap.cat.com

错误场景3:Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching testldap.cat.com found.

完整错误:

javax.naming.CommunicationException: simple bind failed: testldap.cat.com:636
	at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:219) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2791) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:210) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:153) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:83) ~[?:1.8.0_192]
	at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684) ~[?:1.8.0_192]
	at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313) ~[?:1.8.0_192]
	at javax.naming.InitialContext.init(InitialContext.java:244) ~[?:1.8.0_192]
	at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:154) ~[?:1.8.0_192]
	at com.hero.lte.ems.security.service.impl.LdapService.main(LdapService.java:745) [classes/:?]
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching testldap.cat.com found.
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310) ~[?:1.8.0_192]
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639) ~[?:1.8.0_192]
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:965) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:931) ~[?:1.8.0_192]
	at sun.security.ssl.AppInputStream.read(AppInputStream.java:105) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.Connection.run(Connection.java:877) ~[?:1.8.0_192]
	at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_192]
Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching testldap.cat.com found.
	at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:214) ~[?:1.8.0_192]
	at sun.security.util.HostnameChecker.match(HostnameChecker.java:96) ~[?:1.8.0_192]
	at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:459) ~[?:1.8.0_192]
	at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:436) ~[?:1.8.0_192]
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:200) ~[?:1.8.0_192]
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124) ~[?:1.8.0_192]
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621) ~[?:1.8.0_192]
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037) ~[?:1.8.0_192]
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:965) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) ~[?:1.8.0_192]
	at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:931) ~[?:1.8.0_192]
	at sun.security.ssl.AppInputStream.read(AppInputStream.java:105) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[?:1.8.0_192]
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[?:1.8.0_192]
	at com.sun.jndi.ldap.Connection.run(Connection.java:877) ~[?:1.8.0_192]
	at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_192]

错误原因:证书有问题域名不匹配。
问题:那你可能会说了我这个搭建在linux环境上自己公司项目都测试了能通过证书没问题,为啥在windows本地连接就不识别了呢?这个问题可能是windows和linux环境不易当导致解析不一样?这个目前我也不清楚,有知道的可以回复我,共同学习下。

先查询下证书:可以看到证书路径叫“WIN-NNK9AORQHND.testldap.cat.com”,而不是叫“testldap.cat.com”
在这里插入图片描述
在这里插入图片描述

解决方案:2种方式

  • 方式1:把证书重新做,最后路径必须和url上面的域名testldap.cat.com一致
  • 方式2:修改url同时把hosts重新配置

测试代码:

String ldapUrl = "ldaps://WIN-NNK9AORQHND.testldap.cat.com:636";

hosts

10.110.25.51 WIN-NNK9AORQHND.testldap.cat.com
10.110.25.51 testldap.cat.com

本人其他相关文章链接

1.Centos7.9安装openldap
2.Centos7.9安装kerberos
3.Openldap集成Kerberos
4.Centos7.9安装phpldapadmin
5.java连接ldap实现用户查询功能
6.java连接kerberos用户认证
7.javax.security.auth.login.LoginException: Unable to obtain password from user
8.javax.security.auth.login.LoginException: null (68)
9.javax.security.auth.login.LoginException: Message stream modified (41)
10.javax.security.auth.login.LoginException: Checksum failed
11.javax.security.auth.login.LoginException: No CallbackHandler available to garner authentication info
12.javax.security.auth.login.LoginException: Cannot locate KDC
13.javax.security.auth.login.LoginException: Receive timed out
14.java: 无法访问org.springframework.context.ConfigurableApplicationContext
15.LDAP: error code 34 - invalid DN
16.LDAP: error code 32 - No Such Object
17.java: 无法访问org.springframework.ldap.core.LdapTemplate
18.windows server2016搭建AD域服务器
19.java连接AD(Microsoft Active Directory)模拟用户登录认证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘大猫.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值