在Java中执行SCP(Secure Copy Protocol)命令,本质是通过SSH协议实现安全的文件传输,以下是专业、可靠且安全的实现方案,重点推荐使用JSch
库(纯Java实现,无需本地命令),同时提供备选方案及安全实践。JSch
是Java的SSH2实现库,支持SCP/SFTP,无需依赖本地环境,跨平台且安全。
步骤1:添加Maven依赖
<dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency>
步骤2:实现SCP文件上传
import com.jcraft.jsch.*; public class ScpUploader { public static void main(String[] args) { String host = "your.server.com"; int port = 22; String user = "username"; String privateKeyPath = "/path/to/private_key"; // 优先使用密钥认证 String localFile = "/local/path/file.txt"; String remoteDir = "/remote/path/"; try { JSch jsch = new JSch(); // 1. 设置密钥(推荐)或密码 jsch.addIdentity(privateKeyPath); // 密钥方式 // jsch.setPassword("password"); // 密码方式(不推荐硬编码) // 2. 创建SSH会话 Session session = jsch.getSession(user, host, port); session.setConfig("StrictHostKeyChecking", "no"); // 生产环境应改为"yes" session.connect(); // 3. 执行SCP上传 Channel channel = session.openChannel("exec"); ((ChannelExec) channel).setCommand("scp -t " + remoteDir); // -t表示接收模式 try (OutputStream out = channel.getOutputStream(); InputStream in = channel.getInputStream(); FileInputStream fis = new FileInputStream(localFile)) { channel.connect(); checkAck(in); // 检查服务端响应 // 发送文件信息(权限、大小、文件名) File file = new File(localFile); String command = "C0644 " + file.length() + " " + file.getName() + "\n"; out.write(command.getBytes()); out.flush(); checkAck(in); // 传输文件内容 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) > 0) { out.write(buffer, 0, bytesRead); } out.flush(); checkAck(in); // 确认传输完成 } channel.disconnect(); session.disconnect(); System.out.println("文件上传成功"); } catch (Exception e) { e.printStackTrace(); } } // 检查SCP确认信号(0=成功, 1=错误, 2=警告) private static void checkAck(InputStream in) throws IOException { int b = in.read(); if (b == 1 || b == 2) { throw new IOException("SCP错误: " + readError(in)); } } private static String readError(InputStream in) throws IOException { StringBuilder sb = new StringBuilder(); int c; while ((c = in.read()) != '\n') { sb.append((char) c); } return sb.toString(); } }
步骤3:实现SCP文件下载
// 在Session连接后添加以下代码: Channel channel = session.openChannel("exec"); ((ChannelExec) channel).setCommand("scp -f " + remoteFile); // -f表示发送模式 try (OutputStream out = channel.getOutputStream(); InputStream in = channel.getInputStream()) { channel.connect(); out.write(0); // 发送确认字节 out.flush(); // 解析服务端发送的文件头(格式:C<权限> <大小> <文件名>) byte[] buf = new byte[1024]; int len = 0; while (true) { int c = in.read(); if (c < 0) break; if (c == '\n') { String header = new String(buf, 0, len); String[] parts = header.split(" "); long fileSize = Long.parseLong(parts[1]); // 发送确认信号 out.write(0); out.flush(); // 下载文件内容 try (FileOutputStream fos = new FileOutputStream(localPath)) { long remaining = fileSize; while (remaining > 0) { int read = in.read(buf, 0, (int) Math.min(buf.length, remaining)); fos.write(buf, 0, read); remaining -= read; } } checkAck(in); // 确认传输完成 break; } buf[len++] = (byte) c; } }
备选方案(需谨慎使用)
方案1:通过Runtime.exec()
执行本地命令
风险:依赖系统环境,存在命令注入漏洞,不推荐生产环境使用。
String command = "scp -i /path/to/key local.txt user@host:/remote/path/"; Process process = Runtime.getRuntime().exec(command); int exitCode = process.waitFor(); // 等待执行完成 if (exitCode == 0) { System.out.println("SCP成功"); } else { // 读取错误流 try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getErrorStream()) )) { String line; while ((line = reader.readLine()) != null) { System.err.println(line); } } }
方案2:使用Apache Commons VFS
适合与FTP/SFTP/SCP统一接口的场景,但性能较低。
FileSystemManager fsManager = VFS.getManager(); String scpUri = "scp://user:password@host/path/file.txt"; try (FileObject remoteFile = fsManager.resolveFile(scpUri); FileObject localFile = fsManager.resolveFile("file:/local/path/file.txt")) { localFile.copyFrom(remoteFile, Selectors.SELECT_SELF); }
安全实践与常见问题
- 认证方式优先级:
- 密钥认证:通过
jsch.addIdentity()
加载私钥(避免密码硬编码)。 - 密码认证:仅在测试时使用,通过环境变量或配置中心管理密码。
- 密钥认证:通过
- 主机密钥验证:
- 生产环境必须设置
session.setConfig("StrictHostKeyChecking", "yes")
。 - 提前将主机指纹加入
known_hosts
文件。
- 生产环境必须设置
- 资源释放:
- 确保关闭
Channel
和Session
(在finally
块或try-with-resources中操作)。
- 确保关闭
- 大文件传输:
- 使用
ChannelSftp
(SFTP协议)更高效,支持断点续传。
- 使用
- 错误处理:
- 捕获
JSchException
、IOException
,并检查SCP确认信号(checkAck()
)。
- 捕获
- 首选方案:
JSch
库(安全、跨平台、无需外部依赖)。 - 避免方案:
Runtime.exec()
(安全风险高,不可移植)。 - 高级场景:需断点续传/目录同步时,改用
ChannelSftp
或Apache Commons VFS
。
引用说明:
- JSch官方文档
- OpenSSH SCP协议规范
- 密钥生成指南:
ssh-keygen -t rsa -b 4096
(Linux/Mac)
本文代码遵循MIT许可,使用时请替换实际主机/路径并添加异常处理。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/6814.html