欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

php ldap 创建 ad 帐户 merlin chatgpt 我自己亲自做的 有大用 有大大用 有大大大用

 我自己亲自做的

function _readExcelFileSaveAdUsers($file)
{
   global $user;
   $diqu = _my_node_jses_terminal_manager_diqu();
   $filepath = $file->uri;
   require("sites/all/libraries/PHPExcel/Classes/PHPExcel/IOFactory.php");
   // $filepath = iconv('UTF-8', 'GBK', $filepath);
   // Check prerequisites
   if (!file_exists($filepath)) {
       exit("not found $filepath \n");
   }

   // $filepath = "sites/default/files/upload_assets/test_ok2-----ok.xlsx";
   $filepath =  str_replace('public://upload_assets', 'sites/default/files/upload_assets', $filepath);
   $reader = PHPExcel_IOFactory::createReaderForFile($filepath); //设置以Excel5格式(Excel97-2003工作簿)
   $PHPExcel = $reader->load($filepath); // 载入excel文件

   $sheet = $PHPExcel->getActiveSheet();  //读取第一个工作表

   $highestRow = $sheet->getHighestRow(); // 取得总行数

   $headA1 = _removeCnEnSpace($sheet->getCell('A1')->getValue());
   $headB1 = _removeCnEnSpace($sheet->getCell('B1')->getValue());
   $headC1 = _removeCnEnSpace($sheet->getCell('C1')->getValue());
   $headD1 = _removeCnEnSpace($sheet->getCell('D1')->getValue());
   $headE1 = _removeCnEnSpace($sheet->getCell('E1')->getValue());
   $headF1 = _removeCnEnSpace($sheet->getCell('F1')->getValue());

   // var_dump($headD1);exit;

   if (
       $headA1 != '姓名' || $headB1 != '工号' || $headC1 != '邮箱'
       || $headD1 != '初始密码' || $headE1 != '仲裁主管工号'
       || $headF1 != '组织路径'
   ) {
       drupal_set_message('是不是格式或文件头部有错误,请看示例', 'error');
       return;
       //  $redirect = 'assets_save';
       //  return $redirect;
   }


   /** 循环读取每个单元格的数据 */
   $dataset = array();

   for ($row = 2; $row <= $highestRow; $row++) { //行数是以第1行开始
       if(empty(_removeCnEnSpace($sheet->getCell('A' . $row)->getValue()))){
           continue;
       }
       if(empty(_removeCnEnSpace($sheet->getCell('B' . $row)->getValue()))){
           continue;
       }

       $dataset[$row - 2]['xingming'] = _removeCnEnSpace($sheet->getCell('A' . $row)->getValue());
       $dataset[$row - 2]['gh'] = _removeCnEnSpace($sheet->getCell('B' . $row)->getValue());
       $dataset[$row - 2]['mail'] = _removeCnEnSpace($sheet->getCell('C' . $row)->getValue());
       $dataset[$row - 2]['password'] = _removeCnEnSpace($sheet->getCell('D' . $row)->getValue());
       $dataset[$row - 2]['manager_gh'] = _removeCnEnSpace($sheet->getCell('E' . $row)->getValue());
       //如果全称是 全称: CN=牛丽倩(15405430),OU=cccccccc-site,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn  ,那么 zhuzhipath 就是 OU=cccccccc-site,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn
       $dataset[$row - 2]['zhuzhipath'] = _removeCnEnSpace($sheet->getCell('F' . $row)->getValue());
       empty($dataset[$row - 2]['zhuzhipath']) && $dataset[$row - 2]['zhuzhipath'] = "OU=cccccccc-site,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn";

   }

   // 获取当前时间加 60 天的 Unix 时间戳
   $expireTimestamp = strtotime('+60 days');
// 将 Unix 时间戳转换为 Windows 文件时间
   $windowsEpoch = 11644473600; // Unix 时间戳(1601-01-01 00:00:00 UTC)
   $accountExpires = ($expireTimestamp + $windowsEpoch) * 10000000;
   foreach ($dataset as $key => $value) {
       $dataset[$key]['ad_person_dn'] = "CN=".$value['xingming']."(".$value['gh'].")".",".$value['zhuzhipath'];

       $password = $value['password'];

       $display_name = str_replace('.',' ',substr($value['mail'],0,strrpos($value['mail'],'@')))."(".$value['xingming'].")";
       $dataset[$key]['ad_user_property'] = [
           "objectClass"     => array("top", "person", "organizationalPerson", "user"),
           "samAccountName" => $value['gh'], // SAM 帐户名
           "userPrincipalName" => $value['gh'].'@aaaaaaaa.com.cn', // 用户主体名称
           "mail" => $value['mail'], // 用户主体名称
           "unicodePwd"      =>  iconv("UTF-8", "UTF-16LE", '"'.$password.'"'), // 密码必须是 UTF-16LE 编码
           "givenName" => $value['xingming'], // 名字
           "sn" => $value['xingming'], // 姓氏
           "displayName" => $display_name, // 显示名称
           "accountExpires" => $accountExpires, // 帐户60天永不过期
           "userAccountControl" => "512", // 启用帐户
//            "cn" => $value['xingming'], // 通用名称,,,为什么有了cn,会报错 Invalid DN syntax ,,,00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of
       ];
   }


   foreach($dataset as $key=>$value)
   {
       $bool = _create_ad_user($value['ad_person_dn'], $value['ad_user_property'],$diqu);
       if($bool){
           drupal_set_message($value['xingming'].'  '.$value['gh'].' '.'创建成功');

           $manager_ad_info_parter = _get_ad_info_from_ad($value['manager_gh'], array('dn','displayname'), $diqu);
           $ad_manager_dn = $manager_ad_info_parter['dn'];//主管帐号的 dn
           if($value['gh'] == $value['manager_gh']){
               drupal_set_message("自己帐号 {$value['gh']} 的主管不能是自己 ",'warning');
               continue;
           }
           $bool = _my_ldap_replace_ad_manager($value['ad_person_dn'],$ad_manager_dn,$diqu);
           if($bool){
           }else{
               drupal_set_message("自己帐号 {$value['gh']} 的审核人改为  {$value['manager_gh']} 失败",'error');
           }

       }else{
           drupal_set_message($value['xingming'].'  '.$value['gh'].' '.'创建失败');
       }
   }

   return true;
}







//创建 AD 用户
function _create_ad_user($ad_person_dn, $ad_user_property = array('*'), $diqu = 'js')
{

   
$ad_connect_infos = _my_get_ad_connect_infos();
   
$diqu = _my_node_jses_terminal_manager_diqu();
   
$ad_user = $ad_connect_infos[$diqu]['ad_user']; //域用户名 包含域名
   
$ad_pwd = $ad_connect_infos[$diqu]['ad_pwd'];
   
$host_ldaps = $ad_connect_infos[$diqu]['host_ldaps'];
//    $conn = ldap_connect($host, $port); // 不加密的389 端口 不要写成ldap_connect($host.':'.$port)的形式
   
$conn = ldap_connect($host_ldaps); // 加密安全的 636 要写成ldap_connect($host.':'.$port)的形式
   
if ($conn)
   {
       
//设置参数
       
ldap_set_option($conn, LDAP_OPT_DEBUG_LEVEL, 7);
       
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); //声明使用版本3
       
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0); // Binding to ldap server
       
