You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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
}

注:原代码存在两处语法/逻辑问题,已在示例中修正:

  1. LDAP根节点需用字符串包裹
  2. 组查询时硬编码用户DN,改为使用变量userdn

当前输出

[Administrators Schema Admins Enterprise Admins Domain Admins Group Policy Creator Owners gteste1 gtest2]

输出中缺少用户的主组(如Domain Users)。

解决方法

Active Directory中,用户的主组不会出现在组的member属性里,而是通过用户的primaryGroupID属性与组的primaryGroupToken属性关联。要获取主组,需额外执行以下步骤:

  1. 查询用户时获取primaryGroupID:修改第一个LDAP搜索请求,添加primaryGroupID到返回属性列表。
  2. 通过primaryGroupID查询主组:使用获取到的ID,搜索primaryGroupToken匹配的组。
  3. 合并主组到结果列表:将主组名称添加到已获取的普通组列表中。

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 18:55:23