前言
随着企业内部各种开源平台越来越多,例如:gitlab、Jenkins、JumpServer、Rancher等,账号维护变成一件繁琐的事情,这时需要一个统一账号维护的平台,每人只需一个账号,在公司内部平台通用。而大多数开源平台都支持 LDAP,因此只要搭建好 LDAP 服务,将企业内部这些平台都对接到 LDAP,即可实现统一账号管理。
LDAP(Light Directory Access Portocol),它是基于 X.500 标准的轻量级目录访问协议。目录数据库是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
基本概念
- 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。
- 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
- 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
- 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。
关键字 | 英文全称 | 含义 |
---|---|---|
dc | Domain Component | 域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置) |
uid | User Id | 用户ID songtao.xu(一条记录的ID) |
ou | Organization Unit | 组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如“oa组”(一条记录的所属组织) |
cn | Common Name | 公共名称,如“Thomas Johansson”(一条记录的名称) |
sn | Surname | 姓,如“许” |
dn | Distinguished Name | “uid=songtao.xu,ou=oa组,dc=example,dc=com”,一条记录的位置(唯一) |
rdn | Relative dn | 相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=tom”或“cn= Thomas Johansson” |
安装服务器
这里我们使用 docker 来部署 openldap
docker pull osixia/openldap
docker run \
-d \
-p 389:389 \
-p 636:636 \
--env LDAP_ORGANISATION="imooc" \
--env LDAP_DOMAIN="imooc.com" \
--env LDAP_ADMIN_PASSWORD="test123456" \
--name openldap \
osixia/openldap
- -p 389:389 TCP/IP访问端口,-p 636:636 SSL连接端口。
- –name 自行设置容器名称
- –env LDAP_ORGANISATION 配置LDAP组织名称
- –env LDAP_DOMAIN 配置LDAP域名
- –env LDAP_ADMIN_PASSWORD 配置LDAP密码
注意:要开通防火墙的 389 和 636 端口
安装可视化工具:PHPLdapAdmin客户端
docker pull osixia/phpldapadmin
docker run \
-d \
-p 8080:80 \
--name ldap-admin \
--env PHPLDAPADMIN_HTTPS=false \
--env PHPLDAPADMIN_LDAP_HOSTS=your_ip \
osixia/phpldapadmin
- –env PHPLDAPADMIN_HTTPS=false 禁用HTTPS
- –env PHPLDAPADMIN_LDAP_HOSTS 配置openLDAP的IP或者域名
注意:要开通防火墙的 8080 端口
在 LDAP 中创建用户
登录
浏览器访问 http://ip:8080
- 账号为 CN=admin,DC=imooc,DC=com
- 密码为 test123456
创建 ou
创建用户
dn: uid=xiaoming,ou=beijing,dc=imooc,dc=com
ou: beijing
uid: xiaoming
sn: 小明
cn: xiaoming
givenName: xiaoming
displayName: xiaoming
mail: [email protected]
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
userpassword: 123456
最终目录结构为
java客户端调用
根据 cn 从 beijing 这个组织下查询用户信息
使用jdk内置实现
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import java.util.Hashtable;
public class TestLdap {
public static void main(String[] args) throws NamingException {
DirContext dirContext = ldapContext();
NamingEnumeration<SearchResult> enumeration = dirContext.search("ou=beijing,dc=imooc,dc=com", "cn=xiaoming", getDefaultSearchControls());
while (enumeration.hasMoreElements()) {
SearchResult searchResult = enumeration.next();
System.out.println(searchResult);
}
}
private static DirContext ldapContext() throws NamingException {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://ip:389");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=imooc,dc=com");
env.put(Context.SECURITY_CREDENTIALS, "test123456");
return new InitialLdapContext(env, null);
}
private static SearchControls getDefaultSearchControls() {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
return controls;
}
}
使用unboundid这个ldapsdk
添加依赖
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.5</version>
</dependency>
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import java.util.List;
public class TestLdap2 {
public static void main(String[] args) throws LDAPException {
LDAPConnection ldapConnection = new LDAPConnection();
ldapConnection.connect("ip", 389);
ldapConnection.bind("cn=admin,dc=imooc,dc=com","test123456");
SearchRequest searchRequest = new SearchRequest("ou=beijing,dc=imooc,dc=com", SearchScope.SUB,"cn=xiaoming");
SearchResult searchResult = ldapConnection.search(searchRequest);
List<SearchResultEntry> searchEntries = searchResult.getSearchEntries();
System.out.println(searchEntries);
}
}
使用spring封装
添加依赖
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.4.0</version>
</dependency>
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestSpringLdap {
public static void main(String[] args) {
LdapTemplate ldapTemplate = ldapTemplate();
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("cn", "xiaoming"));
List<Map<String, Object>> searchResult = ldapTemplate.search("ou=beijing,dc=imooc,dc=com", filter.encode(), new LdapUserAttributeMapper());
System.out.println(searchResult);
}
private static LdapTemplate ldapTemplate() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl("ldap://ip:389");
ldapContextSource.setUserDn("cn=admin,dc=imooc,dc=com");
ldapContextSource.setPassword("test123456");
ldapContextSource.afterPropertiesSet();
return new LdapTemplate(ldapContextSource);
}
public static class LdapUserAttributeMapper implements AttributesMapper<Map<String, Object>> {
@Override
public Map<String, Object> mapFromAttributes(Attributes attributes) throws NamingException {
Map<String, Object> map = new HashMap<>();
NamingEnumeration<? extends Attribute> enumeration = attributes.getAll();
while (enumeration.hasMoreElements()) {
Attribute attribute = enumeration.next();
map.put(attribute.getID(), attribute.get());
}
return map;
}
}
}
底层就是对 jdk 内置实现的封装。
参考
LDAP统一认证服务解决方案[通俗易懂]
LDAP概念和原理介绍
docker安装openldap
docker部署 OpenLDAP,实现账号的统一管理