背景
项目开发中需要通过Java服务远程登录机器,执行指定路径下的shell脚本文件。经过简单的技术调研后,选择了JSch。
JSch介绍
JSch(Java Secure Channel),是SSH2的一个纯Java实现。 JSch允许用户连接到一个sshd服务器,并使用端口转发,X11转发,文件传输等等功能。
使用
项目是用Maven构建的,故需要引入pom依赖
1、导入Maven依赖
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>${jsch.version}</version>
</dependency>
2、封装工具类
注:我在项目中的需求仅仅是ssh远程登录并执行shell脚本,没有考虑其他复杂场景,更多内容可以参考官方文档。
public class SSHUtil implements Closeable {
private Session session;
private Channel channel;
private ChannelExec channelExec;
private final String host;
private final Integer port;
private final String username;
private final String password;
public SSHUtil(String host, Integer port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
}
/**
* 初始化
* @throws JSchException
*/
private void init() throws JSchException {
// 创建JSch对象
JSch jSch = new JSch();
// 获取一个Session对象
session = jSch.getSession(username, host, port);
// 设置密码
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 进行连接,设置连接超时时间
session.connect(6000);
log.info("session connected!");
// 打开执行shell指令的通道
channel = session.openChannel("exec");
channelExec = (ChannelExec) channel;
}
/**
* 执行命令
* @param command
* @throws Exception
*/
public String execCmd(String command) throws Exception {
init();
if (session == null || channel == null || channelExec == null) {
throw new Exception("please invoke init(...) first!");
}
log.info("执行命令:{}", command);
channelExec.setCommand(command);
channel.setInputStream(null);
// 错误信息输出流,用于输出错误的信息
channelExec.setErrStream(System.err);
// 执行命令
channel.connect();
// 获取命令执行结果
StringBuilder sb = new StringBuilder(16);
try (InputStream in = channelExec.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr)) {
String buffer;
while ((buffer = reader.readLine()) != null) {
sb.append("\n").append(buffer);
}
} catch (Exception e) {
log.info("执行失败:{}", e.toString());
}
log.info("执行成功:{}", sb);
return sb.toString();
}
@Override
public void close() {
if (channelExec != null && channelExec.isConnected()) {
channelExec.disconnect();
}
if (channel != null && channel.isConnected()) {
channel.disconnect();
}
if (session != null && session.isConnected()) {
session.disconnect();
}
}
public static void main(String[] args) {
try(SSHUtil sshUtil = new SSHUtil("11.11.11.11",22,"xxx","xxxxxx")) {
String result = sshUtil.execCmd("sh /data/test.sh");
} catch (Exception e) {
log.error("执行失败",e);
}
}
补充:在测试过程中,代码执行报错connection refused,经排查发现运维将ssh默认的端口号22改成了其他数字,使用时发现类似情况可以借鉴。