【Linux On Web】- 升级社团网站会员系统为AD域认证

天锦 发表于 女票们的新建与保养 分类,标签: PHPLDAPAD修改密码创建账户Linux On Web

What's LDAP

(一)在介绍什么是LDAP之前,我们先来了解一个东西:“什么是目录服务?”

  1. 目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。

  2. 是动态的,灵活的,易扩展的。

(二)了解完目录服务后,我们再来看看LDAP的介绍:

LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。

目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。

目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。

LDAP目录服务是由目录数据库和一套访问协议组成的系统。

LDAP是开放的Internet标准,支持跨平台的Internet协议,在业界中得到广泛认可的,并且市场上或者开源社区上的大多产品都加入了对LDAP的支持,因此对于这类系统,不需单独定制,只需要通过LDAP做简单的配置就可以与服务器做认证交互。“简单粗暴”,可以大大降低重复开发和对接的成本。

(三)目录树概念

    1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。

    2.条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。

    3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。

    4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。

(四)DC、UID、OU、CN、SN、DN、RDN

DC、UID、OU、CN、SN、DN、RDN这些关键词在LDAP中出现的频率相当之高,其所代表的含义一定要清楚。


关键字英文全称含义
DCDomain Component域名部分
UIDUser ID用户ID
OUOrganization Unit组织单位
CNCommon Name公共名称
SNSurname
DNDistinguished Name数据唯一标识符
RDNRelative DN相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分

这里要着重看DC、CN、DN这三个,DC是域名部分,以sub.dimain.com为例,用DC表示就是“DC=sub,DC=domain,DC=com”,这里的DN和咱们的URL相似,他是由包含多个DC和CN的字符串构成的,比如这个DN

$DN="CN=User,DC=sub,DC=domain,DC=com";

PHP LDAP

协会网站是PHP环境,PHP有一个LDAP拓展可以直接操作AD域,有了这个拓展,升级会员系统为AD域统一认证方便了不少。

LDAP拓展在PHP中默认是不安装的,全新安装PHP时可以通过添加“--with-ldap”选项安装LDAP拓展。对于已经安装过PHP环境,需要对PHP添加LDAP支持的,可以到PHP官网,下载对应版本的源码,源码中包含有LDAP,编译安装即可。以PHP5.6.30,安装目录为/www/server/php/56/为例:

[root@localhost ~] wget https://www.php.net/distributions/php-5.6.30.tar.gz    #下载源码包
[root@localhost ~] tar -xzvf php-5.6.30.tar.gz    #解压
[root@localhost ~] cd php-5.6.30/ext/ldap/    #进入LDAP模块源码目录
[root@localhost ~] /www/server/php/56/bin/phpize    #使用phpize准备PHP拓展库编译环境
[root@localhost ~] ./configure  --with-php-config=/www/server/php/56/bin/php-config    #配置编译
[root@localhost ~] make && make install    #编译安装

正常情况下编译成功后会在PHP目录lib/php/extensions下产生一个ldap.so的文件,编辑php.ini文件在文件末尾添加extension =ldap.so即可。

遇到的报错和解决方法

configure: error: Cannot find ldap.h

[root@localhost ~] yum install openldap openldap-devel

configure: error: Cannot find ldap libraries in /usr/lib

[root@localhost ~] cp -frp /usr/lib64/libldap* /usr/lib/

LDAP Browser

对于一个从来没接触过LDAP的我来说,各种介绍在怎么丰富,也不及亲自上手看一看,下载一个LDAP Browser,浏览一下AD域控上面的LDAP里面记录了什么,怎么记录的对刚上手AD域控的同学来说还是很有帮助的。这里推荐的是Free LDAP Browser For Windows

Window Active Directory将用户信息存储在了CN=Users中,网上各种教程有说在Account中的,有说查UID才能查得到的……自己翻了一遍AD域控才确定是在Users中,坑……满地都是坑……

初出茅庐

