首页 > 其他分享 >springBoot集成AD域

springBoot集成AD域

时间:2024-06-07 09:31:10浏览次数:22  
标签:集成 return String person base new AD public springBoot

springBoot集成AD域

简介

Active Directory(AD)是由微软开发的用于管理网络资源和用户身份的目录服务。它是一种目录服务,用于存储组织内的网络资源(如计算机、打印机、用户等)的信息,并提供对这些资源的集中化管理。AD域的作用主要包括以下几个方面:

  1. 身份验证和授权:AD域用于存储和管理用户账号信息,包括用户名、密码、所属组织单位等。通过AD域,用户可以使用统一的账号来访问不同的网络资源,并通过集中管理的方式来进行身份验证和授权。
  2. 集中化管理:AD域提供了集中化的管理方式,管理员可以通过AD域来管理用户账号、计算机、打印机、文件共享等各种网络资源,而无需在每台计算机上单独进行管理。
  3. 安全策略和权限控制:AD域可以定义安全策略和权限控制规则,例如访问控制列表(ACL)、组策略、密码策略等,从而确保网络资源的安全性和合规性。
  4. 资源共享和查找:AD域提供了资源共享的功能,用户可以通过AD域来查找和访问网络中的共享文件夹、打印机等资源。
  5. 单点登录:通过AD域,用户可以实现单点登录,即使用一组凭证就可以访问整个网络中的资源,无需单独登录每个资源

springBoot集成Ldap

1.导入Maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-ldap</artifactId>
        </dependency>

2.yml中进行配置

# ldap连接信息
ldap:
  url: ldap://192.168.140.61:389
  base: OU=test,DC=account,DC=com
  userDn: "[email protected]"
  userPwd: test
  referral: follow
  domainName: "%[email protected]"

AD域中参数对应:

OU:代表AD域中的组织单位

DC:代表指定域的组成部分,其中第一个DC是子域,第二个DC是顶级域

一般情况下 OU=<组织单位>,DC=<子集域>,DC=<顶级域(com)>

顶级域都是com

referral介绍:

LdapContextSource 中用于设置 referral 等级的属性是 referral,它有以下几个可能的取值:

  1. follow:表示在遇到 referral 时,会自动跟随 referral 并完成操作。这意味着当请求需要跨域操作时,会自动向 referral 所指示的位置发出新的请求,并重复操作直到最终目标。
  2. ignore:表示在遇到 referral 时,会忽略 referral 并且不进行任何跟随操作。这个选项适用于希望明确控制每个 LDAP 操作的目标位置的情况。
  3. throw:表示在遇到 referral 时,会抛出异常。这意味着当遇到 referral 时,会立即终止操作并抛出异常,需要由客户端代码来处理。

3.编写ldap配置类

@Configuration
public class LdapConfiguration {

    @Value("${ldap.url}")
    private String ldapUrl;

    @Value("${ldap.base}")
    private String ldapBase;

    @Value("${ldap.userDn}")
    private String ldapUserDn;

    @Value("${ldap.userPwd}")
    private String ldapUserPwd;

    @Value("${ldap.referral}")
    private String ldapReferral;

    private final String AD_PWD_KEY = "LDAP_USER_PASSWORD";

    private final SystemConfigService service;

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

    public LdapConfiguration(SystemConfigService service) {
        this.service = service;
    }

    @Bean
    @ConfigurationProperties("spring.ldap")
    public LdapProperties ldapProperties() {
        return new LdapProperties();
    }

    @Bean
    public LdapContextSource ldapContextSource() {
        SSLLdapContextSource source = new SSLLdapContextSource();
//        LdapContextSource source = new LdapContextSource();
        SystemConfig value = service.getOne(new LambdaQueryWrapper<SystemConfig>().eq(SystemConfig::getCode, AD_PWD_KEY));
        if (Objects.isNull(value)) {
            value = new SystemConfig();
            value.setValue(ldapUserPwd);
        }
        source.setUrl(ldapUrl);
        source.setUserDn(ldapUserDn);
        source.setPassword(value.getValue());
        source.setReferral(ldapReferral);
        return source;
    }

