杰瑞科技汇

Java SSH 教程,如何快速搭建与配置?

目录

  1. SSH 与 Java 简介
  2. 准备工作:添加 JSch 依赖
  3. 核心概念:JSch 的主要组件
  4. 执行远程命令
    • 基础示例
    • 代码解析
    • 处理交互式命令(需要输入密码/yes/no)
  5. 文件传输 (SFTP)
    • 上传文件
    • 下载文件
    • 代码解析
  6. 端口转发 (隧道)
    • 本地端口转发
    • 代码解析
  7. 最佳实践与注意事项
    • 使用密钥认证代替密码
    • 使用 SessionConfig 优化连接
    • 异常处理
    • 资源关闭

SSH 与 Java 简介

SSH (Secure Shell) 是一种网络协议,用于计算机之间的安全通信和数据传输,它广泛用于系统管理员远程管理服务器、开发者部署代码等场景。

Java SSH 教程,如何快速搭建与配置?-图1
(图片来源网络,侵删)

在 Java 世界中,我们无法直接使用操作系统自带的 ssh 命令,我们需要借助第三方库来实现 SSH 功能。JSch 是 Java 平台下最流行、功能最全面的 SSH 客户端库,它实现了 SSH2 协议,支持以下核心功能:

  • 远程命令执行:就像在终端里输入命令一样。
  • 文件传输:通过 SFTP (SSH File Transfer Protocol) 安全地传输文件。
  • 端口转发:将本地或远程端口的数据通过 SSH 隧道转发,实现安全访问。

准备工作:添加 JSch 依赖

如果你使用 Maven,在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version> <!-- 建议使用最新版本 -->
</dependency>

如果你使用 Gradle,在 build.gradle 文件中添加:

implementation 'com.jcraft:jsch:0.1.55' // 建议使用最新版本

核心概念:JSch 的主要组件

在编写代码前,了解 JSch 的几个核心类非常重要:

Java SSH 教程,如何快速搭建与配置?-图2
(图片来源网络,侵删)
  • JSch: 这是主入口点,用于创建和管理 SSH 会话,所有操作都通过它来初始化。
  • Session: 代表一个到远程服务器的 SSH 会话,它包含了连接信息(主机、端口、用户名等)和配置,在执行任何操作前,必须先建立一个 Session 并连接。
  • Channel: 代表一个通信通道。Session 本身不直接传输数据,而是通过打开不同的 Channel 来实现具体功能。
    • ChannelExec: 用于执行远程命令。
    • ChannelSftp: 用于文件传输。
    • ChannelShell / ChannelExec: 用于交互式会话。
  • UserInfo: 一个接口,用于在连接过程中处理用户交互,比如输入密码、确认主机密钥等。

实战一:执行远程命令

这是最基本的功能,比如在远程服务器上执行 ls -lps aux

基础示例

import com.jcraft.jsch.*;
public class SshCommandExample {
    public static void main(String[] args) {
        String host = "your.server.com";
        String user = "your_username";
        String password = "your_password";
        int port = 22;
        JSch jsch = new JSch();
        Session session = null;
        ChannelExec channel = null;
        try {
            // 1. 创建 Session
            session = jsch.getSession(user, host, port);
            session.setPassword(password);
            // 2. 设置严格的主机密钥检查
            // 严格模式下,如果远程主机的密钥不在 known_hosts 文件中,会抛出异常
            // 为方便测试,这里设置为 NO,生产环境建议使用 YES 或 ASK
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            // 3. 连接远程服务器
            System.out.println("Connecting to " + host + "...");
            session.connect();
            // 4. 创建 Channel 执行命令
            String command = "ls -l /tmp"; // 要执行的命令
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(command);
            // 5. 获取命令的输入流并读取输出
            InputStream in = channel.getInputStream();
            InputStream err = channel.extInputStream(); // 获取错误流
            System.out.println("Executing command: " + command);
            channel.connect();
            // 读取命令的标准输出
            String output = readStream(in);
            System.out.println("--- Command Output ---");
            System.out.println(output);
            // 读取命令的错误输出
            String error = readStream(err);
            if (!error.isEmpty()) {
                System.err.println("--- Command Error ---");
                System.err.println(error);
            }
        } catch (JSchException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }
    private static String readStream(InputStream in) throws IOException {
        StringBuilder sb = new StringBuilder();
        byte[] buffer = new byte[1024];
        while (true) {
            int bytesRead = in.read(buffer);
            if (bytesRead == -1) {
                break;
            }
            sb.append(new String(buffer, 0, bytesRead));
        }
        return sb.toString();
    }
}

代码解析

