使用Go-Ldap查询Active Directory时如何获取用户主组?
问题
使用go-ldap库查询Active Directory中特定用户的所有组时,当前代码可正常返回普通组,但无法获取Domain Users这类主组。
现有代码
package main import ( "encoding/json" "errors" "fmt" "github.com/go-ldap/ldap/v3" "github.com/techoner/gophp" "handlers" "log" "reflect" "strconv" "strings" ) func main(){ conn, err := connect(bindServer,BindPort) if err != nil { log.Printf("Failed to connect to AD/LDAP with error: %s", err) return nil, fmt.Errorf("Failed to connect to AD/LDAP with error: %s", err) } errBind := conn.Bind(bindUser, bindPWD) if errBind != nil { if isLdapDebug { log.Printf("Failed to bind to AD/LDAP with error: %s", errBind) } return nil, fmt.Errorf("Failed to bind to AD/LDAP with error: %s", errBind) } searchRequest := ldap.NewSearchRequest( "DC=domain,DC=local", // 修正原代码语法错误:根节点需用字符串包裹 ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=user)(sAMAccountName=%s))", administrator), []string{"dn"}, nil, ) sr, err := conn.Search(searchRequest) if err != nil { return nil, err } if len(sr.Entries) != 1 { return nil, errors.New("User does not exist") } userdn := sr.Entries[0].DN log.Printf("USER DN IS =%s", userdn) searchRequest = ldap.NewSearchRequest( "DC=domain,DC=local", // 修正原代码语法错误:根节点需用字符串包裹 ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=group)(member=%s))", userdn), // 修正原代码硬编码问题:使用变量userdn []string{"cn"}, nil, ) sr, err = conn.Search(searchRequest) if err != nil { return nil, err } groups := []string{} for _, entry := range sr.Entries { groups = append(groups, entry.GetAttributeValue("cn")) } return groups, nil }
注:原代码存在两处语法/逻辑问题,已在示例中修正:
- LDAP根节点需用字符串包裹
- 组查询时硬编码用户DN,改为使用变量
userdn
当前输出
[Administrators Schema Admins Enterprise Admins Domain Admins Group Policy Creator Owners gteste1 gtest2]
输出中缺少用户的主组(如Domain Users)。
解决方法
Active Directory中,用户的主组不会出现在组的member属性里,而是通过用户的primaryGroupID属性与组的primaryGroupToken属性关联。要获取主组,需额外执行以下步骤:
- 查询用户时获取
primaryGroupID:修改第一个LDAP搜索请求,添加primaryGroupID到返回属性列表。 - 通过
primaryGroupID查询主组:使用获取到的ID,搜索primaryGroupToken匹配的组。 - 合并主组到结果列表:将主组名称添加到已获取的普通组列表中。
修改后的完整代码
package main import ( "encoding/json" "errors" "fmt" "github.com/go-ldap/ldap/v3" "github.com/techoner/gophp" "handlers" "log" "reflect" "strconv" "strings" ) func getUserGroups(administrator string) ([]string, error) { conn, err := connect(bindServer,BindPort) if err != nil { log.Printf("Failed to connect to AD/LDAP with error: %s", err) return nil, fmt.Errorf("Failed to connect to AD/LDAP with error: %s", err) } defer conn.Close() // 确保连接资源释放 errBind := conn.Bind(bindUser, bindPWD) if errBind != nil { if isLdapDebug { log.Printf("Failed to bind to AD/LDAP with error: %s", errBind) } return nil, fmt.Errorf("Failed to bind to AD/LDAP with error: %s", errBind) } // 1. 查询用户信息,包含dn和primaryGroupID searchRequest := ldap.NewSearchRequest( "DC=domain,DC=local", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=user)(sAMAccountName=%s))", administrator), []string{"dn", "primaryGroupID"}, // 添加primaryGroupID字段 nil, ) sr, err := conn.Search(searchRequest) if err != nil { return nil, err } if len(sr.Entries) != 1 { return nil, errors.New("User does not exist") } userEntry := sr.Entries[0] userdn := userEntry.DN primaryGroupID := userEntry.GetAttributeValue("primaryGroupID") log.Printf("USER DN IS =%s, PRIMARY GROUP ID=%s", userdn, primaryGroupID) // 2. 查询普通组(通过member属性) searchRequest = ldap.NewSearchRequest( "DC=domain,DC=local", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=group)(member=%s))", ldap.EscapeFilter(userdn)), // 转义DN避免注入风险 []string{"cn"}, nil, ) sr, err = conn.Search(searchRequest) if err != nil { return nil, err } groups := []string{} for _, entry := range sr.Entries { groups = append(groups, entry.GetAttributeValue("cn")) } // 3. 查询主组(通过primaryGroupToken匹配) if primaryGroupID != "" { searchRequest = ldap.NewSearchRequest( "DC=domain,DC=local", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=group)(primaryGroupToken=%s))", primaryGroupID), []string{"cn"}, nil, ) sr, err = conn.Search(searchRequest) if err != nil { log.Printf("Warning: Failed to fetch primary group: %s", err) } else if len(sr.Entries) > 0 { groups = append(groups, sr.Entries[0].GetAttributeValue("cn")) } } return groups, nil } // main函数示例调用 func main() { groups, err := getUserGroups("administrator") if err != nil { log.Fatal(err) } fmt.Println(groups) }
关键说明
- 主组关联逻辑:用户的
primaryGroupID与对应组的primaryGroupToken值完全一致,这是AD识别主组的核心关联方式。 - 安全处理:使用
ldap.EscapeFilter()转义用户DN,避免LDAP注入风险。 - 资源管理:添加
defer conn.Close()确保LDAP连接在函数结束后被正确关闭。
内容的提问来源于stack exchange,提问作者André Bolinhas
相关产品推荐
相关产品推荐

