使用纯Java实现一个WebSSH项目的示例代码

最近由于项目需求,项目中需要实现一个WebSSH连接终端的功能,由于自己第一次做这类型功能,所以首先上了GitHub找了找有没有现成的轮子可以拿来直接用,当时看到了很多这方面的项目,例如:GateOne、webssh、shellinabox等,这些项目都可以很好地实现webssh的功能,但是最终并没有采用,原因是在于这些底层大都是python写的,需要依赖很多文件,自己用的时候可以使用这种方案,快捷省事,但是做到项目中供用户使用时,总不能要求用户做到服务器中必须包含这些底层依赖,这显然不太合理,所以我决定自己动手写一个WebSSH的功能,并且作为一个独立的项目开源出来。

github项目开源地址: https://github.com/NoCortY/WebSSH

技术选型

由于webssh需要实时数据交互,所以会选用长连接的WebSocket,为了开发的方便,框架选用SpringBoot,另外还自己了解了Java用户连接ssh的jsch和实现前端shell页面的xterm.js.

所以,最终的技术选型就是 SpringBoot+Websocket+jsch+xterm.js。

导入依赖

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.1.7.RELEASE</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>
 <!-- 文件上传解析器 -->
 <dependency>
 <groupId>commons-io</groupId>
 <artifactId>commons-io</artifactId>
 <version>1.4</version>
 </dependency>
 <dependency>
 <groupId>commons-fileupload</groupId>
 <artifactId>commons-fileupload</artifactId>
 <version>1.3.1</version>
 </dependency>
</dependencies>

一个简单的xterm案例

由于xterm是一个冷门技术,所以很多同学并没有这方面的知识支撑,我也是为了实现这个功能所以临时学的,所以在这给大家介绍一下。

xterm.js是一个基于WebSocket的容器,它可以帮助我们在前端实现命令行的样式。就像是我们平常再用SecureCRT或者XShell连接服务器时一样。

下面是官网上的入门案例:

<!doctype html>
 <html>
 <head>
 <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" rel="external nofollow" />
 <script src="node_modules/xterm/lib/xterm.js"></script>
 </head>
 <body>
 <div id="terminal"></div>
 <script>
 var term = new Terminal();
 term.open(document.getElementById('terminal'));
 term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
 </script>
 </body>
 </html>

最终测试,页面就是下面这个样子:

可以看到页面已经出现了类似与shell的样式,那就根据这个继续深入,实现一个webssh。

后端实现

由于xterm只要只是实现了前端的样式,并不能真正地实现与服务器交互,与服务器交互主要还是靠我们Java后端来进行控制的,所以我们从后端开始,使用jsch+websocket实现这部分内容。

WebSocket配置

由于消息实时推送到前端需要用到WebSocket,不了解WebSocket的同学可以先去自行了解一下,这里就不过多介绍了,我们直接开始进行WebSocket的配置。

/**
* @Description: websocket配置
* @Author: NoCortY
* @Date: 2020/3/8
*/
@Configuration
@EnableWebSocket
public class WebSSHWebSocketConfig implements WebSocketConfigurer{
 @Autowired
 WebSSHWebSocketHandler webSSHWebSocketHandler;
 @Override
 public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
 //socket通道
 //指定处理器和路径,并设置跨域
 webSocketHandlerRegistry.addHandler(webSSHWebSocketHandler, "/webssh")
  .addInterceptors(new WebSocketInterceptor())
  .setAllowedOrigins("*");
 }
}

处理器(Handler)和拦截器(Interceptor)的实现

刚才我们完成了WebSocket的配置,并指定了一个处理器和拦截器。所以接下来就是处理器和拦截器的实现。

拦截器:

