【OSPP申请书】Hippo4j 用户体系对接 LDAP统一用户认证 ----- 计划及实现思路
  hzIe5RkCbU7L 2023年11月15日 20 0

(目录)


一、项目名称

题目:Hippo4j 用户体系对接 LDAP

二、项目描述/需求

  • Hippo4j Server 模式对接 IDAP 统一用户认证技术框架

  • Hippo4j Server 模式可使用 IDAP 相关账号进行登录操作

三、项目详细方案

1. LDAP相关概念

在开发过程中我们难免需要用到各种各样的开发工具,各个系统都有自己的一套密码,这就让我们很无奈,怎么记住呢?管理员在分配账户的时候也很头疼,团队每加入一个成员他就要在各个系统中为这个成员添加一个账户,管理起来非常麻烦。有没有一个统一管理多个系统的用户信息的东西呢?那就是LDAP

image-20230519215556169

LDAP的目的是为各种软件提供统一标准的认证机制,所有软件就可以不再用独有的用户管理方法,而是通过这种统一的认证机制进行用户认证。

  • OpenLDAP 是可以运行在 Linux 上的 LDAP 协议的开源实现。

  • 微软的 ActiveDirectory 是 LDAP在 Windows 上的实现。

  • 只要支持LDAP协议的系统都能够集成LDAP实现统一的认证管理。

一些名词解释

Entry 项

LDAP是以树形结构存储数据,每一个节点都被称为项。

① dc(Domain Component)

dc就是域组织(可以把它当作关系型中的库)

比如将http://zaq.test这样的域名,可以拆成 dc=zaq,dc=test这样的形式。

② dn(Distinguished Name)

它用于唯一标识一个「项」,以及他在目录信息树中的位置。(一条dn就类似于关系型数据库中的一条数据)

dn示例:ou=group,dc=zaq,dc=testcn=dev,ou=group,dc=zaq,dc=test

dn 字符串从左向右,各组成部分依次向树根靠近。

③ rdn(Relative Distinguished Name)

Rdn 就是「键值对」。dn 由若干个 rdn 组成,以逗号分隔。

比如上面的dn中dc=zaq就是一个rdn

④ ou(Organization Unit)

在 dn 中可能会包含 ou=某某部门 这样的组成部分,这里的 ou 指代组织单元、部门。

⑤ Object Classes

每个「项」里面包含若干个 Object Classes,相当于是项的属性。(一条dn中的这些属性就类似于关系型数据库中一条数据的各个字段)


2. 部署OpenLdap及可视化工具

准备工作:

1.环境要支持docker,具体怎么安装略过

在(我的本地)创建openldap,暴漏两个端口,创建命令注意 \后面不要有空格

docker run \
-p 389:389 \
-p 636:636 \
--name LDAP \
--network bridge \
--hostname openldap-host \
--env LDAP_ORGANISATION="panyujie" \
--env LDAP_DOMAIN="panyujie.com" \
--env LDAP_ADMIN_PASSWORD="12345" \
--detach osixia/openldap

配置LDAP组织者:--env LDAP_ORGANISATION="panyujie"

配置LDAP域:--env LDAP_DOMAIN="panyujie.com"

配置LDAP密码:--env LDAP_ADMIN_PASSWORD="12345"

默认登录用户名:admin


安装一个管理工具,可视化操作界面

docker run \
-d --privileged \
-p 18004:80 \
--name phpldapadmin \
--env PHPLDAPADMIN_HTTPS=false \
--env PHPLDAPADMIN_LDAP_HOSTS=101.43.239.60 \
--detach osixia/phpldapadmin

访问:http://101.43.239.60:18004

点击login进行登录

img

img

Login DN:cn=admin,dc=panyujie,dc=com


3. 代码结合到SpringBoot项目中

① 引入依赖

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

② 配置文件

在application.properties配置文件中添加配置

spring.ldap.urls=ldap://101.43.239.60:389
spring.ldap.base=dc=panyujie,dc=com
spring.ldap.embedded.credential.username=cn=admin,dc=panyujie,dc=com
spring.ldap.embedded.credential.password=12345

③ 用户User实体类

根据实际LDAP服务的person名和具体属性


@Data
@Entry(objectClasses = {"inetOrgPerson", "top"})
public class LdapUserInfo {

    @JsonIgnore
    @Id
    private Name dn;

    @Attribute(name = "cn")
    @DnAttribute(value = "cn")
    private String userName;

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

    @Attribute(name = "description")
    private String description;

    @Attribute(name = "telephoneNumber")
    private String telephoneNumber;

    @Attribute(name = "userPassword")
    private String password;

    @Attribute(name = "ou")
    private String organizational;

}

④ 配置LdapConfiguration


/**
 * Ldap config.
 */
@Configuration
public class LdapConfiguration {

    private LdapTemplate ldapTemplate;

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

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

    @Value("${spring.ldap.embedded.credential.username}")
    private String username;

    @Value("${spring.ldap.embedded.credential.password}")
    private String password;

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap<>(10);
        contextSource.setUrl(url);
        contextSource.setBase(base);
        contextSource.setUserDn(username);
        contextSource.setPassword(password);
        //  fix garbled characters
        config.put("java.naming.ldap.attributes.binary", "objectGUID");
        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        return contextSource;
    }

    @Bean
    public LdapTemplate ldapTemplate() {
        if (null == ldapTemplate) {
            ldapTemplate = new LdapTemplate(contextSource());
        }
        return ldapTemplate;
    }

}

⑤ Controller控制层


/**
 * Ldap controller.
 */
@RestController
@AllArgsConstructor
@RequestMapping(Constants.BASE_PATH + "/auth/users/ldap")
public class LdapController {

