Apache Mina SSHD认证失败:No more authentication methods available问题排查
Apache Mina SSHD私钥认证失败问题排查(对比JSch可行实现)
问题背景
在Spring Boot微服务中,使用Apache Mina SSHD库实现SSH私钥认证时遭遇失败,报错如下:
org.apache.sshd.common.SshException: No more authentication methods available org.apache.sshd.common.future.AbstractSshFuture.verifyResult(AbstractSshFuture.java:141) ~[sshd-common-2.14.0.jar:2.14.0] org.apache.sshd.client.future.DefaultAuthFuture.verify(DefaultAuthFuture.java:56) ~[sshd-core-2.14.0.jar:2.14.0] org.apache.sshd.client.future.DefaultAuthFuture.verify(DefaultAuthFuture.java:35) ~[sshd-core-2.14.0.jar:2.14.0] org.apache.sshd.common.future.VerifiableFuture.verify(VerifiableFuture.java:43) ~[sshd-common-2.14.0.jar:2.14.0]
错误发生在session.auth().verify(ftpTimeOutInSeconds * 1000L);行。
对比之下,基于JSch的实现可正常通过私钥连接,相关代码分别如下:
Apache Mina SSHD 失败代码
try (SshClient sshClient = SshClient.setUpDefaultClient()) { sshClient.start(); try (ClientSession session = sshClient.connect(user, host, port).verify(ftpTimeOutInSeconds * 1000L).getSession()) { Path privateKeyPath = Paths.get(System.getProperty("java.io.tmpdir"), "key.pem"); FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(privateKeyPath); Iterable<KeyPair> keyPairs = fileKeyPairProvider.loadKeys(session); if (keyPairs.iterator().hasNext()) { session.addPublicKeyIdentity(keyPairs.iterator().next()); } session.auth().verify(ftpTimeOutInSeconds * 1000L); LOGGER.info("Session authenticated successfully!"); } } catch (IOException e) { LOGGER.error("I/O Exception occurred: " + e.getMessage()); } catch (Exception e) { LOGGER.error("General Exception occurred: " + e.getMessage()); }
JSch 可行代码
JSch jsch = new JSch(); Session session1 = null; try { Path privateKeyPath = Paths.get(System.getProperty("java.io.tmpdir"), "key.pem"); jsch.addIdentity(privateKeyPath.toString()); session1 = jsch.getSession(user, host, port); session1.setTimeout(ftpTimeOutInSeconds * 1000); java.util.Properties config = new java.util.Properties(); config.put("StrictHostKeyChecking", "no"); session1.setConfig(config); session1.connect(); LOGGER.info("Connected and authenticated successfully!"); } catch (JSchException e) { LOGGER.error("JSch Exception occurred: " + e.getMessage()); } finally { if (session1 != null && session1.isConnected()) { session1.disconnect(); } }
问题原因分析
- 主机密钥检查差异:JSch中显式禁用了
StrictHostKeyChecking,但SSHD默认启用该检查,可能在认证前就阻断了连接 - 私钥认证流程问题:原SSHD代码仅添加公钥身份,但未明确触发公钥认证流程,或私钥加载方式未被服务器认可
- 认证方法优先级问题:SSHD默认会尝试多种认证方法(如密码、公钥等),若其他方法失败后才尝试公钥,可能已耗尽服务器允许的认证次数
解决方法
1. 对齐主机密钥检查配置
在SSHD客户端初始化时添加禁用主机密钥检查的配置,和JSch行为保持一致:
sshClient.setConfig(ClientFactoryManager.STRICT_HOST_KEY_CHECKING, "no");
2. 简化并明确公钥认证流程
替换原私钥加载代码,使用SSHD提供的authPublicKey方法直接触发公钥认证,避免手动添加身份的不确定性:
try (SshClient sshClient = SshClient.setUpDefaultClient()) { // 禁用主机密钥检查 sshClient.setConfig(ClientFactoryManager.STRICT_HOST_KEY_CHECKING, "no"); sshClient.start(); try (ClientSession session = sshClient.connect(user, host, port) .verify(ftpTimeOutInSeconds * 1000L) .getSession()) { Path privateKeyPath = Paths.get(System.getProperty("java.io.tmpdir"), "key.pem"); // 直接调用公钥认证方法,指定用户和私钥路径 boolean authSuccess = session.authPublicKey(user, privateKeyPath) .verify(ftpTimeOutInSeconds * 1000L) .isSuccess(); if (authSuccess) { LOGGER.info("Session authenticated successfully!"); } else { LOGGER.error("Authentication failed: no valid methods succeeded"); } } } catch (IOException e) { LOGGER.error("I/O Exception occurred: " + e.getMessage()); } catch (Exception e) { LOGGER.error("General Exception occurred: " + e.getMessage()); }
3. 处理带密码的私钥(若适用)
如果私钥受密码保护,需在认证时指定密码:
// 假设privateKeyPassword是私钥的密码字符串 boolean authSuccess = session.authPublicKey(user, privateKeyPath, privateKeyPassword) .verify(ftpTimeOutInSeconds * 1000L) .isSuccess();
调试建议
- 开启DEBUG日志:在Spring Boot的
application.yml或application.properties中添加配置,查看SSHD认证的详细交互:
日志会显示服务器支持的认证方法、本地发送的认证尝试、每一步的结果,帮助定位具体失败环节。logging.level.org.apache.sshd=DEBUG - 检查私钥格式:确保私钥是标准PEM格式,若为OpenSSH新格式(开头为
-----BEGIN OPENSSH PRIVATE KEY-----),可转换为PEM格式后重试。 - 强制指定认证方法:限制SSHD仅尝试公钥认证,避免其他无关方法干扰:
session.setPreferredAuthentications("publickey"); - 抓包对比流程:使用Wireshark抓取JSch和SSHD的SSH连接报文,对比两者在认证阶段的请求差异,定位哪一步不符合服务器要求。
内容的提问来源于stack exchange,提问作者Yashasvi Raj Pant
相关产品推荐
相关产品推荐

