添加ssh 服务 2023年3月11日17:53:50

master
zhangmeng 2023-03-11 17:54:01 +08:00
parent 7b0be2127a
commit 2a2910ac73
9 changed files with 458 additions and 3 deletions

13
pom.xml
View File

@ -282,6 +282,19 @@
<artifactId>ftpserver-core</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
</dependencies>
<build>

View File

@ -463,4 +463,8 @@ public class HomeController implements Serializable {
ListView<ResourcesUtils.Player> listView = (ListView) fx.lookup("#listView");
listView.getSelectionModel().select(index);
}
public void ssh_client_menu_item(ActionEvent event) {
load_server_tools(2);
}
}

View File

@ -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<String> uuid = new SimpleObjectProperty<>("2023311");
private SimpleObjectProperty<Integer> 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<String> message_list = SSHServiceImpl.message_list;
message_list.addListener((ListChangeListener<String>) 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);
});
}
}

View File

@ -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));
};
}

View File

@ -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);
}

View File

@ -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<String, Object> sshMap = new ConcurrentHashMap<>();
//线程池
private final ExecutorService executorService = Executors.newCachedThreadPool();
public static ObservableList<String> 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;
}
}
}

View File

@ -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) {

View File

@ -68,6 +68,7 @@
<items>
<MenuItem mnemonicParsing="false" text="http-server 请求工具" onAction="#http_server_menu_item"/>
<MenuItem mnemonicParsing="false" text="ftp-server 请求工具" onAction="#ftp_server_menu_item"/>
<MenuItem mnemonicParsing="false" text="ssh-client 请求工具" onAction="#ssh_client_menu_item"/>
</items>
</Menu>
</menus>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="649.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.zhangmeng.tools.controller.SSHConnectionController">
<children>
<Label layoutX="80.0" layoutY="79.0" text="用户名:" />
<Label layoutX="507.0" layoutY="79.0" text="密码:" />
<Label layoutX="74.0" layoutY="131.0" text="host地址:" />
<TextField fx:id="username" layoutX="144.0" layoutY="75.0" prefHeight="25.0" prefWidth="316.0" />
<TextField fx:id="host" layoutX="144.0" layoutY="127.0" prefHeight="25.0" prefWidth="316.0" />
<TextField fx:id="password" layoutX="553.0" layoutY="75.0" prefHeight="25.0" prefWidth="316.0" />
<TextArea fx:id="show_result" layoutX="144.0" layoutY="181.0" prefHeight="269.0" prefWidth="997.0" AnchorPane.bottomAnchor="199.0" AnchorPane.leftAnchor="144.0" AnchorPane.rightAnchor="59.0" AnchorPane.topAnchor="181.0" />
<Label layoutX="86.0" layoutY="181.0" text="显示:" AnchorPane.bottomAnchor="451.0" />
<Label layoutX="507.0" layoutY="131.0" text="端口:" />
<TextField fx:id="port" layoutX="553.0" layoutY="127.0" prefHeight="25.0" prefWidth="316.0" />
<Button fx:id="connection" layoutX="919.0" layoutY="127.0" mnemonicParsing="false" text="连接" />
<Label layoutX="84.0" layoutY="487.0" text="命令:" AnchorPane.bottomAnchor="145.0" />
<TextField fx:id="command" layoutX="144.0" layoutY="483.0" prefHeight="25.0" prefWidth="997.0" AnchorPane.bottomAnchor="141.0" AnchorPane.leftAnchor="144.0" AnchorPane.rightAnchor="59.0" />
</children>
</AnchorPane>