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

Spring客户端连接SSH键盘交互式服务器时提示访问方法不支持

问题:Spring服务连接键盘交互式SSH服务器返回“access method not supported”

我正在开发一个Spring服务,需要连接支持键盘交互式认证的SSH服务器。目前已成功建立会话和通道,但通过InputStream收到服务器返回的“access method not supported”响应,这并非异常,而是服务器的正常返回内容。

用PuTTY连接该服务器时,能够正常执行命令并获取响应。通过Wireshark抓包对比发现:

  • PuTTY连接时产生58个加密SSH数据包
  • Spring服务连接时仅产生28个加密SSH数据包

当前代码实现

UserAuthKI组件

@Component
public class UserAuthKI {

  @SneakyThrows
  public String connectAndExecuteCommand(String username, String password) {
    Session session = createSession(5, username, password);
    try {
      return createChannelExec("display-alarms", session);
    } catch (Exception e) {
      return "unsuccessful connection to the ssh server";
    }
  }

  private Session createSession(int retries, String username, String password) {
    if (retries == 0) {
      throw new RuntimeException("out of tries, unable to connect to session");
    }
    try {
      String host = "xxx.xx.xxx.xxx";
      Session session = new JSch().getSession(username, host, 22);
      configureAndConnectSession(session, password);
      return session;
    } catch (Exception e) {
      return createSession(retries - 1, username, password);
    }
  }

  @SneakyThrows
  private void configureAndConnectSession(Session session, String password) {
    UserInfo info = new MyUserInfo(password);
    session.setUserInfo(info);
    java.util.Properties config = new java.util.Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);
    session.connect();
  }

  @NotNull
  @SneakyThrows
  private String createChannelExec(String command, Session session) {
    StringBuilder sb = new StringBuilder();
    ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
    channelExec.setCommand(command);
    Scanner sc = new Scanner(channelExec.getInputStream());
    channelExec.connect();
    while (sc.hasNext()) {
      sb.append(sc.next());      
    }
    sc.close();
    closeSessionAndChannel(session, channelExec);
    System.out.println(sb.toString());
    return sb.toString();
  }

  private void closeSessionAndChannel(Session session, ChannelExec channel) {
    channel.disconnect();
    session.disconnect();
  }
}

键盘交互认证实现MyUserInfo

public class MyUserInfo implements UserInfo, UIKeyboardInteractive {

  private String passwd;

  public MyUserInfo(String password) {
    this.passwd = password;
  }

  @Override
  public String getPassword() {
    return passwd;
  }

  @Override
  public boolean promptYesNo(String str) {
    return false;
  }

  @Override
  public String getPassphrase() {
    return null;
  }

  @Override
  public boolean promptPassphrase(String message) {
    return false;
  }

  @Override
  public boolean promptPassword(String message) {
    return true;
  }
  @Override
  public void showMessage(String message) {
  }

  @Override
  public String[] promptKeyboardInteractive(String destination, String name, String instruction,String[] prompt, boolean[] echo) {
    String[] response=new String[prompt.length];
    response[0] = passwd;
    return response;
  }

}

问题分析

  1. 认证方式优先级问题:JSch默认优先尝试密码认证,而非键盘交互式认证,即使实现了UIKeyboardInteractive接口,服务器可能要求必须通过键盘交互式认证才能执行命令,导致认证流程不符合要求。
  2. 键盘交互实现不完善:promptYesNo返回false可能拒绝服务器的某些确认请求,部分场景下会中断认证流程;promptKeyboardInteractive仅处理第一个提示,若服务器有多轮交互式提问会无法正确响应。
  3. 通道流处理顺序错误:先获取输入流再连接通道,可能导致无法完整捕获服务器响应;未处理错误流,可能导致通道因未读取错误输出而阻塞。
  4. 无连接超时:网络延迟可能导致认证流程不完整,数据包数量远少于PuTTY的原因是会话未完成完整的认证交互。

解决方案

1. 强制优先使用键盘交互式认证

在configureAndConnectSession方法中添加认证顺序配置:

@SneakyThrows
private void configureAndConnectSession(Session session, String password) {
    UserInfo info = new MyUserInfo(password);
    session.setUserInfo(info);
    java.util.Properties config = new java.util.Properties();
    config.put("StrictHostKeyChecking", "no");
    // 优先使用键盘交互式认证,再尝试密码认证
    config.put("PreferredAuthentications", "keyboard-interactive,password");
    session.setConfig(config);
    // 设置连接超时,避免无限等待
    session.connect(30000);
}

2. 完善键盘交互实现

调整promptYesNo方法,并确保处理所有交互式提示:

public class MyUserInfo implements UserInfo, UIKeyboardInteractive {

  private String passwd;

  public MyUserInfo(String password) {
    this.passwd = password;
  }

  @Override
  public String getPassword() {
    return passwd;
  }

  @Override
  public boolean promptYesNo(String str) {
    // 允许服务器的确认请求,比如主机密钥相关提示(根据实际场景调整)
    return true;
  }

  @Override
  public String getPassphrase() {
    return null;
  }

  @Override
  public boolean promptPassphrase(String message) {
    return false;
  }

  @Override
  public boolean promptPassword(String message) {
    return true;
  }
  
  @Override
  public void showMessage(String message) {
      // 打印服务器提示信息,便于调试
      System.out.println("Server message: " + message);
  }

  @Override
  public String[] promptKeyboardInteractive(String destination, String name, String instruction,String[] prompt, boolean[] echo) {
      String[] response = new String[prompt.length];
      // 处理所有提示,若有多个提问,根据实际情况填充响应
      for (int i = 0; i < prompt.length; i++) {
          // 假设所有提示都使用密码响应,可根据实际提示内容调整
          response[i] = passwd;
      }
      return response;
  }

}

3. 修正通道流处理逻辑

调整通道连接和流读取顺序,同时处理错误流:

@NotNull
@SneakyThrows
private String createChannelExec(String command, Session session) {
    StringBuilder sb = new StringBuilder();
    ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
    channelExec.setCommand(command);
    
    // 先连接通道
    channelExec.connect(10000);
    
    // 处理标准输出流
    Scanner sc = new Scanner(channelExec.getInputStream(), "UTF-8");
    while (sc.hasNextLine()) {
        sb.append(sc.nextLine()).append("\n");
    }
    
    // 处理错误输出流,避免通道阻塞
    Scanner errorSc = new Scanner(channelExec.getErrStream(), "UTF-8");
    while (errorSc.hasNextLine()) {
        sb.append("Error: ").append(errorSc.nextLine()).append("\n");
    }
    
    sc.close();
    errorSc.close();
    
    // 等待通道完成
    while (!channelExec.isClosed()) {
        Thread.sleep(100);
    }
    
    closeSessionAndChannel(session, channelExec);
    String result = sb.toString().trim();
    System.out.println(result);
    return result;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:15:30