springboot项目中如何集成Webssh连接服务器?

springboot项目中如何集成Webssh连接服务器?

什么是Webssh?

WebSSH 是基于浏览器的 SSH 客户端,依托 WebSocket+HTTPS 加密传输,无需本地安装工具即可远程管理服务器。其优势在于跨平台兼容各类终端设备,支持集中权限管控与操作审计,能绕过部分防火墙限制,部署灵活且可与运维平台快速集成,大幅降低管理成本与使用门槛。

下面,我们就来手把手实现Webssh:

本教程将详细介绍如何在Vue项目中将现有的VNC连接替换为WebSSH连接,以提供更好的终端体验。

1 🌟工作原理🌟

  1. 前端通过WebSocket连接到后端的 /ws/ssh 端点
  2. 前端发送包含SSH连接参数的消息(主机、端口、用户名、密码)
  3. 后端接收消息后,使用JSch库建立到目标SSH服务器的连接
  4. 后端在WebSocket连接和SSH连接之间双向转发数据
  5. 前端的键盘输入通过WebSocket发送到后端,再由后端转发到SSH服务器
  6. SSH服务器的输出通过后端转发回前端,在终端中显示

2 🌟vue前端集成Webssh组件🌟

2.1 🌟安装xterm依赖🌟

首先,我们需要安装用于WebSSH功能的必要依赖包:

npm install xterm xterm-addon-fit xterm-addon-attach 

这些包提供了:

  • xterm: 终端模拟器核心库
  • xterm-addon-fit: 自动适配容器尺寸的插件
  • xterm-addon-attach: 将终端连接到WebSocket的插件

2.2 🌟创建WebSSH组件🌟

创建一个名为WebSSH.vue的新组件,用于处理SSH连接和终端显示:

