尝试使用 Spring 配置服务器时如何修复 "There are not any available ciphers" JSch 异常?

How do I fix "There are not any available ciphers" JSch exception when attempting to use Spring Config Server?

我正在尝试让嵌入式 Spring 配置服务器实现正常工作,从 GitHub 读取配置。我正在学习本教程:

https://mromeh.com/2017/12/04/spring-boot-with-embedded-config-server-via-spring-cloud-config/

当我的 Spring 引导应用程序尝试启动时出现以下异常:

Caused by: com.jcraft.jsch.JSchException: There are not any available ciphers. at com.jcraft.jsch.Session.send_kexinit(Session.java:629) at com.jcraft.jsch.Session.connect(Session.java:307) at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:146) ... 23 more

我的代码中唯一有趣的部分是我的 bootstrap.yml 文件,它看起来像这样:

spring:
  application:
    name: DemoApplication.yml

---
spring:
  cloud:
    config:
      failFast: true
      server:
        bootstrap: true
        git:
          uri: git@github.com:mycompany/demo-config.git

我是 运行 MacOS 上的 OpenJDK 8 v212,根据 运行 以下内容:

#> java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)

我搜索了 Spring 代码和文档,但尚未找到有关传递配置参数或添加代码以影响 Spring 使用的 Jsch 会话的构建方式的任何信息。我发现的一切都表明我正在做的事情应该会奏效。

我不知从何而来。谁能告诉我我缺少什么...我需要做什么才能解决这个问题?

合并之前的评论...

在幕后,Spring 正在使用 JGit 建立 SSH 连接。默认情况下,这使用 JSch 建立 SSH 连接,该连接由 ~/.ssh/config 文件配置。

The wiki 也有如何绕过 JSch 和使用本地 ssh 命令的详细信息,可以设置 GIT_SSH 环境变量,例如到 OS X 或 Linux 中的 /usr/bin/ssh,甚至 C:\Program Files\TortoiseGit\bin\TortoiseGitPlink.exe.


在有关如何避免依赖于设置环境变量的评论之后,请注意如何使用 TransportGitSsh.useExtSession() 方法中的 SystemReader 检查 GIT_SSH 环境变量。

这意味着一种方法是覆盖 SystemReader class。虽然它不是一个小接口,因此会涉及一些包装代码 - getenv():

中的自定义位
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;

public class CustomSystemReader extends SystemReader {
    private final SystemReader systemReader;

    public CustomSystemReader(SystemReader systemReader) {
        this.systemReader = systemReader;
    }

    @Override
    public String getHostname() {
        return systemReader.getHostname();
    }

    @Override
    public String getenv(String variable) {
        if ("GIT_SSH".equals(variable))
            return "/usr/bin/ssh";
        return systemReader.getenv(variable);
    }

    @Override
    public String getProperty(String key) {
        return systemReader.getProperty(key);
    }

    @Override
    public FileBasedConfig openUserConfig(Config parent, FS fs) {
        return systemReader.openUserConfig(parent, fs);
    }

    @Override
    public FileBasedConfig openSystemConfig(Config parent, FS fs) {
        return systemReader.openSystemConfig(parent, fs);
    }

    @Override
    public long getCurrentTime() {
        return systemReader.getCurrentTime();
    }

    @Override
    public int getTimezone(long when) {
        return systemReader.getTimezone(when);
    }
}

然后可以像这样连接:

    SystemReader.setInstance(
            new CustomSystemReader(SystemReader.getInstance()));

@df778899 给了我解决这个问题所需的方向,声明

As you've found, by default this uses JSch, which is configured by the ~/.ssh/config file - if that exists you may find a clue in there.

我已经在该文件中寻找线索,尤其是寻找有关加密设置的任何信息。我看到我在文件顶部附近注释掉了这一行:

#   Ciphers +aes256-cbc

我错过的(我以为我已经对 "cipher" 进行了文本搜索,但很明显,我没有)是在一堆不相关的设置中隐藏在文件中间,我又在做同样的事情,这次没有注释掉:

Host *
    ...
    Ciphers +aes256-cbc
    ...

正是这条线让我对 Jsch 感到厌烦。如果我注释掉这一行,我的简单设置就可以正常工作。 @df778899 关于 ~/.ssh/config 对 Jsch 设置至关重要的声明是我需要的推动力。

这不是我的解决方案的一部分,但我要指出的是,下面的代码似乎是我如何在 Java 中设置 GIT_SSH,在我的顶部main():

public static void main(String[] args) {

    try {
        Map<String, String> env = System.getenv();
        Field field = env.getClass().getDeclaredField("m");
        field.setAccessible(true);
        ((Map<String, String>) field.get(env)).put("GIT_SSH", "/usr/bin/ssh");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    SpringApplication.run(DemoApplication.class, args);
}

这也是"solves"我的问题。关于 getDeclaredField("m") 的一点特别奇怪。 "m" 到底是什么?