    private final LdapTemplate ldapTemplate;

    private final AuthManager authManager;

    private final LdapService ldapService;


    /**
     * LdapUser login
     */
    @PostMapping("/login")
    public Result<Void> login(String username, String password) {
        ldapService.login(username, password);
        return Results.success();
    }

    /**
     * Create ldap User
     */
    @PostMapping("/create")
    public Result<Void> create(@RequestBody LdapUserInfo user) {
        ldapService.create(user);
        return Results.success();
    }


    /**
     * Update ldap User
     */
    @PutMapping("/update")
    public Result<Void> update(@RequestBody LdapUserInfo user) {
        ldapService.update(user);
        return Results.success();
    }

    /**
     * Delete ldap User
     */
    @DeleteMapping("/remove/{username}")
    public Result<Void> delete(@PathVariable String username, String organizational) {
        ldapService.delete(username, organizational);
        return Results.success();
    }

    /**
     * Get userName of all users
     */
    @GetMapping("/info/getAllLdapUserNames")
    public Result<List<String>> getAllUserNames() {
        return Results.success(ldapService.getAllUserNames());
    }

    /**
     * Search domain user
     */
    @GetMapping("/info/searchLdapUser")
    public Result<List<LdapUserInfo>> searchLdapUser(String userName) {
        List<LdapUserInfo> infos = ldapService.searchLdapUser(userName);
        return Results.success(infos);
    }

    /**
     * Query the user by primary key
     */
    @GetMapping("/info/{username}")
    public Result<LdapUserInfo> findByPrimaryKey(@PathVariable String username) {
        return Results.success(ldapService.findByPrimaryKey(username));
    }

}

⑥ Service层

接口:


public interface LdapService {


    /**
     * Login ldap
     */
    void login(String username, String password);

    /**
     * Create ldap User
     */
    void create(LdapUserInfo user);

    /**
     * Update ldap User
     */
    void update(LdapUserInfo user);

    /**
     * Delete ldap User
     */
    void delete(String username, String organizational);

    /**
     * Get user names of all users
     */
    List<String> getAllUserNames();

    /**
     * Get user of all users
     */
    List<LdapUserInfo> searchLdapUser(String userName);

    /**
     * Query the user by primary key
     */
    LdapUserInfo findByPrimaryKey(String fullname);

}

实现类:


import static org.springframework.ldap.query.LdapQueryBuilder.query;

@Service
@AllArgsConstructor
public class LdapServiceImpl implements LdapService {

    private final LdapTemplate ldapTemplate;

    @Override
    public void login(String username, String password) {
        try {
            ldapTemplate.authenticate(query().where("cn").is(username), password);
        } catch (Exception e) {
            throw new ServiceException("用户名或者账户错误!!");
        }
    }

    @Override
    public void create(LdapUserInfo user) {
        try {
            Name dn = buildDn(user.getUserName(), user.getOrganizational());
            user.setDn(dn);
            ldapTemplate.create(user);
        } catch (NameAlreadyBoundException e) {
            throw new ServiceException("用户名已存在,请重新创建");
        }
    }

    @Override
    public void update(LdapUserInfo user) {
        Name dn = buildDn(user.getUserName(), user.getOrganizational());
        user.setDn(dn);
        try {
            ldapTemplate.update(user);
        } catch (NameNotFoundException e) {
            throw new ServiceException("cn=" + user.getUserName() + ",ou=" + user.getOrganizational() + "找不到用户");
        }
    }

    @Override
    public void delete(String username, String organizational) {
        Name dn = buildDn(username, organizational);
        try {
            ldapTemplate.delete(ldapTemplate.findByDn(dn, LdapUserInfo.class));
        } catch (Exception e) {
            throw new ServiceException("cn=" + username + ",ou=" + organizational + "找不到用户");
        }
    }

    @Override
    public List<String> getAllUserNames() {
        List<String> userNameList = ldapTemplate.search(query()
                        .attributes("cn")
                        .where("objectclass").is("inetOrgPerson"),
                (Attributes attributes) -> {
                    try {
                        return attributes.get("cn").get().toString();
                    } catch (NamingException e) {
                        throw new ServiceException("查询不到字段");
                    }
                });
        return userNameList;
    }

    @Override
    public List<LdapUserInfo> searchLdapUser(String userName) {
        String keyword = "*";
        if (StringUtil.isNotBlank(userName)) {
            keyword = keyword + (userName + "*");
        }
        System.out.println(keyword);
        LdapQuery query = query()
                .where("cn").like(keyword);
        return ldapTemplate.find(query, LdapUserInfo.class);
    }

    @Override
    public LdapUserInfo findByPrimaryKey(String username) {
        if (StringUtil.isBlank(username)) {
            throw new ServiceException("用户名不能为空!!");
        }
        LdapQuery query = query()
                .where("cn").like(username);
        LdapUserInfo userInfo;
        try {
            userInfo = ldapTemplate.findOne(query, LdapUserInfo.class);
        } catch (EmptyResultDataAccessException e) {
            throw new ServiceException("查询不到用户");
        }
        return userInfo;
    }

    private LdapName buildDn(String username, String ou) {
        return LdapNameBuilder.newInstance("ou=" + ou)
                .add("cn", username)
                .build();
    }

    private LdapName buildDn(String username) {
        return LdapNameBuilder.newInstance()
                .add("cn", username)
                .build();
    }

}

⑦ API接口文档

https://console-docs.apipost.cn/preview/789bc6d1923368ca/04b8b4524dbf540f

目前实现的功能:

image-20230519221909857


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月15日 0

暂无评论

hzIe5RkCbU7L