From 396546dcd22bac74367a48c1fa8e538cec458383 Mon Sep 17 00:00:00 2001 From: Ronin_Zc <48718694+RoninZc@users.noreply.github.com> Date: Sun, 24 Jul 2022 21:24:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20LDAP=20=E6=B7=BB=E5=8A=A0=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=B1=A0=E6=94=AF=E6=8C=81=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 2 + config/config.go | 1 + public/client/openldap/openldap.go | 30 ++++++- public/common/ldap.go | 130 +++++++++++++++++++++++++++-- service/ildap/group_ildap.go | 51 +++++++++-- service/ildap/user_ildap.go | 50 +++++++++-- 6 files changed, 243 insertions(+), 21 deletions(-) diff --git a/config.yml b/config.yml index 5a0a79c..fd0ba72 100644 --- a/config.yml +++ b/config.yml @@ -85,6 +85,8 @@ email: ldap: # ldap服务器地址 url: ldap://localhost:389 + # ladp最大连接数设置 + max-conn: 10 # ldap服务器基础DN base-dn: "dc=eryajf,dc=net" # ldap管理员DN diff --git a/config/config.go b/config/config.go index da2cab7..bbe7c91 100644 --- a/config/config.go +++ b/config/config.go @@ -135,6 +135,7 @@ type RateLimitConfig struct { type LdapConfig struct { Url string `mapstructure:"url" json:"url"` + MaxConn int `mapstructure:"max-conn" json:"maxConn"` BaseDN string `mapstructure:"base-dn" json:"baseDN"` AdminDN string `mapstructure:"admin-dn" json:"adminDN"` AdminPass string `mapstructure:"admin-pass" json:"adminPass"` diff --git a/public/client/openldap/openldap.go b/public/client/openldap/openldap.go index d7d92ab..290a67e 100644 --- a/public/client/openldap/openldap.go +++ b/public/client/openldap/openldap.go @@ -44,8 +44,16 @@ func GetAllDepts() (ret []*Dept, err error) { []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned nil, ) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return nil, err + } + // Search through ldap built-in search - sr, err := common.LDAP.Search(searchRequest) + sr, err := conn.Search(searchRequest) if err != nil { return ret, err } @@ -81,8 +89,16 @@ func GetAllUsers() (ret []*User, err error) { []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned nil, ) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return nil, err + } + // Search through ldap built-in search - sr, err := common.LDAP.Search(searchRequest) + sr, err := conn.Search(searchRequest) if err != nil { return ret, err } @@ -128,8 +144,16 @@ func GetUserDeptIds(udn string) (ret []string, err error) { []string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned nil, ) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return nil, err + } + // Search through ldap built-in search - sr, err := common.LDAP.Search(searchRequest) + sr, err := conn.Search(searchRequest) if err != nil { return ret, err } diff --git a/public/common/ldap.go b/public/common/ldap.go index a6d9769..4fdce8f 100644 --- a/public/common/ldap.go +++ b/public/common/ldap.go @@ -2,7 +2,10 @@ package common import ( "fmt" + "log" + "math/rand" "net" + "sync" "time" "github.com/eryajf/go-ldap-admin/config" @@ -10,25 +13,40 @@ import ( ldap "github.com/go-ldap/ldap/v3" ) -// 全局ldap数据库变量 -var LDAP *ldap.Conn +var ldapPool *LdapConnPool +var ldapInit = false +var ldapInitOne sync.Once // Init 初始化连接 func InitLDAP() { + if ldapInit { + return + } + + ldapInitOne.Do(func() { + ldapInit = true + }) + // Dail有两个参数 network, address, 返回 (*Conn, error) - ldap, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second})) + ldapConn, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second})) if err != nil { Log.Panicf("初始化ldap连接异常: %v", err) panic(fmt.Errorf("初始化ldap连接异常: %v", err)) } - err = ldap.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass) + err = ldapConn.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass) if err != nil { Log.Panicf("绑定admin账号异常: %v", err) panic(fmt.Errorf("绑定admin账号异常: %v", err)) } - // 全局LDAP赋值 - LDAP = ldap + // 全局变量赋值 + ldapPool = &LdapConnPool{ + conns: make([]*ldap.Conn, 0), + reqConns: make(map[uint64]chan *ldap.Conn), + openConn: 0, + maxOpen: config.Conf.Ldap.MaxConn, + } + PutLADPConn(ldapConn) // 隐藏密码 showDsn := fmt.Sprintf( @@ -39,3 +57,103 @@ func InitLDAP() { Log.Info("初始化ldap完成! dsn: ", showDsn) } + +// GetLDAPConn 获取 LDAP 连接 +func GetLDAPConn() (*ldap.Conn, error) { + return ldapPool.GetConnection() +} + +// PutLDAPConn 放回 LDAP 连接 +func PutLADPConn(conn *ldap.Conn) { + ldapPool.PutConnection(conn) +} + +type LdapConnPool struct { + mu sync.Mutex + conns []*ldap.Conn + reqConns map[uint64]chan *ldap.Conn + openConn int + maxOpen int +} + +// 获取一个 ladp Conn +func (lcp *LdapConnPool) GetConnection() (*ldap.Conn, error) { + lcp.mu.Lock() + // 判断当前连接池内是否存在连接 + connNum := len(lcp.conns) + if connNum > 0 { + lcp.openConn++ + conn := lcp.conns[0] + copy(lcp.conns, lcp.conns[1:]) + lcp.conns = lcp.conns[:connNum-1] + + lcp.mu.Unlock() + // 发现连接已经 close 重新获取连接 + if conn.IsClosing() { + return initLDAPConn() + } + return conn, nil + } + + // 当现有连接池为空时,并且当前超过最大连接限制 + if lcp.maxOpen != 0 && lcp.openConn > lcp.maxOpen { + // 创建一个等待队列 + req := make(chan *ldap.Conn, 1) + reqKey := lcp.nextRequestKeyLocked() + lcp.reqConns[reqKey] = req + lcp.mu.Unlock() + + // 等待请求归还 + return <-req, nil + } else { + lcp.openConn++ + lcp.mu.Unlock() + return initLDAPConn() + } +} + +func (lcp *LdapConnPool) PutConnection(conn *ldap.Conn) { + log.Println("放回了一个连接") + lcp.mu.Lock() + defer lcp.mu.Unlock() + + // 先判断是否存在等待的队列 + if num := len(lcp.reqConns); num > 0 { + var req chan *ldap.Conn + var reqKey uint64 + for reqKey, req = range lcp.reqConns { + break + } + delete(lcp.reqConns, reqKey) + req <- conn + return + } else { + lcp.openConn-- + if !conn.IsClosing() { + lcp.conns = append(lcp.conns, conn) + } + } +} + +// 获取下一个请求令牌 +func (lcp *LdapConnPool) nextRequestKeyLocked() uint64 { + for { + reqKey := rand.Uint64() + if _, ok := lcp.reqConns[reqKey]; !ok { + return reqKey + } + } +} + +// 获取 ladp 连接 +func initLDAPConn() (*ldap.Conn, error) { + ldap, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second})) + if err != nil { + return nil, err + } + err = ldap.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass) + if err != nil { + return nil, err + } + return ldap, err +} diff --git a/service/ildap/group_ildap.go b/service/ildap/group_ildap.go index 951bb66..2433b03 100644 --- a/service/ildap/group_ildap.go +++ b/service/ildap/group_ildap.go @@ -28,21 +28,36 @@ func (x GroupService) Add(g *model.Group) error { //organizationalUnit add.Attribute(g.GroupType, []string{g.GroupName}) add.Attribute("description", []string{g.Remark}) - return common.LDAP.Add(add) + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + return conn.Add(add) } // UpdateGroup 更新一个分组 func (x GroupService) Update(oldGroup, newGroup *model.Group) error { modify := ldap.NewModifyRequest(oldGroup.GroupDN, nil) modify.Replace("description", []string{newGroup.Remark}) - err := common.LDAP.Modify(modify) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + err = conn.Modify(modify) if err != nil { return err } // 如果配置文件允许修改分组名称,且分组名称发生了变化,那么执行修改分组名称 if config.Conf.Ldap.GroupNameModify && newGroup.GroupName != oldGroup.GroupName { modify := ldap.NewModifyDNRequest(oldGroup.GroupDN, newGroup.GroupDN, true, "") - err := common.LDAP.ModifyDN(modify) + err := conn.ModifyDN(modify) if err != nil { return err } @@ -53,7 +68,15 @@ func (x GroupService) Update(oldGroup, newGroup *model.Group) error { // Delete 删除资源 func (x GroupService) Delete(gdn string) error { del := ldap.NewDelRequest(gdn, nil) - return common.LDAP.Del(del) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + return conn.Del(del) } // AddUserToGroup 添加用户到分组 @@ -64,12 +87,28 @@ func (x GroupService) AddUserToGroup(dn, udn string) error { } newmr := ldap.NewModifyRequest(dn, nil) newmr.Add("uniqueMember", []string{udn}) - return common.LDAP.Modify(newmr) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + return conn.Modify(newmr) } // DelUserFromGroup 将用户从分组删除 func (x GroupService) RemoveUserFromGroup(gdn, udn string) error { newmr := ldap.NewModifyRequest(gdn, nil) newmr.Delete("uniqueMember", []string{udn}) - return common.LDAP.Modify(newmr) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + return conn.Modify(newmr) } diff --git a/service/ildap/user_ildap.go b/service/ildap/user_ildap.go index 5c69ecd..35d1b3c 100644 --- a/service/ildap/user_ildap.go +++ b/service/ildap/user_ildap.go @@ -30,7 +30,15 @@ func (x UserService) Add(user *model.User) error { add.Attribute("mobile", []string{user.Mobile}) add.Attribute("uid", []string{user.Username}) add.Attribute("userPassword", []string{tools.NewParPasswd(user.Password)}) - return common.LDAP.Add(add) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + return conn.Add(add) } // Update 更新资源 @@ -47,13 +55,21 @@ func (x UserService) Update(oldusername string, user *model.User) error { modify.Replace("givenName", []string{user.GivenName}) modify.Replace("postalAddress", []string{user.PostalAddress}) modify.Replace("mobile", []string{user.Mobile}) - err := common.LDAP.Modify(modify) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + err = conn.Modify(modify) if err != nil { return err } if config.Conf.Ldap.UserNameModify && oldusername != user.Username { modifyDn := ldap.NewModifyDNRequest(fmt.Sprintf("uid=%s,%s", oldusername, config.Conf.Ldap.UserDN), fmt.Sprintf("uid=%s", user.Username), true, "") - return common.LDAP.ModifyDN(modifyDn) + return conn.ModifyDN(modifyDn) } return nil } @@ -61,13 +77,27 @@ func (x UserService) Update(oldusername string, user *model.User) error { // Delete 删除资源 func (x UserService) Delete(udn string) error { del := ldap.NewDelRequest(udn, nil) - return common.LDAP.Del(del) + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + return conn.Del(del) } // ChangePwd 修改用户密码,此处旧密码也可以为空,ldap可以直接通过用户DN加上新密码来进行修改 func (x UserService) ChangePwd(udn, oldpasswd, newpasswd string) error { modifyPass := ldap.NewPasswordModifyRequest(udn, oldpasswd, newpasswd) - _, err := common.LDAP.PasswordModify(modifyPass) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return err + } + + _, err = conn.PasswordModify(modifyPass) if err != nil { return fmt.Errorf("password modify failed for %s, err: %v", udn, err) } @@ -81,7 +111,15 @@ func (x UserService) NewPwd(username string) (string, error) { udn = config.Conf.Ldap.AdminDN } modifyPass := ldap.NewPasswordModifyRequest(udn, "", "") - newpass, err := common.LDAP.PasswordModify(modifyPass) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return "", err + } + + newpass, err := conn.PasswordModify(modifyPass) if err != nil { return "", fmt.Errorf("password modify failed for %s, err: %v", username, err) }