如何在Java中运行OS X的sudo命令

10
我正在使用JavaFX开发一个应用程序,我试图使用终端命令打开一个应用程序,我正在使用我的Java代码运行该命令,我的命令有一些变量,它们包含了我的安装文件的路径,因为随着版本的更新,文件名可能会不同。以下是一个示例,展示如何运行命令,尽管这不是我实际运行的命令,但命令格式是相同的。
Process process = Runtime.getRuntime().exec("echo password | sudo -S open -a safari");
    String line;
    BufferedReader input = new BufferedReader(new InputStreamReader(pb.getInputStream()));
    while ((line = input.readLine()) != null) {
        System.out.println(line);
    }
    input.close();

这个过程没有任何输出,它停在那里,什么也没有发生。 我已经尝试从终端运行相同的命令,它可以正常工作。

我尝试了这个链接中提到的方法

如何在Java中使用sudo权限执行bash命令?

但是它也没有起作用。

我还从我的Java代码中运行"chmod +x"这样的命令,这些命令可以正常运行。 我的原始命令看起来像这样:

runCommand = "echo" + " " + password + "| sudo -S " + "\"" + a.getAbsolutePath() + "\"" + " --deploymentFile="
                            + "\"" + b.getAbsolutePath() + "\"";

其中a.getAbsolutePath()是安装程序文件的路径,b.getAbsolutePath()是我们用来安装应用程序的部署文件的路径。

pb.getInputStream()

打印出命令,当我将其复制并粘贴到终端中时,它可以正常运行。

pb.getErrorStream()

不返回任何内容。

我尝试运行

String[] cmd = {"/bin/bash","-c","echo tester| sudo -S ","\"",a.getAbsolutePath(),"\"","\""," --deploymentFile=","\"",b.getAbsolutePath()};

String[] cmd = {"/bin/bash","-c","echo tester| sudo -S",a.getAbsolutePath(),"--deploymentFile=","\"",b.getAbsolutePath()};

这里我得到了以下错误