    @Bean
    public LdapTemplate ldapTemplate() {
        LdapTemplate ldapTemplate = new LdapTemplate(ldapContextSource());
        ldapTemplate.setIgnoreNameNotFoundException(true);
        try {
            ldapTemplate.afterPropertiesSet();
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return ldapTemplate;
    }

    public void changeLdapPassword(String newPassword) {
        ldapUserPwd = newPassword;
        // 动态更新密码
        ldapContextSource().setPassword(newPassword);
    }

}
public class CustomSSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory socketFactory;

    public CustomSSLSocketFactory() {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());
            socketFactory = ctx.getSocketFactory();
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
        }
    }

    public static SocketFactory getDefault() {
        return new CustomSSLSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return socketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return socketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {
        return socketFactory.createSocket(socket, string, num, bool);
    }

    @Override
    public Socket createSocket(String string, int num) throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, num);
    }

    @Override
    public Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, num, netAdd, i);
    }

    @Override
    public Socket createSocket(InetAddress netAdd, int num) throws IOException {
        return socketFactory.createSocket(netAdd, num);
    }

    @Override
    public Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {
        return socketFactory.createSocket(netAdd1, num, netAdd2, i);
    }

    /**
     * 绕过证书校验
     */
    public static class DummyTrustmanager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

    }
}

它的作用是绕过证书校验,允许在建立 SSL 连接时不对服务器端的证书进行校验。通常情况下,SSL 连接需要验证服务器端的证书以确保连接的安全性,但在某些特殊情况下,可能希望绕过证书校验。

4.Java中实体与AD域的对应关系

@Entry(objectClasses = {"user"})
public class StaffByAd {
    /**
     * 标识id
     */
    @Id
    private Name id;
    /**
     * 用户名
     */
    @Attribute(name = "userPrincipalName")
    private String cn;
    /**
     * 邮箱
     */
    @Attribute(name = "mail")
    private String mail;
    /**
     * 手机号
     */
    @Attribute(name = "telephoneNumber")
    private String phoneNumber;
    /**
     * 密码
     */
    @Attribute(name = "unicodePwd")
    private String password;
    /**
     * 状态(1启用,其他禁用)
     */
    @Attribute(name = "userAccountControl")
    private Integer state;
    /**
     * 姓名
     */
    @Attribute(name = "displayName")
    private String surname;

    @Attribute(name = "sn")
    private String lastName;

