You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache SSHD因未定义用户主目录抛异常的解决问询

Android环境下Apache SSHD连接异常:解决"No user home"问题

问题背景

在Android中使用Apache SSHD建立SSH连接时触发java.lang.IllegalArgumentException: No user home异常,根源是SSHD库初始化时尝试获取用户主目录,但Android环境没有标准的Unix式用户主目录,导致初始化失败。

SSHD试图获取的主目录是什么?

在标准Java环境中,SSHD通过System.getProperty("user.home")获取用户主目录,用于查找默认的SSH配置文件(如~/.ssh/known_hosts)和密钥文件(如~/.ssh/id_rsa)。但在Android中,user.home属性对应的路径要么不可用,要么没有被正确初始化,导致SSHD抛出"No user home"异常。

如何定义这个主目录?

有两种可行的解决方式:

方式1:提前设置user.home系统属性

在初始化SshClient之前,将user.home设置为Android应用的私有目录(比如应用的文件目录),这个目录只有当前应用可访问,适合存储SSH相关的配置文件:

// 在调用SshClient.setUpDefaultClient()之前执行
// getFilesDir()获取应用私有文件目录,路径类似/data/data/[包名]/files
System.setProperty("user.home", getFilesDir().getAbsolutePath());

// 之后正常初始化SshClient
SshClient client = SshClient.setUpDefaultClient();
client.start();

方式2:手动构建SshClient,跳过默认配置加载

如果你的场景只使用密码认证,不需要加载本地SSH配置和密钥文件,可以手动构建SshClient,避免SSHD去查找主目录下的文件:

// 手动构建客户端,忽略默认的host key解析
SshClient client = SshClient.builder()
        .hostKeyEntryResolver(HostKeyEntryResolver.IGNORING)
        .build();
client.start();

// 后续的会话连接、认证逻辑保持不变
try (ClientSession session = client.connect(username, host, port)
        .verify(defaultTimeoutSeconds, TimeUnit.SECONDS).getSession()) {
    session.addPasswordIdentity(password);
    session.auth().verify(defaultTimeoutSeconds, TimeUnit.SECONDS);
    // ... 其余代码
} finally {
    client.stop();
}

附:异常栈追踪