<template> <div> <div> <div> <span>{{ host }}</span>:<span>{{ port }}</span> </div> <div> <!-- 连接/断开按钮 --> <button @click="connect" :disabled="connected"> <i></i> </button> <button @click="disconnect" :disabled="!connected"> <i></i> </button> </div> </div> <div> <div ref="terminal"></div> </div> </div> </template> <script> import { onMounted, onUnmounted, ref, nextTick } from 'vue'; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import 'xterm/css/xterm.css'; export default { name: 'WebSSH', props: { host: { type: String, required: true }, port: { type: Number, default: 22 }, username: { type: String, required: true }, password: { type: String, required: true } }, emits: ['connect', 'disconnect', 'error'], setup(props, { emit }) { const terminal = ref(null); const xterm = ref(null); const fitAddon = ref(null); const wsConnection = ref(null); const connected = ref(false); const connect = () => { if (connected.value) { console.log('Already connected'); return; } // WebSocket连接到后端SSH代理服务 // 修改为使用后端WebSocket端点 const wsUrl = `ws://localhost:8080/ws/ssh`; // 使用你的后端WebSocket端点 try { wsConnection.value = new WebSocket(wsUrl); wsConnection.value.onopen = () => { console.log('WebSocket连接已建立'); // 发送连接信息到后端 const connectMsg = { type: 'connect', host: props.host, port: props.port, username: userName, password: password }; wsConnection.value.send(JSON.stringify(connectMsg)); }; wsConnection.value.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'connected') { // 连接成功 connected.value = true; emit('connect'); // 初始化终端 if (!xterm.value) { xterm.value = new Terminal({ fontSize: 14, fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace', rows: 30, cols: 80, cursorBlink: true, convertEol: true }); fitAddon.value = new FitAddon(); xterm.value.loadAddon(fitAddon.value); xterm.value.open(terminal.value); fitAddon.value.fit(); // 将终端输入转发到WebSocket xterm.value.onData(data => { if (wsConnection.value && wsConnection.value.readyState === WebSocket.OPEN) { const stdinMsg = { type: 'stdin', data: data }; wsConnection.value.send(JSON.stringify(stdinMsg)); } }); // 当终端大小改变时,通知WebSocket xterm.value.onResize((size) => { if (wsConnection.value && wsConnection.value.readyState === WebSocket.OPEN) { const resizeMsg = { type: 'resize', cols: size.cols, rows: size.rows }; wsConnection.value.send(JSON.stringify(resizeMsg)); } }); } } else if (data.type === 'stdout') { // 输出数据到终端 xterm.value && xterm.value.write(data.data); } else if (data.type === 'error') { // 处理错误 console.error('SSH错误:', data.data); emit('error', data.data); } else if (data.type === 'disconnected') { // 处理断开连接 connected.value = false; emit('disconnect'); } } catch (e) { // 如果不是JSON格式,直接输出(可能是纯文本响应) xterm.value && xterm.value.write(event.data); } }; wsConnection.value.onclose = () => { console.log('WebSocket连接已关闭'); connected.value = false; emit('disconnect'); }; wsConnection.value.onerror = (error) => { console.error('WebSocket连接错误:', error); connected.value = false; emit('error', error); }; // 监听窗口大小变化 window.addEventListener('resize', () => { setTimeout(() => { if (fitAddon.value) { fitAddon.value.fit(); } }); }); } catch (error) { console.error('连接失败:', error); emit('error', error); } }; const disconnect = () => { if (wsConnection.value) { // 发送断开连接消息 const disconnectMsg = { type: 'disconnect' }; wsConnection.value.send(JSON.stringify(disconnectMsg)); wsConnection.value.close(); wsConnection.value = null; } if (xterm.value) { xterm.value.dispose(); xterm.value = null; } connected.value = false; emit('disconnect'); }; onMounted(() => { // 初始化终端尺寸 nextTick(() => { if (fitAddon.value) { fitAddon.value.fit(); } }); }); onUnmounted(() => { disconnect(); }); return { terminal, connect, disconnect, connected }; } }; </script> <style scoped> .webssh-container { display: flex; flex-direction: column; height: 100%; background-color: #1e1e1e; border: 1px solid #333; border-radius: 4px; overflow: hidden; } .terminal-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background-color: #2d2d30; color: #ccc; border-bottom: 1px solid #333; } .connection-info { font-family: monospace; font-size: 12px; } .controls { display: flex; gap: 8px; } .control-btn { background: #3c3c3c; border: 1px solid #555; color: #ccc; border-radius: 4px; padding: 4px 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; } .control-btn:hover { background: #4c4c4c; } .control-btn:disabled { background: #222; color: #666; cursor: not-allowed; } .terminal-wrapper { flex: 1; padding: 10px; overflow: hidden; } .terminal { height: 100%; width: 100%; } .xterm { height: 100%; } .xterm .xterm-viewport { height: 100% !important; } </style> 

2.3 🌟导入WebSSH组件到主页面🌟

在需要使用WebSSH功能的页面(例如workspace/index.vue)中导入WebSSH组件:

<WebSSH v-if="showWebSSH" :key="sshComponentKey" :host="sshHost" :port="sshPort" :username="userInfo.username" :password="sshPassword" @connect="onSSHConnect" @disconnect="onSSHDisconnect" @error="onSSHError"> </WebSSH> <script> import WebSSH from '@/components/WebSSH.vue'; </script> 

2.4 🌟添加SSH事件处理函数🌟

在页面中添加SSH连接相关的事件处理函数:

// SSH事件处理函数constonSSHConnect=()=>{ console.log('SSH连接成功'); ElMessage.success('SSH连接成功');};constonSSHDisconnect=()=>{ console.log('SSH连接断开'); ElMessage.info('SSH连接已断开'); showWebSSH.value =false;};constonSSHError=(error)=>{ console.error('SSH连接错误:', error); ElMessage.error('SSH连接失败: '+ error.message); showWebSSH.value =false;};

3 🌟后端采用Websocket+jsch远程连接终端🌟

本文将详细介绍如何在Spring Boot项目中实现WebSSH功能,使用户可以通过浏览器访问远程SSH服务器。

3.1 🌟添加必要依赖🌟

<!-- SSH Client for WebSSH --><dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version></dependency><!-- Spring WebSocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

3.2 🌟创建 SSH 消息类🌟

创建 SshMessage.java 类来处理前后端通信的数据格式:

importcom.fasterxml.jackson.databind.ObjectMapper;@Data@NoArgsConstructor@AllArgsConstructorpublicclassSshMessage{privateString type;privateString data;privateString host;privateString username;privateString password;privateInteger port;privateInteger cols;privateInteger rows;publicStringtoJson(){try{ObjectMapper mapper =newObjectMapper();return mapper.writeValueAsString(this);}catch(Exception e){ e.printStackTrace();return"{}";}}}

3.3 🌟创建WebSocket SSH处理器🌟

创建 WebSocketSshHandler.java 类来处理WebSocket连接和SSH交互:

packagecn.edu.gzpt.blockchain.handler;importcn.edu.gzpt.blockchain.handler.SshMessage;importcom.jcraft.jsch.ChannelShell;importcom.jcraft.jsch.JSch;importcom.jcraft.jsch.JSchException;importcom.jcraft.jsch.Session;importorg.springframework.stereotype.Component;importorg.springframework.web.socket.*;importjava.io.IOException;importjava.util.concurrent.ConcurrentHashMap;/** * WebSocket SSH处理器,用于处理WebSSH连接请求和数据转发 * 实现WebSocketHandler接口,处理前端WebSocket连接与后端SSH连接之间的数据传输 */@ComponentpublicclassWebSocketSshHandlerimplementsWebSocketHandler{/** * 存储WebSocket会话ID与SSH会话的映射关系 */privatefinalConcurrentHashMap<String,Session> sshSessions =newConcurrentHashMap<>();/** * 存储WebSocket会话ID与SSH Shell通道的映射关系 */privatefinalConcurrentHashMap<String,ChannelShell> shellChannels =newConcurrentHashMap<>();/** * 存储WebSocket会话ID与SSH输出读取线程的映射关系 */privatefinalConcurrentHashMap<String,Thread> readerThreads =newConcurrentHashMap<>();/** * WebSocket连接建立后的回调方法 * 记录连接建立日志 * * @param session WebSocket会话对象 * @throws Exception 异常 */@OverridepublicvoidafterConnectionEstablished(WebSocketSession session)throwsException{System.out.println("WebSocket连接已建立: "+ session.getId());}/** * 处理WebSocket消息的核心方法 * 根据消息类型分发到相应的处理方法 * * @param webSocketSession WebSocket会话对象 * @param message WebSocket消息对象 * @throws Exception 异常 */@OverridepublicvoidhandleMessage(WebSocketSession webSocketSession,WebSocketMessage<?> message)throwsException{String payload =(String) message.getPayload();try{// 解析来自前端的消息SshMessage sshMessage =parseMessage(payload);switch(sshMessage.getType()){case"connect":handleConnect(webSocketSession, sshMessage);break;case"stdin":handleStdin(webSocketSession, sshMessage);break;case"resize":handleResize(webSocketSession, sshMessage);break;case"disconnect":handleDisconnect(webSocketSession);break;default:sendError(webSocketSession,"未知的消息类型: "+ sshMessage.getType());}}catch(Exception e){sendError(webSocketSession,"处理消息时发生错误: "+ e.getMessage()); e.printStackTrace();}}/** * WebSocket传输错误处理方法 * 记录错误日志并断开连接 * * @param session WebSocket会话对象 * @param exception 传输异常对象 * @throws Exception 异常 */@OverridepublicvoidhandleTransportError(WebSocketSession session,Throwable exception)throwsException{System.out.println("WebSocket传输错误: "+ exception.getMessage());handleDisconnect(session);}/** * WebSocket连接关闭后的回调方法 * 记录关闭日志并清理资源 * * @param session WebSocket会话对象 * @param status 连接关闭状态 * @throws Exception 异常 */@OverridepublicvoidafterConnectionClosed(WebSocketSession session,CloseStatus status)throwsException{System.out.println("WebSocket连接已关闭: "+ session.getId()+", 状态: "+ status);handleDisconnect(session);}/** * 判断是否支持部分消息 * * @return 是否支持部分消息,此处返回false表示不支持 */@OverridepublicbooleansupportsPartialMessages(){returnfalse;}/** * 处理SSH连接请求 * 根据前端传递的SSH连接参数建立SSH连接 * * @param session WebSocket会话对象 * @param message 包含SSH连接参数的消息对象 * @throws JSchException SSH连接异常 */privatevoidhandleConnect(WebSocketSession session,SshMessage message)throwsJSchException{String sessionId = session.getId();String host = message.getHost();int port = message.getPort();String username = message.getUsername();String password = message.getPassword();if(host ==null|| username ==null|| password ==null){sendError(session,"缺少必要的SSH连接参数");return;}// 创建JSch实例JSch jsch =newJSch();Session sshSession = jsch.getSession(username, host, port); sshSession.setPassword(password);// 设置不验证主机密钥 sshSession.setConfig("StrictHostKeyChecking","no"); sshSession.connect(30000);// 30秒超时// 打开shell通道ChannelShell channel =(ChannelShell) sshSession.openChannel("shell"); channel.setPty(true); channel.connect();// 存储会话 sshSessions.put(sessionId, sshSession); shellChannels.put(sessionId, channel);// 启动读取线程Thread readerThread =newThread(()->{try{byte[] buffer =newbyte[1024];int i;while((i = channel.getInputStream().read(buffer))!=-1){String output =newString(buffer,0, i);sendToWebSocket(session,"stdout", output);}}catch(IOException e){System.err.println("读取SSH输出时发生错误: "+ e.getMessage());try{ session.close();}catch(IOException ioException){ ioException.printStackTrace();}}}); readerThread.setDaemon(true); readerThread.start(); readerThreads.put(sessionId, readerThread);// 发送连接成功消息sendToWebSocket(session,"connected","SSH连接已建立");}/** * 处理来自前端的键盘输入数据 * 将前端发送的键盘输入数据转发到SSH服务器 * * @param session WebSocket会话对象 * @param message 包含输入数据的消息对象 * @throws IOException IO异常 */privatevoidhandleStdin(WebSocketSession session,SshMessage message)throwsIOException{String data = message.getData();String sessionId = session.getId();ChannelShell channel = shellChannels.get(sessionId);if(channel !=null&& channel.isConnected()){ channel.getOutputStream().write(data.getBytes("UTF-8")); channel.getOutputStream().flush();}else{sendError(session,"SSH连接未建立或已断开");}}/** * 处理终端窗口大小调整请求 * 根据前端发送的窗口大小调整SSH终端尺寸 * * @param session WebSocket会话对象 * @param message 包含窗口尺寸信息的消息对象 */privatevoidhandleResize(WebSocketSession session,SshMessage message){String sessionId = session.getId();ChannelShell channel = shellChannels.get(sessionId);if(channel !=null&& channel.isConnected()){int cols = message.getCols();int rows = message.getRows();if(cols >0&& rows >0){ channel.setPtySize(cols, rows,640,480);}}}/** * 处理断开连接请求 * 清理所有与当前WebSocket会话关联的资源 * * @param session WebSocket会话对象 */privatevoidhandleDisconnect(WebSocketSession session){String sessionId = session.getId();// 关闭读取线程Thread readerThread = readerThreads.remove(sessionId);if(readerThread !=null){ readerThread.interrupt();}// 关闭shell通道ChannelShell channel = shellChannels.remove(sessionId);if(channel !=null&& channel.isConnected()){ channel.disconnect();}// 关闭SSH会话Session sshSession = sshSessions.remove(sessionId);if(sshSession !=null&& sshSession.isConnected()){ sshSession.disconnect();}}/** * 向WebSocket客户端发送消息 * * @param session WebSocket会话对象 * @param type 消息类型 * @param data 消息数据 */privatevoidsendToWebSocket(WebSocketSession session,String type,String data){try{if(session.isOpen()){SshMessage response =newSshMessage(type, data); session.sendMessage(newTextMessage(response.toJson()));}}catch(IOException e){System.err.println("发送WebSocket消息失败: "+ e.getMessage());}}/** * 向WebSocket客户端发送错误消息 * * @param session WebSocket会话对象 * @param errorMessage 错误消息内容 */privatevoidsendError(WebSocketSession session,String errorMessage){try{if(session.isOpen()){SshMessage errorMsg =newSshMessage("error", errorMessage); session.sendMessage(newTextMessage(errorMsg.toJson()));}}catch(IOException e){System.err.println("发送错误消息失败: "+ e.getMessage());}}/** * 解析前端发送的JSON格式消息 * 提取消息类型、数据及SSH连接参数 * * @param json 待解析的JSON字符串 * @return 解析后的SshMessage对象 */privateSshMessageparseMessage(String json){// 简单解析JSON字符串// 在实际项目中建议使用Jackson等库进行解析try{String type =extractJsonValue(json,"type");String data =extractJsonValue(json,"data");String host =extractJsonValue(json,"host");String username =extractJsonValue(json,"username");String password =extractJsonValue(json,"password");Integer port =null;String portStr =extractJsonValue(json,"port");if(portStr !=null&&!portStr.isEmpty()){try{ port =Integer.parseInt(portStr);}catch(NumberFormatException e){// 忽略错误,保持null}}Integer cols =null;String colsStr =extractJsonValue(json,"cols");if(colsStr !=null&&!colsStr.isEmpty()){try{ cols =Integer.parseInt(colsStr);}catch(NumberFormatException e){// 忽略错误,保持null}}Integer rows =null;String rowsStr =extractJsonValue(json,"rows");if(rowsStr !=null&&!rowsStr.isEmpty()){try{ rows =Integer.parseInt(rowsStr);}catch(NumberFormatException e){// 忽略错误,保持null}}returnnewSshMessage(type, data, host, username, password, port, cols, rows);}catch(Exception e){thrownewRuntimeException("解析消息失败: "+ e.getMessage());}}/** * 从JSON字符串中提取指定键的值 * 使用正则表达式解析JSON字符串 * * @param json JSON字符串 * @param key 要提取的键名 * @return 对应键的值,如果不存在则返回null */privateStringextractJsonValue(String json,String key){String pattern ="\""+ key +"\":\"([^\"]*)\"";java.util.regex.Pattern p =java.util.regex.Pattern.compile(pattern);java.util.regex.Matcher m = p.matcher(json);if(m.find()){return m.group(1);}// 尝试匹配数字值 pattern ="\""+ key +"\":([0-9]+)"; p =java.util.regex.Pattern.compile(pattern); m = p.matcher(json);if(m.find()){return m.group(1);}returnnull;}}

上述代码主要是WebSocket SSH处理器,实现远程SSH终端功能。通过WebSocket接收前端消息,支持SSH连接、命令执行、终端重定向和断开连接操作。使用JSch库建立SSH连接,维护会话状态,并将SSH输出实时推送回前端显示。

3.4 🌟配置Websocket端点🌟

创建 WebSocketConfig.java 配置类来注册WebSocket处理器:

importcn.edu.gzpt.blockchain.handler.WebSocketSshHandler;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.socket.config.annotation.EnableWebSocket;importorg.springframework.web.socket.config.annotation.WebSocketConfigurer;importorg.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration@EnableWebSocketpublicclassWebSocketConfigimplementsWebSocketConfigurer{@AutowiredprivateWebSocketSshHandler webSocketSshHandler;@OverridepublicvoidregisterWebSocketHandlers(WebSocketHandlerRegistry registry){ registry.addHandler(webSocketSshHandler,"/ws/ssh").setAllowedOrigins("*");// 生产环境中应设置具体域名}}

4 🌟效果图🌟

在这里插入图片描述

☀️☀️这里是skywalker的博客小记,如果你喜欢我的文章,可以点赞支持一下博主,感谢您对自由的支持~,如果有其他想要探索的内容,还请留言在评论区~

Read more

《Virt A Mate(VAM)》免安装豪华版v1.22中文汉化整合

《Virt A Mate(VAM)》免安装豪华版v1.22中文汉化整合

Virt-A-Mate》由Meshed VR 所开发的虚拟实境游戏,你也可以通过Oculus Rift 或HTC Vive 头戴式装置来进行互动式游玩,一旦你进入《Virt A Mate》的世界,你几乎会忘乎所以,进入一个全新的世界,这个世界遵循基本的物理定力,也就是说游戏中的头发、衣服都很真实,随着你的动作而产生运动,而玩家也能亲自编辑角色的服装。 VAM整合包 解压后30GB 解压密码在里面 请看清楚 包含vam软件本体,mmd跳舞插件,国漫人物。都在整合包里面! vam是软件不是游戏 但完成跳舞是比较简单的 回复关键词:vam

必看:2026年跨维度AR测试工具进化论

必看:2026年跨维度AR测试工具进化论

AR测试工具的热度背景 2026年,增强现实(AR)技术正深度融入金融、电商、医疗等垂直领域,驱动跨维度测试需求激增。软件测试从业者面临空间交互、多设备兼容性等新挑战,公众号内容热度由此聚焦工具进化与实战解决方案。用户痛点如AR场景下的缺陷预测和自动化覆盖,成为流量核心驱动力,其中工具评测与AI融合话题占据主导地位。 一、公众号热度内容全景解析 1. AI驱动的AR测试自动化(热度指数:95%) 生成式AI工具(如ChatGPT)已重塑AR测试工作流,能自动生成80%的回归测试脚本,大幅降低人工耗时。热门内容如《2026年Top 5 AR测试工具》强调实操数据: * 效率提升:AI插件(如Selenium扩展)使AR空间定位测试效率提高30%,缺陷检出率提升40%。 * 爆款案例:某电商AR试穿功能的测试优化指南,单篇引流10万+,核心是提供免费AI脚本资源包。 从业者关注点集中于低代码工具集成(如Testim)和Prompt工程技巧,以应对AR交互复杂性。 2. 云平台支持的跨维度测试(热度指数:88%) 云测试平台(如AWS

AIGC - Raphael AI:全球首个无限制免费 AI 图片生成器

AIGC - Raphael AI:全球首个无限制免费 AI 图片生成器

文章目录 * 引言 * 一、Raphael AI 是什么? * 二、核心引擎:Flux.1-Dev 与 Flux Kontext * 1. Flux.1-Dev:极速与精细的结合 * 2. Flux Kontext:精确的语义理解 * 三、主要功能一览 * 1. 零成本创作 * 2. 多风格引擎 * 3. 高级文本理解 * 4. 极速生成 * 5. 隐私保护 * 四、实测体验与使用方式 * 五、与其他 AI 绘图平台的对比 * 六、未来发展与生态计划 * 七、总结:AI 创意的平权时代 引言 在生成式 AI 技术飞速发展的时代,图像生成的门槛正在被彻底打破。

RMBG-2.0多任务协同方案:接入Stable Diffusion工作流,生成→抠图→合成一体化

RMBG-2.0多任务协同方案:接入Stable Diffusion工作流,生成→抠图→合成一体化 1. 为什么抠图成了AI图像工作流的“卡点”? 你有没有遇到过这样的场景:用Stable Diffusion生成了一张绝美的角色立绘,但背景太杂乱,想换到电商详情页却卡在了抠图环节?手动PS耗时半小时,AI在线工具又担心图片上传泄露隐私,还动不动就崩掉——毛发边缘糊成一片,玻璃杯透明感全无,甚至把飘动的发丝直接切掉。 这不是个别现象。大量设计师、内容创作者、电商运营者反馈:生成容易,落地难;模型很炫,流程断在抠图这一步。 而RMBG-2.0(BiRefNet)的出现,正在悄悄改变这个局面。它不是又一个“差不多能用”的抠图工具,而是首个真正意义上能无缝嵌入本地AI图像工作流的高精度、低延迟、零隐私风险抠图引擎。它不只解决“能不能抠”,更解决“抠完怎么用”——直接对接SD WebUI、ComfyUI、乃至自定义Python脚本,让“生成→