631 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cn.hutool.extra.ssh;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.LocalPortGenerater;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Jsch工具类<br>
* Jsch是Java Secure Channel的缩写。JSch是一个SSH2的纯Java实现。<br>
* 它允许你连接到一个SSH服务器并且可以使用端口转发X11转发文件传输等。<br>
*
* @author Looly
* @since 4.0.0
*/
public class JschUtil {
/**
* 不使用SSH的值
*/
public final static String SSH_NONE = "none";
/**
* 本地端口生成器
*/
private static final LocalPortGenerater portGenerater = new LocalPortGenerater(10000);
/**
* 生成一个本地端口,用于远程端口映射
*
* @return 未被使用的本地端口
*/
public static int generateLocalPort() {
return portGenerater.generate();
}
/**
* 获得一个SSH会话重用已经使用的会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param sshPass 密码
* @return SSH会话
*/
public static Session getSession(String sshHost, int sshPort, String sshUser, String sshPass) {
return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, sshPass);
}
/**
* 获得一个SSH会话重用已经使用的会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKeyPath 私钥路径
* @param passphrase 私钥密码
* @return SSH会话
*/
public static Session getSession(String sshHost, int sshPort, String sshUser, String privateKeyPath, byte[] passphrase) {
return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase);
}
/**
* 获得一个SSH会话重用已经使用的会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKey 私钥内容
* @param passphrase 私钥密码
* @return SSH会话
* @since 5.8.18
*/
public static Session getSession(String sshHost, int sshPort, String sshUser, byte[] privateKey, byte[] passphrase) {
return JschSessionPool.INSTANCE.getSession(sshHost, sshPort, sshUser, privateKey, passphrase);
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param sshPass 密码
* @return SSH会话
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) {
return openSession(sshHost, sshPort, sshUser, sshPass, 0);
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param sshPass 密码
* @param timeout Socket连接超时时长单位毫秒
* @return SSH会话
* @since 5.3.3
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass, int timeout) {
final Session session = createSession(sshHost, sshPort, sshUser, sshPass);
try {
session.connect(timeout);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return session;
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKeyPath 私钥的路径
* @param passphrase 私钥文件的密码可以为null
* @return SSH会话
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, String privateKeyPath, byte[] passphrase) {
return openSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase, 0);
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKey 私钥内容
* @param passphrase 私钥文件的密码可以为null
* @return SSH会话
* @since 5.8.18
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, byte[] privateKey, byte[] passphrase) {
return openSession(sshHost, sshPort, sshUser, privateKey, passphrase, 0);
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKey 私钥内容
* @param passphrase 私钥文件的密码可以为null
* @return SSH会话
* @since 5.8.18
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, byte[] privateKey, byte[] passphrase, int timeOut) {
final Session session = createSession(sshHost, sshPort, sshUser, privateKey, passphrase);
try {
session.connect(timeOut);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return session;
}
/**
* 打开一个新的SSH会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名
* @param privateKeyPath 私钥的路径
* @param passphrase 私钥文件的密码可以为null
* @param timeOut 超时时间,单位毫秒
* @return SSH会话
* @since 5.8.4
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, String privateKeyPath, byte[] passphrase, int timeOut) {
final Session session = createSession(sshHost, sshPort, sshUser, privateKeyPath, passphrase);
try {
session.connect(timeOut);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return session;
}
/**
* 新建一个新的SSH会话此方法并不打开会话既不调用connect方法
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名如果为null默认root
* @param sshPass 密码
* @return SSH会话
* @since 4.5.2
*/
public static Session createSession(String sshHost, int sshPort, String sshUser, String sshPass) {
final JSch jsch = new JSch();
final Session session = createSession(jsch, sshHost, sshPort, sshUser);
if (StrUtil.isNotEmpty(sshPass)) {
session.setPassword(sshPass);
}
return session;
}
/**
* 新建一个新的SSH会话此方法并不打开会话既不调用connect方法
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名如果为null默认root
* @param privateKeyPath 私钥的路径
* @param passphrase 私钥文件的密码可以为null
* @return SSH会话
* @since 5.0.0
*/
public static Session createSession(String sshHost, int sshPort, String sshUser, String privateKeyPath, byte[] passphrase) {
Assert.notEmpty(privateKeyPath, "PrivateKey Path must be not empty!");
final JSch jsch = new JSch();
try {
jsch.addIdentity(privateKeyPath, passphrase);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return createSession(jsch, sshHost, sshPort, sshUser);
}
/**
* 新建一个新的SSH会话此方法并不打开会话既不调用connect方法
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名如果为null默认root
* @param privateKey 私钥内容
* @param passphrase 私钥文件的密码可以为null
* @return SSH会话
* @since 5.8.18
*/
public static Session createSession(String sshHost, int sshPort, String sshUser, byte[] privateKey, byte[] passphrase) {
Assert.isTrue(privateKey != null && privateKey.length > 0, "PrivateKey must be not empty!");
final JSch jsch = new JSch();
final String identityName = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
try {
jsch.addIdentity(identityName, privateKey, null, passphrase);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return createSession(jsch, sshHost, sshPort, sshUser);
}
/**
* 创建一个SSH会话重用已经使用的会话
*
* @param jsch {@link JSch}
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名如果为null默认root
* @return {@link Session}
* @since 5.0.3
*/
public static Session createSession(JSch jsch, String sshHost, int sshPort, String sshUser) {
Assert.notEmpty(sshHost, "SSH Host must be not empty!");
Assert.isTrue(sshPort > 0, "SSH port must be > 0");
// 默认root用户
if (StrUtil.isEmpty(sshUser)) {
sshUser = "root";
}
if (null == jsch) {
jsch = new JSch();
}
Session session;
try {
session = jsch.getSession(sshUser, sshHost, sshPort);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
// 设置第一次登录的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");
return session;
}
/**
* 绑定端口到本地。 一个会话可绑定多个端口
*
* @param session 需要绑定端口的SSH会话
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localPort 本地端口
* @return 成功与否
* @throws JschRuntimeException 端口绑定失败异常
*/
public static boolean bindPort(Session session, String remoteHost, int remotePort, int localPort) throws JschRuntimeException {
return bindPort(session, remoteHost, remotePort, "127.0.0.1", localPort);
}
/**
* 绑定端口到本地。 一个会话可绑定多个端口
*
* @param session 需要绑定端口的SSH会话
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localHost 本地主机
* @param localPort 本地端口
* @return 成功与否
* @throws JschRuntimeException 端口绑定失败异常
* @since 5.7.8
*/
public static boolean bindPort(Session session, String remoteHost, int remotePort, String localHost, int localPort) throws JschRuntimeException {
if (session != null && session.isConnected()) {
try {
session.setPortForwardingL(localHost, localPort, remoteHost, remotePort);
} catch (JSchException e) {
throw new JschRuntimeException(e, "From [{}:{}] mapping to [{}:{}] error", remoteHost, remotePort, localHost, localPort);
}
return true;
}
return false;
}
/**
* 绑定ssh服务端的serverPort端口, 到host主机的port端口上. <br>
* 即数据从ssh服务端的serverPort端口, 流经ssh客户端, 达到host:port上.
*
* @param session 与ssh服务端建立的会话
* @param bindPort ssh服务端上要被绑定的端口
* @param host 转发到的host
* @param port host上的端口
* @return 成功与否
* @throws JschRuntimeException 端口绑定失败异常
* @since 5.4.2
*/
public static boolean bindRemotePort(Session session, int bindPort, String host, int port) throws JschRuntimeException {
if (session != null && session.isConnected()) {
try {
session.setPortForwardingR(bindPort, host, port);
} catch (JSchException e) {
throw new JschRuntimeException(e, "From [{}] mapping to [{}] error", bindPort, port);
}
return true;
}
return false;
}
/**
* 解除端口映射
*
* @param session 需要解除端口映射的SSH会话
* @param localPort 需要解除的本地端口
* @return 解除成功与否
*/
public static boolean unBindPort(Session session, int localPort) {
try {
session.delPortForwardingL(localPort);
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return true;
}
/**
* 打开SSH会话并绑定远程端口到本地的一个随机端口
*
* @param sshConn SSH连接信息对象
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @return 映射后的本地端口
* @throws JschRuntimeException 连接异常
*/
public static int openAndBindPortToLocal(Connector sshConn, String remoteHost, int remotePort) throws JschRuntimeException {
final Session session = openSession(sshConn.getHost(), sshConn.getPort(), sshConn.getUser(), sshConn.getPassword());
final int localPort = generateLocalPort();
bindPort(session, remoteHost, remotePort, localPort);
return localPort;
}
/**
* 打开SFTP连接
*
* @param session Session会话
* @return {@link ChannelSftp}
* @since 4.0.3
*/
public static ChannelSftp openSftp(Session session) {
return openSftp(session, 0);
}
/**
* 打开SFTP连接
*
* @param session Session会话
* @param timeout 连接超时时长,单位毫秒
* @return {@link ChannelSftp}
* @since 5.3.3
*/
public static ChannelSftp openSftp(Session session, int timeout) {
return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout);
}
/**
* 创建Sftp
*
* @param sshHost 远程主机
* @param sshPort 远程主机端口
* @param sshUser 远程主机用户名
* @param sshPass 远程主机密码
* @return {@link Sftp}
* @since 4.0.3
*/
public static Sftp createSftp(String sshHost, int sshPort, String sshUser, String sshPass) {
return new Sftp(sshHost, sshPort, sshUser, sshPass);
}
/**
* 创建Sftp
*
* @param session SSH会话
* @return {@link Sftp}
* @since 4.0.5
*/
public static Sftp createSftp(Session session) {
return new Sftp(session);
}
/**
* 打开Shell连接
*
* @param session Session会话
* @return {@link ChannelShell}
* @since 4.0.3
*/
public static ChannelShell openShell(Session session) {
return (ChannelShell) openChannel(session, ChannelType.SHELL);
}
/**
* 打开Channel连接
*
* @param session Session会话
* @param channelType 通道类型可以是shell或sftp等见{@link ChannelType}
* @return {@link Channel}
* @since 4.5.2
*/
public static Channel openChannel(Session session, ChannelType channelType) {
return openChannel(session, channelType, 0);
}
/**
* 打开Channel连接
*
* @param session Session会话
* @param channelType 通道类型可以是shell或sftp等见{@link ChannelType}
* @param timeout 连接超时时长,单位毫秒
* @return {@link Channel}
* @since 5.3.3
*/
public static Channel openChannel(Session session, ChannelType channelType, int timeout) {
final Channel channel = createChannel(session, channelType);
try {
channel.connect(Math.max(timeout, 0));
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return channel;
}
/**
* 创建Channel连接
*
* @param session Session会话
* @param channelType 通道类型可以是shell或sftp等见{@link ChannelType}
* @return {@link Channel}
* @since 4.5.2
*/
public static Channel createChannel(Session session, ChannelType channelType) {
Channel channel;
try {
if (false == session.isConnected()) {
session.connect();
}
channel = session.openChannel(channelType.getValue());
} catch (JSchException e) {
throw new JschRuntimeException(e);
}
return channel;
}
/**
* 执行Shell命令
*
* @param session Session会话
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @return {@link ChannelExec}
* @since 4.0.3
*/
public static String exec(Session session, String cmd, Charset charset) {
return exec(session, cmd, charset, System.err);
}
/**
* 执行Shell命令使用EXEC方式
* <p>
* 此方法单次发送一个命令到服务端不读取环境变量执行结束后自动关闭channel不会产生阻塞。
* </p>
*
* @param session Session会话
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @param errStream 错误信息输出到的位置
* @return 执行结果内容
* @since 4.3.1
*/
public static String exec(Session session, String cmd, Charset charset, OutputStream errStream) {
if (null == charset) {
charset = CharsetUtil.CHARSET_UTF_8;
}
final ChannelExec channel = (ChannelExec) createChannel(session, ChannelType.EXEC);
channel.setCommand(StrUtil.bytes(cmd, charset));
channel.setInputStream(null);
channel.setErrStream(errStream);
InputStream in = null;
try {
channel.connect();
in = channel.getInputStream();
return IoUtil.read(in, charset);
} catch (IOException e) {
throw new IORuntimeException(e);
} catch (JSchException e) {
throw new JschRuntimeException(e);
} finally {
IoUtil.close(in);
close(channel);
}
}
/**
* 执行Shell命令
* <p>
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞。
* </p>
*
* @param session Session会话
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @return {@link ChannelExec}
* @since 5.2.5
*/
public static String execByShell(Session session, String cmd, Charset charset) {
final ChannelShell shell = openShell(session);
// 开始连接
shell.setPty(true);
OutputStream out = null;
InputStream in = null;
try {
out = shell.getOutputStream();
in = shell.getInputStream();
out.write(StrUtil.bytes(cmd, charset));
out.flush();
return IoUtil.read(in, charset);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(out);
IoUtil.close(in);
close(shell);
}
}
/**
* 关闭SSH连接会话
*
* @param session SSH会话
*/
public static void close(Session session) {
if (session != null && session.isConnected()) {
session.disconnect();
}
JschSessionPool.INSTANCE.remove(session);
}
/**
* 关闭会话通道
*
* @param channel 会话通道
* @since 4.0.3
*/
public static void close(Channel channel) {
if (channel != null && channel.isConnected()) {
channel.disconnect();
}
}
/**
* 关闭SSH连接会话
*
* @param key 主机格式为user@host:port
*/
public static void close(String key) {
JschSessionPool.INSTANCE.close(key);
}
/**
* 关闭所有SSH连接会话
*/
public static void closeAll() {
JschSessionPool.INSTANCE.closeAll();
}
}