• Golang对接Ldap(保姆级教程:概念&搭建&实战)


    Golang对接Ldap(保姆级教程:概念&搭建&实战)

    最近项目需要对接客户的LDAP服务,于是趁机好好了解了一下。LDAP实际是一个协议,对应的实现,大家可以理解为一个轻量级数据库。用户查询。比如:我要查询某个用户有没有对应的访问权限。

    • Windows的AD域就是LDAP的一个具体实现,当然AD域除了实现LDAP还实现了其他协议。

    🚄本文教程所用代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ldap_demo

    0 Ldap(Light Directory Access Portocol)

    我们日常的办公系统是不是有多个?每个系统之间是不是都有独立的账号密码?密码多了,有时候半天想不起来哪个密码对应哪个系统?

    • 如今大家再也不用为上面的的问题头疼了,因为“LDAP统一认证服务”已经帮助大家解决这些问题了

    1 LDAP(轻量级目录访问协议,查询快,特殊的数据库)

    LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。

    • 是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
    • 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。

    LDAP目录服务是由目录数据库和一套访问协议组成的系统。

    2 LDAP主流厂商

    细心的朋友应该会主要到,LDAP的中文全称是:轻量级目录访问协议,说到底LDAP仅仅是一个访问协议,那么我们的数据究竟存储在哪里呢?
    在这里插入图片描述

    3 核心概念&术语

    核心概念:

    1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。
    2. 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
    3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
    4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。
    ①dc(Domain Component):域名部分,dc=com

    域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置)

    ②uid(User Id):用户Id,uid=ziyi.zhou

    用户ID ziyi.zhou(一条记录的ID)

    ③ou(Organization Unit):组织,ou=develop

    组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如“oa组”(一条记录的所属组织)

    ④cn(Common Name):公共名称,cn=jack
    ⑤sn(Surname):姓,sn=周
    ⑥dn(Distinguished Name):类比URL。一条记录的唯一标识。uid=ziyi.zhou,ou=oa组,dc=example,dc=com。

    “uid=ziyi.zhou,ou=oa组,dc=example,dc=com”,一条记录的位置(唯一)

    • 类比URL:唯一定位Ldap服务中的一条记录。
    ⑦rdn(Relative dn):类比文件系统相对路径

    相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=tom”或“cn= Thomas Johansson”

    汇总表

    使用LDAP流程:

    1. 连接到LDAP服务器;
    2. 绑定到LDAP服务器;
    3. 在LDAP服务器上执行所需的任何操作;
    4. 释放LDAP服务器的连接;
      在这里插入图片描述

    参考:https://www.cnblogs.com/wilburxu/p/9174353.html

    1 Linux搭建Ldap

    以Centos为例搭建openldap。

    1.1 搭建Ldap服务(Server端)

    1. 安装openldap
    # 安装openldap
    yum -y install openldap-servers openldap-clients 
    # 对管理员密码进行加密
    slappasswd -s 123456
    #加密后的密码(后面需要用到): {SSHA}VHNPrmccIO/QRS1IOBdwp++K/FkIkFac
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2. 新建Ldap配置文件
    # 新建Ldap配置文件
    vim /etc/openldap/schema/changes.ldif
    
    • 1
    • 2

    changes.ldif:

    # 修改域名
    dn: olcDatabase={2}hdb,cn=config
    changetype: modify
    replace: olcSuffix
    # 注意修改
    olcSuffix: dc=yi,dc=com
    
    # 修改管理员用户
    dn: olcDatabase={2}hdb,cn=config
    changetype: modify
    replace: olcRootDN
    # 注意修改修改管理员用户 (olcRootDN):将管理员账户从原来的cn=admin,dc=ldap,dc=com改为cn=admin,dc=yi,dc=com。
    olcRootDN: cn=admin,dc=yi,dc=com
    
    # 修改管理员密码
    dn: olcDatabase={2}hdb,cn=config
    changetype: modify
    replace: olcRootPW
    # 替换为 slappasswd 生成后的结果
    olcRootPW: {SSHA}VHNPrmccIO/QRS1IOBdwp++K/FkIkFac
    
    # 修改访问权限
    dn: olcDatabase={1}monitor,cn=config
    changetype: modify
    replace: olcAccess
    olcAccess: {0}
      to *
      by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
      by dn.base="cn=admin,dc=yi,dc=com" read
      by * none
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    3. 创建域和组织
    # 创建配置文件
    vim /etc/openldap/schema/basedomain.ldif
    
    • 1
    • 2

    basedomain.ldif:

    # 配置文件内容,dn、dc都改成自己的配置
    # 下列目录:DC=redmond,DC=wa,DC=microsoft,DC=com      
    # 如果我们类比文件系统的话,可被看作如下文件路径:   
    # Com/Microsoft/Wa/Redmond  
    # 例如:CN=test,OU=developer,DC=domainname,DC=com
    # 在上面的代码中 cn=test 可能代表一个用户名,ou=developer 代表一个 active directory 中的组织单位。这句话的含义可能就是说明 test 这个对象处在domainname.com 域的 developer 组织单元中
    
    dn: dc=yi,dc=com
    dc: yi
    objectClass: top
    objectClass: domain
    
    dn: ou=People,dc=yi,dc=com
    ou: People
    objectClass: top
    objectClass: organizationalUnit
    
    dn: ou=Group,dc=yi,dc=com
    ou: Group
    objectClass: top
    objectClass: organizationalUnit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    应用配置:

    # 应用域和配置,回车之后输入未加密前的密码:123456
    ldapadd -x -D cn=admin,dc=yi,dc=com -W -f  /etc/openldap/schema/basedomain.ldif
    
    # 查看用户列表,观察是否创建成功
    ldapsearch -x -b "ou=People,dc=yi,dc=com" | grep dn
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过配置文件新建一个ou

    # 新建配置文件
    vim testGroup.ldif
    
    # 配置文件内容ou:指明为Test Group
    dn: ou=Test,dc=yi,dc=com
    ou: Test
    objectClass: top
    objectClass: organizationalUnit
    
    # 应用配置(cn=admin,dc=yi,dc=com:admin账户)
    # 回车后输入加密前的密码:123456
    ldapadd -x -D "cn=admin,dc=yi,dc=com" -W -f testGroup.ldif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    • 执行命令前:
      在这里插入图片描述
    • 执行命令,应用配置文件新增一个ou
      在这里插入图片描述

    1.2 docker搭建可视化工具

    1. 安装docker环境
    yum install -y yum-utils
    yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo
    yum install docker
    systemctl start docker
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2. 开放ldap端口(399)
    # 开放ldap server389端口或关闭防火墙
    firewall-cmd --zone=public --add-port=389/tcp --permanent
    systemctl restart firewalld
    
    • 1
    • 2
    • 3
    3. docker搭建可视化Ldap管理工具
    # 配置主机地址&不开启HTTPS(默认是开启)
    docker run -d --privileged -p 10004:80 --name myphpldapadmin \
      --env PHPLDAPADMIN_HTTPS=false --env PHPLDAPADMIN_LDAP_HOSTS=10.16.64.147  \
      --detach osixia/phpldapadmin
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    4. 浏览器访问

    浏览器访问http://ip:10004

    账户:

    • dn:cn=admin,dc=yi,dc=com
    • 密码:123456(我们生成密钥之前的明文,slappasswd -s 123456)

    登录:
    在这里插入图片描述
    在这里插入图片描述

    1.3 其他客户端工具推荐

    1. windows:ldapadmin

    官网地址:http://www.ldapadmin.org/

    页面效果:
    在这里插入图片描述

    2. mac:LDAP Browser For MAC

    官网地址:https://ldapbrowsermac.com/

    在这里插入图片描述

    使用效果:
    在这里插入图片描述

    2 Go操作Ldap

    1. 连接到LDAP服务器并绑定到LDAP服务器;(一般以管理员用户绑定,权限更大)
    2. 在LDAP服务器上执行所需的任何操作;
    3. 释放LDAP服务器的连接;

    2.1 连接并以相应角色绑定LDAP服务器

    安装依赖:

    // 安装go操作ldap库
    go get "github.com/go-ldap/ldap/v3"
    
    • 1
    • 2
    func loginBind(config *LdapConfig) (*ldap.Conn, error) {
    	l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    
    	if err != nil {
    		panic(err)
    		return nil, err
    	}
    	_, err = l.SimpleBind(&ldap.SimpleBindRequest{
    		Username: config.BindUserDn, //"cn=admin,dc=yi,dc=com"
    		Password: config.BindUserPassword, //"123456"
    	})
    	if err != nil {
    		fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
    		return nil, err
    	}
    	fmt.Println("bind success...")
    	return l, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.2 执行对应操作(add、select、del等)

    1. add

    以添加用户为例

    func addUser(conn *ldap.Conn, user User) error {
    	//添加用户
    	addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)
    	addRequest.Attribute("objectClass", []string{"inetOrgPerson"})
    	addRequest.Attribute("ou", []string{"QA Group"})
    	addRequest.Attribute("cn", []string{"41234123"})
    	addRequest.Attribute("sn", []string{"xx2"})
    	addRequest.Attribute("uid", []string{"10001"})
    	addRequest.Attribute("userPassword", []string{user.password})
    	err := conn.Add(addRequest)
    	if err != nil {
    		fmt.Println("add user error: ", err)
    		return err
    	}
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行添加前:
    在这里插入图片描述
    运行main,在ou=QA下添加一条记录:

    package main
    
    import (
    	"crypto/tls"
    	"fmt"
    	"github.com/go-ldap/ldap/v3"
    	"github.com/ziyifast/log"
    )
    
    // ldap:未加密
    // ldaps:加密
    var ldapURL = "ldap://10.100.xx.xxx"
    
    type LdapConfig struct {
    	Addr             string
    	BindUserDn       string
    	BindUserPassword string
    	BaseDn           string
    	LoginName        string
    	ObjectClass      []string
    }
    
    type User struct {
    	username    string
    	password    string
    	telephone   string
    	emailSuffix string
    	snUsername  string
    	uid         string
    	gid         string
    }
    
    func loginBind(config *LdapConfig) (*ldap.Conn, error) {
    	l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    
    	if err != nil {
    		panic(err)
    		return nil, err
    	}
    	_, err = l.SimpleBind(&ldap.SimpleBindRequest{
    		Username: config.BindUserDn,
    		Password: config.BindUserPassword,
    	})
    	if err != nil {
    		fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
    		return nil, err
    	}
    	fmt.Println("bind success...")
    	return l, nil
    }
    
    // 创建用户
    func addUser(conn *ldap.Conn, user User) error {
    	//添加用户
    	addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)
    	addRequest.Attribute("objectClass", []string{"inetOrgPerson"})
    	addRequest.Attribute("ou", []string{"QA Group"})
    	addRequest.Attribute("cn", []string{"41234123"})
    	addRequest.Attribute("sn", []string{"xx2"})
    	addRequest.Attribute("uid", []string{"10001"})
    	addRequest.Attribute("userPassword", []string{user.password})
    	err := conn.Add(addRequest)
    	if err != nil {
    		fmt.Println("add user error: ", err)
    		return err
    	}
    	return nil
    }
    func main() {
    	//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)
    	config := new(LdapConfig)
    	config.Addr = "ldap://10.100.xx.xxx"
    	config.BaseDn = "dc=yi,dc=com"
    	config.BindUserDn = "cn=admin,dc=yi,dc=com"
    	config.LoginName = "uid"
    	config.BindUserPassword = "123456"
    	config.ObjectClass = []string{"inetOrgPerson"}
    
    	//与建立ldap服务建立连接(方便后续查询新增删除项)
    	conn, err := loginBind(config)
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    	TestAddUser(conn)
    }
    
    // TestAddUser 测试添加用户
    func TestAddUser(conn *ldap.Conn) {
    	//添加用户
    	user := User{
    		username: "wangmazi",
    		password: "123456",
    	}
    	err := addUser(conn, user)
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println("add success...")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    在这里插入图片描述
    效果:

    添加成功

    在这里插入图片描述

    2. select
    • 拼接查询条件
      • 单个条件(cn=jack):查询cn为jack的资源
      • 多个条件(&(cn=wangmazi)(ou=QA)):查询cn为wangmazi并且ou为QA的资源
    • ldap.NewSearchRequest(fmt.Sprintf(“%s”, config.BaseDn)调用查询接口
    // 查询用户
    func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {
    	//多个条件:(&(cn=wangmazi)(ou=QA))
    	filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))
    	request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),
    		ldap.ScopeWholeSubtree,
    		ldap.NeverDerefAliases,
    		0,
    		0,
    		false,
    		filter,
    		[]string{"userPassword"},
    		nil,
    	)
    	searchResult, err := conn.Search(request)
    	if err != nil {
    		fmt.Println("search user error: ", err)
    		return nil, err
    	}
    	return searchResult, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行:

    package main
    
    import (
    	"crypto/tls"
    	"fmt"
    	"github.com/go-ldap/ldap/v3"
    	"github.com/ziyifast/log"
    )
    
    // ldap:未加密
    // ldaps:加密
    var ldapURL = "ldap://10.100.xx.xxx"
    
    type LdapConfig struct {
    	Addr             string
    	BindUserDn       string
    	BindUserPassword string
    	BaseDn           string
    	LoginName        string
    	ObjectClass      []string
    }
    
    type User struct {
    	username    string
    	password    string
    	telephone   string
    	emailSuffix string
    	snUsername  string
    	uid         string
    	gid         string
    }
    
    func loginBind(config *LdapConfig) (*ldap.Conn, error) {
    	l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    
    	if err != nil {
    		panic(err)
    		return nil, err
    	}
    	_, err = l.SimpleBind(&ldap.SimpleBindRequest{
    		Username: config.BindUserDn,
    		Password: config.BindUserPassword,
    	})
    	if err != nil {
    		fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
    		return nil, err
    	}
    	fmt.Println("bind success...")
    	return l, nil
    }
    
    // 查询用户
    func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {
    	//多个条件:(&(cn=wangmazi)(ou=QA))
    	filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))
    	request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),
    		ldap.ScopeWholeSubtree,
    		ldap.NeverDerefAliases,
    		0,
    		0,
    		false,
    		filter,
    		[]string{"userPassword"},
    		nil,
    	)
    	searchResult, err := conn.Search(request)
    	if err != nil {
    		fmt.Println("search user error: ", err)
    		return nil, err
    	}
    	return searchResult, nil
    }
    func main() {
    	//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)
    	config := new(LdapConfig)
    	config.Addr = "ldap://10.100.xx.xxx"
    	config.BaseDn = "dc=yi,dc=com"
    	config.BindUserDn = "cn=admin,dc=yi,dc=com"
    	config.LoginName = "uid"
    	config.BindUserPassword = "123456"
    	config.ObjectClass = []string{"inetOrgPerson"}
    
    	//与建立ldap服务建立连接(方便后续查询新增删除项)
    	conn, err := loginBind(config)
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    	TestFindUser(conn, config)
    }
    
    
    // TestFindUser 测试查询用户
    func TestFindUser(conn *ldap.Conn, config *LdapConfig) {
    	user := &User{
    		username: "wangmazi",
    	}
    	searchResult, err := findUser(conn, config, *user)
    	if err != nil {
    		panic(err)
    	}
    	for _, entry := range searchResult.Entries {
    		fmt.Println("find user: ", entry.DN)
    		for _, v := range entry.Attributes {
    			fmt.Println(v.Name, v.Values)
    		}
    	}
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    效果:
    在这里插入图片描述

    3. del
    • 拼接要删除的DN(唯一标识,定位一个资源的具体位置)
    • ldap.NewDelRequest(dn, nil)调用删除请求
    // 删除用户
    func deleteUser(conn *ldap.Conn, config *LdapConfig, user User) error {
    	dn := fmt.Sprintf("cn=%s,ou=QA,%s", user.username, config.BaseDn)
    	log.Infof("del dn %v", dn)
    	delRequest := ldap.NewDelRequest(dn, nil)
    	err := conn.Del(delRequest)
    	if err != nil {
    		fmt.Printf("Failed to delete user %s: %v\n", dn, err)
    		return err
    	}
    	fmt.Printf("User %s successfully deleted.\n", dn)
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    删除前:
    在这里插入图片描述
    运行TestDeleteUser删除该记录:

    package main
    
    import (
    	"crypto/tls"
    	"fmt"
    	"github.com/go-ldap/ldap/v3"
    	"github.com/ziyifast/log"
    )
    
    // ldap:未加密
    // ldaps:加密
    var ldapURL = "ldap://10.100.xx.xxx"
    
    type LdapConfig struct {
    	Addr             string
    	BindUserDn       string
    	BindUserPassword string
    	BaseDn           string
    	LoginName        string
    	ObjectClass      []string
    }
    
    type User struct {
    	username    string
    	password    string
    	telephone   string
    	emailSuffix string
    	snUsername  string
    	uid         string
    	gid         string
    }
    
    func loginBind(config *LdapConfig) (*ldap.Conn, error) {
    	l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    
    	if err != nil {
    		panic(err)
    		return nil, err
    	}
    	_, err = l.SimpleBind(&ldap.SimpleBindRequest{
    		Username: config.BindUserDn,
    		Password: config.BindUserPassword,
    	})
    	if err != nil {
    		fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
    		return nil, err
    	}
    	fmt.Println("bind success...")
    	return l, nil
    }
    
    
    // 查询用户
    func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {
    	//多个条件:(&(cn=wangmazi)(ou=QA))
    	filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))
    	request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),
    		ldap.ScopeWholeSubtree,
    		ldap.NeverDerefAliases,
    		0,
    		0,
    		false,
    		filter,
    		[]string{"userPassword"},
    		nil,
    	)
    	searchResult, err := conn.Search(request)
    	if err != nil {
    		fmt.Println("search user error: ", err)
    		return nil, err
    	}
    	return searchResult, nil
    }
    
    func main() {
    	//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)
    	config := new(LdapConfig)
    	config.Addr = "ldap://10.100.xx.xxx"
    	config.BaseDn = "dc=yi,dc=com"
    	config.BindUserDn = "cn=admin,dc=yi,dc=com"
    	config.LoginName = "uid"
    	config.BindUserPassword = "123456"
    	config.ObjectClass = []string{"inetOrgPerson"}
    
    	//与建立ldap服务建立连接(方便后续查询新增删除项)
    	conn, err := loginBind(config)
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    	TestDeleteUser(conn, config)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    运行后效果:
    在这里插入图片描述
    在这里插入图片描述

    2.3 释放连接

    func main() {
    	//与建立ldap服务建立连接(方便后续查询新增删除项)
    	conn, err := loginBind(config)
    	if err != nil {
    		panic(err)
    	}
    	err = conn.Close()
    	if err != nil {
    		panic(err)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    全部代码

    代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ldap_demo

    package main
    
    import (
    	"crypto/tls"
    	"fmt"
    	"github.com/go-ldap/ldap/v3"
    	"github.com/ziyifast/log"
    )
    
    // ldap:未加密
    // ldaps:加密
    var ldapURL = "ldap://10.16.xx.xx"
    
    type LdapConfig struct {
    	Addr             string
    	BindUserDn       string
    	BindUserPassword string
    	BaseDn           string
    	LoginName        string
    	ObjectClass      []string
    }
    
    type User struct {
    	username    string
    	password    string
    	telephone   string
    	emailSuffix string
    	snUsername  string
    	uid         string
    	gid         string
    }
    
    func loginBind(config *LdapConfig) (*ldap.Conn, error) {
    	l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    
    	if err != nil {
    		panic(err)
    		return nil, err
    	}
    	_, err = l.SimpleBind(&ldap.SimpleBindRequest{
    		Username: config.BindUserDn,
    		Password: config.BindUserPassword,
    	})
    	if err != nil {
    		fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
    		return nil, err
    	}
    	fmt.Println("bind success...")
    	return l, nil
    }
    
    // 创建用户
    func addUser(conn *ldap.Conn, user User) error {
    	//添加用户
    	addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)
    	addRequest.Attribute("objectClass", []string{"inetOrgPerson"})
    	addRequest.Attribute("ou", []string{"QA Group"})
    	addRequest.Attribute("cn", []string{"41234123"})
    	addRequest.Attribute("sn", []string{"xx2"})
    	addRequest.Attribute("uid", []string{"10001"})
    	addRequest.Attribute("userPassword", []string{user.password})
    	err := conn.Add(addRequest)
    	if err != nil {
    		fmt.Println("add user error: ", err)
    		return err
    	}
    	return nil
    }
    
    // 查询用户
    func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {
    	//多个条件:(&(cn=wangmazi)(ou=QA))
    	filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))
    	request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),
    		ldap.ScopeWholeSubtree,
    		ldap.NeverDerefAliases,
    		0,
    		0,
    		false,
    		filter,
    		[]string{"userPassword"},
    		nil,
    	)
    	searchResult, err := conn.Search(request)
    	if err != nil {
    		fmt.Println("search user error: ", err)
    		return nil, err
    	}
    	return searchResult, nil
    }
    
    // 删除用户
    func deleteUser(conn *ldap.Conn, config *LdapConfig, user User) error {
    	dn := fmt.Sprintf("cn=%s,ou=QA,%s", user.username, config.BaseDn)
    	log.Infof("del dn %v", dn)
    	delRequest := ldap.NewDelRequest(dn, nil)
    	err := conn.Del(delRequest)
    	if err != nil {
    		fmt.Printf("Failed to delete user %s: %v\n", dn, err)
    		return err
    	}
    	fmt.Printf("User %s successfully deleted.\n", dn)
    	return nil
    }
    
    func main() {
    	//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)
    	config := new(LdapConfig)
    	config.Addr = "ldap://10.16.xx.xx"
    	config.BaseDn = "dc=yi,dc=com"
    	config.BindUserDn = "cn=admin,dc=yi,dc=com"
    	config.LoginName = "uid"
    	config.BindUserPassword = "123456"
    	//客户不配置username,我们需要根据配置的ObjectClass查询出对应的用户。
    	//因为如果用户配置的是cn,那么可能会查询出一些组织、其他设备等,所以为了将Ldap第三方用户纳管过来,我们需要添加ObjectClass
    	config.ObjectClass = []string{"inetOrgPerson"}
    
    	//与建立ldap服务建立连接(方便后续查询新增删除项)
    	conn, err := loginBind(config)
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    	TestDeleteUser(conn, config)
    }
    
    // TestAddUser 测试添加用户
    func TestAddUser(conn *ldap.Conn) {
    	//添加用户
    	user := User{
    		username: "wangmazi",
    		password: "123456",
    	}
    	err := addUser(conn, user)
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println("add success...")
    }
    
    // TestFindUser 测试查询用户
    func TestFindUser(conn *ldap.Conn, config *LdapConfig) {
    	user := &User{
    		username: "wangmazi",
    	}
    	searchResult, err := findUser(conn, config, *user)
    	if err != nil {
    		panic(err)
    	}
    	for _, entry := range searchResult.Entries {
    		fmt.Println("find user: ", entry.DN)
    		for _, v := range entry.Attributes {
    			fmt.Println(v.Name, v.Values)
    		}
    	}
    	return
    }
    
    func TestDeleteUser(conn *ldap.Conn, config *LdapConfig) {
    	user := User{
    		username: "wangmazi",
    	}
    	err := deleteUser(conn, config, user)
    	if err != nil {
    		panic(err)
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168

    3 项目对接思路

    项目登录对接:支持LDAP登录,用户可直接输入LDAP服务端存在的用户,直接登录系统

    1. 页面提供入口配置LDAP服务
      • Addr:LDAP服务端地址
      • BindUserDn:LDAP管理员用户dn
      • BindUserPassword:LDAP管理员密码
      • BaseDn:操作范围(dc=yi,dc=com表明操作这个范围下的数据)
      • LoginName:配置以哪个参数登录
    2. 页面输入LDAP对应账号
    3. 根据LDAP配置连接LDAP服务端,查询用户输入的账号是否存在,密码是否正确
    4. 可以直接纳管LDAP用户到我方系统,建立对应关系。比如:用户审计…
    type LdapConfig struct {
    	Addr             string
    	BindUserDn       string
    	BindUserPassword string
    	BaseDn           string
    	LoginName        string
    	ObjectClass      []string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

  • 相关阅读:
    【音视频】ffplay源码解析-PacketQueue队列
    哈夫曼树实现哈夫曼编码(C++)
    【笔记】使用OrCAD对电路进行蒙特卡洛分析和最坏分析
    详解二叉搜索树【C++实现】
    Nacos系列【25】源码分析篇之Spring Cloud启动器
    vue3学习源码笔记(小白入门系列)------KeepAlive 原理
    Git 使用技巧:5个提高效率的命令,不再只会pull和push
    Vue3属性绑定方式分析
    HDMI2.1与HDMI2.0的区别以及转换PD信号。
    前端知识学习案例10-开发企业网站10-实现案例部分1
  • 原文地址:https://blog.csdn.net/weixin_45565886/article/details/138153649