您现在的位置是: 网站首页 >Django Django

Django使用Channels实现WebSSH网页终端,实现SSH堡垒机雏形

admin2019年7月29日 16:17 Django | JQuery 1379人已围观

本教程基于《Django使用Channels实现WebSocket消息通知功能》 # 逻辑简述 xterm.js : 前端模拟 shell 终端的一个库,当用户每输入一个键,就向后端发送该数据 paramiko : Python 下对 ssh2 封装的一个库,可以使用他来远程连接主机 1. 用户首先通过网页上的WebSocket连接到后端,后端再通过SSH连接到远程主机,实现一个长连接; 2. 连接成功后, xterm.js 在浏览器中模拟shell终端,监听用户按键,将每次按键输入通过已连接好的WebSocket; 3. 后端通过WebSocket收到数据后,将用户输入的内容通过 paramiko 建立的 SSH 通道连接到远程主机上执行; 4. paramiko 将远程主机上的处理结果返回给后端; 5. 后端将返回结果通过WebSocket返回用户页面; 6. xterm.js 将从WebSocket接收的数据显示到到模拟终端中; # 创建webssh app 创建app。名为`webssh` 将应用添加到 settings.py ```python INSTALLED_APPS = [ # 。。。。 'webssh.apps.WebsshConfig', # 网页远程ssh终端 ] ``` 修改应用下的 apps.py ```python from django.apps import AppConfig class WebsshConfig(AppConfig): name = 'webssh' verbose_name = '远程终端' ``` 修改应用下的 \_\_init\_\_.py ```python default_app_config = 'webssh.apps.WebsshConfig' ``` # webssh逻辑代码 ## 前端页面链接webssh ```html {% extends 'pxectrl/base-pxectrl.html' %} {% load static %} {% block title %}远程终端{% endblock %} {% block css %} <link href="{% static 'xterm/xterm.css' %}" rel="stylesheet"> <link href="{% static 'hadmin/css/plugins/toastr/toastr.min.css' %}" rel="stylesheet"> {% endblock %} {% block content %} <div class="wrapper wrapper-content animated fadeInRight"> <div class="row"> <div class="col-sm-12"> <div class="ibox float-e-margins" id="id-box"> <div class="ibox-title"> <h5>在线终端</h5> </div> <div class="ibox-content" id="id-content"> <div class="row"> <form role="form" id="id-form" autocomplete="off"> <div class="col-md-4"> <div class="form-group"> <label>用户名</label> <input type="text" placeholder="User" id="id-user" class="form-control" autocomplete="off" value="user"> </div> </div> <div class="col-sm-4"> <div class="form-group"> <label>主机地址</label> <input type="text" placeholder="Host" id="id-host" class="form-control" required autocomplete="off" value="192.168.96.20"> </div> </div> <div class="col-sm-4"> <div class="form-group"> <label>端口</label> <input type="text" placeholder="Port" id="id-port" class="form-control" value="22" required autocomplete="off"> </div> </div> <div class="col-sm-12"> <div class="form-group"> <label>认证类型</label> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <label class="radio-inline"> <input type="radio" name="auth" id="id-use-pwd" value="pwd" checked> 密码认证 </label> <label class="radio-inline"> <input type="radio" name="auth" id="id-use-key" value="key"> 秘钥认证 </label> </div> </div> <div class="col-sm-6"> <div class="form-group"> <label id="id-auth-type">登录密码</label> <input type="password" placeholder="Pwd" id="id-pwd" class="form-control" required autocomplete="off" value=""> </div> </div> <div class="col-sm-6 hide" id="id-show-upload"> <div class="form-group"> <label>密钥文件</label> <input class="filestyle" id="id-key-file" type="file"> </div> </div> <div class="col-sm-12"> 注意: <p style="color: red; font-size: 10px">1、当认证类型为密码认证时, 秘钥文件上传将不可用</p> <p style="color: red; font-size: 10px">2、当认证类型为秘钥认证时, 如果密码输入框不为空, 则密码输入框的内容将作为秘钥的解密密码</p> <button class="btn btn-sm btn-info" type="button" onclick="connectWebSocket()"> <strong>连接</strong> </button> </div> </form> </div> <br> <hr> <br> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>服务器</th> <th>用途</th> <th>SSH连接</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>192.168.96.20</td> <td>PXE服务器</td> <td> <button type="button" class="btn btn-info btn-sm" onclick="connectWebSocket(host_id=1)">连接 </button> <button type="button" class="btn btn-primary btn-sm" onclick="connectWebSocket(host_id=1, team=true)">协作连接 </button> </td> </tr> <tr> <td>2</td> <td>192.168.96.21</td> <td>Samba服务器:镜像从服务器</td> <td> <button type="button" class="btn btn-info btn-sm" onclick="connectWebSocket(host_id=2)">连接 </button> <button type="button" class="btn btn-primary btn-sm" onclick="connectWebSocket(host_id=2, team=true)">协作连接 </button> </td> </tr> </tbody> </table> </div> <div class="ibox-content hide" id="id-ssh-content" style="padding: 0;"> <p style="position:absolute; padding: 0; right:0; margin-right:17px; text-align: center; z-index: 999"> <button class="btn btn-danger btn-lg" id="id-close-conn">关闭连接</button> </p> <div id="terminal"></div> </div> </div> </div> </div> </div> {% endblock %} {% block js %} <!--首先引入jquery--> <!-- 图片上传按钮样式 --> <script src="{% static 'hadmin/js/plugins/bootstrap-filestyle/bootstrap-filestyle.min.js' %}"></script> <script> //上传文件 $(":file").filestyle({btnClass: "btn-default", text: "更改"}); </script> <script src="{% static 'xterm/xterm.js' %}"></script> <script src="{% static 'hadmin/js/plugins/toastr/toastr.min.js' %}"></script> <script> toastr.options = { // toastr配置 "closeButton": true, "debug": false, "progressBar": true, "positionClass": "toast-top-center", "showDuration": "400", "hideDuration": "1000", "timeOut": "3000", "extendedTimeOut": "1000", "showEasing": "swing", "hideEasing": "linear", "showMethod": "fadeIn", "hideMethod": "fadeOut" }; </script> <script> $("#id-use-pwd").click(function () { $('#id-auth-type').html('登录密码'); $('#id-show-upload').addClass('hide') }); $("#id-use-key").click(function () { $('#id-auth-type').html('解密密码'); $('#id-show-upload').removeClass('hide') }) </script> <script> function get_box_size() { let init_width = 9; let init_height = 22; let windows_width = $('#id-box').width(); let windows_height = $(window).height(); return { cols: Math.floor(windows_width / init_width), rows: Math.floor(windows_height / init_height), } } function connectWebSocket(host_id = null, team = false) { let cols = get_box_size().cols; let rows = get_box_size().rows; console.log(cols); //根据div的大小初始化终端,待WebSocket连接上后,显示终端 let term = new Terminal( { cols: cols, rows: rows, useStyle: true, cursorBlink: true } ); //建立WebSocket连接 if (host_id === null) { //获取表单中的信息,并去掉两端空格 let host = $.trim($('#id-host').val()); if (host === '') { toastr.warning('主机地址不能为空', '提示'); return; } let port = $.trim($('#id-port').val()); if (port === '') { toastr.warning('端口不能为空', '提示'); return; } let user = $.trim($('#id-user').val()); if (user === '') { toastr.warning('用户名不能为空', '提示'); return; } let auth = $("input[name='auth']:checked").val(); console.log('选择认证方式:' + auth); let sshkey_filename = ''; if (auth === 'key') { //上传密钥文件 let key_file = $('#id-key-file')[0].files[0]; let csrf = '{{ csrf_token }}'; let formData = new FormData(); formData.append('key_file', key_file); formData.append('csrfmiddlewaretoken', csrf); $.ajax({ url: '{% url "webssh:upload_ssh_key" %}', type: 'post', data: formData, async: false, processData: false, contentType: false, mimeType: 'multipart/form-data', success: function (data) { sshkey_filename = data; //返回保存文件的名称 } }); } let pwd = $.trim($('#id-pwd').val()); pwd = window.btoa(pwd); //加密密码传输 //组装为ssh连接参数 let ssh_args = `user=${user}&host=${host}&port=${port}&auth=${auth}&pwd=${pwd}&sshkey_filename=${sshkey_filename}`; console.log(ssh_args); let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议 let ws_port = (window.location.port) ? (':' + window.location.port) : ''; // 获取端口 ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + '/ws/webssh/?' + ssh_args + `&width=${cols}&height=${rows}`); } else { //指定服务器id连接 if (team) { let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议 let ws_port = (window.location.port) ? (':' + window.location.port) : ''; // 获取端口 ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + `/ws/webssh/${host_id}/` + `?width=${cols}&height=${rows}&team=${team}`); } else { let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议 let ws_port = (window.location.port) ? (':' + window.location.port) : ''; // 获取端口 ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + `/ws/webssh/${host_id}/` + `?width=${cols}&height=${rows}`); } } //打开websocket连接,并打开终端 ws.onopen = function () { console.log('WebSocket建立连接,打开Web终端'); $('#id-content').addClass('hide'); $('#id-ssh-content').removeClass('hide'); term.open(document.getElementById('terminal')); }; ws.onclose = function (e) { console.error('WebSocket关闭连接,关闭Web终端'); toastr.success('SSH连接已关闭', '消息'); //term.write(message); setTimeout(function () { window.location.reload(); }, 3000); }; //读取服务器发送的数据并写入web终端 ws.onmessage = function (e) { console.log('WebSocket接收消息,ssh交互中'); let data = JSON.parse(e.data); console.log(data); let message = data['message']; if (data.flag === 'msg') { term.write(message); } else if (data.flag === 'fail') { term.write(message); //连接ssh的异常提示 toastr.error(message + "返回登录页", '失败'); setTimeout(function () { window.location.reload(); }, 5000); } else if (data.flag === 'user') { toastr.info(message, '消息'); } else if (data.flag === 'error') { toastr.error(message, '失败'); //term.write(message); setTimeout(function () { window.location.reload(); }, 5000); } }; //向服务器发送数据,flag=1 term.on('data', function (data) { //data为每个按键输入内容,例如按A,就传递给后端:{'flag': 1, 'data': 'a', 'cols': None, 'rows': None} let send_data = JSON.stringify({ 'flag': 'entered_key', 'entered_key': data, 'cols': null, 'rows': null }); //向WebSocket发送消息,也就是输入的每一个按键 ws.send(send_data) }); //终端右上角显示的关闭连接安装,当点击是,关闭ws $('#id-close-conn').click(function () { ws.close(); }); // 监听浏览器窗口, 根据浏览器窗口大小修改终端大小 $(window).resize(function () { let cols = get_box_size().cols; let rows = get_box_size().rows; console.log(`更改显示终端窗口大小,行${rows}列${cols}`); let send_data = JSON.stringify({'flag': 'resize', 'cols': cols, 'rows': rows}); ws.send(send_data); term.resize(cols, rows) //调整页面终端大小 }) } </script> {% endblock %} ``` ## 应用视图 ```python import time import random import hashlib import os from django.conf import settings from django.shortcuts import render, HttpResponse def index(request): return render(request, 'webssh/index.html') def unique(): ctime = str(time.time()) salt = str(random.random()) m = hashlib.md5(bytes(salt, encoding='utf-8')) m.update(bytes(ctime, encoding='utf-8')) return m.hexdigest() def upload_ssh_key(request): if request.method == 'POST': key_file = request.FILES.get('key_file') if not key_file: return HttpResponse('') print(type(key_file.read())) ssh_key = key_file.read().decode('utf-8') # 获取上传文件的内容 sshkey_filename = unique() print('文件保存为唯一名称:', sshkey_filename) ssh_key_path = os.path.join(settings.MEDIA_ROOT, 'sshkey') if not os.path.exists(ssh_key_path): os.mkdir(ssh_key_path) # 创建保存key文件的文件夹 with open(os.path.join(ssh_key_path, sshkey_filename), 'w', encoding='utf-8') as f: f.write(ssh_key) return HttpResponse(sshkey_filename) ``` ## 应用路由 ```python from django.urls import path from .views import index, upload_ssh_key from simpleauth.tools.simpleauth_tool import simple_permission_required app_name = 'webssh' urlpatterns = [ path('', simple_permission_required(permission='it_sys_user')(index), name='index'), # 终端主页 path('upload_ssh_key/', simple_permission_required(permission='it_sys_user')(upload_ssh_key), name='upload_ssh_key'), # 终端主页 ] ``` 访问 http://127.0.0.1/webssh/ 可以连接到主页 ## 应用下创建 consumers.py apps/webssh/consumers.py ```python from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer import json import base64 from django.http.request import QueryDict import paramiko import socket from threading import Thread import time import os from django.utils.six import StringIO from django.conf import settings def get_key_obj(pkeyobj, pkey_file=None, pkey_obj=None, password=None): if pkey_file: with open(pkey_file) as fo: try: pkey = pkeyobj.from_private_key(fo, password=password) return pkey except: pass else: try: pkey = pkeyobj.from_private_key(pkey_obj, password=password) return pkey except: pkey_obj.seek(0) class SSHBridge(object): """ 桥接WebSocket和ssh 参考:https://blog.51cto.com/hongchen99/2336087 """ def __init__(self, websocket, simpleuser): self.websocket = websocket self.simpleuser = simpleuser def connect(self, host, user, pwd=None, key=None, port=22, timeout=6, term='xterm', pty_width=80, pty_height=24): """ 建立SSH连接,放在 self.ssh_channel 通道中,之后直接在通道中交互数据 :param host: :param user: :param pwd: :param key: :param port: :param timeout: :param term: :param pty_width: :param pty_height: :return: """ ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: if key: # 密钥方式认证 pkey = get_key_obj(paramiko.RSAKey, pkey_obj=key, password=pwd) or \ get_key_obj(paramiko.DSSKey, pkey_obj=key, password=pwd) or \ get_key_obj(paramiko.ECDSAKey, pkey_obj=key, password=pwd) or \ get_key_obj(paramiko.Ed25519Key, pkey_obj=key, password=pwd) ssh_client.connect(username=user, hostname=host, port=port, pkey=pkey, timeout=timeout) else: ssh_client.connect(hostname=host, port=port, username=user, password=pwd, timeout=timeout) except Exception as e: # pri00nt(e) message = json.dumps({'flag': 'fail', 'message': str(e)}) self.websocket.send_message_or_team(message) return transport = ssh_client.get_transport() """ 另一种方式建立通道 transport = paramiko.Transport((host, port,)) transport.start_client() transport.auth_password(username=user, password=pwd) """ # 打开一个通道 self.ssh_channel = transport.open_session() # 获取一个终端 self.ssh_channel.get_pty(term=term, width=pty_width, height=pty_height) # 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样 self.ssh_channel.invoke_shell() # 获取ssh连接主机后的返回内容,例如Linux,会显示上次登录等信息,把这些信息通过WebSocket显示到Web终端。 # 连接建立一次,之后交互数据不会再进入该方法 for i in range(2): recv = self.ssh_channel.recv(1024).decode('utf-8') message = json.dumps({'flag': 'msg', 'message': recv}) # pri00nt('【WS --websocket--> Web】建立SSH通道后,返回欢迎信息:', message) self.websocket.send_message_or_team(message) def close(self): message = {'flag': 0, 'message': '关闭WebSocket和SSH连接'} # 向WebSocket发送一个关闭消息 self.websocket.send_message_or_team(json.dumps(message)) try: # 关闭ssh通道 self.ssh_channel.close() # 关闭WebSocket连接 self.websocket.close() except BaseException as e: # pri00nt('关闭WebSocket和SSH连接产生异常:', e) pass def _ws_to_ssh(self, data): """ 尝试发送数据到ssh通道,产生异常则关闭所有连接 """ try: # pri00nt('【Func --paramiko--> SSH】WebSocket中的数据发送数据到ssh通道:', data) self.ssh_channel.send(data) except OSError as e: # pri00nt(e) self.close() def _ssh_to_ws(self): try: # while True: while not self.ssh_channel.exit_status_ready(): data = self.ssh_channel.recv(1024).decode('utf-8') # pri00nt('【SSH --paramiko--> Func】获取ssh通道返回的数据:', data) if len(data) != 0: message = {'flag': 'msg', 'message': data} # pri00nt('【WS --websocket--> Web】通过WebSocket把信息发回前端,显示到Web终端:', message) self.websocket.send_message_or_team(json.dumps(message)) else: break except: self.close() def shell(self, data): Thread(target=self._ws_to_ssh, args=(data,)).start() Thread(target=self._ssh_to_ws).start() """ t1 = Thread(target=self._ws_to_ssh, args=(data,)) t1.setDaemon(True) t1.start() t2 = Thread(target=self._ssh_to_ws) t2.setDaemon(True) t2.start() """ def resize_pty(self, cols, rows): self.ssh_channel.resize_pty(width=cols, height=rows) class WebsshConsumer(WebsocketConsumer): """ 1、xterm.js 在浏览器端模拟 shell 终端, 监听用户输入通过 websocket 将用户输入的内容上传到 django    2、django 接受到用户上传的内容, 将用户在前端页面输入的内容通过 paramiko 建立的 ssh 通道上传到远程服务器执行    3、paramiko 将远程服务器的处理结果返回给 django    4、django 将 paramiko 返回的结果通过 websocket 返回给用户    5、xterm.js 接收 django 返回的数据并将其写入前端页面 """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.host_id = '' self.simple_user = '' self.is_team = False self.team_name = '' def connect(self): """ 建立WebSocket连接,并实例化SSHBridge类,在这个对象中建立SSH连接,放在 self.ssh_channel 通道中 :return: """ self.host_id = self.scope['url_route']['kwargs'].get('host_id') self.simple_user = self.scope["session"]["session_simple_nick_name"] # 获取session中的值 # pri00nt('【Web --websocket--> WS】建立WebSocket通道,当前连接用户:', self.simple_user) self.accept() # WebSocket连接成功后,连接ssh query_string = self.scope.get('query_string') ws_args = QueryDict(query_string=query_string, encoding='utf-8') # # pri00nt(ws_args) # <QueryDict: {'user': ['admin'], 'host': ['192.168.96.20'], 'port': ['22'], 'auth': ['pwd'], 'pwd': ['ZGphbmdvYWRtaW4='], 'key': [''], 'width': ['113'], 'height': ['43']}> # 根据参数判断是否是协作 team = ws_args.get('team') if team: self.is_team = True self.team_name = "team_{}".format(self.host_id) # 加到这个通道组 async_to_sync(self.channel_layer.group_add)( self.team_name, self.channel_name ) # 用户连接时,同一群组发送消息 self.send_message_or_team(json.dumps({'flag': 'user', 'message': '用户 {} 已连接本终端'.format(self.simple_user)})) width = ws_args.get('width') height = ws_args.get('height') width = int(width) height = int(height) # ssh连接要求int类型:required argument is an integer ssh_connect_dict = {} if self.host_id: # 指定连接 # pri00nt('连接的服务器id:', self.host_id) if int(self.host_id) == 1: ssh_connect_dict = { 'host': '192.168.96.20', 'user': 'user', 'port': 22, 'timeout': 30, 'pty_width': width, 'pty_height': height, 'pwd': 'user' } elif int(self.host_id) == 2: ssh_connect_dict = { 'host': '192.168.96.21', 'user': 'user', 'port': 22, 'timeout': 30, 'pty_width': width, 'pty_height': height, 'pwd': 'user' } else: self.close() return else: user = ws_args.get('user') host = ws_args.get('host') port = ws_args.get('port') port = int(port) auth = ws_args.get('auth') pwd = ws_args.get('pwd') if pwd: pwd = base64.b64decode(pwd).decode('utf-8') sshkey_filename = ws_args.get('sshkey_filename') ssh_connect_dict = { 'host': host, 'user': user, 'port': port, 'timeout': 30, 'pty_width': width, 'pty_height': height, 'pwd': pwd } if auth == 'key': sshkey_file = os.path.join(settings.MEDIA_ROOT, 'sshkey', sshkey_filename) if not os.path.exists(sshkey_file): self.send(json.dumps({'flag': 'error', 'message': '密钥文件不存在'})) else: try: f = open(sshkey_file, 'r', encoding='utf-8') key = f.read() string_io = StringIO() string_io.write(key) string_io.flush() string_io.seek(0) ssh_connect_dict['key'] = string_io os.remove(sshkey_file) # 用完之后删除key文件 except BaseException as e: # pri00nt('打开密钥文件出错', e) pass # 建立SSH连接 self.ssh = SSHBridge(websocket=self, simpleuser=self.simple_user) # pri00nt('【WS --SSHBridge--> SSH】连接SSH参数:', ssh_connect_dict) self.ssh.connect(**ssh_connect_dict) def disconnect(self, close_code): # 断开连接 # pri00nt('用户 {} 断开WebSocket连接,断开SSH连接'.format(self.simple_user)) try: if self.is_team: # 用户连接时,同一群组发送消息 self.send_message_or_team(json.dumps({'flag': 'user', 'message': '用户 {} 已断开本终端'.format(self.simple_user)})) # 退出群组 async_to_sync(self.channel_layer.group_discard)( self.team_name, self.channel_name ) self.ssh.close() except BaseException as e: pass def receive(self, text_data=None, bytes_data=None): # 从WebSocket中接收消息 text_data = json.loads(text_data) # json字符串转字典 # pri00nt('\n\n【Web --websocket--> WS】Web终端按键内容通过WebSocket传到后端:', text_data) if type(text_data) == dict: if text_data.get('flag') == 'entered_key': data = text_data.get('entered_key', '') # 获取前端传过来输入的按键值,并传递给shell # pri00nt('【WS --SSHBridge--> Func】WebSocket转发SSHBridge:', text_data) self.ssh.shell(data=data) else: cols = text_data['cols'] rows = text_data['rows'] # 改变通道中终端大小 self.ssh.resize_pty(cols=cols, rows=rows) else: # pri00nt('【!!!】收到的数据不是dict类型') pass def send_message_or_team(self, message): if self.is_team: async_to_sync(self.channel_layer.group_send)( self.team_name, { 'type': 'team_message', 'message': message } ) else: self.send(message) def team_message(self, event): message = event['message'] # 发送消息到WebSocket self.send(message) ``` ## 应用下创建 routing.py apps/webssh/routing.py ```python from django.conf.urls import url from . import consumers websocket_urlpatterns = [ url(r'^ws/webssh/(?P<host_id>\d+)/$', consumers.WebsshConsumer), url(r'^ws/webssh/$', consumers.WebsshConsumer), ] ``` ## 修改项目下 routing.py (主WS路由) 合并多个应用的url ```python from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack from channels.sessions import SessionMiddlewareStack import pxectrl.routing import webssh.routing application = ProtocolTypeRouter({ # (http->django views is added by default) # 【channels】(第6步)添加路由配置指向应用的路由模块 'websocket': SessionMiddlewareStack( # 使用Session中间件,可以请求中session的值 # 一个url # URLRouter( # pxectrl.routing.websocket_urlpatterns, # ), # 多个url合并一起使用,多个子路由列表相加 URLRouter( pxectrl.routing.websocket_urlpatterns + webssh.routing.websocket_urlpatterns, ), ), }) ``` ## 整个流程分析 1. 页面点击**连接**进入`connectWebSocket(host_id = null, team = false)`函数,首先获取`div`的大小,初始化终端大小; 2. 检测用户输入是否完成,如果是密钥认证将先使用`ajax`上传文件,这儿只讲下密码验证,组装成`ws://IP地址/ws/webssh/?参数1=xxx&参数2=xxx`的地址,创建WebSocket连接; 3. `ws.onopen`打开WebSocket连接,并隐藏输入表单,显示网页终端,此时通过`apps/webssh/routing.py`中的url进入`WebsshConsumer(WebsocketConsumer).connect(self)`,接受WebSocket连接,并根据ws的参数,实例化`self.ssh = SSHBridge(websocket=self, simpleuser=self.simple_user)`,连接到SSH`self.ssh.connect(**ssh_connect_dict)`,这儿主要是创建一个SSH通道`self.ssh_channel`,并激活`self.ssh_channel.invoke_shell()`,类似xshell功能,后面通过该通道进行SSH请求主机; 4. SSH通道连接成功后,从该通道中获取返回的数据,并通过WebSocket通道返回到前端,`ws.onmessage`接到后端返回的数据,显示到网页终端; 5. 网页终端按键后,通过`term.on('data', function (data) {...})`实时`ws.send(send_data)`发送到后端; 6. 后端`def receive(self, text_data=None, bytes_data=None)`接收数据,调用`self.ssh.shell(data=data)`函数,这儿创建一个正向命令和反向接收的线程,主要用于实时交互数据; 7. 通过SSH通道`self.ssh_channel.send(data)`传入输入的键,反向获取通道中的内容`self.ssh_channel.recv(1024).decode('utf-8')`,并通过WebSocket返回前端。 遇到的问题:用协作连接时,用户加入到一个通道组,往这个通道组发送命令,这个通道所有用户都能收到,来实现协作的功能,但是从`self.ssh_channel`接收返回的数据,可能会存在和预想的不同,特别是`top`命令。示例如下,不知道怎么解决了!!! ![BLOG_20190729_161900_74](/media/blog/images/2019/07/BLOG_20190729_161900_74.png "博客图集BLOG_20190729_161900_74.png") ## 使用截图 ![BLOG_20190729_161853_25](/media/blog/images/2019/07/BLOG_20190729_161853_25.png "博客图集BLOG_20190729_161853_25.png") ![BLOG_20190729_161848_65](/media/blog/images/2019/07/BLOG_20190729_161848_65.png "博客图集BLOG_20190729_161848_65.png") ![BLOG_20190729_161843_50](/media/blog/images/2019/07/BLOG_20190729_161843_50.png "博客图集BLOG_20190729_161843_50.png") ![BLOG_20190729_161838_38](/media/blog/images/2019/07/BLOG_20190729_161838_38.png "博客图集BLOG_20190729_161838_38.png") 参考链接:https://github.com/huyuan1999/django-webssh https://www.cnblogs.com/52op/articles/9327733.html 【gevent库】

很赞哦! (3)

文章交流

  • emoji
1人参与,1条评论
xiangzi 2020年10月19日 15:46
博主,你里面还差一个CSS样式文件吧

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行1163天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:54 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站