依赖包:
go get github.com/gorilla/websocket
go get golang.org/x/crypto/ssh
主要步骤及注释:
// 定义授权方法
auth := make([]ssh.AuthMethod, 0)
// 使用密码认证
auth = append(auth, ssh.Password("用户密码"))
// 使用私钥认证
// key, _ := ssh.ParsePrivateKey([]byte("私钥内容"))
// auth = append(auth, ssh.PublicKeys(key))
// 客户端配置
clientConfig := &ssh.ClientConfig{
User: "root", // 用户名
Auth: auth, // 授权方法
// 检查主机密钥的回调函数,这里设置为接受任何主机密钥
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 连接ssh服务
if client, err := ssh.Dial("tcp", "IP地址:22", clientConfig); err != nil {
return nil, err
}
defer client.Close()
// 新建会话
session, err := client.NewSession()
if err != nil {
return nil, err
}
defer session.Close()
// 创建ssh通道
channel, inRequests, err := client.OpenChannel("session", nil)
if err != nil {
return nil, err
}
// 终端模式
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
// 存入数组
var modeList []byte
for k, v := range modes {
kv := struct {
Key byte
Val uint32
}{k, v}
modeList = append(modeList, ssh.Marshal(&kv)...)
}
modeList = append(modeList, 0)
// 默认终端行和列数
const defaultCols = 150
const defaultRows = 35
// 定义pty请求消息体
req := PtyRequestMsg{
Term: "xterm",
Columns: defaultCols,
Rows: defaultRows,
Width: uint32(defaultCols * 8),
Height: uint32(defaultRows * 8),
Modelist: string(modeList),
}
// 请求一个标准输出的终端
ok, err := channel.SendRequest("pty-req", true, ssh.Marshal(&req))
if !ok || err != nil {
logrus.Error(err)
return nil, err
}
// 开启用户的默认shell
ok, err = channel.SendRequest("shell", true, nil)
if !ok || err != nil {
logrus.Error(err)
return nil, err
}
// 从websocket读取用户输入
for {
// p为输入内容
_, p, err := ws.ReadMessage()
if err != nil {
return
}
// 写入到ssh通道
_, err = channel.Write(p)
if err != nil && err.Error() != io.EOF.Error() {
return
}
}
br := bufio.NewReader(channel)
buf := []byte{}
t := time.NewTimer(time.Microsecond * 100)
defer t.Stop()
// 创建一个go通道r, 一边接收ssh服务端数据, 一边发送数据给websocket
r := make(chan rune)
go func() {
for {
// 读取ssh通道的数据
x, size, err := br.ReadRune()
if err != nil {
return
}
// 传给通道r
if size > 0 {
r <- x
}
}
}()
for {
select {
case d := <-r:
// 读取数据到buf
if d != utf8.RuneError {
p := make([]byte, utf8.RuneLen(d))
utf8.EncodeRune(p, d)
buf = append(buf, p...)
} else {
buf = append(buf, []byte("@")...)
}
case <-t.C:
// 每隔100微秒将数据发给websocket
if len(buf) != 0 {
err := ws.WriteMessage(websocket.TextMessage, buf)
if err != nil {
return
}
// 重置buf
buf = []byte{}
}
// 重置时间
t.Reset(time.Microsecond * 100)
}
}