前因:处于安全考虑,运维老哥禁止了数据库远程访问,导致我们不能通过本地直连数据库了。
但运维老哥还告诉我们,还是可以通过ssh网络协议做跳板连接,所以就有了以下尝试:
首先现在Navicat 这个工具上尝试了ssh连接能不能通。
ok,确保我们的ssh账号,密码没问题。接下来进行代码层面的ssh连接。
对ssh不太了解的,可以看下我上一篇文章,方便理解下面的代码操作。
下面操作都是基于windows上连接远程linux服务器的操作。
进入代码层面(基于springboot+mybatis框架结构)。
1、首先maven中导入必须的 jsch 包。
<!--SSH 连接-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
2、编写一个ssh连接的工具类。
这里你得先理清自己想要通过什么方式连接?
①、通过账号密码方式进行ssh桥接。
②、通过公私钥方式进行ssh桥接。
第一种方式(账号密码):
package com.xcj.juhe.tool;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import java.util.Properties;
/**
* @author xcj
* @date 2021/7/14 10:26
*/
public class SshConnectionTool {
//ssh连接的用户名
private final static String SSH_USER = "root";
//ssh连接的密码
private final static String SSH_PASSWORD = "123456";
//ssh远程连接的ip地址
private final static String SSH_REMOTE_SERVER = "42.192.77.88";
//ssh连接的端口号
private final static int SSH_REMOTE_PORT = 36000;
//本地mysql发起连接的IP地址
private final static String MYSQL_REMOTE_SERVER = "127.0.0.1";
//本地数据库连接时用的端口号
private final static int LOCAl_PORT = 3307;
//远程数据库端口用的端口号
private final static int REMOTE_PORT = 3306;
private Session sesion; //ssh 会话
public void closeSSH ()
{
sesion.disconnect();
}
public SshConnectionTool () throws Throwable
{
JSch jsch = new JSch();
sesion = jsch.getSession(SSH_USER, SSH_REMOTE_SERVER, SSH_REMOTE_PORT);
sesion.setPassword(SSH_PASSWORD);
//设置连接过程不校验known_hosts文件中的信息
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sesion.setConfig(config);
sesion.connect(); //ssh 建立连接!
//根据安全策略,您必须通过转发端口进行连接
sesion.setPortForwardingL(LOCAl_PORT, MYSQL_REMOTE_SERVER, REMOTE_PORT);
}
}
第二种方式(公私钥)(提前将自己生成的公钥搞到服务器上):
package com.xcj.juhe.tool;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import java.util.Properties;
/**
* @author xcj
* @date 2021/7/14 10:26
*/
public class SshConnectionTool {
//本地的ssh中的knownhost文件路径
private final static String S_PATH_FILE_KNOWN_HOSTS = "C:/Users/lx/.ssh/known_hosts";
//本地的ssh密钥路径
private final static String S_PATH_FILE_PRIVATE_KEY = "C:/Users/lx/.ssh/id_rsa";
//ssh连接的用户名
private final static String SSH_USER = "root";
//ssh远程连接的ip地址
private final static String SSH_REMOTE_SERVER = "42.192.77.88";
//ssh连接的端口号
private final static int SSH_REMOTE_PORT = 36000;
//本地mysql发起连接的IP地址
private final static String MYSQL_REMOTE_SERVER = "127.0.0.1";
//本地数据库连接时用的端口号
private final static int LOCAl_PORT = 3307;
//远程数据库端口用的端口号
private final static int REMOTE_PORT = 3306;
private Session sesion; //ssh 会话
public void closeSSH ()
{
sesion.disconnect();
}
public SshConnectionTool () throws Throwable
{
JSch jsch = null;
jsch = new JSch();
//设置known_hosts文件路径,如:~/.ssh/known_hosts(known_hosts中存储是已认证的远程主机host key)
jsch.setKnownHosts(S_PATH_FILE_KNOWN_HOSTS);
//设置私钥
jsch.addIdentity(S_PATH_FILE_PRIVATE_KEY);
sesion = jsch.getSession(SSH_USER, SSH_REMOTE_SERVER, SSH_REMOTE_PORT);
//设置连接过程不校验known_hosts文件中的信息
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sesion.setConfig(config);
sesion.connect(); //ssh 建立连接!
//根据安全策略,您必须通过转发端口进行连接
sesion.setPortForwardingL(LOCAl_PORT, MYSQL_REMOTE_SERVER, REMOTE_PORT);
}
}
3、编写监听器(监听ServletContext 对象的生命周期,用于ssh桥接)
package com.xcj.juhe.listener;
import com.xcj.juhe.tool.SshConnectionTool;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @author xcj
* @date 2021/7/14 10:36
*/
@Component
public class SshContextListener implements ServletContextListener {
private SshConnectionTool conexionssh;
public SshContextListener() {
super();
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("Context initialized ... !");
try {
conexionssh = new SshConnectionTool();
} catch (Throwable e) {
e.printStackTrace(); // 连接失败
}
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("Context destroyed ... !");
conexionssh.closeSSH(); // 断开连接
}
}
4、编写yml 数据库连接的配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/mint_sdk?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
5、你自己写个controller去进行数据库操作,试下能不能成功。
6、调试期间,我遇到的异常和排雷操作:
①、通过公私钥方式连接,出现公私钥不匹配时会报这个异常(我自己改了私钥,导致私钥过长)。
com.jcraft.jsch.JSchException: fromBase64: invalid base64 data
解决方法:
找到ssh 的 know_hosts文件,删除对应的远程服务器连接信息。
问题解决。
②、ssh工具类中,没有加忽略对比known_hosts文件内容,导致每次内容变更就连接失败。
解决方法:
1)、如上述工具类代码中添加忽略对比代码:
//设置连接过程不校验known_hosts文件中的信息
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sesion.setConfig(config);
2)、删除known_hosts文件中对应的连接主机的信息。
-------------------------------------------------------------------------------
为什么需要known_hosts?
最后探讨下为什么需要known_hosts,这个文件主要是通过Client和Server的双向认证,从而避免中间人(man-in-the-middle attack)攻击,每次Client向Server发起连接的时候,不仅仅Server要验证Client的合法性,Client同样也需要验证Server的身份,SSH client就是通过known_hosts中的host key来验证Server的身份的。
用OpenSSH的人都知ssh会把你每个你访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告。