WebSSH 项目技术实现详解

一、项目概述
本项目实现了一个功能完善的 WebSSH 终端系统,允许用户通过浏览器安全地访问远程 Linux 服务器。系统采用前后端分离架构,前端使用 Vue 3 + xterm.js 实现终端界面,后端使用 Spring Boot + SSHJ 实现 SSH 连接管理。
二、技术架构
2.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 前端层 (Vue 3) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Terminal UI │ │ Server List │ │
│ │ (xterm.js) │ │ (Element Plus) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ └─────────┬───────────┘ │
│ │ WebSocket (STOMP) │
├─────────────────────┼───────────────────────────────────────────┤
│ 后端层 (Spring Boot) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ SshController (WebSocket Handler) │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ SSHManager (SSHJ Connection Pool) │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │ Server1 │ │ Server2 │ │ Server3 │ ... │ │ │
│ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │
│ │ └───────┴───────────┴───────────┴───────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 远程服务器层 │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Linux Server │ │ Linux Server │ │ Linux Server │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 技术栈
| 层次 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 前端框架 | Vue | 3.x | 渐进式 JavaScript 框架 |
| 终端组件 | xterm.js | 5.x | 功能强大的终端模拟器 |
| UI 组件库 | Element Plus | 2.x | Vue 3 组件库 |
| 通信协议 | STOMP over WebSocket | - | 消息队列协议 |
| 后端框架 | Spring Boot | 3.x | Java 微服务框架 |
| SSH 客户端 | SSHJ | 0.37.x | Java SSH 库 |
| 数据库 | MySQL/DM6 | - | 服务器配置存储 |
三、后端实现
3.1 SSH 连接管理器 (SSHManager)
核心类 SSHManager 负责管理 SSH 连接的生命周期,支持密码和私钥两种认证方式。
关键设计要点:
- 连接池机制 :使用
ConcurrentHashMap实现连接复用,避免频繁创建连接
java
private static final Map<Long, SSHManager> connectionPool = new ConcurrentHashMap<>();
- 双认证支持:同时支持密码认证和私钥认证
java
if (privateKey != null && !privateKey.isEmpty()) {
// 私钥认证
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
keyFile.init(new StringReader(privateKey));
sshClient.authPublickey(username, keyFile);
} else {
// 密码认证
sshClient.authPassword(username, password);
}
- Shell 会话管理:独立的 Shell 会话,支持终端大小调整
java
public void startShell() throws Exception {
session = sshClient.startSession();
session.allocateDefaultPTY();
shell = session.startShell();
inputStream = shell.getInputStream();
outputStream = shell.getOutputStream();
}
- SFTP 复用:同一个 SSH 连接可同时支持 Shell 和 SFTP 会话
java
public SFTPClient getSftpClient() throws IOException {
if (sftpClient == null) {
if (!isConnected()) {
throw new IOException("SSH连接已断开");
}
sftpClient = sshClient.newSFTPClient();
}
return sftpClient;
}
3.2 WebSocket 控制器 (SshController)
使用 Spring WebSocket + STOMP 协议实现实时通信。
消息端点设计:
| 端点 | 方法 | 功能 |
|---|---|---|
/app/terminal/init/{sessionId} |
initTerminal | 初始化 SSH 连接 |
/app/terminal/data/{sessionId} |
sendData | 发送命令数据 |
/app/terminal/resize/{sessionId} |
resizeTerminal | 调整终端大小 |
/app/terminal/charset/{sessionId} |
changeCharset | 切换字符编码 |
/app/terminal/disconnect/{sessionId} |
disconnectTerminal | 断开连接 |
消息处理流程:
java
@MessageMapping("/terminal/init/{sessionId}")
public void initTerminal(@DestinationVariable String sessionId,
@Payload String messageJson,
SimpMessageHeaderAccessor headerAccessor) {
// 1. 解析服务器配置
JsonNode jsonNode = objectMapper.readTree(messageJson);
String serverId = jsonNode.get("serverId").asText();
// 2. 获取服务器信息
SshServer server = sshServerService.getById(serverId);
// 3. 创建/复用 SSH 连接
SSHManager sshManager = SSHManager.getOrCreateConnection(server, true);
sshManager.startShell();
sessionMap.put(sessionId, sshManager);
// 4. 设置输出处理器,将 SSH 输出转发给前端
sshManager.setOutputHandler(data -> {
messagingTemplate.convertAndSendToUser(userId, "/queue/terminal", dataMap);
});
}
3.3 连接池策略
java
public static SSHManager getOrCreateConnection(SshServer server, boolean forceNew) {
Long serverId = Long.valueOf(server.getId());
// 强制新建:先清理池中旧连接
if (forceNew) {
SSHManager old = connectionPool.remove(serverId);
if (old != null) {
old.disconnect();
}
} else {
// 尝试复用池中连接
SSHManager manager = connectionPool.get(serverId);
if (manager != null && manager.isConnected()) {
return manager;
}
}
// 创建新连接并加入连接池
SSHManager manager = fromServer(server);
connectionPool.put(serverId, manager);
return manager;
}
四、前端实现
4.1 终端组件初始化
使用 xterm.js 创建终端实例,支持多种主题和自定义配置。
javascript
const initTerminal = async () => {
const { Terminal } = await import('@xterm/xterm')
const { FitAddon } = await import('@xterm/addon-fit')
terminal = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
fontSize: 18,
fontFamily: '"Fira Code", monospace',
theme: {
background: '#282a36',
foreground: '#f8f8f2'
},
scrollback: 10000,
lineWrap: true
})
fitAddon = new FitAddon()
terminal.loadAddon(fitAddon)
terminal.open(terminalRef.value)
// 监听用户输入
terminal.onData(data => {
if (isConnected()) {
sendMessage('/app/terminal/data/' + sessionId, {}, JSON.stringify({ data }))
}
})
}
4.2 WebSocket 连接管理
封装 STOMP 客户端,处理连接建立和消息订阅。
javascript
const connect = async () => {
// 1. 建立 WebSocket 连接
await StompServerConnect()
// 2. 生成唯一会话 ID
sessionId = 'ssh-' + Date.now()
const userId = 'user-' + sessionId
// 3. 订阅终端消息
const stompClient = getStompClient()
sshSubscription = stompClient.subscribe(
'/user/' + userId + '/queue/terminal',
(message) => {
const msg = JSON.parse(message.body)
if (msg.type === 'data') {
terminal.write(msg.data)
}
}
)
// 4. 初始化 SSH 会话
await sendMessage('/app/terminal/init/' + sessionId, { 'userId': userId },
JSON.stringify({
serverId: selectedServer.value.id,
cols: terminal.cols,
rows: terminal.rows,
charset: 'UTF-8'
}))
}
4.3 多面板布局
实现可拖拽的分屏布局,支持同时显示终端、SFTP、监控和 Docker 面板。
vue
<el-splitter :model-value="splitPosition" class="ssh-splitter">
<!-- 左侧服务器列表 -->
<el-splitter-panel :size="splitPosition">
<div class="server-panel">...</div>
</el-splitter-panel>
<!-- 右侧终端区域 -->
<el-splitter-panel>
<el-splitter class="right-splitter">
<!-- 主终端 -->
<el-splitter-panel>
<div class="terminal-panel">...</div>
</el-splitter-panel>
<!-- SFTP 面板 -->
<el-splitter-panel v-if="showSftpPanel">
<SftpPanel />
</el-splitter-panel>
</el-splitter>
</el-splitter-panel>
</el-splitter>
五、核心功能
5.1 服务器管理
支持服务器的增删改查,支持密码和密钥两种认证方式。
服务器配置实体:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long | 主键 |
| serverName | String | 服务器名称 |
| host | String | 主机地址 |
| port | Integer | SSH 端口 |
| username | String | 用户名 |
| password | String | 密码(加密存储) |
| privateKey | String | 私钥内容 |
| authType | String | 认证类型 (password/key) |
| remark | String | 备注 |
5.2 终端设置
支持丰富的终端个性化配置:
- 字体设置:字号、字体样式
- 光标设置:样式(方块/下划线/竖线)、闪烁
- 滚动设置:缓冲区行数
- 主题设置:深空蓝、黑客绿、VS Code Dark、Monokai、Solarized Dark、Dracula
- 编码设置:UTF-8、GBK、GB2312、Latin-1
5.3 安全特性
- 危险命令检测:执行命令前进行安全检测
javascript
const handleExecuteCommand = async (cmd) => {
const res = await securityCheck(cmd.commandContent)
if (res.code !== 200) {
securityWarningMessage.value = res.message
showSecurityWarning.value = true
return
}
executeCommandDirectly(cmd)
}
- 连接超时处理:设置合理的连接超时时间,避免资源泄漏
java
private static final int CONNECT_TIMEOUT = 60000;
sshClient.setConnectTimeout(CONNECT_TIMEOUT);
- 连接状态监控:实时检测连接状态,自动重连
六、性能优化
6.1 连接复用
通过连接池复用 SSH 连接,减少连接建立开销。
6.2 防抖处理
对终端 resize 事件进行防抖处理,避免频繁发送 resize 请求。
javascript
const debounce = (func, wait) => {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}
const handleResizeDebounced = debounce((size) => {
if (isConnected() && size.cols > 0 && size.rows > 0) {
sendMessage('/app/terminal/resize/' + sessionId, {}, JSON.stringify(size))
}
}, 200)
6.3 懒加载
按需加载 xterm.js 组件,减少首屏加载时间。
javascript
const { Terminal } = await import('@xterm/xterm')
七、使用示例
7.1 添加服务器
javascript
const addServer = async () => {
const res = await addSshServer({
serverName: '生产服务器',
host: '192.168.1.100',
port: 22,
username: 'root',
password: '******',
authType: 'password',
remark: '生产环境主服务器'
})
}
7.2 连接服务器
javascript
// 选择服务器
selectedServer.value = serverList.value[0]
// 初始化终端
await initTerminal()
// 建立连接
await connect()
八、总结
本 WebSSH 项目实现了一个功能完整、性能优秀的远程终端系统,具备以下特点:
- 高可用性:连接池机制确保连接复用和故障恢复
- 安全性:危险命令检测、连接超时保护
- 用户体验:丰富的主题配置、流畅的终端交互
- 扩展性:模块化设计,易于添加新功能
项目架构清晰,代码结构合理,是企业级 WebSSH 解决方案的理想选择。
项目文件结构:
server/src/main/java/com/kd/ssh/
├── config/
│ └── SSHManager.java # SSH 连接管理器
├── controller/
│ ├── SshController.java # WebSocket 控制器
│ ├── SftpController.java # SFTP 控制器
│ └── DockerController.java # Docker 控制器
├── entity/
│ └── SshServer.java # 服务器实体
├── mapper/
│ └── SshServerMapper.java # 数据库操作
└── service/
└── SshServerService.java # 业务服务
ui/src/views/ssh/
├── index.vue # 主页面
├── ssh.js # WebSocket 封装
└── components/
├── SftpPanel.vue # SFTP 面板
├── QuickCommandPanel.vue # 快捷命令面板
├── ServerMonitorPanel.vue # 服务器监控面板
└── DockerPanel.vue # Docker 面板