1. JSch介绍
JSch 是SSH2的纯Java实现。JSch允许连接到sshd的服务器并使用端口转发、X11转发、文件传输等,并且可以将其功能集成到自己的Java程序中。
2. 实现原理
- 根据远程主机的IP地址、用户名和密码,建立会话session
- 设置用户信息(包括密码和Userinfo),然后连接session,getSession()只是创建一个session,需要设置必要的认证信息之后,调用connect()才能建立连接
- 设置channel上需要远程执行的shell脚本,连接channel,就可以远程执行该shell脚本,调用openChannel(String type)可以在session上打开指定类型的channel。该channel只是被初始化,使用前需要先调用connect()进行连接
- 可以读取远程执行shell脚本的输出,然后依次断开channel和session的连接
3. 代码工程
实现文件上传到服务器,服务器下载文件以及执行服务器命令
- 引入pom - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.example</groupId>
 <artifactId>JSchDemo</artifactId>
 <version>1.0</version>
 <name>JSchDemo</name>
 <url>http://www.example.com</url>
 <properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <maven.compiler.source>1.8</maven.compiler.source>
 <maven.compiler.target>1.8</maven.compiler.target>
 </properties>
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 <version>2.4.5</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-autoconfigure</artifactId>
 <version>2.4.5</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 <version>2.4.5</version>
 </dependency>
 <dependency>
 <groupId>com.jcraft</groupId>
 <artifactId>jsch</artifactId>
 <version>0.1.55</version>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.24</version>
 </dependency>
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.78</version>
 </dependency>
 <dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.12</version>
 <scope>test</scope>
 </dependency>
 </dependencies>
 </project>
- Remote实体类 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 public class Remote {
 private String host;
 private final int port = 22;
 private String user;
 private String password;
 private final String identity = "~/.ssh/id_rsa";
 private String passphrase;
 }