  1. JSch jsch = new JSch();: 创建 JSch 实例。
  2. jsch.getSession(user, host, port);: 使用用户名、主机和端口创建一个 Session 对象。
  3. session.setPassword(password);: 设置密码。
  4. session.setConfig(config);: 设置会话配置。StrictHostKeyChecking 是一个重要配置。
    • yes: 默认值,拒绝连接不在 known_hosts 中的主机。
    • no: 自动接受所有主机密钥,不安全,仅用于测试。
    • ask: (默认行为)如果主机密钥未知,会通过 UserInfo 询问用户。
  5. session.connect();: 建立连接,这是阻塞调用,直到连接成功或失败。
  6. session.openChannel("exec");: 打开一个类型为 "exec" 的通道,用于执行命令。
  7. channel.setCommand("ls -l /tmp");: 设置要执行的命令。
  8. channel.connect();: 连接通道,命令开始执行。
  9. channel.getInputStream(): 获取命令的标准输出流。
  10. finally: 至关重要! 必须确保关闭 ChannelSession,否则会导致资源泄漏。

实战二:文件传输

使用 SFTP 协议上传或下载文件。

上传文件

import com.jcraft.jsch.*;
public class SftpUploadExample {
    public static void main(String[] args) {
        String host = "your.server.com";
        String user = "your_username";
        String password = "your_password";
        int port = 22;
        String localFile = "C:/path/to/local/file.txt";
        String remoteDir = "/tmp/"; // 远程目录,必须以 '/' 
        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;
        try {
            session = jsch.getSession(user, host, port);
            session.setPassword(password);
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
            // 打开 SFTP 通道
            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;
            System.out.println("Uploading " + localFile + " to " + remoteDir);
            // 上传文件
            channelSftp.put(localFile, remoteDir);
            System.out.println("Upload complete.");
        } catch (JSchException | SftpException e) {
            e.printStackTrace();
        } finally {
            if (channelSftp != null) {
                channelSftp.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }
}

下载文件

下载文件与上传类似,只需调用 get() 方法。

// ... (连接代码与上面相同)
// 下载文件
String remoteFile = "/tmp/file.txt";
String localDir = "C:/path/to/local/download/";
System.out.println("Downloading " + remoteFile + " to " + localDir);
// 下载文件
channelSftp.get(remoteFile, localDir);
System.out.println("Download complete.");
// ... (关闭资源代码与上面相同)

代码解析

  1. session.openChannel("sftp");: 打开类型为 "sftp" 的通道。
  2. channelSftp = (ChannelSftp) channel;: 将通 Channel 转换为 ChannelSftp
  3. channelSftp.put(localFile, remoteDir);: 上传文件,第一个参数是本地文件路径,第二个参数是远程目标路径(可以是目录或完整文件名)。
  4. channelSftp.get(remoteFile, localDir);: 下载文件,第一个参数是远程文件路径,第二个参数是本地目标路径。

实战三:端口转发

端口转发(或称 SSH 隧道)非常强大,可以让你安全地访问远程服务。

本地端口转发

场景:你的本地电脑无法直接访问数据库,但远程服务器 server.com 可以,数据库在 server.com3306 端口上运行。

目标:在本地 localhost:3307 端口上访问,实际流量会被安全地转发到 server.com3306 端口。

import com.jcraft.jsch.*;
public class PortForwardingExample {
    public static void main(String[] args) {
        String host = "your.server.com";
        String user = "your_username";
        String password = "your_password";
        int port = 22;
        // 转发配置
        int localPort = 3307;   // 本地监听端口
        String remoteHost = "localhost"; // 远程服务器上数据库的地址 (通常是 localhost)
        int remotePort = 3306;  // 远程服务器上数据库的端口
        JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession(user, host, port);
            session.setPassword(password);
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
            // 设置端口转发
            // 参数: 本地端口, 远程主机, 远程端口, Session
            session.setPortForwardingR(localPort, remoteHost, remotePort);
            System.out.println("Port forwarding established.");
            System.out.println("You can now connect to localhost:" + localPort);
            System.out.println("Press Enter to stop the forwarding.");
            // 保持程序运行,直到用户按下回车
            System.in.read();
        } catch (JSchException | IOException e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                // 关闭端口转发
                session.delPortForwardingR(localPort);
                session.disconnect();
                System.out.println("Port forwarding closed.");
            }
        }
    }
}

代码解析

