java实现web ssh客户端

java实现web ssh客户端使用 java 语言实现 webssh 客户端 使用 websocket 的 stomp 协议 xterm js 实现效果如下 0 引入依赖 parent groupId org springframew boot groupId artifactId spring boot starter parent artifactId version 2 4 0 version parent

 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencies> <!-- Web相关 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jsch支持 --> <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.54</version> </dependency> <!-- WebSocket 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> </dependencies> 

1、定义两个实体

package com.iscas.ssh.server.model; import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import lombok.Data; import org.springframework.web.socket.WebSocketSession; / * SSH连接信息 * * @author zhuquanwen * @vesion 1.0 * @date 2020/12/27 14:07 * @since jdk1.8 */ @Data public class SSHConnection { 
    private WebSocketSession webSocketSession; private String connectionId; private JSch jSch; private Channel channel; } 
package com.iscas.ssh.server.model; import lombok.Data; / * SSH信息 * * @author zhuquanwen * @vesion 1.0 * @date 2020/12/27 14:09 * @since jdk1.8 */ @Data public class WebSSHData { 
    //操作 private String operate; private String host; //端口号默认为22 private Integer port = 22; private String username; private String password; private String command = ""; private String connectionId; } 

2、定义service,通过JSCH建立SSH连接,并发送前端用户的输入,接收返回结果。

package com.iscas.ssh.server.service; import com.iscas.ssh.server.constant.CommonConstants; import com.iscas.ssh.server.model.SSHConnection; import com.iscas.ssh.server.model.WebSSHData; import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.security.Principal; 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; / * 处理SSH连接的业务 * * @author zhuquanwen * @vesion 1.0 * @date 2020/12/27 14:10 * @since jdk1.8 */ @Service @Slf4j public class SSHService { 
    //存放ssh连接信息的map private static Map<String, Object> sshMap = new ConcurrentHashMap<>(); //连接ID对应的用户 private static Map<String, String> connectionUserMap = new ConcurrentHashMap<>(); //线程池 private ExecutorService executorService = Executors.newCachedThreadPool(); private int connectionTimeout = 30; private int channelTimeout = 3; @Autowired private SimpMessagingTemplate messagingTemplate; / * 初始化连接 */ public void initConnection(String connectionId, Principal user) { 
    JSch jSch = new JSch(); SSHConnection sshConnection = new SSHConnection(); sshConnection.setJSch(jSch); sshConnection.setConnectionId(connectionId); // 将这个ssh连接信息放入map中 sshMap.put(connectionId, sshConnection); connectionUserMap.put(connectionId, user.getName()); } / * @Description: 处理客户端发送的数据 */ public void recvHandle(WebSSHData webSSHData) throws IOException, JSchException { 
    String connectionId = webSSHData.getConnectionId(); if (CommonConstants.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) { 
    //找到刚才存储的ssh连接对象 SSHConnection sshConnection = (SSHConnection) sshMap.get(connectionId); //启动线程异步处理 WebSSHData finalWebSSHData = webSSHData; executorService.execute(new Runnable() { 
    @Override public void run() { 
    try { 
    connectToSSH(sshConnection, finalWebSSHData); } catch (JSchException | IOException e) { 
    log.error("webssh连接异常", e.getMessage()); close(connectionId); } } }); } else if (CommonConstants.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) { 
    String command = webSSHData.getCommand(); SSHConnection sshConnection = (SSHConnection) sshMap.get(connectionId); if (sshConnection != null) { 
    try { 
    transToSSH(sshConnection.getChannel(), command); } catch (IOException e) { 
    log.error("webssh连接异常", e.getMessage()); close(connectionId); } } } else { 
    log.error("不支持的操作"); close(connectionId); } } public void sendMessage(String connectionId, byte[] buffer) throws IOException { 
    String username = connectionUserMap.get(connectionId); if (username == null) { 
    throw new RuntimeException(String.format("未找到connectionId:[%s]对应的websocket连接用户", connectionId)); } messagingTemplate.convertAndSendToUser(username, "/queue/".concat(connectionId), new String(buffer, "utf-8")); } public void sendMessage(String connectionId, String data) throws IOException { 
    String username = connectionUserMap.get(connectionId); if (username == null) { 
    throw new RuntimeException(String.format("未找到connectionId:[%s]对应的websocket连接用户", connectionId)); } messagingTemplate.convertAndSendToUser(username, "/queue/".concat(connectionId), data); } public void close(String connectionId) { 
    SSHConnection sshConnection = (SSHConnection) sshMap.get(connectionId); if (sshConnection != null) { 
    //断开连接 if (sshConnection.getChannel() != null) sshConnection.getChannel().disconnect(); //map中移除 sshMap.remove(connectionId); } } / * 使用jsch连接终端 */ private void connectToSSH(SSHConnection sshConnection, WebSSHData webSSHData) throws JSchException, IOException { 
    Session session = null; Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); //获取jsch的会话 session = sshConnection.getJSch().getSession(webSSHData.getUsername(), webSSHData.getHost(), webSSHData.getPort()); session.setConfig(config); //设置密码 session.setPassword(webSSHData.getPassword()); //连接 超时时间30s session.connect(connectionTimeout * 1000); //开启shell通道 Channel channel = session.openChannel("shell"); //通道连接 超时时间3s channel.connect(channelTimeout * 1000); //设置channel sshConnection.setChannel(channel); //转发消息 transToSSH(channel, "\r\n"); // //读取终端返回的信息流 InputStream inputStream = channel.getInputStream(); // try { 
    // InputStreamReader isr = new InputStreamReader(inputStream); // BufferedReader br = new BufferedReader(isr); // String line = null; // while ((line = br.readLine()) != null) { 
    // sendMessage(sshConnection.getConnectionId(), line); // } // } finally { 
    // //断开连接后关闭会话 // session.disconnect(); // channel.disconnect(); // if (inputStream != null) { 
    // inputStream.close(); // } // } try { 
    //循环读取 byte[] buffer = new byte[1024]; int i = 0; //如果没有数据来,线程会一直阻塞在这个地方等待数据。 // byte[] toSendBytes = null; // List 
   
     bytes = new ArrayList<>(); 
    // List 
   
     lastBytes = new ArrayList<>(); 
    // while ((i = inputStream.read(buffer)) != -1) { 
    // for (int j = 0; j < i; j++) { 
    // // byte b = buffer[j]; // System.out.print((char)b); // lastBytes.add(b); // if (b == '\n' || b == '\r' || b == '>') { 
    // bytes.addAll(lastBytes); // lastBytes.clear(); // } // } // if (bytes.size() > 0) { 
    // toSendBytes = new byte[bytes.size()]; // for (int i1 = 0; i1 < bytes.size(); i1++) { 
    // toSendBytes[i1] = bytes.get(i1); // } // sendMessage(webSSHData.getConnectionId(), toSendBytes); // } // } while ((i = inputStream.read(buffer)) != -1) { 
    sendMessage(webSSHData.getConnectionId(), Arrays.copyOfRange(buffer, 0, i)); } } finally { 
    //断开连接后关闭会话 session.disconnect(); channel.disconnect(); if (inputStream != null) { 
    inputStream.close(); } } } / * 将消息转发到终端 */ private void transToSSH(Channel channel, String command) throws IOException { 
    if (channel != null) { 
    OutputStream outputStream = channel.getOutputStream(); outputStream.write(command.getBytes()); outputStream.flush(); } } private void transToSSH(PrintWriter pw, String command) throws IOException { 
    if (pw != null) { 
    pw.println(command); pw.flush(); } } } 

