javaspring,java jsch session 多久會斷開_What?純Java居然能實現Xshell!

 2023-10-08 阅读 26 评论 0

摘要:前言最近由于項目需求,項目中需要實現一個WebSSH連接終端的功能,由于自己第一次做這類型功能,所以首先上了GitHub找了找有沒有現成的輪子可以拿來直接用,當時看到了很多這方面的項目,例如:GateOne、webssh、shellinabox等,

前言

最近由于項目需求,項目中需要實現一個WebSSH連接終端的功能,由于自己第一次做這類型功能,所以首先上了GitHub找了找有沒有現成的輪子可以拿來直接用,當時看到了很多這方面的項目,例如:GateOne、webssh、shellinabox等,這些項目都可以很好地實現webssh的功能。

但是最終并沒有采用,原因是在于這些底層大都是python寫的,需要依賴很多文件,自己用的時候可以使用這種方案,快捷省事,但是做到項目中供用戶使用時,總不能要求用戶做到服務器中必須包含這些底層依賴,這顯然不太合理,所以我決定自己動手寫一個WebSSH的功能,并且作為一個獨立的項目開源出來。

github項目開源地址:https://github.com/NoCortY/WebSSH

技術選型

由于webssh需要實時數據交互,所以會選用長連接的WebSocket,為了開發的方便,框架選用SpringBoot,另外還自己了解了Java用戶連接ssh的jsch和實現前端shell頁面的xterm.js.

javaspring、所以,最終的技術選型就是 SpringBoot+Websocket+jsch+xterm.js

導入依賴

<parent>
????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-starter-parentartifactId>
????<version>2.1.7.RELEASEversion>
????<relativePath?/>?
parent>
<dependencies>
????
????<dependency>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-webartifactId>
????dependency>
????
????<dependency>
????????<groupId>com.jcraftgroupId>
????????<artifactId>jschartifactId>
????????<version>0.1.54version>
????dependency>
????
????<dependency>
????????<groupId>org.springframework.bootgroupId>
????????<artifactId>spring-boot-starter-websocketartifactId>
????dependency>
????
????<dependency>
????????<groupId>commons-iogroupId>
????????<artifactId>commons-ioartifactId>
????????<version>1.4version>
????dependency>
????<dependency>
????????<groupId>commons-fileuploadgroupId>
????????<artifactId>commons-fileuploadartifactId>
????????<version>1.3.1version>
????dependency>
dependencies>

一個簡單的xterm案例

由于xterm是一個冷門技術,所以很多同學并沒有這方面的知識支撐,我也是為了實現這個功能所以臨時學的,所以在這給大家介紹一下。

xterm.js是一個基于WebSocket的容器,它可以幫助我們在前端實現命令行的樣式。就像是我們平常再用SecureCRT或者XShell連接服務器時一樣。

下面是官網上的入門案例:

html>
?<html>
??<head>
????<link?rel="stylesheet"?href="node_modules/xterm/css/xterm.css"?/>
????<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>

最終測試,頁面就是下面這個樣子:

e79bcf85cc48407c31471be495d1f508.png
xterm入門

可以看到頁面已經出現了類似與shell的樣式,那就根據這個繼續深入,實現一個webssh。

后端實現

javaweb的session、由于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?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的業務邏輯實現(核心)

java自己寫session,剛才我們實現了websocket的配置,都是一些死代碼,實現了接口再根據自身需求即可實現,現在我們將進行后端主要業務邏輯的實現,在實現這個邏輯之前,我們先來想想,WebSSH,我們主要想要呈現一個什么效果。

我這里做了一個總結:

1.首先我們得先連接上終端(初始化連接)

2.其次我們的服務端需要處理來自前端的消息(接收并處理前端消息)

3.我們需要將終端返回的消息回寫到前端(數據回寫前端)

4.關閉連接

javasession詳解、根據這四個需求,我們先定義一個接口,這樣可以讓需求明了起來。

??/**
???*?@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);
??}

現在我們可以根據這個接口去實現我們定義的功能了。

1.初始化連接

由于我們的底層是依賴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);
?????}

2.處理客戶端發送的數據

在這一步驟中,我們會分為兩個分支。

  • java中的session?第一個分支:如果客戶端發來的是終端的用戶名和密碼等信息,那么我們進行終端的連接。

  • 第二個分支:如果客戶端發來的是操作終端的命令,那么我們就直接轉發到終端并且獲取終端的執行結果。

具體代碼實現:

?????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);
?????????????}
?????}

3.數據通過websocket發送到前端

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

4.關閉連接

?????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);
?????????????}
?????}

至此,我們的整個后端實現就結束了,由于篇幅有限,這里將一些操作封裝成了方法,就不做過多展示了,重點講邏輯實現的思路吧。接下來我們將進行前端的實現。

前端實現

java如何利用session、前端工作主要分為這么幾個步驟:

  1. 頁面的實現

  2. 連接WebSocket并完成數據的接收并回寫

  3. 數據的發送

所以我們一步一步來實現它。

頁面實現

頁面的實現很簡單,我們只不過需要在一整個屏幕上都顯示終端那種大黑屏幕,所以我們并不用寫什么樣式,只需要創建一個div,之后將terminal實例通過xterm放到這個div中,就可以實現了。

??html>
??<html>
??<head>
??????<title>WebSSHtitle>
??????<link?rel="stylesheet"?href="../css/xterm.css"?/>
??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);
??????????????}
??????????});
??????}

效果展示

連接

3a406835205404cc323390deef40de97.png
連接

連接成功

79ddc9e7d3dbed867a222b5b6a51f0f3.png
連接成功

命令操作

java獲取session的值。ls命令:

4230dcd6e158037c6cb5910c9e99cd68.png
ls命令

vim編輯器:

6767da25e0b1f88b46bfb325d590fc2c.png
vim編輯器

top命令:

43c03962f9157ec30b8db78cf85a8516.png
top命令

結語

這樣我們就完成了一個webssh項目的實現,沒有依賴其它任何的組件,后端完全使用Java實現,由于用了SpringBoot,非常容易部署。

但是,我們還可以對這個項目進行擴展,比如新增上傳或下載文件,就像Xftp一樣,可以很方便地拖拽式上傳下載文件。

這個項目之后我會持續更新,上述功能也會慢慢實現,Github:https://github.com/NoCortY/WebSSH

java獲取當前session?喜歡可以給個Star哦~

推薦閱讀

  • 秒殺系統是如何防止超賣的?
  • Spring Data Redis 最佳實踐!
  • 優化if-else代碼的八種方案!
  • 一個不容錯過的Spring Cloud實戰項目!
  • 127.0.0.1和0.0.0.0地址的區別!
  • SpringBoot中處理校驗邏輯的兩種方式,真的很機智!
  • Spring Boot + Vue 如此強大?竟然可以開發基于 C/S 架構的應用!
  • 盤點下我用的順手的那些工具!
  • Github標星25K+Star,SpringBoot實戰電商項目mall出SpringCloud版本啦!
  • 我的Github開源項目,從0到20000 Star!

444523f118f85de92ced99159ab22359.png

歡迎關注,點個在看

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/5/129090.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息