PHP执行SFTP命令下载文件的实现问题及方案咨询
我的数据供应商仅提供SFTP连接用于下载文件,SFTP采用SSH-RSA公私钥认证,相关配置均已正确设置。我通过以下两条命令手动执行SSH已成功下载到文件:
sftp -o PORT=6010 JJEE@XX.160.XX.XX Connected to JJEE@XX.160.XX.XX sftp> get /BACKUP/01December2021.zip
为了实现自动下载,我尝试使用PHP编写脚本,先后测试了两种实现方案:
方案1
<?php if (!function_exists("ssh2_connect")) {die("function ssh2_connect doesn't exist");} $connection = ssh2_connect("XX.160.XX.XX","6010",array('hostkey'=>'ssh-rsa'),array()); $connect=ssh2_auth_pubkey_file($connection,'JJEE','/location/id_rsa.pub','/location/id_rsa',''); if ($connect) {echo "Public Key Authentication Successful <br/>";} else {echo "Public Key Authentication failed <br/>";} ?>
这个简单脚本运行时报错Public Key Authentication failed,同时触发PHP警告PHP Warning: ssh2_auth_pubkey_file(): Authentication failed for JPL_EOD using public key。我已确认id_rsa.pub和id_rsa的文件权限配置正确。
我推测是供应商仅开放了SFTP权限,导致我无法通过ssh2_connect创建SSH连接。我看到很多推荐使用phpseclib的方案,但我不想只为了两行逻辑引入第三方类库,因此尝试了第二种方案。
方案2
<?php passthru('sftp -o PORT=6010 JJEE@XX.160.XX.XX && get /BACKUP/01December2021.zip'); ?>
我在终端执行php /location-to-script/method-2.php运行该脚本时,能看到Connected to JJEE@XX.160.XX.XX的输出,但后续的get命令被识别为SSH命令而非SFTP子命令,导致下载失败。
咨询问题
- 登录SFTP后,如何正确发送get指令作为SFTP命令完成文件下载?
- 如果需要连续执行多条SFTP命令,应该如何实现?
- 是否有其他更优的实现方案?
问题解答
1. 单条SFTP命令执行方法
你方案2失败的原因是&&是Shell逻辑运算符,会先等待前面的sftp命令执行结束(退出交互模式)后才会执行后面的get指令,自然会被识别为系统命令而非SFTP子命令。
正确的做法是将SFTP指令通过标准输入传递给sftp进程,写法如下:
<?php // 单条命令直接通过字符串传递 passthru('echo "get /BACKUP/01December2021.zip" | sftp -o PORT=6010 JJEE@XX.160.XX.XX'); ?>
也可以使用Shell的Here字符串语法简化写法:
passthru('sftp -o PORT=6010 JJEE@XX.160.XX.XX <<< "get /BACKUP/01December2021.zip"');
2. 多条SFTP命令连续执行方法
如果需要执行多条命令,可以用Here文档的方式批量传递指令:
<?php $sftp_cmd = <<<EOF cd /BACKUP get 01December2021.zip get 02December2021.zip ls quit EOF; passthru('sftp -o PORT=6010 JJEE@XX.160.XX.XX << EOF '.$sftp_cmd.' EOF'); ?>
所有写在EOF之间的指令都会被当做SFTP子命令按顺序执行,不需要额外的交互逻辑。
3. 更优实现方案
- 方案A:curl调用
大部分服务器环境默认预装了curl,curl原生支持sftp协议,一行命令即可完成下载,不需要处理交互逻辑:
这个方案稳定性更高,错误返回也更规范,方便后续加错误判断逻辑。<?php passthru('curl -u JJEE: --key /location/id_rsa --pubkey /location/id_rsa.pub sftp://XX.160.XX.XX:6010/BACKUP/01December2021.zip -o /本地保存路径/01December2021.zip'); ?> - 方案B:ssh2扩展兼容调整
你之前ssh2扩展认证失败大概率不是供应商限制SSH连接,而是老版本ssh2扩展默认使用的RSA-SHA1算法被服务端禁用了,可以在连接时指定兼容的加密算法尝试修复:$methods = [ 'hostkey' => 'ssh-rsa', 'kex' => 'diffie-hellman-group-exchange-sha256', 'client_to_server' => [ 'crypt' => 'aes256-ctr,aes192-ctr,aes128-ctr', 'mac' => 'hmac-sha2-256,hmac-sha1' ], 'server_to_client' => [ 'crypt' => 'aes256-ctr,aes192-ctr,aes128-ctr', 'mac' => 'hmac-sha2-256,hmac-sha1' ] ]; $connection = ssh2_connect("XX.160.XX.XX","6010",$methods);
所有方案都需要注意:PHP脚本的运行用户(通常是www-data、apache或者php-fpm运行用户)需要有私钥文件的读取权限,以及本地下载目录的写入权限。
内容的提问来源于stack exchange,提问作者jahajee.com