先写一个基本的程序测试AD域的连接。

<?php
//LDAP连接测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldap://192.168.1.2:389";
//用户唯一辨识符,也可以使用administrator@sub.domain.com这种格式
$user_dn="CN=Administrator,CN=Users,DC=lab,DC=pypy,DC=fun";
//用户密码
$user_password="password";
echo "<h3>LDAP query test</h3>";
echo "Connecting ...";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
echo "connect result is " . $ds . "<br />";
if ($ds) {
    echo "Binding ...";
    //绑定连接,如果用户唯一辨识符和密码不正确,将无法绑定
    $r=ldap_bind($ds,$user_dn,$user_password);
    echo "Bind result is " . $r . "<br />";
    echo "Searching for Users...";
    //查询用户
    $sr=ldap_search($ds, "CN=Users,DC=lab,DC=pypy,DC=fun", "CN=Administrator");
    echo "Search result is " . $sr . "<br />";
    echo "Number of entries returned is " . ldap_count_entries($ds, $sr) . "<br />";
    echo "Getting entries ...<p>";
    $info = ldap_get_entries($ds, $sr);
    echo "Data for " . $info["count"] . " items returned:<p>";
    for ($i=0; $i<$info["count"]; $i++) {
        echo "dn is: " . $info[$i]["dn"] . "<br />";
        echo "first cn entry is: " . $info[$i]["cn"][0] . "<br />";
        echo "first email entry is: " . $info[$i]["mail"][0] . "<br /><hr />";
    }
    echo "Closing connection";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

可以看到,成功连接了,并查找到了这个Administrator的User。

connect_test.png

登陆验证

对这个程序稍加修改就能变成我们的登陆验证程序。

<?php
//LDAP连接测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldap://10.60.60.254:389";
//需要认证的用户账户
$user_post="upn_login@lab.pypy.fun";
//用户密码
$user_password="password";
echo "<h3>LDAP query test</h3>";
echo "Connecting ...";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
if ($ds) {
    echo "connect result is " . $ds . "<br />";
    ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);//声明使用版本3
    ldap_set_option($ds, LDAP_OPT_REFERRALS, 0); // Binding to ldap server
    echo "Binding ...";
    $r=@ldap_bind($ds,$user_post,$user_password);
    ldap_get_option($ds, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error);
    if(!$error){
        echo '登陆成功</br>';
        echo "Bind result is " . $r . "<br />";
        echo "查询用户组信息</br>";
        $user_name=explode('@',$user_post);
        $sr=ldap_search($ds, "CN=Users,DC=lab,DC=pypy,DC=fun", "CN=".$user_name[0]);
        $info = ldap_get_entries($ds, $sr);
        echo "分组信息为";
        echo($info[0]['memberof'][$info[0]['memberof']['count']-1]);
    }else{
        echo "登陆失败:".$error."</br>";
    }
    echo "Closing connection";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

替换网站现有的相关认证代码就可以了。

以下是试验结果

login.png

升级到LDAPS

坑……满地的坑……

LDAP只能查询AD域控制器中的信息,无法修改内容,若要修改,必须用SSL加密的LDAP,也就是LDAPS。

所以,我又踏上了把LDAP升级成LDAPS的不归路。

先耍个小聪明,直接改协议怎么样:

$server_host="ldap://10.60.60.254:389";

改为:

$server_host="ldaps://10.60.60.254:636";

显然,这是行不通的,报错:

ldap_bind: Can't contact LDAP server

首先,要确保我的AD域控是支持LDAPS的,然鹅,我并没有部署SSL,先前只是部署了AD DS。并没有部署AD CS(证书服务)。若要AD域控使用SSL加密,则必须安装AD CS证书服务。

安装AD CS证书服务跟安装AD DS域控制器一样,详情参考《Windows Server 2016 安装AD并开启SSL》这篇总结性的博文。

LDAP on SSL部署之后可以用Free LDAP Browser尝试SSL连接。

ssl_browser.png

服务器那边加了证书,SSL加密也能用了,PHP这边再用$server_host="ldaps://10.60.60.254:636";试一下吧,结果嘛……

ldap_bind: Can't contact LDAP server

查阅了PHP官网,Andrew(a.whyte@cqu.edu.au)的一个Note的帮助很大。

1.要确保OpenLDAP编译安装是受OpenSSL的支持。

2.导出AD根证书并且配置OpenSSL信任该根证书

3.要配置OpenLDAP信任根证书

4.要在Web根目录中创建.ldaprc配置文件以便PHP可以获取OpenLDAP相关配置。

步骤有点多,一步一步来,关于OpenSSL的问题,我的OpenLDAP是用yum包管理器安装的,应该不存在这个问题。跳过。如果你是手动安装的,记得检查。

到AD域控上面,运行mmc.exe,添加证书管理单元

mmc_add.png

mmc_add2.png

ca_exp2.png

导出格式为Base64编码。

将导出的adCA.cer上传到/usr/local/openssl/certs/下面,并执行c_rehash

[root@localhost ~] mv adCA.cer /usr/local/openssl/certs/
[root@localhost ~] /usr/local/openssl/bin/c_rehash

接下来就是配置OpenLDAP了,编辑/etc/openldap/ldap.conf

#--begin--
# Instruct client to NOT request a server's cert.
TLS_REQCERT never
# Define location of CA Cert
TLS_CACERT /usr/local/openssl/certs/adCA.cer
TLS_CACERTDIR /usr/local/openssl/certs
#--end--

此外,你还要在Apache网页根目录下建立一个.ldaprc的文件存储以上配置方便PHP读取OpenLDAP配置

[root@localhost ~] cp /etc/openldap/ldap.conf  /www/wwwroot/ldap.lab.pypy.fun/

如果你的apache中没有对站点定义HOME目录的话,还要在站点的you-apache-site.conf中设置HOME目录

SetEnv HOME "/www/wwwroot/ldap.pypy.fun

配置完成后重启服务器。

记得连接时将IP改为域名来连接,不然要报错。使用

$server_host=ldaps://ad.lab.pypy.fun:636

来进行连接,终于出来了。我太难了……

ldaps_test.png

修改密码

终于走到了这一步,可以尝试修改密码了。然鹅通过LDAP设置AD中用户密码必须满足非常严格的三个条件,否则会提示Warning:server is unwilling to perform。

1.必须使用SSL方式连接AD(ldaps://);

2.密码必须使用引号括起来;

3.引号中的密码必须使用16位unicode编码(UTF-16LE);

对于问题1,LDAPS我已经升级好了。问题2在新设置的密码前后加括号就行了,编码的问题可以用iconv转换就行了。

PS.说出来的坑都不再是坑,鬼知道我摸索了多久(:

最终测试程序如下:

<?php
//LDAP改密测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldaps://ad.lab.pypy.fun:636";
//用于连接LDAP的管理用户唯一辨识符,也可以使用administrator@sub.domain.com这种格式
$manager_user_dn="CN=Administrator,CN=Users,DC=lab,DC=pypy,DC=fun";
//用户密码
$user_password="password";
//需要修改密码的目标用户DN
$target_user_dn="CN=test_account,CN=Users,DC=lab,DC=pypy,DC=fun";
$target_pass="newPass123";
echo "<h3>LDAPS Change Password test</h3>";
echo "Server Host is:".$server_host."</br>";
echo "Manager User DN is:".$manager_user_dn."</br>";
echo "Target User DN is:".$target_user_dn."</br>";
echo "Connecting ...</br>";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
echo "connect result is " . $ds . "</br>";
if ($ds) {
    echo "Binding ...</br>";
    //绑定连接,如果用户唯一辨识符和密码不正确,将无法绑定
    $r=ldap_bind($ds,$manager_user_dn,$user_password);
    echo "Bind result is " . $r . "</br>";
    echo "目标用户: " . $target_user_dn . "</br>";
    //改密码
    //编码
    $target_pass='"'.$target_pass.'"';
    $newPass = iconv( 'UTF-8', 'UTF-16LE', $target_pass );
    //通过重写unicodepwd值来更改密码
    $userdata["unicodepwd"] = $newPass; 
    $result = ldap_mod_replace($ds,$target_user_dn,$userdata); 
    if($result===true){
        echo "修改密码成功!</br>";
    }else{
        echo "修改密码失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    echo "Closing connection</br>";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

让test_account账户从加入域的计算机上注销,再次登陆改用新密码登陆,可以成功登陆。

另外我在查找资料的时候还看见了一个关于改密码之后,老密码还能使用的问题的帖子,记下来先,需要的时候可以去看看>>https://blog.51cto.com/lidongni/1760480

添加账户

添加账户也是非常常见的操作,每年纳新都有好多同学加入我们社团,也不可能挨个去添加到域里面,也是直接改网站注册模块,使之直接注册到域控里面。由于前面把坑踩得差不多了,这里也简单了许多,安装AD域控里面用户的数据结构,像添加数据库那样添加进去就行了。

然鹅……是我太小看它了……

加数据很好加用ldap_add()函数添加就行了,可是这数据结构可不是一般的……

/**
 * 表分类信息,保持不变即可
 */
$userdata["objectClass"][0] = 'top';
$userdata["objectClass"][1] = 'person';
$userdata["objectClass"][2] = 'organizationalPerson';
$userdata["objectClass"][3] = 'user';
$userdata["instanceType"] = 4;
/**
 * UserPrincipalName:指定要由客户端进行身份验证的服务的用户主体名称 (UPN)。
 *      一个用户帐户名(有时称为“用户登录名”)
 *      和一个域名(标识用户帐户所在的域),
 *      这是登录到Windows域的标准用法。
 *      格式是: xiaowen@azureyun.com (类电子邮件地址)。
 * SamAccountName:在AD属性AMAccountName中,存储帐户登录名或用户对象,
 *      实际上是命名符号“Domain\LogonName ”中使用的旧NetBIOS表单,
 *      该属性是域用户对象的必需属性;而SAMAccountName应始终与UPN主体名称保持一致,
 *      即SAMAccountName必须等于属性“UserPrincipalName” 的前缀部分。
 *      并且应该与CN保持相同。
 *      
 * 格式规定
 *      不能超过20个字符,不允许出现\ / [] :; | =,+ *?<> @等特殊字符;
 */
$userdata["sAMAccountName"] = "NewUser";
$userdata["userPrincipalName"] = "NewUser"."@domain.com";
/**
 * name:账户名称,应与sAMAccountName保持一致
 */
$userdata["name"] = "NewUser";
/**
 * userAccountControl记录了用户的AD账号的很多属性信息,该属性标志是累积性的。
 *      也就是各属性相加之和。常见的有以下几种属性:
 * 禁用账户-----------------2
 * 需要主文件夹-------------8
 * 不需要密码--------------32
 * 不能更改密码-------------64
 * 使用可逆加密存储密码-----128
 * 默认账户类型------------512
 * 密码永不过期----------65536
 * 
 *      没有声明该值为514即默认账户类型+禁用账户
 *      设为66048即为默认账户类型+密码永不过期
 */
$userdata["userAccountControl"] = 66048;
$userdata["displayName"] = '张三';//用户姓名
$userdata["company"] = '公司名称';
$userdata["department"] = '部门名称';
$userdata["physicalDeliveryOfficeName"] = '办公室';
$userdata["title"] = '职务';
$userdata["wWWHomePage"] = 'http://blog.tianjinkun.com';//个人主页
$userdata["description"] = '描述';
$userdata["telephoneNumber"] = '电话号码';
$userdata["ipPhone"] = 'IP电话,可以用来存储QQ号码';

摸清楚结构了,一切都好说,添加一个账户试试:

<?php
//LDAPS添加用户测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldaps://ad.lab.pypy.fun:636";
//用于连接LDAP的管理用户唯一辨识符,也可以使用administrator@sub.domain.com这种格式
$manager_user_dn="CN=Administrator,CN=Users,DC=lab,DC=pypy,DC=fun";
//用户密码
$user_password="password";
//需要添加目标用户
//设要添加的用户为17100001@lab.pypy.fun
$User_account='17100001';
$target_user_dn="CN=".$User_account.",CN=Users,DC=lab,DC=pypy,DC=fun";
/**
 * 表分类信息,保持不变即可
 */
$userdata["objectClass"][0] = 'top';
$userdata["objectClass"][1] = 'person';
$userdata["objectClass"][2] = 'organizationalPerson';
$userdata["objectClass"][3] = 'user';
$userdata["instanceType"] = 4;
/**
 * UserPrincipalName:指定要由客户端进行身份验证的服务的用户主体名称 (UPN)。
 *      一个用户帐户名(有时称为“用户登录名”)
 *      和一个域名(标识用户帐户所在的域),
 *      这是登录到Windows域的标准用法。
 *      格式是: xiaowen@azureyun.com (类电子邮件地址)。
 * SamAccountName:在AD属性AMAccountName中,存储帐户登录名或用户对象,
 *      实际上是命名符号“Domain\LogonName ”中使用的旧NetBIOS表单,
 *      该属性是域用户对象的必需属性;而SAMAccountName应始终与UPN主体名称保持一致,
 *      即SAMAccountName必须等于属性“UserPrincipalName” 的前缀部分。
 *      并且应该与CN保持相同。
 *      
 * 格式规定
 *      不能超过20个字符,不允许出现\ / [] :; | =,+ *?<> @等特殊字符;
 */
$userdata["sAMAccountName"] = $User_account;
$userdata["userPrincipalName"] = $User_account."@lab.pypy.fun";
/**
 * name:账户名称,应与sAMAccountName保持一致
 */
$userdata["name"] = $User_account;
/**
 * userAccountControl记录了用户的AD账号的很多属性信息,该属性标志是累积性的。
 *      也就是各属性相加之和。常见的有以下几种属性:
 * 禁用账户-----------------2
 * 需要主文件夹-------------8
 * 不需要密码--------------32
 * 不能更改密码-------------64
 * 使用可逆加密存储密码-----128
 * 默认账户类型------------512
 * 密码永不过期----------65536
 * 
 *      没有声明该值为514即默认账户类型+禁用账户
 *      设为66048即为默认账户类型+密码永不过期
 */
$userdata["userAccountControl"] = 66048;
$userdata["displayName"] = '张三';//用户姓名
$userdata["company"] = '公司名称';
$userdata["department"] = '部门名称';
$userdata["physicalDeliveryOfficeName"] = '办公室';
$userdata["title"] = '职务';
$userdata["wWWHomePage"] = 'http://blog.tianjinkun.com';//个人主页
$userdata["mail"] = 'mail@test.com';
$userdata["description"] = '个人描述';
$userdata["telephoneNumber"] = '110';
$userdata["ipPhone"] = '8888888';//IP电话,可以用来存储QQ号码
echo "<h3>LDAPS Add Account test</h3>";
echo "Server Host is:".$server_host."</br>";
echo "Manager User DN is:".$manager_user_dn."</br>";
echo "Target User DN is:".$target_user_dn."</br>";
echo "Connecting ...</br>";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
echo "connect result is " . $ds . "</br>";
if ($ds) {
    echo "Binding ...</br>";
    //绑定连接,如果用户唯一辨识符和密码不正确,将无法绑定
    $r=ldap_bind($ds,$manager_user_dn,$user_password);
    echo "Bind result is " . $r . "</br>";
    echo "目标用户: " . $target_user_dn . "</br>";
    $result = ldap_add($ds,$target_user_dn,$userdata); 
    if($result===true){
        echo "添加成功!</br>";
    }else{
        echo "添加失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    echo "Closing connection</br>";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

理论上运行是没有问题的,运行一下,便得到了报错:

Warning: ldap_add(): server is unwilling to perform in /www/wwwroot/ldap.pypy.fun/add.php on line 99

添加失败!

当我把userAccountControl这个值屏蔽后便可以正常写入了,为什么呢……

一番研究之后找到了原因,我没有为这个账户设置密码!所以添加账户的操作要分开来做,添加账户信息>设置密码>修改userAccountControl,启用账户。

修改之后的代码如下:

<?php
//LDAPS添加用户测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldaps://ad.lab.pypy.fun:636";
//用于连接LDAP的管理用户唯一辨识符,也可以使用administrator@sub.domain.com这种格式
$manager_user_dn="CN=Administrator,CN=Users,DC=lab,DC=pypy,DC=fun";
//用户密码
$user_password="password";
//需要添加目标用户
//设要添加的用户为17100001@lab.pypy.fun
$User_account='17100001';
$target_user_dn="CN=".$User_account.",CN=Users,DC=lab,DC=pypy,DC=fun";
/**
 * 表分类信息,保持不变即可
 */
$userdata["objectClass"][0] = 'top';
$userdata["objectClass"][1] = 'person';
$userdata["objectClass"][2] = 'organizationalPerson';
$userdata["objectClass"][3] = 'user';
$userdata["instanceType"] = 4;
/**
 * UserPrincipalName:指定要由客户端进行身份验证的服务的用户主体名称 (UPN)。
 *      一个用户帐户名(有时称为“用户登录名”)
 *      和一个域名(标识用户帐户所在的域),
 *      这是登录到Windows域的标准用法。
 *      格式是: xiaowen@azureyun.com (类电子邮件地址)。
 * SamAccountName:在AD属性AMAccountName中,存储帐户登录名或用户对象,
 *      实际上是命名符号“Domain\LogonName ”中使用的旧NetBIOS表单,
 *      该属性是域用户对象的必需属性;而SAMAccountName应始终与UPN主体名称保持一致,
 *      即SAMAccountName必须等于属性“UserPrincipalName” 的前缀部分。
 *      并且应该与CN保持相同。
 *      
 * 格式规定
 *      不能超过20个字符,不允许出现\ / [] :; | =,+ *?<> @等特殊字符;
 */
$userdata["sAMAccountName"] = $User_account;
$userdata["userPrincipalName"] = $User_account."@lab.pypy.fun";
/**
 * name:账户名称,应与sAMAccountName保持一致
 */
$userdata["name"] = $User_account;
/**
 * userAccountControl记录了用户的AD账号的很多属性信息,该属性标志是累积性的。
 *      也就是各属性相加之和。常见的有以下几种属性:
 * 禁用账户-----------------2
 * 需要主文件夹-------------8
 * 不需要密码--------------32
 * 不能更改密码-------------64
 * 使用可逆加密存储密码-----128
 * 默认账户类型------------512
 * 密码永不过期----------65536
 * 
 *      没有声明该值为514即默认账户类型+禁用账户
 *      设为66048即为默认账户类型+密码永不过期
 */
//$userdata["userAccountControl"] = 66048;
$userdata["displayName"] = '张三';//用户姓名
$userdata["company"] = '公司名称';
$userdata["department"] = '部门名称';
$userdata["physicalDeliveryOfficeName"] = '办公室';
$userdata["title"] = '职务';
$userdata["wWWHomePage"] = 'http://blog.tianjinkun.com';//个人主页
$userdata["mail"] = 'mail@test.com';
$userdata["description"] = '个人描述';
$userdata["telephoneNumber"] = '110';
$userdata["ipPhone"] = '8888888';//IP电话,可以用来存储QQ号码
echo "<h3>LDAPS Add Account test</h3>";
echo "Server Host is:".$server_host."</br>";
echo "Manager User DN is:".$manager_user_dn."</br>";
echo "Target User DN is:".$target_user_dn."</br>";
echo "Connecting ...</br>";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
echo "connect result is " . $ds . "</br>";
if ($ds) {
    echo "Binding ...</br>";
    //绑定连接,如果用户唯一辨识符和密码不正确,将无法绑定
    $r=ldap_bind($ds,$manager_user_dn,$user_password);
    echo "Bind result is " . $r . "</br>";
    echo "目标用户: " . $target_user_dn . "</br>";
    $result = ldap_add($ds,$target_user_dn,$userdata); 
    if($result===true){
        echo "添加成功!</br>";
    }else{
        echo "添加失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    $target_pass='"password"';
    $newPass = iconv( 'UTF-8', 'UTF-16LE', $target_pass );
    //通过重写unicodepwd值来更改密码
    $passdata["unicodepwd"] = $newPass; 
    $result = ldap_mod_replace($ds,$target_user_dn,$passdata); 
    if($result===true){
        echo "修改密码成功!</br>";
    }else{
        echo "修改密码失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    $account_right["userAccountControl"] = 66048;
    $change=ldap_mod_replace($ds,$target_user_dn,$account_right); 
    if($change===true){
        echo "添加权限成功!</br>";
    }else{
        echo "添加权限失败!</br>";
        var_dump($change);
        echo "</br>";
    }
    echo "Closing connection</br>";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

如此一来,账户可以成功添加了。

add_success.png

先不要太高兴,到AD上看看添加的信息怎么样,所有中文的信息全部乱码。这也在意料之中,我PHP环境用的是UTF-8编码,Windows是出了名的GB2313。接下来就是整理字符编码的收尾工作了。写一个字符转码的函数,转到GB2312上去,挨个替换就行了。

最终的代码如下

<?php
//LDAPS添加用户测试
//LDAP服务器器地址,这里就是AD域控的地址
$server_host="ldaps://ad.lab.pypy.fun:636";
//用于连接LDAP的管理用户唯一辨识符,也可以使用administrator@sub.domain.com这种格式
$manager_user_dn="CN=Administrator,CN=Users,DC=lab,DC=pypy,DC=fun";
//用户密码
$user_password="password";
//需要添加目标用户
//设要添加的用户为17100001@lab.pypy.fun
$User_account='17100001';
$target_user_dn="CN=".$User_account.",CN=Users,DC=lab,DC=pypy,DC=fun";
function to_gb2312($string_in){
    $str_encode = iconv( 'UTF-8', 'EUC-CN',$string_in );
    return $str_encode;
}
/**
 * 表分类信息,保持不变即可
 */
$userdata["objectClass"][0] = 'top';
$userdata["objectClass"][1] = 'person';
$userdata["objectClass"][2] = 'organizationalPerson';
$userdata["objectClass"][3] = 'user';
$userdata["instanceType"] = 4;
/**
 * UserPrincipalName:指定要由客户端进行身份验证的服务的用户主体名称 (UPN)。
 *      一个用户帐户名(有时称为“用户登录名”)
 *      和一个域名(标识用户帐户所在的域),
 *      这是登录到Windows域的标准用法。
 *      格式是: xiaowen@azureyun.com (类电子邮件地址)。
 * SamAccountName:在AD属性AMAccountName中,存储帐户登录名或用户对象,
 *      实际上是命名符号“Domain\LogonName ”中使用的旧NetBIOS表单,
 *      该属性是域用户对象的必需属性;而SAMAccountName应始终与UPN主体名称保持一致,
 *      即SAMAccountName必须等于属性“UserPrincipalName” 的前缀部分。
 *      并且应该与CN保持相同。
 *      
 * 格式规定
 *      不能超过20个字符,不允许出现\ / [] :; | =,+ *?<> @等特殊字符;
 */
$userdata["sAMAccountName"] = $User_account;
$userdata["userPrincipalName"] = $User_account."@lab.pypy.fun";
/**
 * name:账户名称,应与sAMAccountName保持一致
 */
$userdata["name"] = $User_account;
/**
 * userAccountControl记录了用户的AD账号的很多属性信息,该属性标志是累积性的。
 *      也就是各属性相加之和。常见的有以下几种属性:
 * 禁用账户-----------------2
 * 需要主文件夹-------------8
 * 不需要密码--------------32
 * 不能更改密码-------------64
 * 使用可逆加密存储密码-----128
 * 默认账户类型------------512
 * 密码永不过期----------65536
 * 
 *      没有声明该值为514即默认账户类型+禁用账户
 *      设为66048即为默认账户类型+密码永不过期
 */
// $userdata["userAccountControl"] = 66048;
$userdata["displayName"] = to_gb2312('张三');//用户姓名
$userdata["company"] = to_gb2312('公司名称');
$userdata["department"] = to_gb2312('部门名称');
$userdata["physicalDeliveryOfficeName"] = to_gb2312('办公室');
$userdata["title"] = to_gb2312('职务');
$userdata["wWWHomePage"] = 'http://blog.tianjinkun.com';//个人主页
$userdata["mail"] = 'mail@test.com';
$userdata["description"] = to_gb2312('个人描述');
$userdata["telephoneNumber"] = '110';
$userdata["ipPhone"] = '8888888';//IP电话,可以用来存储QQ号码
echo "<h3>LDAPS Add Account test</h3>";
echo "Server Host is:".$server_host."</br>";
echo "Manager User DN is:".$manager_user_dn."</br>";
echo "Target User DN is:".$target_user_dn."</br>";
echo "Connecting ...</br>";
//创建一个LDAP连接
$ds=ldap_connect($server_host);
echo "connect result is " . $ds . "</br>";
if ($ds) {
    echo "Binding ...</br>";
    //绑定连接,如果用户唯一辨识符和密码不正确,将无法绑定
    $r=ldap_bind($ds,$manager_user_dn,$user_password);
    echo "Bind result is " . $r . "</br>";
    echo "目标用户: " . $target_user_dn . "</br>";
    $result = ldap_add($ds,$target_user_dn,$userdata); 
    if($result===true){
        echo "添加成功!</br>";
    }else{
        echo "添加失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    $target_pass='"password"';
    $newPass = iconv( 'UTF-8', 'UTF-16LE', $target_pass );
    //通过重写unicodepwd值来更改密码
    $passdata["unicodepwd"] = $newPass; 
    $result = ldap_mod_replace($ds,$target_user_dn,$passdata); 
    if($result===true){
        echo "修改密码成功!</br>";
    }else{
        echo "修改密码失败!</br>";
        var_dump($result);
        echo "</br>";
    }
    $account_right["userAccountControl"] = 66048;
    $change=ldap_mod_replace($ds,$target_user_dn,$account_right); 
    if($change===true){
        echo "添加权限成功!</br>";
    }else{
        echo "添加权限失败!</br>";
        var_dump($change);
        echo "</br>";
    }
    echo "Closing connection</br>";
    ldap_close($ds);
} else {
    echo "<h4>Unable to connect to LDAP server</h4>";
}
?>

到域控上面查看信息,正常:

encode_success.png

随意找一台加入了域的计算机登陆,成功。

login_success.png

至此,所有和PHP网站账户系统和AD域控统一账户,统一登陆的相关测试就全部完成了。待疫情结束,返校之后修改网站相关代码就可以了。

对于网站已有的老用户来说,在登陆时判断一下他的账户,引导他再设置一次密码,迁移相关信息升级成域账号即可。新用户直接注册成为域账户即可。

3 篇评论

发表我的评论