LDAP修改密码报错:协议错误(未知扩展请求OID)求助
LDAP修改Active Directory密码报错问题解决
问题重现
使用LDAP V3通过PasswordModifyRequest传递旧密码、新密码及域BaseDN尝试修改密码时,出现以下错误:
LDAP Result Code 2 "Protocol Error": 0000203D: LdapErr: DSID-0C091137, comment: Unknown extended request OID, data 0, v3839
相关代码:
baseDN := "dc=" + strings.Replace(ldap.Domain, ".", ",dc=", -1) pass := ldap2.PasswordModifyRequest{ UserIdentity: baseDN , OldPassword: OldPassword, NewPassword: NewPassword, } // Send the ModifyRequest to the server res, err := li.Conn.PasswordModify(&pass) if err != nil { fmt.Printf("Error changing the password: %s\n", err) return err }
问题原因
- Active Directory不支持PasswordModify扩展操作:该操作基于RFC 3062定义,但AD并未实现这个LDAP扩展,这是报错"Unknown extended request OID"的核心原因。
- 参数错误:代码中
UserIdentity传入的是域的BaseDN(如dc=example,dc=com),但即使AD支持该扩展,这里也应该传入目标用户的完整LDAP DN,而非域根DN。
正确解决方案
修改AD用户密码必须使用LDAP标准的Modify操作来更新unicodePwd属性,同时满足以下前提:
- 连接必须加密:使用LDAPS(636端口)或StartTLS升级连接,AD禁止明文连接修改密码。
- 密码格式要求:需将密码转换为UTF-16LE编码,并用双引号包裹。
- 执行操作的账号需拥有修改目标用户密码的权限。
示例代码
密码修改核心逻辑
import ( "encoding/utf16" "fmt" "github.com/go-ldap/ldap/v3" ) func changeADPassword(conn *ldap.Conn, userDN, oldPassword, newPassword string) error { // 处理新密码:双引号包裹 + UTF-16LE编码 quotedPwd := fmt.Sprintf(`"%s"`, newPassword) utf16Encoded := utf16.Encode([]rune(quotedPwd)) var pwdBytes []byte for _, char := range utf16Encoded { pwdBytes = append(pwdBytes, byte(char&0xff)) pwdBytes = append(pwdBytes, byte(char>>8)) } // 创建修改请求,替换unicodePwd属性 modifyReq := ldap.NewModifyRequest(userDN, nil) modifyReq.Replace("unicodePwd", []string{string(pwdBytes)}) // 验证旧密码(可选,根据AD配置决定是否需要) if oldPassword != "" { if err := conn.Bind(userDN, oldPassword); err != nil { return fmt.Errorf("旧密码验证失败: %w", err) } } // 执行修改操作 if err := conn.Modify(modifyReq); err != nil { return fmt.Errorf("修改密码失败: %w", err) } return nil }
获取用户完整DN(通过域登录名)
如果不知道目标用户的完整LDAP DN,可以通过sAMAccountName(域登录名)查询获取:
func getUserDN(conn *ldap.Conn, baseDN, sAMAccountName string) (string, error) { searchReq := ldap.NewSearchRequest( baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(sAMAccountName=%s)", sAMAccountName), []string{"dn"}, nil, ) searchResult, err := conn.Search(searchReq) if err != nil { return "", err } if len(searchResult.Entries) == 0 { return "", fmt.Errorf("未找到用户: %s", sAMAccountName) } return searchResult.Entries[0].DN, nil }
加密连接示例
import "crypto/tls" // LDAPS连接(636端口) conn, err := ldap.DialTLS("tcp", "ad.example.com:636", &tls.Config{ // 生产环境请勿跳过证书验证,需配置正确的根证书 InsecureSkipVerify: true, }) if err != nil { panic(err) } defer conn.Close() // 或使用StartTLS升级连接(389端口) // conn, err := ldap.Dial("tcp", "ad.example.com:389") // if err != nil { // panic(err) // } // defer conn.Close() // err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true}) // if err != nil { // panic(err) // }
内容的提问来源于stack exchange,提问作者Amanzhol Yergali
相关产品推荐
相关产品推荐