FATAL EXCEPTION: main
Process: com.example.atest, PID: 22330
java.lang.ExceptionInInitializerError
    at org.apache.sshd.common.config.keys.IdentityUtils.getUserHomeFolder(IdentityUtils.java:66)
    at org.apache.sshd.common.config.keys.PublicKeyEntry$LazyDefaultKeysFolderHolder.<clinit>(PublicKeyEntry.java:501)
    at org.apache.sshd.common.config.keys.PublicKeyEntry.getDefaultKeysFolderPath(PublicKeyEntry.java:514)
    at org.apache.sshd.client.config.hosts.HostConfigEntry$LazyDefaultConfigFileHolder.<clinit>(HostConfigEntry.java:108)
    at org.apache.sshd.client.config.hosts.HostConfigEntry.getDefaultHostConfigFile(HostConfigEntry.java:1151)
    at org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver.<init>(DefaultConfigFileHostEntryResolver.java:49)
    at org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver.<clinit>(DefaultConfigFileHostEntryResolver.java:39)
    at org.apache.sshd.client.ClientBuilder.<clinit>(ClientBuilder.java:106)
    at org.apache.sshd.client.SshClient.setUpDefaultClient(SshClient.java:778)
    at com.example.atest.MainActivity.sshConnection(MainActivity.java:364)
    at com.example.atest.MainActivity.onResults(MainActivity.java:329)
    at android.speech.SpeechRecognizer$InternalRecognitionListener$1.handleMessage(SpeechRecognizer.java:1062)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:230)
    at android.os.Looper.loop(Looper.java:319)
    at android.app.ActivityThread.main(ActivityThread.java:8893)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Caused by: java.lang.IllegalArgumentException: No user home
    at org.apache.sshd.common.util.ValidateUtils.$r8$lambda$9swTO1FsdJCT_rq3b8lw5Edwaiw(Unknown Source:2)
    at org.apache.sshd.common.util.ValidateUtils$$ExternalSyntheticLambda0.apply(D8$$SyntheticClass:0)
    at org.apache.sshd.common.util.ValidateUtils.createFormattedException(ValidateUtils.java:213)
    at org.apache.sshd.common.util.ValidateUtils.throwIllegalArgumentException(ValidateUtils.java:179)
    at org.apache.sshd.common.util.ValidateUtils.checkTrue(ValidateUtils.java:156)
    at org.apache.sshd.common.util.ValidateUtils.checkNotNullAndNotEmpty(ValidateUtils.java:56)
    at org.apache.sshd.common.config.keys.IdentityUtils$LazyDefaultUserHomeFolderHolder.<clinit>(IdentityUtils.java:52)
    at org.apache.sshd.common.config.keys.IdentityUtils.getUserHomeFolder(IdentityUtils.java:66) 
    at org.apache.sshd.common.config.keys.PublicKeyEntry$LazyDefaultKeysFolderHolder.<clinit>(PublicKeyEntry.java:501) 
    at org.apache.sshd.common.config.keys.PublicKeyEntry.getDefaultKeysFolderPath(PublicKeyEntry.java:514) 
    at org.apache.sshd.client.config.hosts.HostConfigEntry$LazyDefaultConfigFileHolder.<clinit>(HostConfigEntry.java:108) 
    at org.apache.sshd.client.config.hosts.HostConfigEntry.getDefaultHostConfigFile(HostConfigEntry.java:1151) 
    at org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver.<init>(DefaultConfigFileHostEntryResolver.java:49) 
    at org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver.<clinit>(DefaultConfigFileHostEntryResolver.java:39) 
    at org.apache.sshd.client.ClientBuilder.<clinit>(ClientBuilder.java:106) 
    at org.apache.sshd.client.SshClient.setUpDefaultClient(SshClient.java:778) 
    at com.example.atest.MainActivity.sshConnection(MainActivity.java:364) 
    at com.example.atest.MainActivity.onResults(MainActivity.java:329) 
    at android.speech.SpeechRecognizer$InternalRecognitionListener$1.handleMessage(SpeechRecognizer.java:1062) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:230) 
    at android.os.Looper.loop(Looper.java:319) 
    at android.app.ActivityThread.main(ActivityThread.java:8893) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) 

附:相关代码片段

SSHD库中触发异常的代码:

@SuppressWarnings("synthetic-access")
public static Path getUserHomeFolder() {
    return LazyDefaultUserHomeFolderHolder.PATH;
}

原SSH连接方法:

private static void sshConnection(String username, String password, String host, int port, long defaultTimeoutSeconds, String command) throws IOException {
    
SshClient client = SshClient.setUpDefaultClient();
client.start();

try (ClientSession session = client.connect(username, host, port)
        .verify(defaultTimeoutSeconds, TimeUnit.SECONDS).getSession()) {
    session.addPasswordIdentity(password);
    session.auth().verify(defaultTimeoutSeconds, TimeUnit.SECONDS);

    try (ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
             ClientChannel channel = session.createChannel(Channel.CHANNEL_SHELL)) {
        channel.setOut(responseStream);
        try {
            channel.open().verify(defaultTimeoutSeconds, TimeUnit.SECONDS);
            try (OutputStream pipedIn = channel.getInvertedIn()) {
                pipedIn.write(command.getBytes());
                pipedIn.flush();
            }

            channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED),
                    TimeUnit.SECONDS.toMillis(defaultTimeoutSeconds));
            String responseString = new String(responseStream.toByteArray());
            Log.d(TAG,responseString);
        } finally {
            channel.close(false);
        }
    }
} finally {
    client.stop();
}

内容的提问来源于stack exchange,提问作者user1933677

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:20:00