public class WebSocketInterceptor implements HandshakeInterceptor {
 /**
 * @Description: Handler处理前调用
 * @Param: [serverHttpRequest, serverHttpResponse, webSocketHandler, map]
 * @return: boolean
 * @Author: NoCortY
 * @Date: 2020/3/1
 */
 @Override
 public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
 if (serverHttpRequest instanceof ServletServerHttpRequest) {
  ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
  //生成一个UUID,这里由于是独立的项目,没有用户模块,所以可以用随机的UUID
  //但是如果要集成到自己的项目中,需要将其改为自己识别用户的标识
  String uuid = UUID.randomUUID().toString().replace("-","");
  //将uuid放到websocketsession中
  map.put(ConstantPool.USER_UUID_KEY, uuid);
  return true;
 } else {
  return false;
 }
 }

 @Override
 public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

 }
}

处理器:

/**
* @Description: WebSSH的WebSocket处理器
* @Author: NoCortY
* @Date: 2020/3/8
*/
@Component
public class WebSSHWebSocketHandler implements WebSocketHandler{
 @Autowired
 private WebSSHService webSSHService;
 private Logger logger = LoggerFactory.getLogger(WebSSHWebSocketHandler.class);

 /**
 * @Description: 用户连接上WebSocket的回调
 * @Param: [webSocketSession]
 * @return: void
 * @Author: Object
 * @Date: 2020/3/8
 */
 @Override
 public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
 logger.info("用户:{},连接WebSSH", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY));
 //调用初始化连接
 webSSHService.initConnection(webSocketSession);
 }

 /**
 * @Description: 收到消息的回调
 * @Param: [webSocketSession, webSocketMessage]
 * @return: void
 * @Author: NoCortY
 * @Date: 2020/3/8
 */
 @Override
 public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
 if (webSocketMessage instanceof TextMessage) {
  logger.info("用户:{},发送命令:{}", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY), webSocketMessage.toString());
  //调用service接收消息
  webSSHService.recvHandle(((TextMessage) webSocketMessage).getPayload(), webSocketSession);
 } else if (webSocketMessage instanceof BinaryMessage) {

 } else if (webSocketMessage instanceof PongMessage) {

 } else {
  System.out.println("Unexpected WebSocket message type: " + webSocketMessage);
 }
 }

 /**
 * @Description: 出现错误的回调
 * @Param: [webSocketSession, throwable]
 * @return: void
 * @Author: Object
 * @Date: 2020/3/8
 */
 @Override
 public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
 logger.error("数据传输错误");
 }

 /**
 * @Description: 连接关闭的回调
 * @Param: [webSocketSession, closeStatus]
 * @return: void
 * @Author: NoCortY
 * @Date: 2020/3/8
 */
 @Override
 public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
 logger.info("用户:{}断开webssh连接", String.valueOf(webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY)));
 //调用service关闭连接
 webSSHService.close(webSocketSession);
 }

 @Override
 public boolean supportsPartialMessages() {
 return false;
 }
}

需要注意的是,我在拦截器中加入的用户标识是使用了 随机的UUID ,这是因为作为一个独立的websocket项目,没有用户模块,如果需要将这个项目集成到自己的项目中,需要修改这部分代码,将其改为自己项目中识别一个用户所用的用户标识。

WebSSH的业务逻辑实现(核心)

刚才我们实现了websocket的配置,都是一些死代码,实现了接口再根据自身需求即可实现,现在我们将进行后端主要业务逻辑的实现,在实现这个逻辑之前,我们先来想想,WebSSH,我们主要想要呈现一个什么效果。

我这里做了一个总结:

1.首先我们得先连接上终端(初始化连接)
2.其次我们的服务端需要处理来自前端的消息(接收并处理前端消息)
3.我们需要将终端返回的消息回写到前端(数据回写前端)
4.关闭连接

根据这四个需求,我们先定义一个接口,这样可以让需求明了起来。

/**
 * @Description: WebSSH的业务逻辑
 * @Author: NoCortY
 * @Date: 2020/3/7
 */
public interface WebSSHService {
 /**
 * @Description: 初始化ssh连接
 * @Param:
 * @return:
 * @Author: NoCortY
 * @Date: 2020/3/7
 */
 public void initConnection(WebSocketSession session);

