(目录)
一、项目名称
题目:Hippo4j 用户体系对接 LDAP
二、项目描述/需求
Hippo4j Server 模式对接 IDAP 统一用户认证技术框架
Hippo4j Server 模式可使用 IDAP 相关账号进行登录操作
三、项目详细方案
1. LDAP相关概念
在开发过程中我们难免需要用到各种各样的开发工具,各个系统都有自己的一套密码,这就让我们很无奈,怎么记住呢?管理员在分配账户的时候也很头疼,团队每加入一个成员他就要在各个系统中为这个成员添加一个账户,管理起来非常麻烦。有没有一个统一管理多个系统的用户信息的东西呢?那就是LDAP
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=test
、cn=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进行登录
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
目前实现的功能: