核心原理
Java 本身不直接提供 SSH 客户端功能,它依赖于第三方库来实现,这些库通常通过两种方式与 SSH 服务器通信:

- 基于 JSch (Java Secure Channel):这是 Java 中最流行、最成熟的 SSH 实现库,它是一个纯 Java 实现的 SSH2 协议库,功能非常强大,支持密码、密钥、端口转发、文件传输等,大多数 Java SSH 客户端库(如 Apache Commons VFS, Ganymed SSH-2)的底层都是基于 JSch。
- 基于 JCraft 的其他库:JSch 本身就是由 JCraft 公司开发的。
JSch 是你学习和使用 Java SSH 的首选和基础。
准备工作
在编写代码之前,请确保你已经准备好以下环境:
- Java 开发环境:安装 JDK (如 JDK 8 或更高版本) 和 IDE (如 IntelliJ IDEA 或 Eclipse)。
- Maven 项目:推荐使用 Maven 来管理依赖,可以轻松引入 JSch 库。
- Linux 服务器信息:
- IP 地址或主机名
- SSH 端口号 (默认为 22)
- 用户名
- 密码 或 SSH 私钥
添加依赖 (Maven)
在你的 pom.xml 文件中添加 JSch 的依赖:
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version> <!-- 建议使用较新稳定版本 -->
</dependency>
核心代码示例
下面我们通过几个最常用的场景来展示如何使用 JSch。

远程执行命令并获取输出
这是最基本也是最常用的功能。
import com.jcraft.jsch.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class SshCommandExecutor {
public static void main(String[] args) {
String host = "your_linux_server_ip";
String user = "your_username";
String password = "your_password";
int port = 22;
String command = "ls -l /home"; // 要执行的Linux命令
JSch jsch = new JSch();
Session session = null;
try {
// 1. 创建Session
session = jsch.getSession(user, host, port);
session.setPassword(password);
// 2. 设置严格的主机密钥检查
// 如果是第一次连接,需要将服务器的主机密钥添加到known_hosts文件中
// 否则,会抛出 JSchException: UnknownHostKey
// 为了方便测试,这里暂时禁用检查,生产环境强烈不建议这样做!
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 3. 连接服务器
System.out.println("Connecting to " + host + "...");
session.connect();
System.out.println("Connected.");
// 4. 创建Channel,类型为"exec"
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
// 5. 获取输入流,读取命令的输出
InputStream in = channel.getInputStream();
InputStream err = channel.getExtInputStream(); // 获取错误流
// 6. 执行命令
channel.connect();
// 7. 读取并打印标准输出
System.out.println("\n--- Command Output ---");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 8. 读取并打印错误输出
System.out.println("\n--- Error Output (if any) ---");
reader = new BufferedReader(new InputStreamReader(err));
while ((line = reader.readLine()) != null) {
System.err.println(line);
}
// 9. 检查退出状态码 (0表示成功,非0表示失败)
// 注意:JSch的ChannelExec不直接提供getExitStatus,需要等待channel关闭后获取
// 更好的做法是使用ChannelShell,但exec对于单次命令更轻量
// 这里我们通过检查错误流和连接状态来判断
if (channel.getExitStatus() != 0) {
System.err.println("\nCommand failed with exit status: " + channel.getExitStatus());
} else {
System.out.println("\nCommand executed successfully.");
}
} catch (JSchException | IOException e) {
e.printStackTrace();
} finally {
// 10. 关闭会话
if (session != null) {
session.disconnect();
System.out.println("Session disconnected.");
}
}
}
}
使用 SSH 密钥进行认证
使用密钥比密码更安全,你需要将你的公钥(通常是 ~/.ssh/id_rsa.pub)添加到 Linux 服务器的 ~/.ssh/authorized_keys 文件中。
import com.jcraft.jsch.*;
public class SshKeyAuthExample {
public static void main(String[] args) {
String host = "your_linux_server_ip";
String user = "your_username";
String privateKeyPath = "C:/Users/YourUser/.ssh/id_rsa"; // 你的私钥文件路径
int port = 22;
JSch jsch = new JSch();
Session session = null;
try {
// 1. 添加私钥
jsch.addIdentity(privateKeyPath);
// 2. 创建Session
session = jsch.getSession(user, host, port);
// 无需设置密码
// 3. 设置主机密钥检查
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 4. 连接服务器
System.out.println("Connecting with key to " + host + "...");
session.connect();
System.out.println("Connected.");
// ... 后续执行命令或文件传输等操作 ...
} catch (JSchException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.disconnect();
}
}
}
}
文件上传与下载 (SFTP)
SFTP (SSH File Transfer Protocol) 是基于 SSH 的安全文件传输协议。
import com.jcraft.jsch.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class SftpExample {
public static void main(String[] args) {
String host = "your_linux_server_ip";
String user = "your_username";
String password = "your_password";
int port = 22;
JSch jsch = new JSch();
Session session = null;
ChannelSftp channelSftp = null;
try {
// 1. 创建并连接Session
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();
// 2. 打开SFTP通道
Channel channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
System.out.println("SFTP Connected.");
// --- 文件上传示例 ---
String localFile = "C:/temp/local_file.txt";
String remoteDir = "/home/your_username/";
System.out.println("Uploading " + localFile + " to " + remoteDir);
File file = new File(localFile);
InputStream inputStream = new FileInputStream(file);
channelSftp.put(inputStream, remoteDir + file.getName());
inputStream.close();
System.out.println("Upload complete.");
// --- 文件下载示例 ---
String remoteFile = "/home/your_username/remote_file.txt";
String localDir = "C:/temp/downloaded/";
System.out.println("Downloading " + remoteFile + " to " + localDir);
File localDirFile = new File(localDir);
if (!localDirFile.exists()) {
localDirFile.mkdirs();
}
OutputStream outputStream = new FileOutputStream(localDir + "downloaded_file.txt");
channelSftp.get(remoteFile, outputStream);
outputStream.close();
System.out.println("Download complete.");
} catch (JSchException | SftpException | IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
if (channelSftp != null) {
channelSftp.disconnect();
}
if (session != null) {
session.disconnect();
}
System.out.println("SFTP Session disconnected.");
}
}
}
最佳实践与注意事项
-
管理 Session 和 Channel 的生命周期:
(图片来源网络,侵删)session.connect()和session.disconnect()应该放在try...finally块中,确保无论是否发生异常,连接都能被正确关闭,避免资源泄漏。- 每个
Channel(如ChannelExec,ChannelSftp) 在使用完毕后也必须调用disconnect()。
-
处理主机密钥 (
StrictHostKeyChecking):- 生产环境:绝对不要设置
StrictHostKeyChecking=no,这会使得你的连接容易受到中间人攻击。- 正确做法:在首次连接时,手动将服务器的主机密钥(指纹)记录下来,然后将
known_hosts文件或公钥内容提供给你的应用,JSch 会自动验证后续连接。
- 正确做法:在首次连接时,手动将服务器的主机密钥(指纹)记录下来,然后将
- 测试环境:为了方便,可以临时设置为
no。
- 生产环境:绝对不要设置
-
使用连接池:
- 频繁地创建和销毁 SSH 连接(
Session)是低效的,在高并发或频繁调用的场景下,应该使用连接池来管理Session对象。 - 有一些优秀的开源库基于 JSch 实现了连接池,
min-io/minio-jsch中的JSchPool,或者自己实现一个简单的Session池。
- 频繁地创建和销毁 SSH 连接(
-
处理异常:
JSchException:通常与连接、认证、通道创建等问题有关。SftpException:专门用于 SFTP 操作,如文件不存在、权限不足等,注意它的id字段,可以用来判断具体的错误类型。
-
字符编码:
- 在执行命令或处理文件时,如果涉及到非英文字符,需要注意字符编码,Linux 服务器默认使用
UTF-8,在InputStreamReader中明确指定编码是个好习惯:new BufferedReader(new InputStreamReader(in, "UTF-8"));
- 在执行命令或处理文件时,如果涉及到非英文字符,需要注意字符编码,Linux 服务器默认使用
-
超时设置:
- 为了防止程序因网络问题或服务器无响应而长时间挂起,可以设置超时时间。
session.setTimeout(10000); // 设置会话超时为10秒 channel.connect(5000); // 设置通道连接超时为5秒
- 为了防止程序因网络问题或服务器无响应而长时间挂起,可以设置超时时间。
常见问题与解决方案
-
问题1:
com.jcraft.jsch.JSchException: UnknownHostKey- 原因:JSch 缓存了已知主机密钥的文件(
~/.ssh/known_hosts),但服务器的密钥发生了变化,或者你是第一次连接。 - 解决方案:
- 临时方案:在代码中设置
config.put("StrictHostKeyChecking", "no");(仅限测试)。 - 正确方案:将服务器的新密钥添加到你的
known_hosts文件中,或者将服务器公钥字符串提供给 JSch。
- 临时方案:在代码中设置
- 原因:JSch 缓存了已知主机密钥的文件(
-
问题2:
com.jcraft.jsch.JSchException: Auth fail- 原因:认证失败,可能的原因有:用户名或密码错误、密码或密钥不匹配、服务器上的 SSH 服务配置禁止了你的认证方式(如只允许密钥登录)。
- 解决方案:仔细核对用户名、密码,并确保你的密钥已正确添加到服务器的
authorized_keys文件中。
-
问题3:读取命令输出时乱码
- 原因:Java 程序的默认编码与 Linux 终端的输出编码不一致。
- 解决方案:在创建
InputStreamReader时,显式指定编码为UTF-8。
-
问题4:
ChannelSftp操作文件时权限被拒绝- 原因:连接的用户对目标文件或目录没有读写权限。
- 解决方案:在 Linux 服务器上使用
chmod和chown命令调整文件权限和所有者。
希望这份详尽的指南能帮助你顺利地在 Java 中实现 SSH 连接!
