代码拉取完成,页面将自动刷新
要开发一个webssh,需要对接两个对象,一个是用户,一个ssh服务器,我们需要做的是接收用户传入的输入,转发到ssh服务器上,并且将ssh的输出发送到前端页面进行展示
功能:
web:
前端技术栈:
后端技术栈:
SSH是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露。
最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者Tatu Ylonen设计了SSH协议,将登录信息全部加密,成为互联网安全的一个基本解决方案,迅速在全世界获得推广,目前已经成为Linux系统的标准配置。以上摘自廖雪峰博客
#### 1.1 go代码里的连接方式
golang 创建一个sshClient,并且通过client创建一个会话,并将会话跟远程pty进行绑定,会话有三个通道
正常情况下,我们只需要建立连接以后使用 stdin 向ssh会话发送数据,使用stdout、stderr 读取数据即可
首先我们需要使用Gin开启一个服务来监听请求:
package main
import (
"fmt"
"more_ssh/myssh01"
"more_ssh/ssh"
"more_ssh/util"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(util.Cors()) //解决跨域问题
r.GET("/myssh", myssh01.RunWebSSH)
r.Run() //默认8080端口
}
新建一个文件夹 myssh01,在里面创建一个myssh.go文件
package myssh01
import (
"bytes"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
// 定义一个结构体 方便保存各种连接信息
type MySSH struct {
Websocket *websocket.Conn
Stdin io.WriteCloser
Stdout *wsBufferWriter
Session *ssh.Session
}
// 定义一个wsBufferWriter 并且写入时候加锁 防止stdout跟stderr同时写入
type wsBufferWriter struct {
buffer bytes.Buffer
mu sync.Mutex
}
//定义write方法, 防止stdout跟stderr同时写入
func (w *wsBufferWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Write(p)
}
// 程序入口
func RunWebSSH(c *gin.Context) {
mySSH := &MySSH{}
// 1. 升级请求websocket
upGrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{"webssh"},
}
webcon, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
fmt.Println("升级http 为websoket失败:", err)
}
mySSH.Websocket = webcon // 将websocket连接保存到对象中
// 创建一个ssh的配置
config := &ssh.ClientConfig{
Timeout: time.Second * 10, //ssh 连接time out 时间一秒钟, 如果ssh验证错误 会在一秒内返回
User: "root",
HostKeyCallback: ssh.InsecureIgnoreHostKey(), //这个可以, 但是不够安全
//HostKeyCallback: hostKeyCallBackFunc(h.Host),
Auth: []ssh.AuthMethod{ssh.Password("more@123")},
}
// 创建一个客户端
sshClient, err := ssh.Dial("tcp", "162.14.109.53:22", config)
if err != nil {
fmt.Println(err)
return
}
session, err := sshClient.NewSession()
if err != nil {
fmt.Println(err)
return
}
mySSH.Session = session
// 保存输入流
mySSH.Stdin, err = session.StdinPipe()
if err != nil {
fmt.Println(err)
return
}
//保存ssh输出流
sshOut := new(wsBufferWriter)
session.Stdout = sshOut
session.Stderr = sshOut
mySSH.Stdout = sshOut
modes := ssh.TerminalModes{
ssh.ECHO: 1, // disable echo
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty("xterm", 30, 120, modes); err != nil {
fmt.Println("绑定pty失败:", err)
return
}
session.Shell()
//执行远程命令
go Send2SSH(mySSH)
go Send2Web(mySSH)
}
// 读取websocket数据,发送到ssh输入流中
func Send2SSH(mySSh *MySSH) {
for {
//read websocket msg 需要通过msgType 判断是传输类型
_, wsData, err := mySSh.Websocket.ReadMessage()
if err != nil {
fmt.Println("读取websocket数据失败:", err)
return
}
_, err = mySSh.Stdin.Write(wsData)
if err != nil {
fmt.Println("ssh发送数据失败:", err)
}
// fmt.Println("ssh发送数据:", string(wsData))
}
}
// 读取ssh输出,发送到websocket中
func Send2Web(mySSh *MySSH) {
for {
if mySSh.Stdout.buffer.Len() > 0 {
err := mySSh.Websocket.WriteMessage(websocket.TextMessage, mySSh.Stdout.buffer.Bytes())
fmt.Printf(string(mySSh.Stdout.buffer.Bytes()))
if err != nil {
fmt.Println("websocket发送数据失败:", err)
}
mySSh.Stdout.buffer.Reset() //读完清空
}
}
}
到此,一个简单的ssh后端就实现了
<template>
<div>
<div ref="terminalBox" style="height: 100vh;"></div>
</div>
</template>
<script setup>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { ref, onMounted } from 'vue'
import { useMessage } from "naive-ui";
const message = useMessage();
let terminalBox = ref(null)
let term
let socket
onMounted(() => {
//创建一个客户端
term = new Terminal({
rendererType: 'canvas', //使用这个能解决vim不显示或乱码
cursorBlink: true,
cursorStyle: "bar",
})
// term.write
// 将客户端挂载到dom上
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(terminalBox.value)
fitAddon.fit()
// 创建socket连接
term.write('正在连接...\r\n');
socket = new WebSocket('ws://127.0.0.1:8080/myssh')
socket.binaryType = "arraybuffer";
// 打开socket监听事件的方法
socket.onopen = function () {
fitAddon.fit()
term.onData(function (data) {
// socket.send(JSON.stringify({ type: "stdin", data: data }))
socket.send(data)
console.log(data)
});
message.success("会话成功连接!")
}
socket.onclose = function () {
term.writeln('连接关闭');
}
socket.onerror = function (err) {
// console.log(err)
term.writeln('读取数据异常:', err);
}
// 接收数据
socket.onmessage = function (recv) {
try {
term.write(recv.data)
} catch (e) {
console.log('unsupport data', recv.data)
}
}
window.addEventListener("resize", () => {
fitAddon.fit()
}, false)
})
</script>
<style scoped>
.upload {
min-height: 100px;
}
</style>
golang逻辑分析:
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。