ldap_set_option($conn, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
       
$bd = ldap_bind($conn, $ad_user, $ad_pwd);
       
if ($bd)
       {
         
//这里  dn 就是  distinguishedname ,就是尊贵的与别人可以区分开来的长名字
           
$status = ldap_add($conn, $ad_person_dn, $ad_user_property);
           
if ($status)
           {
               
$infos = true;
           
}
           
else
           
{
//                var_dump($ad_person_dn);
//                var_dump($ad_user_property);
//                var_dump(ldap_error($conn));
//
//                var_dump(ldap_errno($conn));
//                ldap_get_option($conn, LDAP_OPT_ERROR_STRING, $extendedError);
//                var_dump($extendedError);
               
$infos = false;

           
}
       }
       
else
       
{
           
$infos = false;
           
//   drupal_set_message('修LDAP 绑定失败', 'error');
       
}
   }
   
else
   
{
       
$infos = false;
//          drupal_set_message('LDAP 连接失败', 'warning');
   
}

   
ldap_close($conn);
   
return $infos;
}





function _get_ad_info_from_ad($gh = '', $ad_user_property = array('*'), $diqu = 'js')
{
   
$ad_connect_infos = _my_get_ad_connect_infos();
   
$ad_user = $ad_connect_infos[$diqu]['ad_user'];  //域用户名 包含域名
   
$ad_pwd = $ad_connect_infos[$diqu]['ad_pwd'];
   
$host = $ad_connect_infos[$diqu]['host'];
   
$port = $ad_connect_infos[$diqu]['port'];//一般都是389
   
$conn = ldap_connect($host);
//    $conn = ldap_connect($host, $port); //不要写成ldap_connect($host.':'.$port)的形式
   
if ($conn) {
       
//设置参数
       
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); //声明使用版本3
       
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0); // Binding to ldap server
       
$bd = ldap_bind($conn, $ad_user, $ad_pwd);

       
$basedn = $ad_connect_infos[$diqu]['basedn'];
       
if ($_GET['q'] == 'query_ad_info_ext' && $diqu = 'js') {//这是一种范围更大的 basedn
           
$basedn = 'dc=aaaaaaaa,dc=com,dc=cn';
       }
       
else if($_GET['q'] == 'checking_ad_pwd' && $diqu = 'js'){//检查密码的时候,也检查所有的 范围更大的 basedn 吧
           
$basedn = 'dc=aaaaaaaa,dc=com,dc=cn';
       }

       
// $filter = "(objectClass=*)";//选择器
       // $filter = "(&(sAMAccountName=史平忠))";//选择器
       
$filter = "SamAccountName=" . $gh; //根据工号 比如 16666739
       //$filter="(|(sn=史平忠*)(givenname=史平忠*))";
//    $justthese = array('*'); //选择要获取的用户属性
       
$justthese = $ad_user_property; //选择要获取的用户属性

       //$sr=ldap_search($conn, $basedn,$filter );
       
$sr = ldap_search($conn, $basedn, $filter, $justthese);
       
$infos = ldap_get_entries($conn, $sr);

       
$ad_info = $infos[0];


       
if ($bd) {
           
//drupal_set_message('LDAP 连接成功'); //相当于登录成功
       
} else {
           
$ad_info = false;
           drupal_set_message(
'LDAP 连接失败', 'warning');
       }
   }
else {
       
$ad_info = false;
       drupal_set_message(
'无法连接到AD域服务器', 'error');
   }
   
ldap_close($conn);
   
if (empty($ad_info)) {//这就是为了昆山的与嘉善的工号相同时进行的处理
       
if ($diqu == 'ks' && strlen($gh) == 9 && _startWith($gh, "C")) {
           
return _get_ad_info_from_ad(_make_ks_gh_reduce_C($gh), $ad_user_property, $diqu);
       }
   }

   
return $ad_info;
}







//给用户换领导
//这里  $ad_person_dn 就是   普通用户的 distinguishedname ,就是尊贵的与别人可以区分开来的长名字
//这里  $ad_manager_dn 就是  领导的 distinguishedname ,就是尊贵的与别人可以区分开来的长名字
function _my_ldap_replace_ad_manager($ad_person_dn, $ad_manager_dn, $diqu = 'js')
{
   
$ad_connect_infos = _my_get_ad_connect_infos();
   
$diqu = _my_node_jses_terminal_manager_diqu();
   
$ad_user = $ad_connect_infos[$diqu]['ad_user']; //域用户名 包含域名
   
$ad_pwd = $ad_connect_infos[$diqu]['ad_pwd'];
   
$host = $ad_connect_infos[$diqu]['host'];
   
$port = $ad_connect_infos[$diqu]['port'];//一般都是389

   
$conn = ldap_connect($host, $port); //不要写成ldap_connect($host.':'.$port)的形式
   
if ($conn)
   {
       
//设置参数
       
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); //声明使用版本3
       
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0); // Binding to ldap server
       
$bd = ldap_bind($conn, $ad_user, $ad_pwd);
       
if ($bd)
       {
           
//drupal_set_message('LDAP 连接成功'); //相当于登录成功
//这里  dn 就是  distinguishedname ,就是尊贵的与别人可以区分开来的长名字
           
$status = ldap_mod_replace($conn, $ad_person_dn, array('manager' => $ad_manager_dn));
           
if ($status)
           {
               
$infos = true;

           
}
           
else
           
{
               
$infos = false;
           
}
       }
       
else
       
{
           
$infos = false;
           
//   drupal_set_message('修LDAP 绑定失败', 'error');
       
}
   }
   
else
   
{
       
$infos = false;
//          drupal_set_message('LDAP 连接失败', 'warning');
   
}


   
ldap_close($conn);
   
return $infos;


}






php ldap 创建 ad 帐户

要使用 PHP 和 LDAP 创建 Active Directory (AD) 帐户,你需要连接到 AD 服务器,使用具有适当权限的凭据进行绑定,然后使用所需的属性添加用户帐户 13

