<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