3、定义一个controller,接收前端的websocket消息,websocket+stomp的配置的过程略过了,可以翻阅之前的文章

package com.iscas.ssh.server.controller; import com.iscas.ssh.server.model.WebSSHData; import com.iscas.ssh.server.service.SSHService; import com.iscas.templet.common.BaseController; import com.iscas.templet.common.ResponseEntity; import com.jcraft.jsch.JSchException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.security.Principal; / * ssh连接控制器 * * @author zhuquanwen * @vesion 1.0 * @date 2020/12/27 14:26 * @since jdk1.8 */ @RestController @Api(tags = "SSH连接控制器") public class WebSSHCotroller extends BaseController { 
    @Autowired private SSHService sshService; @MessageMapping("/connect") @ApiOperation(value="开启一个新的会话连接窗口-2020-12-27", notes="create by:朱全文") @ApiImplicitParams( { 
    @ApiImplicitParam(name = "sshData", value = "连接信息", required = true, dataType = "WebSSHData") } ) public ResponseEntity connect(Principal user, WebSSHData sshData) throws IOException, JSchException { 
    ResponseEntity response = getResponse(); String connectionId = sshData.getConnectionId(); sshService.initConnection(connectionId, user); sshService.recvHandle(sshData); return response; } @MessageMapping("/command") @ApiOperation(value="开启一个新的会话连接窗口-2020-12-27", notes="create by:朱全文") @ApiImplicitParams( { 
    @ApiImplicitParam(name = "sshData", value = "命令信息", required = true, dataType = "WebSSHData") } ) public ResponseEntity command(Principal user, WebSSHData sshData) throws IOException, JSchException { 
    ResponseEntity response = getResponse(); sshService.recvHandle(sshData); return response; } } 

4、用到的常量