getErrorStreamusage: sudo -h | -K | -k | -L | -V
getErrorStreamusage: sudo -v [-AknS] [-g groupname|#gid] [-p prompt] [-u user name|#uid]
getErrorStreamusage: sudo -l[l] [-AknS] [-g groupname|#gid] [-p prompt] [-U user name] [-u
getErrorStream            user name|#uid] [-g groupname|#gid] [command]
getErrorStreamusage: sudo [-AbEHknPS] [-C fd] [-g groupname|#gid] [-p prompt] [-u user
getErrorStream            name|#uid] [-g groupname|#gid] [VAR=value] [-i|-s] [<command>]
getErrorStreamusage: sudo -e [-AknS] [-C fd] [-g groupname|#gid] [-p prompt] [-u user
getErrorStream            name|#uid] file ...

为什么要运行 echo tester?而且,-S 不是必需的:一个简单的 sudo open -a safari 就足够了。 - fedorqui
实际上,测试员是系统的密码,我需要用这个密码运行一个命令。使用sudo不需要密码吗? - user3649361
1
如果 tester 是密码,将其 echo 出来可能不是最安全的想法... - Arc676
我已经尝试过sudo open -a safari,但结果相同。实际上,我正在尝试使用命令行安装某些东西,因此需要输入密码。 - user3649361
5个回答

10

sudo

我强烈建议编辑sudoers文件,允许运行应用程序的用户使用sudo特定命令而不提示输入密码,而不是进行echo passwd | sudo ... 构造。

以此方式可以避免在应用程序或配置文件中以明文形式(或稍微混淆)存储密码,并且避免需要调用带有shell脚本的shell来调用sudo等。

可以通过命令visudo编辑sudoers。这里提供了一个示例,说明在unbuntu上如何完成该操作,但在任何Unix上都是相同的。 https://askubuntu.com/questions/159007/how-do-i-run-specific-sudo-commands-without-a-password

其他参考:https://www.sudo.ws/man/1.8.16/sudoers.man.html

虽然我认为你正在提出错误的问题...

Mac上的授权

在Mac上,需要执行需要额外权限的操作的应用程序本应该不使用sudo开始。

应用程序应该使用授权服务。

参考文献:

  • 我需要给一个Java应用程序超级用户访问权限以查看Mac上受保护的文件

  • Mac OS X有任何图形化“sudo”吗?


  • 1
    And nothing about java. - Andremoniy
    我能通过Java运行它吗?还有,我需要在不同的系统上运行这个应用程序,我能做到吗? - user3649361
    我认为你应该研究一下在Mac上使用授权服务,而不是专注于sudo来完成你似乎正在做的事情。(编辑上面的内容以给你一些指针) - user3277192

    3

    我不建议这样做 - 如果你弄坏了什么东西,请不要对我大喊大叫 :)

    但是,您想要的可以通过以下方式实现:

    String[] cmd = {"/bin/bash","-c","echo password | sudo -S open -a <path to app>"};
    Process pb = Runtime.getRuntime().exec(cmd);
    

    3
    Runtime.exec()方法直接运行给定的命令,而不是通过shell执行。你正在使用的特定重载在空格处对命令进行标记化,并将结果标记解释为命令的名称和参数。你调用的唯一命令是...
    Runtime.getRuntime().exec("echo password | sudo -S open -a safari");
    

    因此需要打印到标准输出的是echo;其他所有内容都是参数。结果是将以下输出发送到进程的标准输出:

    password | sudo -S open -a safari


    有至少两种方法可以实现您想要的内容。对您原始代码的最简单修改可能是:

    Process process = Runtime.getRuntime().exec(
            new String[] { "bash", "-c", "echo password | sudo -S open -a safari" });
    

    通过显式调用shell运行命令,可以实现您认为自己正在获取的内容。

    但这是一个重大的安全风险,因为密码将以明文形式出现在进程列表中。相反,您可以让Java程序直接输入密码,从而避免涉及到bash

    Process process = Runtime.getRuntime().exec("/usr/bin/sudo -S open -a safari");
    Writer toSudo = new OutputStreamWriter(process.getOutputStream());
    String password = "password";
    
    toSudo.write(password);
    toSudo.write('\n');  // sudo's docs demand a newline after the password
    toSudo.close();      // but closing the stream might be sufficient
    

    其他考虑事项:

    • 明智的做法是给sudo命令提供完整的路径,以避免运行在路径中首先找到的不同的sudo命令。由于您将为特权帐户提供密码,因此减少运行假程序的可能性非常重要。

    • 也应该避免将密码存储在程序或配置文件中; 因此,涉及将密码提供给sudo的解决方案也应涉及从用户交互式输入密码。

    • 有安全意识的Java程序通常更喜欢将密码保存在char数组中,而不是在String中,以便可以在不再需要时主动清除。 String无法更改,并且它们可能会在VM内无限期挂起(甚至比许多其他对象更长时间)。

    • 一般来说,无论是否对内容进行任何处理,都需要同时排空Process的输入和错误流。如果不这样做,则外部进程可能会阻塞和/或可能无法退出。然而,这对于特定的safari来说可能不是问题,因为我认为它通常不会在这些流上产生任何输出。


    3

    我认为你必须使用应用程序的完整路径。这应该可以解决问题:

    Runtime rt = Runtime.getRuntime();
    rt.exec("echo password | sudo -S open -a /Applications/Safari.app");
    
    更新: 根据您的评论,您可以尝试将该过程分开。很有可能open需要一个交互式会话。
    创建一个脚本(例如openSafari.sh),它将作为用户打开Safari。
    #!/etc/bash
    echo $1 | sudo -S open -a /Applications/Safari.app &
    

    使其可执行:chmod +x openSafari.sh,然后从Java中调用该脚本。
    Runtime rt = Runtime.getRuntime();
    rt.exec("/pathTo/openSafari.sh 'sudoPassword'");
    

    这似乎也不起作用,像“ditto -xkV”+ filepathwos +“”+ outputPath; 这样的命令可以正常工作,但需要启动某些应用程序的命令似乎无法正常工作。 - user3649361
    我的命令有一些变量,例如safari,它包含了我安装文件的路径,但由于版本更新,文件名可能会不同,因此路径也不总是相同的,那么在这种情况下,我是否可以使用脚本? - user3649361

    0

    或许可以使用这个

    如何设置SUDO_ASKPASS环境变量?

    您可以在进程调用中设置SUDO_PROMPT环境变量

    将其与Jon Bollinger、Maciej等人提到的处理方式修复结合起来。

    然后,让它为您的用户带来一个密码提示,或者访问一个包含您凭据的PKI。(如果您选择PKI,请至少使用AES加密)

    正如您所说,这是要在其他人的机器上运行的,它基本上就是一个糟糕的UAC提示,这听起来不像是一个非常适合Mac/Linux的解决方案。Swa66是最好的方法,但这样做会快速而肮脏。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接