diff --git a/pom.xml b/pom.xml index 885624a..de1e5e2 100644 --- a/pom.xml +++ b/pom.xml @@ -282,6 +282,19 @@ ftpserver-core 1.1.1 + + + commons-net + commons-net + 3.6 + + + + com.jcraft + jsch + 0.1.54 + + diff --git a/src/main/java/com/zhangmeng/tools/controller/HomeController.java b/src/main/java/com/zhangmeng/tools/controller/HomeController.java index ebfab01..f3c2c25 100644 --- a/src/main/java/com/zhangmeng/tools/controller/HomeController.java +++ b/src/main/java/com/zhangmeng/tools/controller/HomeController.java @@ -463,4 +463,8 @@ public class HomeController implements Serializable { ListView listView = (ListView) fx.lookup("#listView"); listView.getSelectionModel().select(index); } + + public void ssh_client_menu_item(ActionEvent event) { + load_server_tools(2); + } } \ No newline at end of file diff --git a/src/main/java/com/zhangmeng/tools/controller/SSHConnectionController.java b/src/main/java/com/zhangmeng/tools/controller/SSHConnectionController.java new file mode 100644 index 0000000..7873dab --- /dev/null +++ b/src/main/java/com/zhangmeng/tools/controller/SSHConnectionController.java @@ -0,0 +1,124 @@ +package com.zhangmeng.tools.controller; + +import com.zhangmeng.tools.ssh.SSHService; +import com.zhangmeng.tools.ssh.SSHServiceImpl; +import com.zhangmeng.tools.utils.AlertUtils; +import javafx.application.Platform; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import lombok.extern.slf4j.Slf4j; + +/** + * @author : 芊芊墨客 + * @version : 1.0 + * @date : 2023-03-11 17:10 + */ +@Slf4j +public class SSHConnectionController { + + @FXML + public Button connection; + + @FXML + public TextArea show_result; + + @FXML + public TextField command; + + @FXML + private TextField username; + + @FXML + private TextField password; + + @FXML + private TextField host; + + @FXML + private TextField port; + + private SSHService sshService = null; + + private SimpleObjectProperty uuid = new SimpleObjectProperty<>("2023311"); + private SimpleObjectProperty status = new SimpleObjectProperty<>(0); + + @FXML + public void initialize() { + + sshService = new SSHServiceImpl(); + + connection.setOnAction(event -> { + if (username.getText().length() == 0 ){ + AlertUtils.alert_warning("用户名不能为空!"); + return; + } + + if (password.getText().length() == 0 ){ + AlertUtils.alert_warning("密码不能为空!"); + return; + } + + if (host.getText().length() == 0 ){ + AlertUtils.alert_warning("服务地址不能为空!"); + return; + } + + if (port.getText().length() == 0 ){ + AlertUtils.alert_warning("服务端口不能为空!"); + return; + } + SSHServiceImpl.SSHConnectInfo sshConnectInfo = new SSHServiceImpl.SSHConnectInfo(); + sshConnectInfo.setUsername(username.getText()); + sshConnectInfo.setPassword(password.getText()); + sshConnectInfo.setHost(host.getText()); + sshConnectInfo.setPort(Integer.parseInt(port.getText())); + + //init + this.sshService.initConnection(uuid.get(),sshConnectInfo); + + //connection + this.sshService.recvHandle("", SSHServiceImpl.Type.connect,uuid.get()); + + AlertUtils.alert_msg("连额成功!"); + status.set(1); + }); + + command.setOnAction(event -> { + if (status.getValue() != 1){ + AlertUtils.alert_warning("请连接后再试!"); + return; + } + if (command.getText().length() == 0 ){ + AlertUtils.alert_warning("请输入命令后再试!"); + return; + } + this.sshService.recvHandle(command.getText(),SSHServiceImpl.Type.command, uuid.get()); + }); + ObservableList message_list = SSHServiceImpl.message_list; + message_list.addListener((ListChangeListener) c -> { + while (c.next()) { + if (c.wasAdded()) { + StringBuilder stringBuilder = new StringBuilder(); + for (String s : message_list) { + stringBuilder.append(s); + } + receive(stringBuilder.toString()); + } + } + }); + + } + + public void receive(String msg){ + Platform.runLater(() -> { + this.show_result.setText(msg); + }); + } +} diff --git a/src/main/java/com/zhangmeng/tools/controller/ServerToolsController.java b/src/main/java/com/zhangmeng/tools/controller/ServerToolsController.java index e409e5e..9e73920 100644 --- a/src/main/java/com/zhangmeng/tools/controller/ServerToolsController.java +++ b/src/main/java/com/zhangmeng/tools/controller/ServerToolsController.java @@ -40,6 +40,7 @@ public class ServerToolsController { private AnchorPane root; private AnchorPane httpServer; private AnchorPane ftpServer; + private AnchorPane sshClient; public static final String color_cell = "#f4f4f4"; @@ -260,21 +261,42 @@ public class ServerToolsController { } ftpServer(flag); } + if (newValue.getIndex() == 2) { + if (sshClient != null){ + flag = true; + } + sshClient(flag); + } } }); } + private void sshClient(boolean flag) { + listView.getSelectionModel().select(2); + if (!flag){ + try { + root = FXMLLoader.load(ResourcesUtils.getResource("ssh-connection")); + } catch (IOException e) { + e.printStackTrace(); + } + ftpServer = root; + }else { + root = ftpServer; + } + common_method(); + } + private void ftpServer(boolean flag) { - listView.getSelectionModel().select(0); + listView.getSelectionModel().select(1); if (!flag){ try { root = FXMLLoader.load(ResourcesUtils.getResource("ftp-server")); } catch (IOException e) { e.printStackTrace(); } - httpServer = root; + ftpServer = root; }else { - root = httpServer; + root = ftpServer; } common_method(); } @@ -298,6 +320,7 @@ public class ServerToolsController { return switch (player){ case Http_Server -> new Image(ImagePath.path(ImagePath.ImagePathType.MD5)); case Ftp_Server -> new Image(ImagePath.path(ImagePath.ImagePathType.SPRING_SECURITY)); + case SSH_Client -> new Image(ImagePath.path(ImagePath.ImagePathType.SPRING_SECURITY)); }; } diff --git a/src/main/java/com/zhangmeng/tools/ssh/SSHService.java b/src/main/java/com/zhangmeng/tools/ssh/SSHService.java new file mode 100644 index 0000000..6baf71a --- /dev/null +++ b/src/main/java/com/zhangmeng/tools/ssh/SSHService.java @@ -0,0 +1,16 @@ +package com.zhangmeng.tools.ssh; + +/** + * @author : 芊芊墨客 + * @version : 1.0 + * @date : 2023-03-11 16:16 + * 接口地址: https://blog.51cto.com/zhongmayisheng/5216135 + */ +public interface SSHService { + + public void initConnection(String USER_UUID_KEY, SSHServiceImpl.SSHConnectInfo sshConnectInfo); + + public void recvHandle(String command, SSHServiceImpl.Type type, String USER_UUID_KEY); + + public void close(String USER_UUID_KEY); +} diff --git a/src/main/java/com/zhangmeng/tools/ssh/SSHServiceImpl.java b/src/main/java/com/zhangmeng/tools/ssh/SSHServiceImpl.java new file mode 100644 index 0000000..b807642 --- /dev/null +++ b/src/main/java/com/zhangmeng/tools/ssh/SSHServiceImpl.java @@ -0,0 +1,248 @@ +package com.zhangmeng.tools.ssh; + +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author : 芊芊墨客 + * @version : 1.0 + * @date : 2023-03-11 16:18 + */ +@Slf4j +public class SSHServiceImpl implements SSHService { + + //存放ssh连接信息的map + private static final Map sshMap = new ConcurrentHashMap<>(); + //线程池 + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + public static ObservableList message_list = FXCollections.observableArrayList(); + + @Override + public void initConnection(String USER_UUID_KEY,SSHConnectInfo sshConnectInfo) { + JSch jSch = new JSch(); + sshConnectInfo.setjSch(jSch); + //将这个ssh连接信息放入map中 + sshMap.put(USER_UUID_KEY, sshConnectInfo); + } + + @Override + public void recvHandle(String command, Type type,String USER_UUID_KEY) { + + if (Type.connect.equals(type)) { + //如果是连接请求 + //找到刚才存储的ssh连接对象 + SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(USER_UUID_KEY); + //启动线程异步处理 + executorService.execute(() -> { + try { + //连接到终端 + connectToSSH(sshConnectInfo); + } catch (JSchException | IOException e) { + log.error("webssh连接异常"); + log.error("异常信息:{}", e.getMessage()); + close(USER_UUID_KEY); + } + }); + } + if (Type.command.equals(type)) { + //如果是发送命令的请求 + SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(USER_UUID_KEY); + if (sshConnectInfo != null) { + try { + //发送命令到终端 + transToSSH(sshConnectInfo.getChannel(), command); + } catch (IOException e) { + log.error("webssh连接异常"); + log.error("异常信息:{}", e.getMessage()); + close(USER_UUID_KEY); + } + } + } else { + log.error("不支持的操作"); + close(USER_UUID_KEY); + } + } + + @Override + public void close(String USER_UUID_KEY) { + SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(USER_UUID_KEY); + if (sshConnectInfo != null) { + //断开连接 + if (sshConnectInfo.getChannel() != null) sshConnectInfo.getChannel().disconnect(); + //map中移除 + sshMap.remove(USER_UUID_KEY); + } + } + + /** + * @Description: 使用jsch连接终端 + * @Param: [cloudSSH, webSSHData, webSocketSession] + * @return: void + * @Author: NoCortY + * @Date: 2020/3/7 + */ + private void connectToSSH(SSHConnectInfo sshConnectInfo) throws JSchException, IOException { + Session session = null; + Properties config = new Properties(); + config.put("StrictHostKeyChecking", "no"); + //获取jsch的会话 + JSch jSch = new JSch(); + session = jSch.getSession(sshConnectInfo.username, sshConnectInfo.host, sshConnectInfo.port); + session.setConfig(config); + //设置密码 + session.setPassword(sshConnectInfo.password); + //连接 超时时间30s + session.connect(30000); + + //开启shell通道 + Channel channel = session.openChannel("shell"); + + //通道连接 超时时间3s + channel.connect(3000); + + //设置channel + sshConnectInfo.setChannel(channel); + + //转发消息 + transToSSH(channel, "\r"); + + //读取终端返回的信息流 + try (InputStream inputStream = channel.getInputStream()) { + //循环读取 + byte[] buffer = new byte[1024]; + int i = 0; + //如果没有数据来,线程会一直阻塞在这个地方等待数据。 + while ((i = inputStream.read(buffer)) != -1) { + sendMessage(Arrays.copyOfRange(buffer, 0, i)); + } + } finally { + //断开连接后关闭会话 + session.disconnect(); + channel.disconnect(); + } + } + + public void sendMessage(byte[] buffer){ + message_list.add(buf_to_string(buffer)); + message_list.add(System.lineSeparator()); + } + + public static String buf_to_string(byte[] buffer){ + return new String(buffer, StandardCharsets.UTF_8); + } + + /** + * @Description: 将消息转发到终端 + */ + private void transToSSH(Channel channel, String command) throws IOException { + if (channel != null) { + OutputStream outputStream = channel.getOutputStream(); + outputStream.write(command.getBytes()); + outputStream.flush(); + } + } + + public enum Type{ + connect("连接"), + command("命令"), + ; + + private String desc; + + Type(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + } + + public static class SSHConnectInfo{ + public String host; + private JSch jSch; + private String username; + private String password; + private int port; + private Channel channel; + + public Channel getChannel() { + return channel; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } + + public SSHConnectInfo() { + } + + public SSHConnectInfo(JSch jSch, String username, String password, int port) { + this.jSch = jSch; + this.username = username; + this.password = password; + this.port = port; + } + + public JSch getjSch() { + return jSch; + } + + public void setjSch(JSch jSch) { + this.jSch = jSch; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + } +} diff --git a/src/main/java/com/zhangmeng/tools/utils/ResourcesUtils.java b/src/main/java/com/zhangmeng/tools/utils/ResourcesUtils.java index 0d3341e..58591d8 100644 --- a/src/main/java/com/zhangmeng/tools/utils/ResourcesUtils.java +++ b/src/main/java/com/zhangmeng/tools/utils/ResourcesUtils.java @@ -274,6 +274,7 @@ public class ResourcesUtils { Http_Server("Http Server工具",0), Ftp_Server("Ftp Server工具",1), + SSH_Client("ssh 连接工具",2), ; ServerTools(String title, int index) { diff --git a/src/main/resources/fxml/home.fxml b/src/main/resources/fxml/home.fxml index b931d52..e8d1c93 100644 --- a/src/main/resources/fxml/home.fxml +++ b/src/main/resources/fxml/home.fxml @@ -68,6 +68,7 @@ + diff --git a/src/main/resources/fxml/ssh-connection.fxml b/src/main/resources/fxml/ssh-connection.fxml new file mode 100644 index 0000000..1206ec1 --- /dev/null +++ b/src/main/resources/fxml/ssh-connection.fxml @@ -0,0 +1,25 @@ + + + + + + + + + + +