ssh 隧道
parent
086ec04309
commit
da1d060a08
|
|
@ -1,19 +0,0 @@
|
|||
package com.njzscloud.common.mp.config;
|
||||
|
||||
import com.njzscloud.common.mp.support.DBTunnel;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(prefix = "mybatis-plus.tunnel", name = "enable", havingValue = "true")
|
||||
@EnableConfigurationProperties({DbTunnelProperties.class})
|
||||
public class DBTunnelAutoConfiguration {
|
||||
|
||||
@Bean(destroyMethod = "close")
|
||||
public DBTunnel dbTunnel(DbTunnelProperties dbTunnelProperties) {
|
||||
return new DBTunnel(dbTunnelProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.njzscloud.common.mp.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties("mybatis-plus.tunnel")
|
||||
public class DbTunnelProperties {
|
||||
private boolean enable = false;
|
||||
|
||||
private SSHProperties ssh;
|
||||
private DBProperties db;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class SSHProperties {
|
||||
private String host;
|
||||
private int port = 22;
|
||||
private String user;
|
||||
private String credentials;
|
||||
private String passphrase;
|
||||
private int localPort;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class DBProperties {
|
||||
private String host;
|
||||
private int port = 3306;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package com.njzscloud.common.mp.support;
|
||||
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.njzscloud.common.mp.config.DbTunnelProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class DBTunnel {
|
||||
|
||||
private final DbTunnelProperties dbTunnelProperties;
|
||||
private JSch jsch;
|
||||
private Session session;
|
||||
private ScheduledExecutorService heartbeatExecutor;
|
||||
|
||||
public DBTunnel(DbTunnelProperties dbTunnelProperties) {
|
||||
this.dbTunnelProperties = dbTunnelProperties;
|
||||
if (dbTunnelProperties.isEnable()) {
|
||||
log.info("数据库 SSH 隧道已启用");
|
||||
connect();
|
||||
startHeartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
try {
|
||||
if (isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DbTunnelProperties.SSHProperties ssh = dbTunnelProperties.getSsh();
|
||||
String credentials = ssh.getCredentials();
|
||||
String passphrase = ssh.getPassphrase();
|
||||
String user = ssh.getUser();
|
||||
String host = ssh.getHost();
|
||||
int port = ssh.getPort();
|
||||
int localPort = ssh.getLocalPort();
|
||||
DbTunnelProperties.DBProperties db = dbTunnelProperties.getDb();
|
||||
String dbHost = db.getHost();
|
||||
int dbPort = db.getPort();
|
||||
|
||||
jsch = new JSch();
|
||||
// 使用私钥认证
|
||||
File keyFile = new File(credentials);
|
||||
if (!keyFile.exists()) {
|
||||
throw new RuntimeException("私钥文件不存在: " + credentials);
|
||||
}
|
||||
|
||||
if (passphrase != null) {
|
||||
jsch.addIdentity(credentials, passphrase);
|
||||
} else {
|
||||
jsch.addIdentity(credentials);
|
||||
}
|
||||
|
||||
// 创建SSH会话
|
||||
session = jsch.getSession(user, host, port);
|
||||
session.setConfig("StrictHostKeyChecking", "no"); // 禁用主机密钥检查
|
||||
|
||||
// 设置保持活动状态
|
||||
session.setServerAliveInterval(60000); // 每分钟发送一次保持活动的数据包
|
||||
session.setServerAliveCountMax(3); // 允许3次失败
|
||||
|
||||
session.connect();
|
||||
|
||||
// 设置端口转发 (本地端口 -> 远程数据库)
|
||||
session.setPortForwardingL(localPort, dbHost, dbPort);
|
||||
log.info("SSH 隧道已成功连接并转发端口 {} -> {}:{}", localPort, dbHost, dbPort);
|
||||
} catch (JSchException e) {
|
||||
log.error("SSH 隧道连接失败", e);
|
||||
throw new RuntimeException("SSH 隧道连接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startHeartbeat() {
|
||||
heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (!isConnected()) {
|
||||
log.warn("SSH 隧道连接已断开,尝试重新连接...");
|
||||
connect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("SSH 隧道心跳检测失败", e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS); // 每30秒检查一次连接状态
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return session != null && session.isConnected();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (heartbeatExecutor != null) {
|
||||
heartbeatExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
if (session != null && session.isConnected()) {
|
||||
session.disconnect();
|
||||
log.info("SSH隧道已关闭");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
|
||||
com.njzscloud.common.mp.config.MpAutoConfiguration,\
|
||||
com.njzscloud.common.mp.config.DBTunnelAutoConfiguration
|
||||
com.njzscloud.common.mp.config.MpAutoConfiguration
|
||||
|
|
|
|||
|
|
@ -19,10 +19,14 @@
|
|||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
package com.njzscloud.common.sshtunnel;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.njzscloud.common.core.tuple.Tuple2;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Slf4j
|
||||
public class SSHTunnel {
|
||||
|
||||
private final List<Tuple2<JSch, Session>> tunnels = new LinkedList<>();
|
||||
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
public SSHTunnel(SSHTunnelProperties sshTunnelProperties) {
|
||||
if (sshTunnelProperties.isEnable()) {
|
||||
log.info("SSH 隧道已启用");
|
||||
running.set(true);
|
||||
List<SSHTunnelProperties.SSHProperties> tunnelsProperties = sshTunnelProperties.getTunnels();
|
||||
tunnelsProperties.stream()
|
||||
.filter(SSHTunnelProperties.SSHProperties::isEnable)
|
||||
.forEach(it -> {
|
||||
Tuple2<JSch, Session> tunnel = createTunnel(it);
|
||||
tunnels.add(tunnel);
|
||||
});
|
||||
reconnectTask();
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple2<JSch, Session> createTunnel(SSHTunnelProperties.SSHProperties sshProperties) {
|
||||
try {
|
||||
String host = sshProperties.getHost();
|
||||
int port = sshProperties.getPort();
|
||||
String user = sshProperties.getUser();
|
||||
String pwd = sshProperties.getPwd();
|
||||
String credentials = sshProperties.getCredentials();
|
||||
String passphrase = sshProperties.getPassphrase();
|
||||
log.info("正在连接服务器:{}:{}", host, port);
|
||||
JSch jsch = new JSch();
|
||||
Session session;
|
||||
if (StrUtil.isNotBlank(credentials)) {
|
||||
// 使用私钥认证
|
||||
File keyFile = new File(credentials);
|
||||
if (!keyFile.exists()) {
|
||||
throw new RuntimeException("私钥文件不存在: " + credentials);
|
||||
}
|
||||
|
||||
if (passphrase != null) {
|
||||
jsch.addIdentity(credentials, passphrase);
|
||||
} else {
|
||||
jsch.addIdentity(credentials);
|
||||
}
|
||||
session = jsch.getSession(user, host, port);
|
||||
} else if (StrUtil.isNotBlank(pwd)) {
|
||||
// 创建SSH会话
|
||||
session = jsch.getSession(user, host, port);
|
||||
// 使用密码认证
|
||||
session.setPassword(pwd);
|
||||
} else {
|
||||
throw new RuntimeException("未提供有效认证方式(私钥或密码)");
|
||||
}
|
||||
|
||||
|
||||
session.setConfig("StrictHostKeyChecking", "no"); // 禁用主机密钥检查
|
||||
|
||||
// 设置保持活动状态
|
||||
session.setServerAliveInterval(60000); // 每分钟发送一次保持活动的数据包
|
||||
session.setServerAliveCountMax(3); // 允许3次失败
|
||||
|
||||
session.connect();
|
||||
|
||||
List<SSHTunnelProperties.ProxyProperties> proxyProperties = sshProperties.getProxy();
|
||||
for (SSHTunnelProperties.ProxyProperties proxyProperty : proxyProperties) {
|
||||
int localPort = proxyProperty.getLocalPort();
|
||||
String targetHost = proxyProperty.getTargetHost();
|
||||
int targetPort = proxyProperty.getTargetPort();
|
||||
// 设置端口转发 (本地端口 -> 目标地址)
|
||||
session.setPortForwardingL(localPort, targetHost, targetPort);
|
||||
log.info("已创建隧道,本地端口:{} --> 服务器:{}:{} --> 目标地址:{}:{}", localPort, host, port, targetHost, targetPort);
|
||||
}
|
||||
return Tuple2.create(jsch, session);
|
||||
} catch (JSchException e) {
|
||||
log.error("SSH 隧道连接失败", e);
|
||||
throw new RuntimeException("SSH 隧道连接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void reconnectTask() {
|
||||
reconnectExecutor.scheduleAtFixedRate(() -> {
|
||||
if (!running.get()) return;
|
||||
try {
|
||||
for (Tuple2<JSch, Session> tunnel : tunnels) {
|
||||
Session session = tunnel.get_1();
|
||||
if (!session.isConnected()) {
|
||||
log.warn("SSH 隧道连接已断开,尝试重新连接...");
|
||||
session.connect();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("重连任务执行失败", e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS); // 每30秒检查一次连接状态
|
||||
}
|
||||
|
||||
public void close() {
|
||||
running.set(false);
|
||||
reconnectExecutor.shutdownNow();
|
||||
for (Tuple2<JSch, Session> tunnel : tunnels) {
|
||||
Session session = tunnel.get_1();
|
||||
if (session != null && session.isConnected()) {
|
||||
String host = session.getHost();
|
||||
int port = session.getPort();
|
||||
session.disconnect();
|
||||
log.info("SSH 隧道已关闭,服务器:{}:{}", host, port);
|
||||
}
|
||||
}
|
||||
tunnels.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.njzscloud.common.sshtunnel;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(prefix = "ssh-tunnel", name = "enable", havingValue = "true")
|
||||
@EnableConfigurationProperties({SSHTunnelProperties.class})
|
||||
public class SSHTunnelAutoConfiguration {
|
||||
|
||||
@Bean(destroyMethod = "close")
|
||||
public SSHTunnel sshTunnel(SSHTunnelProperties SSHTunnelProperties) {
|
||||
return new SSHTunnel(SSHTunnelProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.njzscloud.common.sshtunnel;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties("ssh-tunnel")
|
||||
public class SSHTunnelProperties {
|
||||
private boolean enable = false;
|
||||
|
||||
private List<SSHProperties> tunnels;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class SSHProperties {
|
||||
private boolean enable = true;
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* SSH 端口
|
||||
*/
|
||||
private int port = 22;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String user;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String pwd;
|
||||
/**
|
||||
* 秘钥文件
|
||||
*/
|
||||
private String credentials;
|
||||
/**
|
||||
* 私钥密码
|
||||
*/
|
||||
private String passphrase;
|
||||
private List<ProxyProperties> proxy;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ProxyProperties {
|
||||
/**
|
||||
* 本地端口
|
||||
*/
|
||||
private int localPort;
|
||||
/**
|
||||
* 目标主机
|
||||
*/
|
||||
private String targetHost;
|
||||
/**
|
||||
* 目标端口
|
||||
*/
|
||||
private int targetPort;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
|
||||
com.njzscloud.common.sshtunnel.SSHTunnelAutoConfiguration
|
||||
|
|
@ -26,6 +26,10 @@
|
|||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-http</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-sshtunnel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-wechat</artifactId>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://47.115.226.143:3306/njzscloud?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
|
||||
# url: jdbc:mysql://47.115.226.143:3306/njzscloud?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
|
||||
# username: root
|
||||
# password: zsy2022
|
||||
url: jdbc:mysql://localhost:33061/njzscloud?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
|
||||
username: root
|
||||
password: zsy2022
|
||||
# url: jdbc:mysql://localhost:33061/njzscloud?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
|
||||
# username: root
|
||||
# password: admin888999
|
||||
password: admin888999
|
||||
security:
|
||||
auth-ignores:
|
||||
- /auth/obtain_code
|
||||
|
|
@ -27,6 +27,7 @@ spring:
|
|||
- /payment/wechat/refundNotify
|
||||
- /district/areaList
|
||||
- /biz_audit_config/copy
|
||||
- /order_info/gps_test
|
||||
app:
|
||||
default-place:
|
||||
province: 320000
|
||||
|
|
@ -47,18 +48,18 @@ oss:
|
|||
secret-key: zllX0ZJ1EwsZXT6dE6swCLgTF4ImGg
|
||||
bucket-name: cdn-zsy
|
||||
|
||||
mybatis-plus:
|
||||
tunnel:
|
||||
enable: false
|
||||
ssh:
|
||||
ssh-tunnel:
|
||||
enable: true
|
||||
tunnels:
|
||||
- enable: true
|
||||
host: 139.224.54.144
|
||||
port: 22
|
||||
user: root
|
||||
credentials: D:/我的/再昇云/服务器秘钥/139.224.54.144_YZS_S1.pem
|
||||
localPort: 33061
|
||||
db:
|
||||
host: localhost
|
||||
port: 33061
|
||||
proxy:
|
||||
- localPort: 33061
|
||||
targetHost: localhost
|
||||
targetPort: 33061
|
||||
|
||||
wechat:
|
||||
# app-id: wx3c06d9dd4e56c58d
|
||||
|
|
@ -75,7 +76,7 @@ wechat:
|
|||
cert-serial-no: 1BCB1533688F349541C7B636EF67C666828BADBA
|
||||
# 文件路径
|
||||
private-key-path: classpath:cert/apiclient_cert.p12
|
||||
# private-key-path: D:/project/再昇云/代码/njzscloud/njzscloud-svr/src/main/resources/cert/apiclient_cert.p12
|
||||
# private-key-path: D:/project/再昇云/代码/njzscloud/njzscloud-svr/src/main/resources/cert/apiclient_cert.p12
|
||||
# 支付回调地址
|
||||
notify-url: http://115.29.236.92:8082/payment/wechat/notify
|
||||
# 退款回调地址
|
||||
|
|
|
|||
5
pom.xml
5
pom.xml
|
|
@ -48,6 +48,11 @@
|
|||
<artifactId>njzscloud-common-ws</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-sshtunnel</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njzscloud</groupId>
|
||||
<artifactId>njzscloud-common-sichen</artifactId>
|
||||
|
|
|
|||
Loading…
Reference in New Issue