 /**
 * @Description: 处理客户段发的数据
 * @Param:
 * @return:
 * @Author: NoCortY
 * @Date: 2020/3/7
 */
 public void recvHandle(String buffer, WebSocketSession session);

 /**
 * @Description: 数据写回前端 for websocket
 * @Param:
 * @return:
 * @Author: NoCortY
 * @Date: 2020/3/7
 */
 public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException;

 /**
 * @Description: 关闭连接
 * @Param:
 * @return:
 * @Author: NoCortY
 * @Date: 2020/3/7
 */
 public void close(WebSocketSession session);
}

现在我们可以根据这个接口去实现我们定义的功能了。

初始化连接

由于我们的底层是依赖jsch实现的,所以这里是需要使用jsch去建立连接的。而所谓初始化连接,实际上就是将我们所需要的连接信息,保存在一个Map中,这里并不进行任何的真实连接操作。为什么这里不直接进行连接?因为这里前端只是连接上了WebSocket,但是我们还需要前端给我们发来linux终端的用户名和密码,没有这些信息,我们是无法进行连接的。

public void initConnection(WebSocketSession session) {
 JSch jSch = new JSch();
 SSHConnectInfo sshConnectInfo = new SSHConnectInfo();
 sshConnectInfo.setjSch(jSch);
 sshConnectInfo.setWebSocketSession(session);
 String uuid = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
 //将这个ssh连接信息放入map中
 sshMap.put(uuid, sshConnectInfo);
}

处理客户端发送的数据

在这一步骤中,我们会分为两个分支。

第一个分支:如果客户端发来的是终端的用户名和密码等信息,那么我们进行终端的连接。

第二个分支:如果客户端发来的是操作终端的命令,那么我们就直接转发到终端并且获取终端的执行结果。

具体代码实现:

public void recvHandle(String buffer, WebSocketSession session) {
 ObjectMapper objectMapper = new ObjectMapper();
 WebSSHData webSSHData = null;
 try {
  //转换前端发送的JSON
  webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
 } catch (IOException e) {
  logger.error("Json转换异常");
  logger.error("异常信息:{}", e.getMessage());
  return;
 }
 //获取刚才设置的随机的uuid
 String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
 if (ConstantPool.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) {
  //如果是连接请求
  //找到刚才存储的ssh连接对象
  SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
  //启动线程异步处理
  WebSSHData finalWebSSHData = webSSHData;
  executorService.execute(new Runnable() {
  @Override
  public void run() {
   try {
   //连接到终端
   connectToSSH(sshConnectInfo, finalWebSSHData, session);
   } catch (JSchException | IOException e) {
   logger.error("webssh连接异常");
   logger.error("异常信息:{}", e.getMessage());
   close(session);
   }
  }
  });
 } else if (ConstantPool.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) {
  //如果是发送命令的请求
  String command = webSSHData.getCommand();
  SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
  if (sshConnectInfo != null) {
  try {
   //发送命令到终端
   transToSSH(sshConnectInfo.getChannel(), command);
  } catch (IOException e) {
   logger.error("webssh连接异常");
   logger.error("异常信息:{}", e.getMessage());
   close(session);
  }
  }
 } else {
  logger.error("不支持的操作");
  close(session);
 }
}

数据通过websocket发送到前端

public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
 session.sendMessage(new TextMessage(buffer));
}

关闭连接

public void close(WebSocketSession session) {
 //获取随机生成的uuid
 String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
 SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
 if (sshConnectInfo != null) {
  //断开连接
  if (sshConnectInfo.getChannel() != null) sshConnectInfo.getChannel().disconnect();
  //map中移除该ssh连接信息
  sshMap.remove(userId);
 }
}

至此,我们的整个后端实现就结束了,由于篇幅有限,这里将一些操作封装成了方法,就不做过多展示了,重点讲逻辑实现的思路吧。接下来我们将进行前端的实现。

前端实现

前端工作主要分为这么几个步骤:

  • 页面的实现
  • 连接WebSocket并完成数据的接收并回写
  • 数据的发送

所以我们一步一步来实现它。

页面实现

