需求描述
在 Gerrit 的使用中,发现 Google 在这个框架中没有提供用户自助修改密码的功能,和 SVN 的情况相似,需要添加一个修改密码的页面。公司的 Gerrit 平台使用 HTTP Basic Authentication 方式来进行用户登录认证,由 htpasswd 命令生成的密码集中保存在一个默认的文件中。那么该页面的需求就可以细化为:对用户输入合法性做基本判断,从指定路径读取密码文件,用系统命令 htpasswd 修改密码。本文选择用 PHP 语言来实现该页面需求。
htpasswd 命令
使用 Apache 服务器时,如果是 HTTP Basic Authentication 方式来限制用户访问,那么可以用 htpasswd 命令来创建和更新一个保存用户名、密码的文件。
1 | $ htpasswd -bcs file/to/sava/passwd username passwd |
上面的命令在指定路径生成了一个密码文件passwd
,保存着输入的用户名和密码。因为使用了 -b 参数,可以在同一行命令中输入用户名和密码。这个参数非常重要,因为用 PHP 调用该命令时,不方便使用交互式的命令输入。
htpasswd 有很多种密码加密方式可以选择。本次使用 SHA1 加密方式(-s),因为这种加密方式不像 MD5 等方式加入了混淆参数,所以每次生成的加密字符串都是相同的,有利于密码修改时的验证过程的实现。
在 PHP 代码中执行系统命令
PHP 语言提供了三种执行外部 shell 脚本程序或命令的方法,分别是 system(), exec(), passthru()。本次使用了 system() 方法实现执行 htpasswd 命令的需求。
system() 方法的声明如下:
1 | string system(string $command[, int &$return_var]) |
可以带两个参数,分别是 command 所指定的命令,和可选参数 return_var。如果外部命令执行后返回状态为 0,则执行成功,return_var 会置 0,否则返回状态为 1,执行失败。
实际调试过程中,如果命令执行失败,错误信息无从查找,给调试带来了很大的困难,如果在要执行的命令的后面加上2>&1
,则可以打印出命令或执行后的成功或错误信息,有助于调试的进行:
1 | $command = '"/usr/bin/htpasswd" -bs ' . $passwdfile . " " . $input_username . " " . $newpass . "2>&1"; |
切换 Apache 执行权限
当我把程序本地跑通,愉快地把这个页面 scp 到服务器上运行,问题来了。 PHP 语句里两处 htpasswd 命令都没能成功运行。打印出错误信息,发现无法在指定路径创建密码文件。那么最有可能就是 Apache 在执行命令时权限不够造成的,需要把 Apache 的执行用户切换到 root 权限。
- 首先用命令
lsof -i:80
查看一下 Apache 执行用户: 再给这个用户赋予 root 权限。用 Vim 打开
/etc/sudoers
,添加如下内容:1
your_exec_user ALL=(ALL) NOPASSWD:ALL
这样 Apache 执行用户使用
sudo
,并不需要密码确认。再将
/etc/sudoers
里面的配置设置好,注释掉下面这句:1
#Defaults requiretty
这样
sudo
命令不需要终端就可以执行。
再运行 PHP 页面,问题解决了!