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 核心概念&术语
核心概念:
- 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。
- 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
- 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
- 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。
①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流程:
- 连接到LDAP服务器;
- 绑定到LDAP服务器;
- 在LDAP服务器上执行所需的任何操作;
- 释放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
2. 新建Ldap配置文件
# 新建Ldap配置文件
vim /etc/openldap/schema/changes.ldif
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
3. 创建域和组织
# 创建配置文件
vim /etc/openldap/schema/basedomain.ldif
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
应用配置:
# 应用域和配置,回车之后输入未加密前的密码: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
通过配置文件新建一个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
- 执行命令前:
- 执行命令,应用配置文件新增一个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
2. 开放ldap端口(399)
# 开放ldap server389端口或关闭防火墙
firewall-cmd --zone=public --add-port=389/tcp --permanent
systemctl restart firewalld
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
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
- 连接到LDAP服务器并绑定到LDAP服务器;(一般以管理员用户绑定,权限更大)
- 在LDAP服务器上执行所需的任何操作;
- 释放LDAP服务器的连接;
2.1 连接并以相应角色绑定LDAP服务器
安装依赖:
// 安装go操作ldap库
go get "github.com/go-ldap/ldap/v3"
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
}
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
}
执行添加前:
运行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...")
}
效果:
添加成功
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
}
运行:
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
}
效果:
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
}
删除前:
运行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)
}
运行后效果:
2.3 释放连接
func main() {
//与建立ldap服务建立连接(方便后续查询新增删除项)
conn, err := loginBind(config)
if err != nil {
panic(err)
}
err = conn.Close()
if err != nil {
panic(err)
}
}
全部代码
代码地址: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)
}
}
3 项目对接思路
项目登录对接:支持LDAP登录,用户可直接输入LDAP服务端存在的用户,直接登录系统
- 页面提供入口配置LDAP服务
- Addr:LDAP服务端地址
- BindUserDn:LDAP管理员用户dn
- BindUserPassword:LDAP管理员密码
- BaseDn:操作范围(dc=yi,dc=com表明操作这个范围下的数据)
- LoginName:配置以哪个参数登录
- 页面输入LDAP对应账号
- 根据LDAP配置连接LDAP服务端,查询用户输入的账号是否存在,密码是否正确
- 可以直接纳管LDAP用户到我方系统,建立对应关系。比如:用户审计…
type LdapConfig struct {
Addr string
BindUserDn string
BindUserPassword string
BaseDn string
LoginName string
ObjectClass []string
}