This commit is contained in:
Looly 2023-09-24 17:58:37 +08:00
parent 9461336639
commit 6b4c5ede6a
4 changed files with 170 additions and 29 deletions

View File

@ -86,7 +86,9 @@ public class GanymedSession implements Session {
}
/**
* 绑定端口到本地 一个会话可绑定多个端口
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
@ -99,7 +101,9 @@ public class GanymedSession implements Session {
}
/**
* 绑定端口到本地 一个会话可绑定多个端口
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
@ -116,7 +120,7 @@ public class GanymedSession implements Session {
throw new IORuntimeException(e);
}
if(null == this.localPortForwarderMap){
if (null == this.localPortForwarderMap) {
this.localPortForwarderMap = new HashMap<>();
}
@ -133,12 +137,12 @@ public class GanymedSession implements Session {
* @throws IORuntimeException 端口解绑失败异常
*/
public void unBindLocalPort(final int localPort) throws IORuntimeException {
if(MapUtil.isEmpty(this.localPortForwarderMap)){
if (MapUtil.isEmpty(this.localPortForwarderMap)) {
return;
}
final LocalPortForwarder localPortForwarder = this.localPortForwarderMap.remove(localPort);
if(null != localPortForwarder){
if (null != localPortForwarder) {
try {
localPortForwarder.close();
} catch (final IOException e) {

View File

@ -76,7 +76,9 @@ public class JschSession implements Session {
}
/**
* 绑定端口到本地 一个会话可绑定多个端口
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
@ -89,7 +91,9 @@ public class JschSession implements Session {
}
/**
* 绑定端口到本地 一个会话可绑定多个端口
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口

View File

@ -13,15 +13,26 @@
package org.dromara.hutool.extra.ssh.engine.sshj;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Parameters;
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder;
import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.net.Ipv4Util;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.extra.ssh.Connector;
import org.dromara.hutool.extra.ssh.Session;
import org.dromara.hutool.extra.ssh.SshException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 基于SSHJhttps://github.com/hierynomus/sshj的Session封装
@ -33,6 +44,8 @@ public class SshjSession implements Session {
private SSHClient ssh;
private final net.schmizz.sshj.connection.channel.direct.Session raw;
private Map<Integer, ServerSocket> localPortForwarderMap;
/**
* 构造
*
@ -56,15 +69,6 @@ public class SshjSession implements Session {
}
}
/**
* 构造
*
* @param raw {@link net.schmizz.sshj.connection.channel.direct.Session}
*/
public SshjSession(final net.schmizz.sshj.connection.channel.direct.Session raw) {
this.raw = raw;
}
@Override
public net.schmizz.sshj.connection.channel.direct.Session getRaw() {
return raw;
@ -85,6 +89,121 @@ public class SshjSession implements Session {
IoUtil.closeQuietly(this.ssh);
}
/**
* 打开SFTP会话
*
* @param charset 编码
* @return {@link SshjSftp}
*/
public SshjSftp openSftp(final Charset charset) {
return new SshjSftp(this.ssh, charset);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localPort 本地端口
* @return 成功与否
* @throws SshException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final int localPort) throws SshException {
return bindLocalPort(remoteHost, remotePort, Ipv4Util.LOCAL_IP, localPort);
}
/**
* 绑定端口到本地 一个会话可绑定多个端口<br>
* 当请求localHost:localPort时通过SSH到服务器转发请求到remoteHost:remotePort<br>
* 此方法用于访问本地无法访问但是服务器可以访问的地址如内网数据库库等
*
* @param remoteHost 远程主机
* @param remotePort 远程端口
* @param localHost 本地主机
* @param localPort 本地端口
* @return 成功与否
* @throws IORuntimeException 端口绑定失败异常
*/
public boolean bindLocalPort(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IORuntimeException {
final Parameters params = new Parameters(localHost, localPort, remoteHost, remotePort);
final ServerSocket ss;
try {
ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress(params.getLocalHost(), params.getLocalPort()));
ssh.newLocalPortForwarder(params, ss).listen();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
if (null == this.localPortForwarderMap) {
this.localPortForwarderMap = new HashMap<>();
}
//加入记录
this.localPortForwarderMap.put(localPort, ss);
return true;
}
/**
* 解除本地端口映射
*
* @param localPort 需要解除的本地端口
* @throws IORuntimeException 端口解绑失败异常
*/
public void unBindLocalPort(final int localPort) throws IORuntimeException {
if (MapUtil.isEmpty(this.localPortForwarderMap)) {
return;
}
IoUtil.closeQuietly(this.localPortForwarderMap.remove(localPort));
}
/**
* 绑定ssh服务端的serverPort端口, 到host主机的port端口上. <br>
* 即数据从ssh服务端的serverPort端口, 流经ssh客户端, 达到host:port上.
*
* @param bindPort ssh服务端上要被绑定的端口
* @param host 转发到的host
* @param port host上的端口
* @return 成功与否
* @throws IORuntimeException 端口绑定失败异常
*/
public boolean bindRemotePort(final int bindPort, final String host, final int port) throws IORuntimeException {
try {
this.ssh.getRemotePortForwarder().bind(
new RemotePortForwarder.Forward(bindPort),
new SocketForwardingConnectListener(new InetSocketAddress(host, port))
);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return true;
}
/**
* 解除远程端口映射
*
* @param localPort 需要解除的本地端口
*/
public void unBindRemotePort(final int localPort) {
final RemotePortForwarder remotePortForwarder = this.ssh.getRemotePortForwarder();
final Set<RemotePortForwarder.Forward> activeForwards = remotePortForwarder.getActiveForwards();
for (final RemotePortForwarder.Forward activeForward : activeForwards) {
if (localPort == activeForward.getPort()) {
try {
remotePortForwarder.cancel(activeForward);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return;
}
}
}
/**
* 执行Shell命令使用EXEC方式
* <p>

View File

@ -12,19 +12,19 @@
package org.dromara.hutool.extra.ssh.engine.sshj;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.extra.ftp.AbstractFtp;
import org.dromara.hutool.extra.ftp.FtpConfig;
import org.dromara.hutool.extra.ftp.FtpException;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.xfer.FileSystemFile;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.extra.ftp.AbstractFtp;
import org.dromara.hutool.extra.ftp.FtpConfig;
import org.dromara.hutool.extra.ftp.FtpException;
import org.dromara.hutool.extra.ssh.Connector;
import java.io.File;
import java.io.IOException;
@ -99,11 +99,22 @@ public class SshjSftp extends AbstractFtp {
* @param config FTP配置
* @since 5.3.3
*/
protected SshjSftp(final FtpConfig config) {
public SshjSftp(final FtpConfig config) {
super(config);
init();
}
/**
* 构造
* @param sshClient {@link SSHClient}
* @param charset 编码
*/
public SshjSftp(final SSHClient sshClient, final Charset charset) {
super(FtpConfig.of().setCharset(charset));
this.ssh = sshClient;
init();
}
/**
* SSH 初始化并创建一个sftp客户端.
*
@ -111,11 +122,14 @@ public class SshjSftp extends AbstractFtp {
* @since 5.7.18
*/
public void init() {
this.ssh = new SSHClient();
ssh.addHostKeyVerifier(new PromiscuousVerifier());
this.ssh = SshjUtil.openClient(new Connector(
ftpConfig.getHost(),
ftpConfig.getPort(),
ftpConfig.getUser(),
ftpConfig.getPassword(),
ftpConfig.getConnectionTimeout()));
try {
ssh.connect(ftpConfig.getHost(), ftpConfig.getPort());
ssh.authPassword(ftpConfig.getUser(), ftpConfig.getPassword());
ssh.setRemoteCharset(ftpConfig.getCharset());
this.sftp = ssh.newSFTPClient();
} catch (final IOException e) {