package com.iscas.ssh.server.constant; / * * 公用常量 * @author zhuquanwen * @vesion 1.0 * @date 2020/12/27 14:05 * @since jdk1.8 */ public interface CommonConstants { 
    / * 随机生成uuid的key名 */ String USER_UUID_KEY = "user_uuid"; / * 发送指令:连接 */ String WEBSSH_OPERATE_CONNECT = "connect"; / * 发送指令:命令 */ String WEBSSH_OPERATE_COMMAND = "command"; } 

5、JS+HTML代码,xterm.js+xterm.css+jquery+sockjs+stompjs可以从网上下载

function WSSHClient() { 
    }; var stompClient = null; WSSHClient.prototype.connect = function (options) { 
    // var endpoint = this._generateEndpoint(); if (window.WebSocket) { 
    //如果支持websocket var socket = new SockJS("http://localhost:7901/demo/webSsh"); stompClient = Stomp.over(socket); var connectionId = options.connectInfo.connectionId; stompClient.connect({ 
    Authorization: "这是一个随机数" }, function connectCallback(frame) { 
    // 连接成功时(服务器响应 CONNECTED 帧)的回调方法 // alert("success"); subscribe(connectionId, options); options.onConnect(); }, function errorCallBack(error) { 
    // 连接失败时(服务器响应 ERROR 帧)的回调方法 options.onError('连接失败'); }); }else { 
    //否则报错 options.onError('WebSocket Not Supported'); return; } // this._connection.onopen = function () { 
    // options.onConnect(); // }; // // this._connection.onmessage = function (evt) { 
    // var data = evt.data.toString(); // //data = base64.decode(data); // options.onData(data); // }; // // // this._connection.onclose = function (evt) { 
    // options.onClose(); // }; }; //订阅消息 function subscribe(connectionId, options) { 
    stompClient.subscribe('/user/queue/' + connectionId, function (response) { 
    console.log("你接收到的消息为:" + response); options.onData(response.body); }); } WSSHClient.prototype.send = function (data) { 
    this._connection.send(JSON.stringify(data)); }; WSSHClient.prototype.sendInitData = function (options) { 
    //连接参数 stompClient.send("/app/connect", { 
   }, JSON.stringify(options)); } WSSHClient.prototype.sendClientData = function (connectionId, data) { 
    //发送指令 stompClient.send("/app/command", { 
   }, JSON.stringify({ 
   "operate": "command", "command": data, "connectionId": connectionId})) } var client = new WSSHClient(); 
<html> <head> <meta charset="UTF-8"> <title>WebSSH</title> <link rel="stylesheet" href="../css/xterm.css" /> <script src="../js/jquery-3.4.1.min.js"></script> <script src="../js/xterm.js" charset="utf-8"></script> <script src="../js/stomp.min.js"></script> <script src="../js/sockjs.min.js"></script> <script src="../js/webssh.js" charset="utf-8"></script> <script> function connect() { 
    var ip = document.getElementById('ip').value; var port = document.getElementById('port').value; var username = document.getElementById('username').value; var pwd = document.getElementById('pwd').value; openTerminal( { 
    operate:'connect', host: ip,//IP port: port,//端口号 username: username,//用户名 password: pwd, //密码 connectionId: 'xxxx' }); document.getElementById("terminal").style.display = "block"; document.getElementById("input").style.display = "none"; } function openTerminal(options){ 
    var client = new WSSHClient(); var term = new Terminal({ 
    cols: 97, rows: 37, cursorBlink: true, // 光标闪烁 cursorStyle: "block", // 光标样式 null | 'block' | 'underline' | 'bar' scrollback: 800, //回滚 tabStopWidth: 8, //制表宽度 screenKeys: true }); term.on('data', function (data) { 
    //键盘输入时的回调函数 client.sendClientData(options.connectionId, data); }); term.open(document.getElementById('terminal')); //在页面上显示连接中... term.write('Connecting...'); //执行连接操作 client.connect({ 
    connectInfo: options, onError: function (error) { 
    //连接失败回调 term.write('Error: ' + error + '\r\n'); }, onConnect: function () { 
    //连接成功回调 client.sendInitData(options); }, onClose: function () { 
    //连接关闭回调 term.write("\rconnection closed"); }, onData: function (data) { 
    //收到数据时回调 term.write(data); } }); } </script> </head> <body> <div id="terminal" style="width: 100%;height: 100%; display: none;" ></div> <div id = "input" style="display:block;"> IP<input id = "ip" value="localhost"/><br/> 端口:<input id="port" value="22"/><br/> 用户名:<input id="username" value="root"/><br/> 密码:<input id="pwd" value=""/><br/> <input type="submit" value="连接" onclick="connect();"/> </div> </body> </html> 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224893.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午10:54
下一篇 2026年3月17日 上午10:54


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号