心辰·Dev

PHP 实现 Gerrit 用户密码修改

需求描述

在 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
2
$command = '"/usr/bin/htpasswd" -bs ' . $passwdfile . " " . $input_username . " " . $newpass . "2>&1";
system ( $command, $result );

切换 Apache 执行权限

当我把程序本地跑通,愉快地把这个页面 scp 到服务器上运行,问题来了。 PHP 语句里两处 htpasswd 命令都没能成功运行。打印出错误信息,发现无法在指定路径创建密码文件。那么最有可能就是 Apache 在执行命令时权限不够造成的,需要把 Apache 的执行用户切换到 root 权限。

  1. 首先用命令lsof -i:80查看一下 Apache 执行用户:
  2. 再给这个用户赋予 root 权限。用 Vim 打开 /etc/sudoers,添加如下内容:

    1
    your_exec_user ALL=(ALL) NOPASSWD:ALL

    这样 Apache 执行用户使用sudo,并不需要密码确认。

  3. 再将/etc/sudoers里面的配置设置好,注释掉下面这句:

    1
    #Defaults requiretty

    这样sudo命令不需要终端就可以执行。

再运行 PHP 页面,问题解决了!