页面的实现很简单,我们只不过需要在一整个屏幕上都显示终端那种大黑屏幕,所以我们并不用写什么样式,只需要创建一个div,之后将terminal实例通过xterm放到这个div中,就可以实现了。

<!doctype html>
<html>
<head>
 <title>WebSSH</title>
 <link rel="stylesheet" href="../css/xterm.css" rel="external nofollow" />
</head>
<body>
<div id="terminal" style="width: 100%;height: 100%"></div>

<script src="../lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<script src="../js/xterm.js" charset="utf-8"></script>
<script src="../js/webssh.js" charset="utf-8"></script>
<script src="../js/base64.js" charset="utf-8"></script>
</body>
</html>

连接WebSocket并完成数据的发送、接收、回写

openTerminal( {
 //这里的内容可以写死,但是要整合到项目中时,需要通过参数的方式传入,可以动态连接某个终端。
 operate:'connect',
 host: 'ip地址',
 port: '端口号',
 username: '用户名',
 password: '密码'
 });
 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(data);
 });
 term.open(document.getElementById('terminal'));
 //在页面上显示连接中...
 term.write('Connecting...');
 //执行连接操作
 client.connect({
  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);
  }
 });
 }

效果展示

连接

连接成功

命令操作

ls命令:

vim编辑器:

top命令:

结语

这样我们就完成了一个webssh项目的实现,没有依赖其它任何的组件,后端完全使用Java实现,由于用了SpringBoot,非常容易部署。

但是,我们还可以对这个项目进行扩展,比如新增上传或下载文件,就像Xftp一样,可以很方便地拖拽式上传下载文件。

这个项目之后我会持续更新,上述功能也会慢慢实现,Github: https://github.com/NoCortY/WebSSH

