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

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
}

问题原因

  1. Active Directory不支持PasswordModify扩展操作:该操作基于RFC 3062定义,但AD并未实现这个LDAP扩展,这是报错"Unknown extended request OID"的核心原因。
  2. 参数错误:代码中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:35:19