- JschUtils工具类 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493- package org.example; 
 import com.jcraft.jsch.*;
 import lombok.extern.slf4j.Slf4j;
 import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Function;
 /**
 * @author xiaoyuge
 */
 public class JschUtils {
 public static final int SESSION_TIMEOUT = 30000;
 public static final int CONNECT_TIMEOUT = 3000;
 public static Session getSession(Remote remote) throws JSchException {
 JSch jSch = new JSch();
 if (Files.exists(Paths.get(remote.getIdentity()))) {
 jSch.addIdentity(remote.getIdentity(), remote.getPassphrase());
 }
 Session session = jSch.getSession(remote.getUser(), remote.getHost(), remote.getPort());
 session.setPassword(remote.getPassword());
 session.setConfig("StrictHostKeyChecking", "no");
 return session;
 }
 public static List<String> remoteExecute(Session session, String command) throws JSchException {
 log.debug(">> {}", command);
 List<String> resultLines = new ArrayList<>();
 ChannelExec channel = null;
 try {
 channel = openExecChannel(session);
 channel.setCommand(command);
 InputStream input = channel.getInputStream();
 channel.connect(CONNECT_TIMEOUT);
 try {
 BufferedReader inputReader = new BufferedReader(new InputStreamReader(input));
 String inputLine;
 while ((inputLine = inputReader.readLine()) != null) {
 log.debug(" {}", inputLine);
 resultLines.add(inputLine);
 }
 } finally {
 if (input != null) {
 try {
 input.close();
 } catch (Exception e) {
 log.error("JSch inputStream close error:", e);
 }
 }
 }
 } catch (IOException e) {
 log.error("IOException:", e);
 } finally {
 disconnect(channel);
 }
 return resultLines;
 }
 /**
 * scp file to remote server
 *
 * @param session session
 * @param source local file
 * @param destination remote target file
 * @return file size
 */
 public static long scpTo(Session session, String source, String destination) {
 FileInputStream fileInputStream = null;
 ChannelExec channel = null;
 try {
 channel = openExecChannel(session);
 OutputStream out = channel.getOutputStream();
 InputStream in = channel.getInputStream();
 boolean ptimestamp = false;
 String command = "scp";
 if (ptimestamp) {
 command += " -p";
 }
 command += " -t " + destination;
 channel.setCommand(command);
 channel.connect(CONNECT_TIMEOUT);
 if (checkAck(in) != 0) {
 return -1;
 }
 File _lfile = new File(source);
 if (ptimestamp) {
 command = "T " + (_lfile.lastModified() / 1000) + " 0";
 // The access time should be sent here,
 // but it is not accessible with JavaAPI ;-<
 command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
 out.write(command.getBytes());
 out.flush();
 if (checkAck(in) != 0) {
 return -1;
 }
 }
 //send "C0644 filesize filename", where filename should not include '/'
 long fileSize = _lfile.length();
 command = "C0644 " + fileSize + " ";
 if (source.lastIndexOf('/') > 0) {
 command += source.substring(source.lastIndexOf('/') + 1);
 } else {
 command += source;
 }
 command += "\n";
 out.write(command.getBytes());
 out.flush();
 if (checkAck(in) != 0) {
 return -1;
 }
 //send content of file
 fileInputStream = new FileInputStream(source);
 byte[] buf = new byte[1024];
 long sum = 0;
 while (true) {
 int len = fileInputStream.read(buf, 0, buf.length);
 if (len <= 0) {
 break;
 }
 out.write(buf, 0, len);
 sum += len;
 }
 //send '\0'
 buf[0] = 0;
 out.write(buf, 0, 1);
 out.flush();
 if (checkAck(in) != 0) {
 return -1;
 }
 return sum;
 } catch (JSchException e) {
 log.error("scp to caught jsch exception, ", e);
 } catch (IOException e) {
 log.error("scp to caught io exception, ", e);
 } catch (Exception e) {
 log.error("scp to error, ", e);
 } finally {
 closeInputStream(fileInputStream);
 disconnect(channel);
 }
 return -1;
 }
 /**
 * scp remote file to local
 *
 * @param session session
 * @param source remote file
 * @param destination local file
 * @return file size
 */
 public static long scpFrom(Session session, String source, String destination) {
 FileOutputStream fileOutputStream = null;
 ChannelExec channel = null;
 try {
 channel = openExecChannel(session);
 channel.setCommand("scp -f " + source);
 OutputStream out = channel.getOutputStream();
 InputStream in = channel.getInputStream();
 channel.connect();
 byte[] buf = new byte[1024];
 //send '\0'
 buf[0] = 0;
 out.write(buf, 0, 1);
 out.flush();
 while (true) {
 if (checkAck(in) != 'C') {
 break;
 }
 }
 //read '644 '
 in.read(buf, 0, 4);
 long fileSize = 0;
 while (true) {
 if (in.read(buf, 0, 1) < 0) {
 break;
 }
 if (buf[0] == ' ') {
 break;
 }
 fileSize = fileSize * 10L + (long) (buf[0] - '0');
 }
 String file = null;
 for (int i = 0; ; i++) {
 in.read(buf, i, 1);
 if (buf[i] == (byte) 0x0a) {
 file = new String(buf, 0, i);
 break;
 }
 }
 // send '\0'
 buf[0] = 0;
 out.write(buf, 0, 1);
 out.flush();
 // read a content of lfile
 if (Files.isDirectory(Paths.get(destination))) {
 fileOutputStream = new FileOutputStream(destination + File.separator + file);
 } else {
 fileOutputStream = new FileOutputStream(destination);
 }
 long sum = 0;
 while (true) {
 int len = in.read(buf, 0, buf.length);
 if (len <= 0) {
 break;
 }
 sum += len;
 if (len >= fileSize) {
 fileOutputStream.write(buf, 0, (int) fileSize);
 break;
 }
 fileOutputStream.write(buf, 0, len);
 fileSize -= len;
 }
 return sum;
 } catch (JSchException e) {
 log.error("scp to caught jsch exception, ", e);
 } catch (IOException e) {
 log.error("scp to caught io exception, ", e);
 } catch (Exception e) {
 log.error("scp to error, ", e);
 } finally {
 closeOutputStream(fileOutputStream);
 disconnect(channel);
 }
 return -1;
 }
 /**
 * remote edit
 *
 * @param session session
 * @param source target file
 * @param process edit command collect
 * @return isSuccess
 */
 private static boolean remoteEdit(Session session, String source, Function<List<String>, List<String>> process) {
 InputStream in = null;
 OutputStream out = null;
 try {
 String fileName = source;
 int index = source.lastIndexOf('/');
 if (index >= 0) {
 fileName = source.substring(index + 1);
 }
 //backup source
 remoteExecute(session, String.format("cp %s %s", source, source + ".bak." + System.currentTimeMillis()));
 //scp from remote
 String tmpSource = System.getProperty("java.io.tmpdir") + session.getHost() + "-" + fileName;
 scpFrom(session, source, tmpSource);
 in = new FileInputStream(tmpSource);
 //edit file according function process
 String tmpDestination = tmpSource + ".des";
 out = new FileOutputStream(tmpDestination);
 List<String> inputLines = new ArrayList<>();
 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
 String inputLine = null;
 while ((inputLine = reader.readLine()) != null) {
 inputLines.add(inputLine);
 }
 List<String> outputLines = process.apply(inputLines);
 for (String outputLine : outputLines) {
 out.write((outputLine + "\n").getBytes());
 out.flush();
 }
 //scp to remote
 scpTo(session, tmpDestination, source);
 return true;
 } catch (Exception e) {
 log.error("remote edit error, ", e);
 return false;
 } finally {
 closeInputStream(in);
 closeOutputStream(out);
 }
 }
 /**
 * update file
 *
 * @param session session
 * @param in file stream
 * @param directory local dir
 * @param fileName FTP server file name:xxx.txt ||xxx.txt.zip
 */
 public static boolean uploadFile(Session session, InputStream in, String directory, String fileName) {
 log.info(">>>>>>>>uploadFile--ftp start>>>>>>>>>>>>>");
 ChannelSftp channel = null;
 try {
 channel = openSftpChannel(session);
 channel.connect(CONNECT_TIMEOUT);
 String[] folders = directory.split("/");
 try {
 for (int i = 0; i < folders.length; i++) {
 if (i == 0 && folders[i].length() == 0) {
 channel.cd("/");
 } else if (folders[i].length() > 0) {
 try {
 channel.cd(folders[i]);
 } catch (SftpException e) {
 channel.mkdir(folders[i]);
 channel.cd(folders[i]);
 }
 }
 }
 } catch (SftpException e) {
 log.error("ftp create file fail" + directory, e);
 return false;
 }
 try {
 channel.put(in, fileName);
 } catch (SftpException e) {
 log.error("sftp error-->" + e.getMessage(), e);
 return false;
 }
 log.info(">>>>>>>>uploadFile--ftp upload end>>>>>>>>>>>>>");
 log.info(">>>>>>>>ftp upload dir:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
 return true;
 } catch (JSchException e) {
 log.error("JSch error-->" + e.getMessage(), e);
 return false;
 } finally {
 closeInputStream(in);
 disconnect(channel);
 }
 }
 /**
 * @param channel sftp connect
 * @param directory
 * @param fileName
 * @return
 */
 public static InputStream stream(ChannelSftp channel, String directory, String fileName) {
 try {
 channel.connect(CONNECT_TIMEOUT);
 InputStream inputStream = channel.get(directory + "/" + fileName);
 log.info(">>>>>>>>ftp file directory:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
 return inputStream;
 } catch (SftpException e) {
 log.error("sftp error-->" + e.getMessage());
 return null;
 } catch (JSchException e) {
 log.error("JSch error-->" + e.getMessage());
 return null;
 }
 }
 /**
 * ftp delete remote file
 *
 * @param session session
 * @param directory directory
 * @param fileName filename
 * @return is Success
 */
 public static boolean deleteFile(Session session, String directory, String fileName) {
 log.info(">>>>>>>>deleteFile--ftp delete file end>>>>>>>>>>>>>");
 ChannelSftp channel = null;
 try {
 channel = openSftpChannel(session);
 channel.connect(CONNECT_TIMEOUT);
 channel.rm(directory + "/" + fileName);
 log.info(">>>>>>>>deleteFile--deletefile end>>>>>>>>>>>>>");
 log.info(">>>>>>>>ftp delete file directory:{},filename:{}>>>>>>>>>>>>>", directory, fileName);
 } catch (SftpException e) {
 log.error("ftp create directory fail" + directory);
 return false;
 } catch (JSchException e) {
 log.error("JSch error-->" + e.getMessage());
 return false;
 } finally {
 disconnect(channel);
 }
 return true;
 }
 public static Channel openChannel(Session session, String type) throws JSchException {
 if (!session.isConnected()) {
 session.connect(SESSION_TIMEOUT);
 }
 return session.openChannel(type);
 }
 public static ChannelSftp openSftpChannel(Session session) throws JSchException {
 return (ChannelSftp) openChannel(session, "sftp");
 }
 public static ChannelExec openExecChannel(Session session) throws JSchException {
 return (ChannelExec) openChannel(session, "exec");
 }
 /**
 * disconnect
 *
 * @param session
 */
 public static void disconnect(Session session) {
 if (session != null) {
 if (session.isConnected()) {
 try {
 session.disconnect();
 log.info("session disconnect successfully");
 } catch (Exception e) {
 log.error("JSch session disconnect error:", e);
 }
 }
 }
 }
 /**
 * close connection
 *
 * @param channel channel connection
 */
 public static void disconnect(Channel channel) {
 if (channel != null) {
 if (channel.isConnected()) {
 try {
 channel.disconnect();
 log.info("channel is closed already");
 } catch (Exception e) {
 log.error("JSch channel disconnect error:", e);
 }
 }
 }
 }
 public static int checkAck(InputStream in) throws IOException {
 int b = in.read();
 // b may be 0 for success,
 // 1 for error,
 // 2 for fatal error,
 // -1
 if (b == 0) {
 return b;
 }
 if (b == -1) {
 return b;
 }
 if (b == 1 || b == 2) {
 StringBuilder sb = new StringBuilder();
 int c;
 do {
 c = in.read();
 sb.append((char) c);
 }
 while (c != '\n');
 if (b == 1) { // error
 log.debug(sb.toString());
 }
 if (b == 2) { // fatal error
 log.debug(sb.toString());
 }
 }
 return b;
 }
 public static void closeInputStream(InputStream in) {
 if (in != null) {
 try {
 in.close();
 } catch (IOException e) {
 log.error("Close input stream error." + e.getMessage());
 }
 }
 }
 public static void closeOutputStream(OutputStream out) {
 if (out != null) {
 try {
 out.close();
 } catch (IOException e) {
 log.error("Close output stream error." + e.getMessage());
 }
 }
 }
 }
- 启动类Application - 1 
 2
 3
 4
 5
 6
 public class DemoApplication {
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 }
 }
- 测试 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76- package org.example; 
 import com.alibaba.fastjson.JSONObject;
 import com.jcraft.jsch.JSchException;
 import com.jcraft.jsch.Session;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.jupiter.api.Test;
 import org.junit.runner.RunWith;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.List;
 public class AppTest {
 private Logger log = LoggerFactory.getLogger(getClass());
 Session session;
 
 public void before() throws JSchException {
 Remote remote= new Remote();
 remote.setHost("xxxx");
 remote.setUser("xxxx");
 remote.setPassword("xxxx");
 session= JschUtils.getSession(remote);
 }
 
 public void after(){
 JschUtils.disconnect(session);
 }
 
 public void remoteExecute() throws JSchException {
 List<String> list= JschUtils.remoteExecute(session,"ls");
 System.out.println(JSONObject.toJSON(list));
 }
 
 public void uploadFile() throws JSchException, FileNotFoundException {
 String filestr ="D:\\tmp\\test\\file_utils\\file1.txt";
 File file = new File(filestr);
 InputStream in = new FileInputStream(file);
 String directory="/root/test";
 String fileName="test.txt";
 boolean flag= JschUtils.uploadFile(session,in,directory,fileName);
 System.out.println(flag);
 }
 
 public void deleteFile() throws JSchException, FileNotFoundException {
 String directory="/root/test";
 String fileName="test.txt";
 boolean flag= JschUtils.deleteFile(session,directory,fileName);
 System.out.println(flag);
 }
 
 public void scpFrom() throws JSchException, FileNotFoundException {
 String source="/root/test/file1.txt";
 String destination ="D:\\tmp\\scfFrom.txt";
 long filesize= JschUtils.scpFrom(session,source,destination);
 System.out.println(filesize);
 }
 
 public void scpTo() throws JSchException, FileNotFoundException {
 String filestr ="D:\\tmp\\test\\file_utils\\file1.txt";
 String destination="/root/test/file1.txt";
 long filesize= JschUtils.scpTo(session,filestr,destination);
 System.out.println(filesize);
 }
 }
 
         
              