    @Attribute(name = "givenName")
    private String firstName;

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "id=" + id +
                ", cn='" + cn + '\'' +
                ", mail='" + mail + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", password='" + password + '\'' +
                ", state=" + state +
                '}';
    }

    public String getCn() {
        return cn;
    }

    public void setCn(String cn) {
        this.cn = cn;
    }

    public String getMail() {
        return mail;
    }

    public void setMail(String mail) {
        this.mail = mail;
    }

    public Name getId() {
        return id;
    }

    public void setId(Name id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public String getPassword() {
        return password;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

@Attribute注解是将实体属性与AD域中的属性进行一一对应

userPrincipalName:代表用户的编号

mail:邮箱

unicodePwd:密码

displayName:显示名称(姓+名组合)

sn:姓

givenName:名

userAccountControl:表示用户的禁用状态,一般情况下AD域中512、544、66048、262656均表示启用状态,其余状态为禁用状态。

5.编写工具类

@Component
public class LdapUtil {

    private final LdapTemplate ldapTemplate;

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

    @Value("${ldap.domainName}")
    private String ldapDomainName;

    @Value("${ldap.base}")
    private String defaultBase;

    @Autowired
    public LdapUtil(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

    /**
     * 获取所有用户信息
     *
     * @param base: AD域的根节点
     * @return List<Staff>
     */
    public List<StaffByAd> getUserByBase(String base) {
        //获取用户信息
        LdapQuery ldapQuery = LdapQueryBuilder
                .query()
                .base(base)
                .searchScope(SearchScope.SUBTREE)
                .filter("(objectClass=*)");
        // 获取当前base下的所有人员
        List<StaffByAd> staffByAds = ldapTemplate.find(ldapQuery, StaffByAd.class);
        staffByAds.forEach(staffByAd -> {
            String cn = staffByAd.getCn();
            if (!cn.contains("@")) {
                throw new BusinessException("数据错误,请检查数据的完整性!");
            }
            if (StringUtils.isNotBlank(cn)) {
                staffByAd.setCn(cn.substring(0, cn.indexOf("@")));
            }
        });
        return staffByAds;
    }

    /**
     * 添加用户
     *
     * @param person: 用户对象
     * @param base:   AD域的根节点
     */
    public void addUser(StaffByAd person, String base) {
        String bind = buildUserDn(person, base);
        //添加用户
        ldapTemplate.bind(bind, buildContext(person), null);
        log.info("添加人员数据成功,添加的数据为{}", person);
    }

    /**
     * 删除用户
     *
     * @param username: 用户名
     * @param base:     AD域的根节点
     */
    public void deleteUser(String username, String base) {
        String bind = "CN=" + username + "," + base;
        ldapTemplate.unbind(bind);
        log.info("添加人员数据成功,添加的数据为{}", username);
    }

    /**
     * 绑定用户所在节点
     *
     * @param person:      person对象
     * @param base:AD域的根节点
     * @return String
     */
    private String buildUserDn(StaffByAd person, String base) {
        // 根据需要适配目录结构来构建用户的DN
        // 例如:"cn=username,ou=users,dc=example,dc=com"
        return "CN=" + person.getCn() + "," + base;
    }

    /**
     * 设置用户属性
     *
     * @param person: 用户对象
     * @return DirContextAdapter
     */
    private DirContextAdapter buildContext(StaffByAd person) {
        DirContextAdapter context = new DirContextAdapter();
        // 设置用户属性
        context.setAttributeValues("objectclass", new String[]{"user"});
        context.setAttributeValue("unicodePwd", person.getPassword());
        context.setAttributeValue("mail", person.getMail());
        context.setAttributeValue("cn", new String[]{person.getSurname()});
        context.setAttributeValue("telephoneNumber", person.getPhoneNumber());
        context.setAttributeValue("sAMAccountName", person.getCn());
        context.setAttributeValue("sn", person.getLastName());
        context.setAttributeValue("givenName", person.getFirstName());
        return context;
    }

    /**
     * 进行账号过期时间的设置
     *
     * @param expireDate: 过期时间,格式为(20230823021045Z)
     * @param base:       AD域的根节点
     * @param username:   用户名
     */
    public void changeAccountExpire(String expireDate, String base, String username) {
        String userDn = "CN=" + username + "," + base;
        // 创建 ModificationItem 对象用于设置有效期属性
        ModificationItem[] modificationItems = new ModificationItem[]{
                new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("accountExpires", expireDate))
        };
        ldapTemplate.modifyAttributes(userDn, modificationItems);
    }


    /**
     * 获取组织架构信息
     *
     * @param base: AD域的根节点
     * @date 2023/8/18 13:59
     */
    public List<Group> getOrgByBase(String base) {
        // 获取组织架构信息
        LdapQuery ldapQuery = LdapQueryBuilder
                .query()
                .base(base)
                .searchScope(SearchScope.SUBTREE)
                .filter("(objectClass=organizationalUnit)");
        List<Group> groups = ldapTemplate.find(ldapQuery, Group.class);
        ArrayList<String> strings = new ArrayList<>();
        // 遍历获取父级
        for (int i = 0; i < groups.size(); i++) {
            Group group = groups.get(i);
            String[] split = group.getId().toString().split(",");
            for (String s : split) {
                String[] ous = s.split("OU=");
                // 如果长度大于1,说明split成功,就是需要的OU
                if (ous.length > 1) {
                    strings.add(ous[1]);
                }
            }
            // size>2说明有父级,并将父级存储到parent属性里面
            if (strings.size() >= 2) {
                String parentName = strings.get(1);
                group.setParent(parentName);
            }
            System.out.println(strings);
            strings.clear();
        }
        log.info("获取组织架构成功,{}", groups);
        return groups;
    }

    /**
     * 身份验证
     *
     * @param userName:用户名
     * @param password:密码
     * @return Boolean
     */
    public Boolean authenticate(String userName, String password) {
        // 进行参数拼接
        String userDomainName = String.format(ldapDomainName, userName);
        DirContext ctx = null;
        try {
            // 校验账号密码是否正确
            ctx = ldapTemplate.getContextSource().getContext(userDomainName, password);
            return true;
        } catch (Exception e) {
            log.error("认证失败,原因{}", e.getMessage());
        }
        // 返回
        return false;
    }

    /**
     * 根据条件查询用户
     *
     * @param person: 用户对象
     * @param base:   AD域的根节点
     * @date 2023/8/21 15:58
     */
    public List<StaffByAd> getUser(StaffByAd person, String base) {
        // 构建查询的builder
        ContainerCriteria containerCriteria = LdapQueryBuilder
                .query()
                .base(base)
                .searchScope(SearchScope.SUBTREE)
                .where("objectclass")
                .is("user");
        String cn = person.getCn();
        // 判断获取到的用户名是否为空
        if (!StringUtils.isBlank(cn)) {
            // 封装查询条件
            String userDomainName = String.format(ldapDomainName, person.getCn());
            containerCriteria.and("userPrincipalName").is(userDomainName);
            // 查询结果
            List<StaffByAd> staffByAds = ldapTemplate.find(containerCriteria, StaffByAd.class);
            log.info("获取用户数据成功,{}", staffByAds);
            staffByAds.forEach(staffByAd -> {
                String cn2 = staffByAd.getCn();
                if (StringUtils.isNotBlank(cn2)) {
                    staffByAd.setCn(cn2.substring(0, cn2.indexOf("@")));
                }
            });
            return staffByAds;
        }
        // 如果传递的用户名为空则返回空
        log.info("无该用户信息{}", person);
        return null;
    }

    /**
     * 根据条件查询用户
     *
     * @param person
     * @return List<StaffByAd>
     */
    public List<StaffByAd> getUser(StaffByAd person) {
        // 构建查询的builder
        ContainerCriteria containerCriteria = LdapQueryBuilder
                .query()
                .base(defaultBase)
                .searchScope(SearchScope.SUBTREE)
                .where("objectclass")
                .is("user");
        String cn = person.getCn();
        // 判断获取到的用户名是否为空
        if (!StringUtils.isBlank(cn)) {
            // 封装查询条件
            String userDomainName = String.format(ldapDomainName, person.getCn());
            containerCriteria.and("userPrincipalName").is(userDomainName);
            // 查询结果
            List<StaffByAd> staffByAds = ldapTemplate.find(containerCriteria, StaffByAd.class);
            staffByAds.forEach(staffByAd -> {
                String cn2 = staffByAd.getCn();
                if (StringUtils.isNotBlank(cn2)) {
                    staffByAd.setCn(cn2.substring(0, cn2.indexOf("@")));
                }
            });
            return staffByAds;
        }
        // 如果传递的用户名为空则返回空
        log.info("无该用户信息{}", person);
        return null;
    }

    /**
     * 修改用户信息
     *
     * @param person: 用户对象
     * @param base:   AD域的根节点
     */
    public void updateUser(StaffByAd person, String base) {
        //根据条件获取用户
        List<StaffByAd> users = getUser(person, base);
        //判断list是否为空
        if (users.size() == 0) {
            return;
        }
        // 进行遍历修改
        for (StaffByAd user : users) {
            ldapTemplate.modifyAttributes(user.getId(), getModificationItems(person));
            log.info("更改用户信息成功,{}", person);
        }
    }

    /**
     * 封装修改的用户属性
     *
     * @param person: 用户对象
     * @return ModificationItem[]
     * @author weilingfeng
     * @date 2023/8/23 9:52
     */
    private ModificationItem[] getModificationItems(StaffByAd person) {
        // 创建集合用来存储属性
        List<ModificationItem> modificationItems = new ArrayList<>();
        // 判断person中的邮箱是否为空
        if (StringUtils.isNotBlank(person.getMail())) {
            // 将属性添加至集合中
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("mail", person.getMail())));
        }
        // 判断person中的手机号是否为空
        if (StringUtils.isNotBlank(person.getPhoneNumber())) {
            // 将属性添加至集合中
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("telephoneNumber", person.getPhoneNumber())));
        }
        // 判断密码是否为空
        if (StringUtils.isNotBlank(person.getPassword())) {
            // 将属性添加至集合中
            byte[] bytes =("\""+ person.getPassword()+"\"").getBytes(StandardCharsets.UTF_16LE);
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("unicodePwd", bytes)));
        }
        // 判断姓是否为空
        if (StringUtils.isNotBlank(person.getSurname())) {
            // 将属性添加至集合中
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("sn", person.getLastName())));
        }
        // 判断名是否为空
        if (StringUtils.isNotBlank(person.getSurname())) {
            // 将属性添加至集合中
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("givenName", person.getFirstName())));
        }
        // 判断姓名是否为空
        if (StringUtils.isNotBlank(person.getSurname())) {
            // 将属性添加至集合中
            modificationItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("displayName", person.getSurname())));
        }
        return modificationItems.toArray(new ModificationItem[0]);
    }

    /**
     * 解除锁定状态
     *
     * @param username:用户名
     * @param base:AD域的根节点
     */
    public void unlockUser(String username, String base) {
        String userDn = "CN=" + username + "," + base;
        ModificationItem[] modificationItems = new ModificationItem[]{
                new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("lockoutTime"))
        };
        ldapTemplate.modifyAttributes(userDn, modificationItems);
        log.info("解除绑定成功,{}", username);
    }

    /**
     * 禁用指定账户。
     *
     * @param uid 用户 uid
     * @return 禁用成功返回 true,否则返回 false
     */
    public boolean disableUser(String uid, String base) {
        try {
            // 根据 uid 查询用户 DN
            Name userDn = getUserDn(uid, base);

            // 使用修改项更新 userAccountControl 属性值
            ldapTemplate.modifyAttributes(userDn,
                    new ModificationItem[]{
                            new ModificationItem(
                                    DirContext.REPLACE_ATTRIBUTE,
                                    new BasicAttribute("userAccountControl", "514")
                            )
                    }
            );
            // 禁用成功
            return true;
        } catch (Exception e) {
            log.error("禁用失败,原因{}", e.getMessage());
            // 禁用失败
            return false;
        }
    }


    /**
     * 解禁指定账户。
     *
     * @param uid 用户 uid
     * @return 解禁成功返回 true,否则返回 false
     */
    public boolean enableUser(String uid, String base) {
        try {
            // 根据 uid 查询用户 DN

            Name userDn = getUserDn(uid, base);
            // 使用修改项更新 userAccountControl 属性值
            ldapTemplate.modifyAttributes(userDn,
                    new ModificationItem[]{
                            new ModificationItem(
                                    DirContext.REPLACE_ATTRIBUTE,
                                    new BasicAttribute("userAccountControl", "512")
                            )
                    }
            );
            // 解禁成功
            return true;
        } catch (Exception e) {
            log.error("解禁成功{}", e.getMessage());
            // 解禁失败
            return false;
        }
    }

    /**
     * 根据 uid 查询用户 DN。
     *
     * @param uid 用户 uid
     * @return 用户 DN
     */
    private Name getUserDn(String uid, String base) {
        StaffByAd person = new StaffByAd();
        person.setCn(uid);
        List<StaffByAd> user = getUser(person, base);
        if (user.size() != 0) {
            return user.get(0).getId();
        }
        throw new RuntimeException("账号不存在");
    }

}

注意事项

AD域操作时,如果需要添加用户的密码时需要保证使用的账号是超级管理员以及AD域需要导入SSL证书。二者条件不满足的话就会导致更新时报错.

标签:集成,return,String,person,base,new,AD,public,springBoot
From: https://blog.csdn.net/m0_56512023/article/details/139472002

相关文章