到此这篇关于使用纯Java实现一个WebSSH项目的文章就介绍到这了,更多相关Java实现WebSSH项目内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java的web开发中SSH框架的协作处理应用笔记

    相信SSH没人不知道了吧,struts2+spring+hibernate,企业开发的"基础"架构,为什么基础标上引号,因为这个基础只是很多人自以为的而已,最基础的是servlet,很多培训机构很多基础都不教,直接上来就三大框架了,SSH不然就SSI,搞得很多人以为JAVAWEB开发就一定要框架,没框架就跟没了手一个样.三大框架没有什么坏处,实用,很多公司都在用,直接上手开发.但毕业以为真的好久没用三大框架了,spring倒是有用,特别是springMVC,感觉用起来比struts2爽

  • 使用纯Java实现一个WebSSH项目的示例代码

    最近由于项目需求,项目中需要实现一个WebSSH连接终端的功能,由于自己第一次做这类型功能,所以首先上了GitHub找了找有没有现成的轮子可以拿来直接用,当时看到了很多这方面的项目,例如:GateOne.webssh.shellinabox等,这些项目都可以很好地实现webssh的功能,但是最终并没有采用,原因是在于这些底层大都是python写的,需要依赖很多文件,自己用的时候可以使用这种方案,快捷省事,但是做到项目中供用户使用时,总不能要求用户做到服务器中必须包含这些底层依赖,这显然不太合理,

  • 3分钟纯 Java 注解搭个管理系统的示例代码

    最近接触到个新项目,发现它用了一个比较有意思的框架,可以说实现了我刚入行时候的梦想,所以这里马不停蹄的和大家分享下. 在我刚开始工作接触的项目都还没做前后端分离,经常需要后端来维护页面,有时候觉得自己好像天生不适合干前端,你要是让我研究研究后端的技术,看个中间件源码啊,分析分析什么框架底层原理啊,这都问题不大,偶尔搞一下JS也可以.你要是让我写个css样式,那简直要命了,一点也提不起兴趣,不知道有没有跟我一样的. 今天要介绍的框架直接不用写页面了,话不多说,下边咱们直奔主题 Erupt一个通用后

  • java实现一个简单的网络爬虫代码示例

    目前市面上流行的爬虫以python居多,简单了解之后,觉得简单的一些页面的爬虫,主要就是去解析目标页面(html).那么就在想,java有没有用户方便解析html页面呢?找到了一个jsoup包,一个非常方便解析html的工具呢. 使用方式也非常简单,引入jar包: <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.8.

  • Java实现手写一个线程池的示例代码

    目录 概述 线程池框架设计 代码实现 阻塞队列的实现 线程池消费端实现 获取任务超时设计 拒绝策略设计 概述 线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?如果直接去看jdk源码的话,可能有一定的难度,那么我们可以先通过手写一个简单的线程池框架,去掌握线程池的基本原理后,再去看jdk的线程池源码就会相对容易,而且不容易忘记. 线程池框架设计 我们都知道,线程资源的创建和销毁并不是没有代价的,甚至开销是非常高的.同

  • Java实现简单的五子棋游戏示例代码

    目录 项目结构 核心代码 ArrComparator.java类 ChessMap.java类 ChessPanel.java类 效果图展示 项目结构 这个是在网上找的资源,出处记不得了,记录一下.程序的总体结构,很简单的: 核心代码 代码如下: ArrComparator.java类 import java.util.Comparator; /** * 排序 Comparator */ class ArrComparator implements Comparator<Object> { i

  • Java 使用 FFmpeg 处理视频文件示例代码详解

    目前在公司做一个小东西,里面用到了 FFmpeg 简单处理音视频,感觉功能特别强大,在做之前我写了一个小例子,现在记录一下分享给大家,希望大家遇到这个问题知道解决方案. FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的. FFmpeg在Linux平

  • IDEA 中创建Spring Data Jpa 项目的示例代码

    一.IDEA 创建工程 使用IDEA 创建工程的过程,使用文字做简单描述. 选择工程类别[Spring Initializr]. 设置工程的元数据[Metadata],根据自己的情况填写即可. 设置工程的依赖:在[Web]中选择"Spring Web";在[SQL]中选中"Spring Data JPA"."Spring Data JDBC"."MySQL Driver"."JDBC API".选中的可能有

  • Java实现短信验证码的示例代码

    目录 项目需求 需求来由 代码实现 发送验证码方法 注册方法 忘记密码 前端代码 编码中遇到的问题 如何改进 短信验证码相信大家都不陌生吗,但是短信验证码怎么生成的你真的了解吗,本文揭示本人项目中对短信验证码的. 项目需求 用户注册/忘记密码添加短信验证码 需求来由 登录注册页面需要确保用户同一个手机号只关联一个账号确保非人为操作,避免系统用户信息紊乱增加系统安全性 代码实现 同事提供了WebService接口,很好,之前没调过,又增加了困难. 这边用的阿里云的短信服务,废话少说上图,呸,上代码

  • 基于Java实现扫码登录的示例代码

    目录 基本介绍 原理解析 1. 身份认证机制 2. 流程概述 代码实现 1. 环境准备 2. 主要依赖 3. 生成二维码 4. 扫描二维码 5. 确认登录 6. PC 端轮询 7. 拦截器配置 效果演示 1. 工具准备 2. 数据准备 3. 扫码登录流程展示 结语 基本介绍 相信大家对二维码都不陌生,生活中到处充斥着扫码登录的场景,如登录网页版微信.支付宝等.最近学习了一下扫码登录的原理,感觉蛮有趣的,于是自己实现了一个简易版扫码登录的 Demo,以此记录一下学习过程. 实际上是面试的时候被问到

  • Java实现PDF转Word的示例代码(无水印无页数限制)

    目录 一.前言 二.jar破解 1.项目远程仓库配置 2.pom文件引入相关依赖 3.破解代码 三.pdf转word 一.前言 学习概述:简单的介绍一下本篇文章要讲解的Java知识点 学习目标:读者读完这篇文章之后,你希望他掌握你讲解的哪些重要的知识点 二.jar破解 1.项目远程仓库配置 aspose-pdf 这个需要配置单独的仓库地址才能下载,不会配置的可以去官网直接下载jar引入项目代码中. <repositories> <repository> <id>Aspo

随机推荐