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
相关产品推荐
相关产品推荐

