基于Apache MINA SSHD的浏览器终端命令过滤方案咨询
基于Apache MINA SSHD的浏览器终端命令过滤方案
针对你用Apache MINA SSHD(后端)+ JavaScript(前端)实现浏览器终端,需要过滤可用命令的需求,直接给你落地可行的方案:
先明确两个思路的可行性
- 缓存变量控制输入:完全可行,也是最直接的落地方向,下文会给出具体实现逻辑。
- PTY flags定义可用命令:不可行。PTY flags仅用于配置终端的基础属性(比如终端类型、尺寸、波特率等),和命令权限控制无关联,不用在这个方向浪费精力。
方案一:后端Websocket层拦截校验(快速落地)
因为前端输入是通过Websocket传到后端,再转发给SSH输入流,所以可以在Websocket消息处理环节加拦截逻辑:
核心逻辑
- 为每个用户会话维护一个命令缓存(比如用
StringBuilder),用来拼接用户逐字符输入的内容。 - 当收到换行符(
\n/\r\n)时,判定为命令输入完成,提取命令名和允许列表对比。 - 允许的命令转发到SSH输入流;不允许的直接向前端返回错误提示,不传给SSH。
Java代码示例
// 每个会话的命令缓存,用会话ID做键 private Map<String, StringBuilder> sessionCmdCache = new ConcurrentHashMap<>(); // 预定义允许的命令列表 private Set<String> allowedCmds = Set.of("ls", "pwd", "cat", "whoami"); // Websocket消息处理方法 public void handleWebsocketMsg(String sessionId, String input) { StringBuilder cache = sessionCmdCache.computeIfAbsent(sessionId, k -> new StringBuilder()); // 拆分输入中的换行,处理完整/未完成的命令 String[] inputParts = input.split("\\r?\\n"); for (int i = 0; i < inputParts.length; i++) { String part = inputParts[i]; if (i < inputParts.length - 1) { // 带换行的部分,是完整命令 cache.append(part); validateAndForwardCmd(sessionId, cache.toString()); cache.setLength(0); // 清空缓存 } else { // 最后一段无换行,继续缓存 cache.append(part); } } } // 命令校验与转发 private void validateAndForwardCmd(String sessionId, String cmd) { // 提取命令名(简单处理:取第一个空格前的内容) String cmdName = cmd.trim().split("\\s+")[0]; if (allowedCmds.contains(cmdName)) { // 允许的命令,写入SSH输入流 SshClient sshClient = getSshClientBySession(sessionId); if (sshClient != null && sshClient.isConnected()) { sshClient.getShell().getIn().write((cmd + "\n").getBytes()); sshClient.getShell().getIn().flush(); } } else { // 不允许的命令,返回错误给前端 sendWebsocketMsg(sessionId, "错误:禁止执行命令 - " + cmdName + "\n"); } }
方案二:自定义ShellFactory(SSH原生层处理)
如果想更贴合Apache MINA SSHD的架构,可自定义ShellFactory,在SSH Shell层面处理命令过滤,无需依赖Websocket拦截:
核心逻辑
- 实现
Shell接口,在start方法中接管输入输出流,循环读取用户命令。 - 校验命令后,要么调用真实Shell执行允许的命令,要么返回错误提示。
- 配置SshServer使用自定义ShellFactory。
Java代码示例
public class RestrictedShell implements Shell { private InputStream in; private OutputStream out; private OutputStream err; private final Set<String> allowedCmds = Set.of("ls", "pwd", "cat"); @Override public void start(Environment env) throws IOException { // 启动线程处理命令输入 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { String line; // 输出提示符 out.write("$ ".getBytes()); out.flush(); while ((line = reader.readLine()) != null) { String cmdName = line.trim().split("\\s+")[0]; if (allowedCmds.contains(cmdName)) { // 执行允许的命令,转发到系统Shell Process process = new ProcessBuilder("/bin/sh", "-c", line).start(); // 把命令输出转接到SSH输出流 IOUtils.copy(process.getInputStream(), out); IOUtils.copy(process.getErrorStream(), err); process.waitFor(); } else { err.write(("禁止执行命令:" + cmdName + "\n").getBytes()); err.flush(); } // 重新输出提示符 out.write("$ ".getBytes()); out.flush(); } } catch (Exception e) { e.printStackTrace(); } }).start(); } // 实现Shell接口的其他方法 @Override public void setIn(InputStream in) { this.in = in; } @Override public void setOut(OutputStream out) { this.out = out; } @Override public void setErr(OutputStream err) { this.err = err; } @Override public void setSignalListener(SignalListener listener) {} @Override public void destroy() {} } // 配置SshServer使用自定义Shell SshServer sshd = SshServer.setUpDefaultServer(); sshd.setShellFactory(() -> new RestrictedShell());
命令缓存与校验的补充建议
- 会话级缓存:用用户会话ID作为键,将
StringBuilder缓存存在线程安全的Map中,避免多用户输入混淆。 - 复杂命令处理:如果需要支持管道、重定向等语法,可改用命令解析库(比如Apache Commons Exec)拆解命令结构,精准校验命令名和参数。
- 历史命令保存:可在用户会话中维护一个
List<String>,记录用户输入过的合法命令,方便后续回溯或统计。
内容的提问来源于stack exchange,提问作者Omar RB
相关产品推荐
相关产品推荐