  1. session.setPortForwardingR(localPort, remoteHost, remotePort);:
    • R 代表 "Remote Port Forwarding" 的本地端(即本地转发)。
    • localPort: 本地机器上要监听的端口。
    • remoteHost: 流量最终要到达的远程主机(在远程服务器看来)。
    • remotePort: 流量最终要到达的远程端口。
  2. session.delPortForwardingR(localPort);: 在关闭会话前,先取消端口转发设置。

最佳实践与注意事项

使用密钥认证代替密码

密码认证容易受到暴力破解攻击,使用 SSH 密钥对(公钥/私钥)更安全。

  1. 生成密钥对(在本地机器上,通常已经存在):

    ssh-keygen -t rsa -b 4096

    这会生成 ~/.ssh/id_rsa (私钥) 和 ~/.ssh/id_rsa.pub (公钥)。

  2. 将公钥上传到远程服务器

    ssh-copy-id -i ~/.ssh/id_rsa.pub your_username@your.server.com

    这会将你的公钥追加到远程服务器 ~/.ssh/authorized_keys 文件中。

  3. 修改 Java 代码使用密钥

    JSch jsch = new JSch();
    // 添加私钥,JSch 会自动去 ~/.ssh/ 目录下寻找
    jsch.addIdentity("~/.ssh/id_rsa"); 
    Session session = jsch.getSession("your_username", "your.server.com", 22);
    // 不再需要 setPassword
    // session.setPassword("...");
    // 注意:使用密钥时,如果服务器要求密码,你仍然需要提供 UserInfo
    // UserInfo userInfo = ...;
    // session.setUserInfo(userInfo);

使用 SessionConfig 优化连接

可以通过 setConfig 调整连接行为,

java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "ask"); // 推荐生产环境使用
config.put("PreferredAuthentications", "publickey,password"); // 优先使用密钥认证
config.put("Compression", "yes"); // 启用压缩
session.setConfig(config);

异常处理

JSchExceptionSftpException 等都是检查型异常,必须处理,异常信息通常能帮助定位问题,如认证失败、网络不通、命令不存在等。

资源关闭

务必在 finally 块中关闭所有打开的资源,顺序通常是:先关闭 Channel,再关闭 Session,如果忘记关闭,会导致服务器上的会话和通道资源无法释放,可能造成服务器资源耗尽。


本教程涵盖了使用 Java 和 JSch 库进行 SSH 操作的核心内容:

  • 执行命令:通过 ChannelExec 实现,适合自动化运维任务。
  • 文件传输:通过 ChannelSftp 实现,适合备份、部署等场景。
  • 端口转发:通过 Session.setPortForwarding 实现,是安全访问内网服务的利器。

掌握了这些,你就可以在 Java 应用中实现强大的远程服务器管理和数据交互功能,在生产环境中,请务必使用密钥认证并妥善管理你的私钥文件

分享:
扫描分享到社交APP
上一篇
下一篇