以下是分步指南:

  1. 准备工作:

  • 确保你有一个 PHP 环境 1

  • 确保 PHP LDAP 扩展已安装并启用 13

  • 拥有对 Active Directory 服务器的访问权限 1

  • 安装 LDAP 扩展(如果尚未安装):

    • 对于 Debian/Ubuntu: sudo apt-get install php-ldap 3

    • 对于 CentOS/RHEL: sudo yum install php-ldap 3

    • 安装后重启你的 Web 服务器以启用该扩展 3

  • 建立 LDAP 连接:

    php                
    $ldap_host = "ldap://your-ad-server.com";
    $ldap_port = 389;  # 因为是创建,是增加或修改数据 不要用不加密的389,要用 ldaps,即 端口是加密的 636
    $ldap_conn = ldap_connect($ldap_host, $ldap_port) or die("无法连接到 LDAP 服务器。");
    
    ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
    
           
    • 使用 ldap_connect() 连接到 AD 服务器 13

    • 指定 AD 服务器地址和端口 13。对于安全连接,使用 LDAPS (ldaps://your-ad-server.com13

    • 使用 ldap_set_option() 设置 LDAP 协议版本 2

  • 绑定到 LDAP 服务器:

    php                
    $ldap_user   = "cn=admin,dc=your,dc=com";
    $ldap_pass   = "admin-password";
    $ldap_bind   = ldap_bind($ldap_conn, $ldap_user, $ldap_pass);
    
    if (!$ldap_bind) {
        die("LDAP 绑定失败...");
    }
    
           
    • 使用 ldap_bind() 以及具有足够权限在指定的 OU(组织单元)中创建用户帐户的帐户的 DN(专有名称)和密码 13

  • 准备用户属性:

    php                
    $user_data = array(
        "objectClass"     => array("top", "person", "organizationalPerson", "user"),
        "samaccountname"  => "newuser",
        "userPrincipalName" => "newuser@yourdomain.com",
        "unicodePwd"      =>  iconv("UTF-8", "UTF-16LE", "P@ssword123"), // 密码必须是 UTF-16LE 编码
        "givenName"       => "New",
        "sn"              => "User",
        "displayName"     => "New User"
    );
    
           
    • 重要提示: unicodePwd 属性需要采用 UTF-16LE 编码 1

    • 默认情况下,该帐户将被禁用。要启用它,请将 userAccountControl 属性设置为 512 1。要创建一个启用的帐户,请将 userAccountControl 设置为 512

    • 定义一个数组,其中包含新用户帐户的属性。必需的属性通常包括 objectClasssamaccountnameuserPrincipalName 和 unicodePwd 13

  • 添加新用户帐户:

    php                
    $dn = "cn=New User,ou=YourOU,dc=your,dc=com"; // 调整 OU
    $result = ldap_add($ldap_conn, $dn, $user_data);
    
    if ($result) {
        echo "用户创建成功";
    } else {
        echo "创建用户失败: " . ldap_error($ldap_conn);
    }
    
           
    • 使用 ldap_add() 在指定的 OU 中创建新的用户帐户。新用户的 DN 应该遵循 cn=New User,ou=YourOU,dc=your,dc=com 格式。

  • 关闭 LDAP 连接:

    php                
    ldap_close($ldap_conn);
    
           
  • 重要注意事项:

    • 安全性: 使用 LDAPS 加密 LDAP 连接 13

    • 错误处理: 实施适当的错误处理以捕获并记录该过程中的任何问题 3

    • 密码复杂度: 确保密码符合你的 Active Directory 的复杂度要求 1

    • 权限: 用于绑定的帐户需要具有在目标 OU 中创建用户帐户的相应权限 13

    • 编码: 密切注意字符编码,尤其是密码 1

    通过执行这些步骤,你可以使用 PHP 和 LDAP 协议创建 AD 用户帐户 13。 请记住安全地处理敏感信息并实施适当的错误处理 13



    把 "unicodePwd" 拿掉 可以正常创建有户,但是 如果有 "unicodePwd" 会报错 Server is unwilling to perform 0000001F: SvcErr: DSID-031A126A, problem 5003 (WILL_NOT_PERFORM), data 0

    当你在 PHP LDAP 创建脚本中包含 "unicodePwd" 属性时,出现

    text        
    Server is unwilling to perform 0000001F: SvcErr: DSID-031A126A, problem 5003 (WILL_NOT_PERFORM), data 0

    错误,这表明在用户创建期间设置密码时存在问题 1。 这通常与密码复杂度要求或用于设置密码的方法有关 13


    以下是潜在原因和解决方案的细分:

    • 密码复杂度策略: Active Directory 强制执行密码复杂度策略。 如果提供的密码不符合这些要求(长度、字符类型等),服务器将拒绝该操作 1

      • 解决方案: 确保你设置的密码符合域的密码策略 1。 这可能需要在域安全策略中禁用密码复杂度要求以进行测试,但不建议在生产环境中使用 1

    • 不正确的 unicodePwd 格式: unicodePwd 属性需要特定的格式 - 用引号括起来的 UTF-16LE 编码 1

      php                
      "unicodePwd" => iconv("UTF-8", "UTF-16LE", '"P@ssword123"') ;     # 切记,密码是这样设置的 
      
             
      • 解决方案: 确保密码已正确转换为 UTF-16LE 并用引号括起来 1

    • 权限不足: 你用于绑定到 LDAP 服务器的帐户可能没有设置新用户帐户密码的必要权限 1

      • 解决方案: 使用具有足够权限的帐户(例如域管理员帐户)进行 LDAP 绑定操作 1

    • SSL/TLS 要求: 修改 unicodePwd 属性通常需要安全连接 (LDAPS) 5

      • 解决方案: 确保你使用 LDAPS(端口 636)而不是 LDAP(端口 389)连接到 LDAP 服务器 5

    • 过早设置 userAccountControl 尝试在设置密码 同时 将 userAccountControl 属性设置为启用帐户有时会导致问题 47

      • 解决方案:  在最初设置 userAccountControl 的情况下创建用户帐户。 然后,在单独的 LDAP 修改操作中,启用该帐户 47

    这是一个演示创建用户  初始设置密码或启用帐户的示例,然后在以后修改帐户以设置密码并启用它:

    php        
    <?php
    
    // LDAP 连接详情
    $ldap_host = "ldap://your-ad-server.com";
    $ldap_port = 389;
    $ldap_dn = "cn=admin,dc=your,dc=com";
    $ldap_password = "admin-password";
    $base_dn = "dc=your,dc=com";
    $user_ou = "ou=users," . $base_dn;
    
    // 新用户详情
    $username = "testuser";
    $password = "P@ssword123";
    $display_name = "Test User";
    $user_principal_name = $username . "@yourdomain.com";
    
    // 连接到 LDAP
    $ldap_conn = ldap_connect($ldap_host, $ldap_port) or die("无法连接到 LDAP 服务器。");
    ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
    
    // 绑定到 LDAP
    $ldap_bind = ldap_bind($ldap_conn, $ldap_dn, $ldap_password);
    if (!$ldap_bind) {
        die("LDAP 绑定失败: " . ldap_error($ldap_conn));
    }
    
    // 1. 创建用户帐户(禁用,无密码)
    $user_dn = "cn=" . $display_name . "," . $user_ou;
    $user_data = array(
        "objectClass" => array("top", "person", "organizationalPerson", "user"),
        "samaccountname" => $username,
        "userPrincipalName" => $user_principal_name,
        "givenName" => "Test",
        "sn" => "User",
        "displayName" => $display_name
    );
    
    $result = ldap_add($ldap_conn, $user_dn, $user_data);
    if (!$result) {
        echo "创建用户失败: " . ldap_error($ldap_conn) . "\n";
        ldap_close($ldap_conn);
        exit;
    } else {
        echo "用户帐户已创建(已禁用)。\n";
    }
    
    // 2. 修改帐户以设置密码并启用它
    $new_password_utf16le = iconv("UTF-8", "UTF-16LE", '"' . $password . '"');
    
    $mod_data = array(
        "unicodePwd" => array($new_password_utf16le),
        "userAccountControl" => array(512) //普通帐户,已启用
    );
    //修改用户
    $result = ldap_modify($ldap_conn, $user_dn, $mod_data);
    
    if ($result) {
        echo "用户帐户已启用且密码已设置。\n";
    } else {
        echo "无法启用用户/设置密码: " . ldap_error($ldap_conn) . "\n";
    }
    
    ldap_close($ldap_conn);
    
    ?>
    

    主要改进和说明:

    • 两步过程: 该代码现在在一个步骤中创建用户(没有密码或启用它),然后 修改帐户以设置密码并启用它。 这是解决 WILL_NOT_PERFORM 错误的常见方法 47

    • 错误处理: 改进的错误消息,以帮助诊断问题。 在每次 LDAP 操作后使用 ldap_error() 以提供更具体的错误详细信息。

    • LDAP 关闭: 在所有操作完成后调用 ldap_close() 函数以关闭连接。

    • userAccountControl 值: 在第二步中,userAccountControl 属性设置为 512

    • 密码编码: 密码编码与以前相同,但在修改阶段应用。

    通过分离用户创建和密码设置步骤,你可以绕过一些触发 WILL_NOT_PERFORM 错误的常见问题 47。 如果你仍然 遇到问题,请仔细检查密码复杂度要求以及用于 LDAP 绑定的帐户的权限


    php ldap 创建 ad  帐户

    在 PHP 中通过 LDAP 创建 Active Directory (AD) 账户需要遵循以下步骤。请注意,操作前需确保已安装并启用 PHP 的 ldap 扩展。


    步骤 1:连接 AD 服务器

    使用 ldap_connect() 建立与 AD 服务器的连接,并通过管理员账号绑定。

    php
    复制
    $ldapServer = 'ldap://ad.example.com'; // AD 服务器地址
    $ldapPort = 389;                       // 默认 LDAP 端口
    $adminUser = 'cn=admin,dc=example,dc=com'; // 管理员 DN
    $adminPass = 'admin_password';         // 管理员密码
    
    // 连接 AD
    $ldapConn = ldap_connect($ldapServer, $ldapPort);
    if (!$ldapConn) {
        die("无法连接 AD 服务器");
    }
    
    // 设置协议版本(通常需要 LDAPv3)
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
    
    // 绑定管理员账号
    $bind = ldap_bind($ldapConn, $adminUser, $adminPass);
    if (!$bind) {
        die("绑定失败: " . ldap_error($ldapConn));
    }

    步骤 2:准备用户属性

    AD 用户条目需要包含必填属性,如 sAMAccountNameuserPrincipalName 等。注意密码需按 AD 要求编码。

    php
    复制
    $username = 'johndoe'; // 用户名(sAMAccountName)
    $userDn = 'cn=' . $username . ',ou=Users,dc=example,dc=com'; // 新用户的 DN
    
    // 用户属性数组
    $userInfo = [
        'objectClass' => ['top', 'person', 'organizationalPerson', 'user'],
        'cn' => $username,//不懂,为什么有了cn,会报错 Invalid DN syntax ,,,00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of
        'sAMAccountName' => $username,
        'userPrincipalName' => $username . '@example.com',
        'displayName' => 'John Doe',
        'givenName' => 'John',
        'sn' => 'Doe',
        'mail' => 'johndoe@example.com',
        'userAccountControl' => 512, // 512 表示启用账户(正常状态)
    ];
    
    // 设置密码(需 UTF-16LE 编码并用双引号包裹)
    $password = 'P@ssw0rd!';
    $encodedPassword = '"' . $password . '"';
    $userInfo['unicodePwd'] = iconv('UTF-8', 'UTF-16LE', $encodedPassword);

    步骤 3:创建用户条目

    使用 ldap_add() 将用户添加到 AD 中。

    php
    复制
    // 添加用户条目
    $result = ldap_add($ldapConn, $userDn, $userInfo);
    if (!$result) {
        die("创建用户失败: " . ldap_error($ldapConn));
    }
    
    echo "用户 $username 创建成功!";
    
    // 关闭连接
    ldap_unbind($ldapConn);

    关键注意事项

    1. LDAPS 加密
      生产环境建议使用 LDAPS (端口 636) 加密通信。需配置 ldap:// 为 ldaps:// 并确保服务器证书可信。

    2. 密码策略
      AD 密码需符合复杂度要求(如长度、大小写、符号)。示例中的 P@ssw0rd! 仅为演示,实际应使用安全密码。

    3. 错误处理
      检查每一步的返回值,使用 ldap_error() 捕获错误信息。

    4. 账户启用
      userAccountControl 的 512 表示启用账户。若设为 514 则为禁用状态。


    完整代码示例

    php
    复制
    <?php
    // AD 连接配置
    $ldapServer = 'ldap://ad.example.com';
    $ldapPort = 389;
    $adminUser = 'cn=admin,dc=example,dc=com';
    $adminPass = 'admin_password';
    
    // 连接 AD
    $ldapConn = ldap_connect($ldapServer, $ldapPort);
    if (!$ldapConn) die("连接失败");
    
    // 设置选项并绑定
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
    $bind = ldap_bind($ldapConn, $adminUser, $adminPass);
    if (!$bind) die("绑定失败: " . ldap_error($ldapConn));
    
    // 新用户信息
    $username = 'johndoe';
    $userDn = "cn=$username,ou=Users,dc=example,dc=com";
    $password = 'P@ssw0rd!';
    
    $userInfo = [
        'objectClass' => ['top', 'person', 'organizationalPerson', 'user'],
        'cn' => $username, //不懂,为什么有了cn,会报错 Invalid DN syntax ,,,00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of
        'sAMAccountName' => $username,
        'userPrincipalName' => "$username@example.com",
        'displayName' => 'John Doe',
        'givenName' => 'John',
        'sn' => 'Doe',
        'mail' => 'johndoe@example.com',
        'userAccountControl' => 512,
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', "\"$password\"")
    ];
    
    // 创建用户
    $result = ldap_add($ldapConn, $userDn, $userInfo);
    if (!$result) die("创建失败: " . ldap_error($ldapConn));
    
    echo "用户 $username 已成功创建!";
    ldap_unbind($ldapConn);
    ?>

    常见问题排查

    • 连接失败:检查防火墙、AD 服务器地址/端口。

    • 绑定失败:确认管理员 DN 和密码正确。

    • 属性错误:确保 objectClass 包含必要的类,属性符合 AD 架构。

    • 密码问题:确保密码编码正确且符合复杂度策略,使用 LDAPS 传输密码。

    建议在测试环境中验证代码后再部署到生产环境。




    ldap_add  会报错 Invalid DN syntax

    在使用 ldap_add 创建 AD 账户时遇到 “Invalid DN syntax” 错误,通常是因为提供的 Distinguished Name (DN) 不符合 LDAP 格式要求。以下是可能的原因和解决方案:


    1. DN 格式错误

    DN 必须遵循严格的层次结构,例如:
    cn=用户名,ou=组织单元,dc=域组件1,dc=域组件2
    常见格式错误包括:

    • 缺少层级分隔符(逗号 ,:例如 cn=userou=Users,dc=example,dc=com(缺少逗号)。

    • 层级顺序错误:例如 ou=Users,cn=user,dc=example,dc=comcn 应该在 ou 之后)。

    • 特殊字符未转义:如果用户名或 OU 名称包含 ,=+<>#; 或空格,必须用反斜杠 \ 转义。
      示例
      若用户名为 Doe, John,应写为 cn=Doe\, John,ou=Users,dc=example,dc=com


    2. OU 路径不存在

    如果指定的 ou=Users,dc=example,dc=com 在 AD 中不存在,操作会失败。
    解决方法

    1. 使用 LDAP 管理工具(如 ADUC、Apache Directory Studio) 确认 OU 路径存在。

    2. 如果 OU 不存在,先创建 OU 或调整 DN 到有效路径。


    3. 变量拼接错误

    在动态生成 DN 时,如果变量包含空格或特殊字符,需确保正确转义。
    错误示例(未处理特殊字符):

    php
    复制
    $username = "John Doe"; // 包含空格
    $userDn = "cn=$username,ou=Users,dc=example,dc=com"; 
    // 实际 DN 为 `cn=John Doe,ou=Users,dc=example,dc=com`(空格未转义)

    正确做法(转义特殊字符):

    php
    复制
    $username = "John Doe";
    // 转义空格(AD 通常允许空格,但某些场景需要转义)
    $escapedUsername = ldap_escape($username, "", LDAP_ESCAPE_DN);
    $userDn = "cn=$escapedUsername,ou=Users,dc=example,dc=com";

    4. 使用 ldap_escape() 函数处理动态内容

    PHP 的 ldap_escape() 函数可自动转义 DN 中的特殊字符:

    php
    复制
    $username = "Doe, John";
    $ou = "Sales+IT"; // 包含特殊字符 `+`
    
    // 转义用户名和 OU
    $escapedUsername = ldap_escape($username, "", LDAP_ESCAPE_DN);
    $escapedOU = ldap_escape($ou, "", LDAP_ESCAPE_DN);
    
    // 拼接 DN
    $userDn = "cn=$escapedUsername,ou=$escapedOU,dc=example,dc=com";

    5. 调试 DN 字符串

    在代码中打印生成的 DN,检查是否符合格式:

    php
    复制
    $userDn = "cn=$username,ou=Users,dc=example,dc=com";
    echo "尝试创建的 DN: " . $userDn; // 输出后检查格式

    完整修正示例

    php
    复制
    // 假设 $username 可能包含特殊字符
    $username = "John Doe (Admin)";
    $baseDn = "ou=Users,dc=example,dc=com";
    
    // 转义用户名中的特殊字符
    $escapedUsername = ldap_escape($username, "", LDAP_ESCAPE_DN);
    
    // 拼接 DN
    $userDn = "cn=$escapedUsername,$baseDn";
    
    // 检查 DN 格式
    var_dump($userDn); 
    // 正确示例:string(35) "cn=John Doe \28Admin\29,ou=Users,dc=example,dc=com"
    
    // 使用 ldap_add
    $result = ldap_add($ldapConn, $userDn, $userInfo);

    其他注意事项

    1. 检查管理员绑定 DN
      确保管理员账号的 DN 格式正确,例如 cn=admin,dc=example,dc=com,而非 admin@example.com(除非是 UPN 格式绑定)。

    2. 确认 OU 的 distinguishedName
      在 AD 中,OU 的 DN 可能包含其他属性(如 OU=Users,DC=example,DC=com),注意大小写是否匹配。

    3. 使用 LDAP 工具验证
      用 ldapsearch 或 GUI 工具直接尝试创建相同 DN,确认是否是代码问题:

      bash
      复制
      ldapadd -x -H ldap://ad.example.com -D "cn=admin,dc=example,dc=com" -W -f user.ldif
             

      user.ldif 内容:

      ldif
      复制
      dn: cn=testuser,ou=Users,dc=example,dc=com
      objectClass: user
      sAMAccountName: testuser
             

    通过逐步检查 DN 格式、转义特殊字符并验证路径有效性,即可解决 “Invalid DN syntax” 错误



    ldap_error($ldapConn)  太简单,,,ldap_add 如何获得 详细的报错信息

    在 PHP 中通过 ldap_add 创建 AD 用户时,如果 ldap_error($ldapConn) 返回的报错信息过于简略(例如仅提示 Invalid DN syntax),可以通过以下方法获取更详细的错误信息,帮助精准定位问题:


    1. 启用 LDAP 调试日志

    PHP 的 LDAP 扩展支持设置调试级别,通过 LDAP_OPT_DEBUG_LEVEL 记录详细的通信日志。
    步骤

    1. 在代码中启用调试模式:

      php
      复制
      // 设置调试级别(需在绑定前设置)
      ldap_set_option($ldapConn, LDAP_OPT_DEBUG_LEVEL, 7);
             
    2. 运行代码时,调试信息会直接输出到 PHP 错误日志 或 Web 服务器日志(如 Apache 的 error_log)。

    示例输出

    log
    复制
    ldap_create
    ldap_bind_s
    ldap_sasl_bind_s
    ldap_send_initial_request
    ldap_new_connection
    ...
    ldap_write: send request with msgid 1
    ldap_result: msgid 1, all 1
    ldap_parse_result
    ldap_err2string: Invalid DN syntax (34)

    2. 获取 LDAP 错误代码和扩展信息

    除了 ldap_error(),使用 ldap_errno() 获取错误代码,并结合 ldap_get_option() 提取服务器返回的扩展错误信息:

    php
    复制
    // 执行 ldap_add 后检查错误
    if (!ldap_add($ldapConn, $userDn, $userInfo)) {
        $errorCode = ldap_errno($ldapConn);
        $errorMsg = ldap_error($ldapConn);
        
        // 尝试获取扩展错误信息(AD 服务器可能返回更多细节)
        ldap_get_option($ldapConn, LDAP_OPT_ERROR_STRING, $extendedError);
        
        die("错误代码: $errorCode\n错误信息: $errorMsg\n扩展信息: $extendedError");
    }

    输出示例

    复制
    错误代码: 34
    错误信息: Invalid DN syntax
    扩展信息: 0000208F: AtrErr: DSID-030F1141, #1:
        0: 0000208F: DSID-030F1141, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att: unicodePwd (13)

    3. 通过 AD 服务器日志排查

    如果扩展信息仍不清晰,可直接检查 AD 服务器的事件日志

    1. 打开 Windows 服务器的 事件查看器(Event Viewer)。

    2. 导航到 Applications and Services Logs > Directory Service

    3. 查找与 LDAP 操作相关的错误事件,通常包含详细的失败原因(如属性约束、权限问题等)。


    4. 使用 ldap_explode_dn() 分解 DN

    如果怀疑 DN 格式错误,可以用 ldap_explode_dn() 分解并验证 DN 的每一部分:

    php
    复制
    $userDn = 'cn=johndoe,ou=Users,dc=example,dc=com';
    $dnParts = ldap_explode_dn($userDn, 0); // 第二个参数 0 表示不解析属性值
    
    if ($dnParts === false) {
        die("DN 格式无效");
    }
    
    print_r($dnParts);

    输出

    php
    复制
    Array (
        [0] => cn=johndoe
        [1] => ou=Users
        [2] => dc=example
        [3] => dc=com
        [count] => 4
    )

    5. 手动验证 DN 和属性

    通过 LDAP 命令行工具 或 GUI 工具 直接操作 AD,验证 DN 和属性的有效性:

    1. 使用 ldapadd 命令

      bash
      复制
      ldapadd -x -H ldap://ad.example.com -D "cn=admin,dc=example,dc=com" -W -f user.ldif
             

      user.ldif 文件内容:

      ldif
      复制
      dn: cn=testuser,ou=Users,dc=example,dc=com
      objectClass: user
      sAMAccountName: testuser
      userPrincipalName: testuser@example.com
      unicodePwd:: "IgBQAEAAcwBzAHcAMAByAGQAIQAiAA==" # Base64 编码的密码
             
    2. 使用 Apache Directory Studio
      通过 GUI 工具直接创建条目,观察是否报错。


    6. 常见错误场景及解决方案

    (1) 属性值不符合 AD 约束

    • 错误示例
      CONSTRAINT_ATT_TYPE (unicodePwd) 表示密码不符合策略。

    • 解决方案
      确保密码编码为 UTF-16LE 并用双引号包裹:

      php
      复制
      $password = 'P@ssw0rd!';
      $encodedPassword = '"' . $password . '"';
      $userInfo['unicodePwd'] = iconv('UTF-8', 'UTF-16LE', $encodedPassword);
             

    (2) 权限不足

    • 错误示例
      Insufficient access rights 或 00002098: SecErr: DSID-031A11C3

    • 解决方案
      确保绑定用户(如管理员)有权限在目标 OU 下创建对象。

    (3) 必填属性缺失

    • 错误示例
      OBJECT_CLASS_VIOLATION 表示缺少必填属性。

    • 解决方案
      检查 objectClass 是否包含必要类(如 user),并补全必填属性(如 sAMAccountName)。


    完整调试代码示例

    php
    复制
    <?php
    $ldapServer = 'ldap://ad.example.com';
    $ldapPort = 389;
    $adminUser = 'cn=admin,dc=example,dc=com';
    $adminPass = 'admin_password';
    
    // 连接并启用调试
    $ldapConn = ldap_connect($ldapServer, $ldapPort);
    ldap_set_option($ldapConn, LDAP_OPT_DEBUG_LEVEL, 7);
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
    
    if (!ldap_bind($ldapConn, $adminUser, $adminPass)) {
        die("绑定失败: " . ldap_error($ldapConn));
    }
    
    // 准备用户数据
    $username = 'johndoe';
    $userDn = "cn=$username,ou=Users,dc=example,dc=com";
    $password = 'P@ssw0rd!';
    
    $userInfo = [
        'objectClass' => ['top', 'person', 'organizationalPerson', 'user'],
        'cn' => $username,   //不懂,为什么有了cn,会报错 Invalid DN syntax ,,,00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of
        'sAMAccountName' => $username,
        'userPrincipalName' => "$username@example.com",
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', "\"$password\""),
        'userAccountControl' => 512,
    ];
    
    // 添加用户并捕获错误
    if (!ldap_add($ldapConn, $userDn, $userInfo)) {
        $errorCode = ldap_errno($ldapConn);
        $errorMsg = ldap_error($ldapConn);
        ldap_get_option($ldapConn, LDAP_OPT_ERROR_STRING, $extendedError);
        
        echo "错误代码: $errorCode\n";
        echo "错误信息: $errorMsg\n";
        echo "扩展信息: $extendedError\n";
        
        // 分解 DN 检查格式
        $dnParts = ldap_explode_dn($userDn, 0);
        if ($dnParts === false) {'
            echo "DN 格式无效!";
        } else {
            echo "DN 分解结果: " . print_r($dnParts, true);
        }
    } else {
        echo "用户创建成功!";
    }
    
    ldap_unbind($ldapConn);
    ?>

    总结

    通过 启用调试日志获取扩展错误信息 和 直接操作 AD 验证,可以精准定位 ldap_add 失败的原因。若问题仍无法解决,建议结合 AD 服务器日志和网络抓包(如 Wireshark)进一步分析。



    ldap_errno($conn) 为 34 ,,,"00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of: 'CN=test6,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn'

    我自己亲自做的  是因为 上面的 $userInfo 数组中 有一个键是 cn ,,把cn 键删掉就可以了.所以关于这一段内容可以不看了  


    根据错误信息 ldap_errno=34 和扩展错误 00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX),结合你的示例 DN CN=test6,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn,问题可能由以下原因导致:


    1. DN 中的特殊字符未正确转义

    AD 要求 DN 中的某些字符(如中文、空格、逗号等)必须转义
    虽然中文本身不需要转义,但如果 OU 名称(如 bbbbbb集团)在 AD 中的实际名称包含 隐藏的特殊字符(如 ,=# 等),需用反斜杠 \ 转义。

    错误示例

    假设 AD 中 OU 的实际名称包含逗号(如 bbbbbb,集团),但代码中未转义:

    php
    复制
    $ou = "bbbbbb,集团"; // 未转义逗号
    $userDn = "CN=test6,OU=$ou,DC=aaaaaaaa,DC=com,DC=cn";
    // 实际 DN 为 `CN=test6,OU=bbbbbb,集团,DC=...`,逗号被解析为层级分隔符,导致语法错误

    解决方案

    使用 ldap_escape() 对 OU 和 CN 的名称进行转义:

    php
    复制
    $ou = "bbbbbb集团";
    // 转义 OU 名称中的特殊字符
    $escapedOU = ldap_escape($ou, "", LDAP_ESCAPE_DN); 
    $userDn = "CN=test6,OU=$escapedOU,DC=aaaaaaaa,DC=com,DC=cn";

    2. OU 路径不存在

    错误信息中的 NameErr: DSID-03050EC7 表示 AD 服务器找不到指定的 OU 路径 OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn

    验证步骤

    1. 使用 ldapsearch 检查 OU 是否存在

      bash
      复制
      ldapsearch -x -H ldap://AD服务器地址 -D "管理员DN" -W -b "DC=aaaaaaaa,DC=com,DC=cn" "(ou=bbbbbb集团)"
             

      如果无返回结果,说明 OU 不存在。

    2. 通过 AD 管理工具(如 Active Directory Users and Computers)确认 OU 路径

    解决方案

    • 创建缺失的 OU
      通过代码或管理工具先创建 OU。

    • 调整 DN 到有效路径
      确保 OU=bbbbbb集团 存在于 DC=aaaaaaaa,DC=com,DC=cn 下。


    3. 域名组件(DC)顺序错误

    AD 的域名组件(DC)按层级从高到低排列,例如:
    DC=com,DC=cn 表示顶级域为 com.cn,子域为 aaaaaaaa,完整域名为 aaaaaaaa.com.cn
    若实际域名是 aaaaaaaa.com,则 DC 应为 DC=aaaaaaaa,DC=com,而非 DC=aaaaaaaa,DC=com,DC=cn

    检查域名的正确性

    1. 通过 AD 服务器查询根域

      bash
      复制
      ldapsearch -x -H ldap://AD服务器地址 -D "管理员DN" -W -b "" -s base "(objectClass=*)" defaultNamingContext
             

      返回的 defaultNamingContext 是 AD 的根域,例如 DC=aaaaaaaa,DC=com,DC=cn

    2. 确保代码中的 DC 顺序与根域一致


    4. 编码问题

    如果 AD 服务器或 PHP 环境未正确处理 UTF-8 编码,中文字符可能被错误解析。

    解决方案

    1. 设置 LDAP 连接的字符集

      php
      复制
      ldap_set_option($ldapConn, LDAP_OPT_ENCODING, LDAP_OPT_OFF);
             
    2. 确保 PHP 文件保存为 UTF-8 无 BOM 格式


    完整修正代码示例

    php
    复制
    // 连接 AD 服务器
    $ldapConn = ldap_connect("ldap://ad.aaaaaaaa.com.cn");
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
    ldap_set_option($ldapConn, LDAP_OPT_ENCODING, LDAP_OPT_OFF); // 处理中文字符
    
    // 绑定管理员
    ldap_bind($ldapConn, "cn=admin,dc=aaaaaaaa,dc=com,dc=cn", "password");
    
    // 准备 DN(转义 OU 和 CN)
    $ou = "bbbbbb集团";
    $username = "test6";
    
    $escapedOU = ldap_escape($ou, "", LDAP_ESCAPE_DN);
    $escapedCN = ldap_escape($username, "", LDAP_ESCAPE_DN);
    
    $userDn = "CN=$escapedCN,OU=$escapedOU,DC=aaaaaaaa,DC=com,DC=cn";
    
    // 检查 DN 格式
    var_dump($userDn); // 输出:string(59) "CN=test6,OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn"
    
    // 添加用户
    $userInfo = [
        'objectClass' => ['user'],
        'sAMAccountName' => $username,
        'userPrincipalName' => "$username@aaaaaaaa.com.cn",
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', '"P@ssw0rd"'),
        'userAccountControl' => 512
    ];
    
    if (!ldap_add($ldapConn, $userDn, $userInfo)) {
        $errorCode = ldap_errno($ldapConn);
        $errorMsg = ldap_error($ldapConn);
        ldap_get_option($ldapConn, LDAP_OPT_ERROR_STRING, $extendedError);
        die("错误代码: $errorCode\n错误信息: $errorMsg\n扩展信息: $extendedError");
    }
    
    echo "用户创建成功!";

    关键排查步骤

    1. 使用 ldap_escape() 转义所有动态内容
      确保 OU、CN 等名称中的特殊字符被正确处理。

    2. 验证 OU 路径
      通过 ldapsearch 或 AD 管理工具确认路径存在。

    3. 检查域名层级
      确保 DC 顺序与 AD 实际域名一致。

    4. 查看 AD 服务器日志
      在 Windows 事件查看器中搜索与 LDAP 操作相关的错误事件。

    通过以上步骤,可解决 Invalid DN syntax 和 BAD_ATT_SYNTAX 问题。



    Server is unwilling to perform   错误代号 53 0000001F: SvcErr: DSID-031A126A, problem 5003 (WILL_NOT_PERFORM), data 0

    逐步解决方案

    遇到 Server is unwilling to perform(错误代码 53,扩展错误 0000001F: SvcErr: DSID-031A126A, problem 5003 (WILL_NOT_PERFORM),通常由以下原因导致:


    1. 密码策略不满足要求

    Active Directory 要求密码必须符合复杂度规则(如长度、大小写字母、数字、符号),且 unicodePwd 属性需正确编码为 UTF-16LE

    错误示例

    php
    复制
    $password = "123456"; // 密码太简单
    $userInfo['unicodePwd'] = iconv('UTF-8', 'UTF-16LE', $password); // 未包裹双引号

    解决方案

    • 使用符合复杂度的密码
      例如 P@ssw0rd2023!

    • 正确编码密码
      密码需用双引号包裹后转换为 UTF-16LE。

    php
    复制
    $password = 'P@ssw0rd2023!';
    $encodedPassword = '"' . $password . '"';
    $userInfo['unicodePwd'] = iconv('UTF-8', 'UTF-16LE', $encodedPassword);

    2. 权限不足

    绑定账户(如管理员)可能没有在目标 OU 下创建用户的权限。

    验证步骤

    1. 使用 AD 管理工具 确认绑定账户的权限:

    • 右键点击目标 OU(如 OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn)。

    • 选择 属性 > 安全 > 高级,检查账户是否有 “创建子对象” 权限。

  • 临时提升权限测试
    使用域管理员账户(如 administrator@aaaaaaaa.com.cn)进行绑定。

  • 修正代码(绑定管理员账户)

    php
    复制
    $adminUser = 'administrator@aaaaaaaa.com.cn'; // 使用 UPN 格式
    $adminPass = 'AdminPassword123!';
    
    $ldapConn = ldap_connect("ldap://ad.aaaaaaaa.com.cn");
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_bind($ldapConn, $adminUser, $adminPass);

    3. 未正确设置 userAccountControl

    如果账户需要启用,userAccountControl 必须设为 512;若还需密码永不过期,可设为 66048512 + 65536)。

    正确设置属性

    php
    复制
    $userInfo = [
        'objectClass' => ['top', 'person', 'organizationalPerson', 'user'],
        'sAMAccountName' => $username,
        'userPrincipalName' => "$username@aaaaaaaa.com.cn",
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', '"' . $password . '"'),
        'userAccountControl' => 512, // 512 表示启用账户
    ];

    4. 未启用 LDAPS(SSL 加密)

    某些 AD 环境强制要求通过 LDAPS(端口 636) 修改敏感属性(如密码)。

    解决方案

    1. 使用 ldaps:// 协议连接:

      php
      复制
      $ldapServer = 'ldaps://ad.aaaaaaaa.com.cn'; // 注意协议为 ldaps://
      $ldapPort = 636; // LDAPS 默认端口
      $ldapConn = ldap_connect($ldapServer, $ldapPort);
             
    2. 导入 AD 的 CA 证书
      在 PHP 环境中配置信任 AD 的证书,避免 SSL 验证失败:

      php
      复制
      // 忽略证书验证(仅测试环境使用)
      ldap_set_option($ldapConn, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
             

    5. 检查 AD 服务器日志

    通过 Windows 事件查看器 获取详细错误原因:

    1. 打开 AD 服务器的 事件查看器

    2. 导航至 Applications and Services Logs > Directory Service

    3. 查找与 LDAP 操作相关的错误事件,通常会包含具体拒绝原因(如密码策略、权限问题)。


    完整修正代码示例

    php
    复制
    <?php
    // 配置参数
    $ldapServer = 'ldaps://ad.aaaaaaaa.com.cn';
    $ldapPort = 636;
    $adminUser = 'administrator@aaaaaaaa.com.cn';
    $adminPass = 'AdminPassword123!';
    $baseDn = 'OU=bbbbbb集团,DC=aaaaaaaa,DC=com,DC=cn';
    $username = 'test6';
    $password = 'P@ssw0rd2023!'; // 符合复杂度要求的密码
    
    // 连接 AD(LDAPS)
    $ldapConn = ldap_connect($ldapServer, $ldapPort);
    if (!$ldapConn) die("连接失败");
    
    // 设置选项
    ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
    ldap_set_option($ldapConn, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); // 忽略证书验证
    
    // 绑定管理员
    if (!ldap_bind($ldapConn, $adminUser, $adminPass)) {
        die("绑定失败: " . ldap_error($ldapConn));
    }
    
    // 准备用户 DN 和属性
    $userDn = "CN=$username,$baseDn";
    $encodedPassword = iconv('UTF-8', 'UTF-16LE', '"' . $password . '"');
    
    $userInfo = [
        'objectClass' => ['top', 'person', 'organizationalPerson', 'user'],
        'cn' => $username,  //不懂,为什么有了cn,会报错 Invalid DN syntax ,,,00002081: NameErr: DSID-03050EC7, problem 2003 (BAD_ATT_SYNTAX), data 0, best match of
        'sAMAccountName' => $username,
        'userPrincipalName' => "$username@aaaaaaaa.com.cn",
        'unicodePwd' => $encodedPassword,
        'userAccountControl' => 512,
    ];
    
    // 创建用户
    if (!ldap_add($ldapConn, $userDn, $userInfo)) {
        $errorCode = ldap_errno($ldapConn);
        $errorMsg = ldap_error($ldapConn);
        ldap_get_option($ldapConn, LDAP_OPT_ERROR_STRING, $extendedError);
        die("错误代码: $errorCode\n错误信息: $errorMsg\n扩展信息: $extendedError");
    }
    
    echo "用户 $username 创建成功!";
    ldap_unbind($ldapConn);
    ?>

    关键排查步骤总结

    1. 密码复杂度与编码

    • 确保密码长度 ≥ 8,包含大小写、数字、符号。

    • 正确使用 iconv('UTF-8', 'UTF-16LE', '"密码"') 编码。

  • 权限验证

    • 使用域管理员账户绑定。

    • 检查目标 OU 的 ACL 权限。

  • 强制使用 LDAPS

    • 将协议改为 ldaps://,端口 636

  • 服务器日志分析

    • 通过事件查看器定位具体拒绝原因。


    通过上述步骤,可解决 WILL_NOT_PERFORM 错误。如问题仍未解决,需结合服务器日志和网络抓包进一步分析。


    'userAccountControl' => 512,  与 'userAccountControl' => 514 的区别

    在 Active Directory 中,userAccountControl 是一个用于控制用户账户状态的 位掩码属性(Bitmask)。值 512 和 514 是两种常见配置,对应账户的启用和禁用状态。以下是它们的核心区别和用法:


    一、userAccountControl = 512

    • 含义
      表示账户处于 正常启用状态,可以登录、访问资源。

    • 二进制分解
      512 的二进制为 1000000000,对应第 9 位(UF_NORMAL_ACCOUNT) 被激活。

    • 典型场景
      创建新用户时,若希望用户 立即生效,应设置 userAccountControl 为 512


    二、userAccountControl = 514

    • 含义
      表示账户处于 禁用状态(用户无法登录),但账户本身存在。
      514 的二进制为 1000000010,由以下两个标志位组成:

      • 512 (1000000000): UF_NORMAL_ACCOUNT(正常账户类型)

      • 2 (0000000010): UF_ACCOUNTDISABLE(账户禁用)

    • 典型场景
      创建用户时暂时禁用账户(例如需要后续手动激活),或禁用已有账户。


    三、关键区别对比

    属性值账户状态二进制分解适用场景
    512启用            1000000000            用户可正常登录
    514禁用            1000000010            创建时禁用,或临时锁定账户

    四、其他常见值(组合)

    userAccountControl 支持通过 位运算 组合多个标志:

    • 512 + 65536 = 66048
      启用账户 (512) + 密码永不过期 (65536,即 UF_DONT_EXPIRE_PASSWD)。

    • 512 + 32 = 544
      启用账户 (512) + 用户下次登录需修改密码 (32,即 UF_PASSWORD_EXPIRED)。


    五、代码示例

    1. 创建并启用用户

    php
    复制
    $userInfo = [
        'objectClass' => ['user'],
        'sAMAccountName' => 'johndoe',
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', '"P@ssw0rd"'),
        'userAccountControl' => 512, // 启用账户
    ];

    2. 创建后禁用用户

    php
    复制
    $userInfo = [
        'objectClass' => ['user'],
        'sAMAccountName' => 'johndoe',
        'unicodePwd' => iconv('UTF-8', 'UTF-16LE', '"P@ssw0rd"'),
        'userAccountControl' => 514, // 创建时禁用
    ];

    3. 启用密码永不过期

    php
    复制
    $userInfo['userAccountControl'] = 66048; // 512 + 65536

    六、验证账户状态

    1. 通过 AD 管理工具
      打开 Active Directory Users and Computers,检查用户属性中的 “账户已禁用” 复选框状态。

    2. 通过 LDAP 查询
      使用 ldapsearch 或代码读取 userAccountControl 值:

      bash
      复制
      ldapsearch -x -H ldap://ad.example.com -D "admin@example.com" -W -b "cn=johndoe,ou=Users,dc=example,dc=com" userAccountControl
             

    七、注意事项

    1. 权限要求
      修改 userAccountControl 需要足够的权限(如域管理员)。

    2. 密码策略
      即使账户启用,密码仍需符合复杂度要求。

    3. LDAPS 加密
      敏感操作建议使用 ldaps:// 协议(端口 636)。

    掌握 userAccountControl 的标志位组合,可精准控制账户行为(如禁用、密码策略、登录限制等)。



    普通分类: