WebSSH之录屏安全审计(三)
阅读原文时间:2023年07月30日阅读:1

第一篇:Gin+Xterm.js实现WebSSH远程Kubernetes Pod(一)

第二篇:WebSSH远程管理Linux服务器、Web终端窗口自适应(二)

  • 支持用户名密码认证

  • 支持SSH密钥认证

  • 支持Web终端窗口自适应

  • 支持录屏审计

Asciinema

Asciinema是一款开源的终端会话录制工具。

**官网: https://asciinema.org/**

  • CentOS

    yum install asciinema -y

**以下内容来自官方文档:https://asciinema.org/docs/usage**

  • 通过运行

    rec [filename]

您可以开始一个新的录制会话。当您退出shell时Ctrl+D或exit完成录制。如果记录的进程不是shell,则记录在进程退出时结束。

如果filename省略该参数,则(在要求确认后)将生成的 asciicast 上传到 asciinema-server(默认为 asciinema.org),在那里可以观看和共享。

如果filename给出参数,则生成的记录(称为 asciicast)将保存到本地文件中。稍后可以使用 .asciinema 重播和asciinema play /或上传到 asciinema 服务器asciinema upload

ASCIINEMA_REC=1添加到记录的进程环境变量中。.bashrcshell 的配置文件 ( , )可以使用它来.zshrc更改提示或在录制 shell 时播放声音。

可用选项:

  • --stdin- 启用标准输入(键盘)录制(见下文)

  • --append- 附加到现有录音

  • --raw- 保存原始 STDOUT 输出,没有计时信息或其他元数据

  • --overwrite- 如果录音已存在,则覆盖录音

  • -c, --command=- 指定要记录的命令,默认为$SHELL

  • -e, --env=- 要捕获的环境变量列表,默认为SHELL,TERM

  • -t, --title=- 指定asciicast的标题</p></li> <li><p>-i, --idle-time-limit=<sec>- 将记录的终端不活动限制为最大<sec>秒数</p></li> <li><p>-y, --yes- 对所有提示回答“是”(例如上传确认)</p></li> <li><p>-q, --quiet- 保持安静,禁止所有通知/警告(隐含 -y)</p></li> </ul> <p>标准输入记录允许捕获用户在当前记录的 shell 中输入的所有字符。播放器(例如asciinema-player )可以使用它 来显示按下的键。因为它基本上是一个按键记录(范围仅限于单个 shell 实例),所以默认情况下它是禁用的,并且必须通过 --stdin选项显式启用。</p> <pre><code>play <filename></code></pre> <ul> <li><p>从本地文件播放</p> <p>asciinema play /path/to/asciicast.cast</p></li> <li><p>从HTTP(S) URL播放</p> <p>asciinema play <a rel="nofollow noopener noreferrer" href="https://asciinema.org/a/22124.cast">https://asciinema.org/a/22124.cast</a><br /> asciinema play <a rel="nofollow noopener noreferrer" href="http://example.com/demo.cast">http://example.com/demo.cast</a></p></li> <li><p>从 asciicast 页面 URL 播放(需要<link rel="alternate" type="application/x-asciicast" href="/my/ascii.cast">在页面的 HTML 中)</p> <p>asciinema play <a rel="nofollow noopener noreferrer" href="https://asciinema.org/a/22124">https://asciinema.org/a/22124</a><br /> asciinema play <a rel="nofollow noopener noreferrer" href="http://example.com/blog/post.html">http://example.com/blog/post.html</a></p></li> <li><p>从标准输入播放</p> <p>cat /path/to/asciicast.cast | asciinema play -<br /> ssh user@host cat asciicast.cast | asciinema play -</p></li> <li><p>从 IPFS 播放</p> <p>asciinema play dweb:/ipfs/QmNe7FsYaHc9SaDEAEXbaagAzNw9cH7YbzN4xV7jV1MCzK/ascii.cast</p></li> <li><p>可用选项</p> <p>-i, --idle-time-limit=<sec>- 将重播的终端不活动限制为最大<sec>秒数<br /> -s, --speed=<factor>- 播放速度(可以是小数)</p></li> <li><p>快捷键</p> <p>空格:切换暂停<br /> Ctrl+C: exit</p></li> </ul> <p>您可以通过在 处创建配置文件来配置 asciinema</p> <pre><code>$HOME/.config/asciinema/config</code></pre> <p>配置分为 ( [api], [record], [play]) 部分。以下是每个部分的所有可用选项的列表</p> <pre><code>[api] ; API服务器的URL,默认值:https://asciinema.org ; 如果你运行自己的 asciinema-server 实例,那么在这里设置它的地址 ; 也可以通过设置环境变量 ASCIINEMA_API_URL 来覆盖此设置 url = https://asciinema.example.com [record] ;要记录的命令,默认值: $SHELL command = /bin/bash -l ; 启用 stdin (键盘) 记录, 默认值: no stdin = yes ; 要捕获的环境变量列表,默认值: SHELL,TERM env = SHELL,TERM,USER ; 将记录的终端不活动限制为最多 n 秒,默认值: off idle_time_limit = 2 ; 自动回答所有交互式提示的"是",默认值: no yes = true ; 安静模式,禁止所有通知/警告,默认值: no quiet = true [play] ; 回放速度(可以是小数),默认值: 1 speed = 2 ; 将重播的终端不活动限制到最多 n 秒,默认值: off idle_time_limit = 1</code></pre> <h1 id="asciicast数据格式"><strong>Asciicast数据格式</strong></h1> <p><a rel="nofollow noopener noreferrer" href="https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v1.md">https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v1.md</a></p> <p>asciicast 文件是 JSON 文件,包含元数据(例如录制的持续时间或标题)以及录制期间打印到终端标准输出的实际内容。</p> <p>asciinema 记录器版本 1.0 至 1.4 使用该格式的版本 1。</p> <p>每个 asciicast 都包含以下属性集:</p> <ul> <li><p>version:设置为 1,</p></li> <li><p>width:终端宽度(列数),</p></li> <li><p>height:终端高度(行数),</p></li> <li><p>duration:asciicast 作为浮点数的总持续时间,</p></li> <li><p>command:记录的命令,通过-c选项给出rec,</p></li> <li><p>title:asciicast 的标题,通过-t选项给出rec,</p></li> <li><p>env:对于调试播放问题有用的环境变量图,</p></li> <li><p>stdout: 最重要的部分是 "stdout" 数组。此数组中的每个元素都包含一个浮点数和一个字符串。浮点数标记着从会话开始到当前输出发生时经过的时间(单位:秒),字符串则记录了该时间点的具体输出内容。[5.4321, "foo\rbar\u0007…"]</p></li> </ul> <p><strong>Asciicast示例</strong></p> <pre><code>{ "version": 1, "width": 80, "height": 24, "duration": 1.515658, "command": "/bin/zsh", "title": "", "env": { "TERM": "xterm-256color", "SHELL": "/bin/zsh" }, "stdout": [ [ 0.248848, "\u001b[1;31mHello \u001b[32mWorld!\u001b[0m\n" ], [ 1.001376, "I am \rThis is on the next line." ] ] }</code></pre> <p><a rel="nofollow noopener noreferrer" href="https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md">https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md</a></p> <p>asciicast v2 文件是以换行符分隔的 JSON文件,其中:</p> <ul> <li><p>第一行包含标头(初始终端大小、时间戳和其他元数据),编码为 JSON 对象,</p></li> <li><p>以下所有行形成一个事件流,每行代表一个单独的事件,编码为 3 元素 JSON 数组。</p></li> </ul> <p><strong>示例</strong></p> <pre><code>{"version": 2, "width": 80, "height": 24, "timestamp": 1504467315, "title": "Demo", "env": {"TERM": "xterm-256color", "SHELL": "/bin/zsh"}} [0.248848, "o", "\u001b[1;31mHello \u001b[32mWorld!\u001b[0m\n"] [1.001376, "o", "That was ok\rThis is better."] [1.500000, "m", ""] [2.143733, "o", "Now... "] [6.541828, "o", "Bye!"]</code></pre> <h1 id="在html中使用asciinema-player"><strong><em><em>在HTML中使用asciinema-player</em></strong></em></h1> <p><a rel="nofollow noopener noreferrer" href="https://github.com/asciinema/asciinema-player">https://github.com/asciinema/asciinema-player</a></p> <p>在releases page中下载最新的asciinema-player.min.js和asciinema-player.css文件</p> <p>首先,将 和您的录音文件添加asciinema-player.min.js到asciinema-player.css您.cast网站的资产中。下面的 HTML 代码段假设它们位于 Web 服务器的根目录中。</p> <p>然后将必要的包含添加到您的 HTML 文档中并在空元素内初始化播放器<div></p> <pre><code><!DOCTYPE html> <html> <head> ... <link rel="stylesheet" type="text/css" href="/asciinema-player.css" /> ... </head> <body> ... <div id="demo"></div> ... <script src="/asciinema-player.min.js"></script> <script> AsciinemaPlayer.create('/demo.cast', document.getElementById('demo')); </script> </body> </html></code></pre> <h1 id="后端go-ssh支持asciicastnbspnbspv1"><strong>后端go-ssh支持asciicast  v1</strong></h1> <ul> <li><p><strong>定义AsciiCast结构体</strong></p> <p>type AsciiCast struct {<br /> Version int <code>json:"version"</code><br /> Width int <code>json:"width"</code><br /> Height int <code>json:"height"</code><br /> // 可选属性<br /> Timestamp float64 <code>json:"timestamp"</code><br /> Duration float64 <code>json:"duration"</code><br /> Command string <code>json:"command"</code><br /> Title string <code>json:"title"</code><br /> Env struct {<br /> Term string <code>json:"TERM"</code><br /> Shell string <code>json:"SHELL"</code><br /> } <code>json:"env"</code><br /> Stdout [][]interface{} <code>json:"stdout"</code><br /> }</p></li> <li><p>修改WSClient,增加如下属性</p> <p>type WSClient struct {<br /> // AsciiCast<br /> asciiCast *AsciiCast<br /> // 会话开始时间<br /> startTime time.Time<br /> // 持续时间<br /> duration float64<br /> // 上次用户交互时间<br /> prevTime float64<br /> }</p></li> <li><p>Read方法中记录持续时间,上次记录时间(间隔时间)</p> <p>func (c *WSClient) Read(p []byte) (n int, err error) {<br /> // …<br /> c.duration = time.Since(c.startTime).Seconds()<br /> c.prevTime = c.duration<br /> // …<br /> }</p></li> <li><p>Write方法中记录持续时间,上次记录时间(间隔时间)、记录stdin、stdout、stder添加到asciiCast.Stdout</p> <p>func (c *WSClient) Write(p []byte) (n int, err error) {<br /> // …<br /> err = c.ws.WriteMessage(websocket.TextMessage, p)<br /> c.duration = time.Since(c.startTime).Seconds()<br /> timeDiff := c.duration - c.prevTime<br /> // 增加间隔时间,<br /> timeDiff += 300.0 / 1000.0<br /> c.asciiCast.Stdout = append(c.asciiCast.Stdout, []interface{}{timeDiff, string(p)}) // …<br /> }</p></li> <li><p>SSHHandler中记录会话开始时间、会话终止时保存AsciiCast文件</p> <p>func SSHHandler(wsClient *WSClient, user, password, ip, authType, command string, port int) {<br /> // …<br /> wsClient.startTime = time.Now()</p> <p>// …<br /> SaveAsciiCast(wsClient)<br /> }</p></li> <li><p>SaveAsciiCast保存AsciiCast文件</p> <p>func SaveAsciiCast(c *WSClient) {<br /> c.asciiCast.Duration = c.duration<br /> castData, err := json.MarshalIndent(c.asciiCast, "", " ")<br /> if err != nil {<br /> log.Fatal(err)<br /> }<br /> if err := os.WriteFile(fmt.Sprintf("./assets/%s.json", time.Now().Format("20060102150405000")), castData, 0644); err != nil {<br /> log.Fatal(err)<br /> }<br /> }</p></li> </ul> <p>后端项目完整代码:<a rel="nofollow noopener noreferrer" href="https://gitee.com/KubeSec/webssh/tree/master/go-ssh">https://gitee.com/KubeSec/webssh/tree/master/go-ssh</a></p> <h1 id="vue中使用asciinema-player">Vue中使用asciinema-player</h1> <p>更多用法请参考官方文档:<a rel="nofollow noopener noreferrer" href="https://github.com/asciinema/asciinema-player">https://github.com/asciinema/asciinema-player</a></p> <p>从<a rel="nofollow noopener noreferrer" href="https://github.com/asciinema/asciinema-player/releases页面中下载最新的asciinema-player.min.js和asciinema-player.css文件。">https://github.com/asciinema/asciinema-player/releases页面中下载最新的asciinema-player.min.js和asciinema-player.css文件。</a></p> <p>把asciinema-player.min.js和asciinema-player.css文件放到项目public目录下</p> <p>在public目录下新建asciinema.html</p> <pre><code><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="/asciinema-player.css" /> <title>asciinema-player</title> </head> <body> <div id="demo"></div> <script src="/asciinema-player.min.js"></script> <script> AsciinemaPlayer.create('http://127.0.0.1:9191/assets/20230713200759000.json', document.getElementById('demo')); </script> </body> </html></code></pre> <p>在src/views/ssh目录下新建log.vue文件</p> <pre><code><template> <div style="height: 800px; width: 100%"> <iframe src="/asciinema.html" width="100%" height="100%"></iframe> </div> </template> <script> export default { data() { return { } }, methods: { } } </script> <style scoped> html, body { height: 100%; } </style></code></pre> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/b761d69b-55f7-4a89-a5f0-ff53365a546c.png" alt="" /></p> <ul> <li><p>在src/router/index.js文件中增加路由</p> <p>{<br /> path: '/ssh-logs',<br /> component: Layout,<br /> children: [<br /> {<br /> path: 'ssh-logs',<br /> name: 'SSH-logs',<br /> component: () => import('@/views/ssh/log.vue'),<br /> meta: { title: 'SSH-log', icon: 'form' }<br /> }<br /> ]<br /> },</p></li> <li><p>启动项目</p> <p>npm install<br /> npm run dev</p></li> <li><p>前端全部代码</p></li> </ul> <p><a rel="nofollow noopener noreferrer" href="https://gitee.com/KubeSec/webssh/tree/master/webssh">https://gitee.com/KubeSec/webssh/tree/master/webssh</a></p> <h1 id="测试">测试</h1> <ul> <li><p>在go-ssh项目目录下新建目录assets</p></li> <li><p>登录WebSSH连接主机,输入ls、top等指令,输入exit后关闭会话</p></li> </ul> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/3205f787-0896-4480-862d-4eb76ba50ade.png" alt="" /></p> <p>查看go-ssh项目assets目录下录制的asciicast文件</p> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/8fa78b64-2475-4c6d-a021-5dc14b2e7f35.png" alt="" /></p> <ul> <li>修改前端项目public/asciinema.html</li> </ul> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/a374fe70-c04d-4fc8-b4fa-9bbf1b818d7b.png" alt="" /></p> <ul> <li>浏览器访问:http://localhost:9528/#/ssh-logs/ssh-logs 播放录制的视频</li> </ul> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/4252b146-0ce5-4a56-81b4-7de1fd16a872.png" alt="" /></p> <p><img class='lazyload' data-src="https://article.cdnof.com/2307/a33d13b5-05ab-403d-a426-128b3c9c780a.png" alt="" /></p></div></div><div class="MuiGrid-root jss8 MuiGrid-item MuiGrid-grid-xs-true MuiGrid-grid-md-3"><div class="MuiTypography-root jss26 MuiTypography-body1"><div class="MuiTypography-root jss27 MuiTypography-body1"><canvas style="height:108px;width:108px" height="108" width="108"></canvas><div class="MuiTypography-root jss28 MuiTypography-body1"><p class="MuiTypography-root jss29 MuiTypography-body1">手机扫一扫</p><p class="MuiTypography-root jss29 MuiTypography-body1">移动阅读更方便</p></div></div></div><div class="MuiTypography-root jss9 MuiTypography-body1"><div class="MuiTypography-root jss30 MuiTypography-body1" style="height:150px"><div class="swiper-container jss32"><div class="swiper-pagination"></div><div class="swiper-wrapper"><div class="swiper-slide jss32"><a class="MuiTypography-root MuiLink-root MuiLink-underlineHover jss32 MuiTypography-colorInherit" target="_blank" rel="nofollow noopener noreferrer" href="https://qd.rs/aliyun"><img alt="阿里云服务器" class="jss31" src="https://article.cdnof.com/promotion/aliyun.jpg"/></a></div><div class="swiper-slide jss32"><a class="MuiTypography-root MuiLink-root MuiLink-underlineHover jss32 MuiTypography-colorInherit" target="_blank" rel="nofollow noopener noreferrer" href="https://qd.rs/tencent"><img alt="腾讯云服务器" class="jss31" src="https://article.cdnof.com/promotion/tencent.jpg"/></a></div><div class="swiper-slide jss32"><a class="MuiTypography-root MuiLink-root MuiLink-underlineHover jss32 MuiTypography-colorInherit" target="_blank" rel="nofollow noopener noreferrer" href="https://qd.rs/qiniu"><img alt="七牛云服务器" class="jss31" src="https://article.cdnof.com/promotion/qiniu.png"/></a></div></div></div></div></div><div class="MuiTypography-root MuiTypography-body1"><div class="MuiTypography-root jss33 MuiTypography-body1"><p class="MuiTypography-root jss34 MuiTypography-body1">你可能感兴趣的文章</p><div class="MuiList-root MuiList-padding" aria-label="main mailbox folders"></div></div></div></div></div></div><footer style="margin-top:30px"><p class="MuiTypography-root MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-alignCenter">Copyright © <a class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorInherit" href="https://v2as.com" title="哇哦,有大量工具等你探索">V2AS | 问路</a> <!-- -->2024<!-- --> <!-- -->.</p><p class="MuiTypography-root MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-alignCenter"><a class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorInherit" rel="nofollow noopener noreferrer" href="https://beian.miit.gov.cn/">浙ICP备15029886号</a></p></footer></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"article":{"article_id":"ed4cf1b7-1310-4121-904a-f40bdca53bd0","title":"WebSSH之录屏安全审计(三)","link":"","description":"第一篇:Gin+Xterm.js实现WebSSH远程Kubernetes Pod(一)\n第二篇:WebSSH远程管理Linux服务器、Web终端窗口自适应(二)\n支持用户名密码认证\n支持SSH密钥认证\n支持Web终端窗口自适应\n支持录屏审计\n\nAsciinema\nAsciinema是一款开源的终端会话录制工具。\n官网: https://asciinema.org/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","image":"https://article.cdnof.com/2307/712e9cf5-4b4d-442e-b9ee-f27f625234f6.png","keywords":["asciinema","asciicast","录屏","player","录制","com","https","json","play","script"],"created_at":"2023-07-30T09:22:39.902Z","html":"\u003cp\u003e第一篇:\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://mp.weixin.qq.com/s?__biz=Mzg4NTk4MDk2OQ==\u0026mid=2247483901\u0026idx=1\u0026sn=18b850fb83f83fe3c9c2a03a83a9fab8\u0026chksm=cfa1ebbbf8d662ad504b9a602a39b751b4e2f72776e5e18c585baa215f5bc8e5b4faeb02135f\u0026token=569797240\u0026lang=zh_CN#rd\"\u003eGin+Xterm.js实现WebSSH远程Kubernetes\u0026nbsp;Pod(一)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e第二篇:\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://mp.weixin.qq.com/s?__biz=Mzg4NTk4MDk2OQ==\u0026mid=2247483925\u0026idx=1\u0026sn=f3ab00d2ae7f54f4b24a37ccdb1aeee9\u0026chksm=cfa1e853f8d6614596290dcd6de9b1dbcdcc240628b3f9bed551611eb221272884fe07b12ecb\u0026cur_album_id=3011376182031826954\u0026scene=189#wechat_redirect\"\u003eWebSSH远程管理Linux服务器、Web终端窗口自适应(二)\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e支持用户名密码认证\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e支持SSH密钥认证\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e支持Web终端窗口自适应\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e支持录屏审计\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/712e9cf5-4b4d-442e-b9ee-f27f625234f6.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003ch1 id=\"asciinema\"\u003eAsciinema\u003c/h1\u003e\n\u003cp\u003eAsciinema是一款开源的终端会话录制工具。\u003c/p\u003e\n\u003cp\u003e**官网: \u003ca rel=\"nofollow noopener noreferrer\" href=\"https://asciinema.org/**\"\u003ehttps://asciinema.org/**\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eCentOS\u003c/p\u003e\n\u003cp\u003eyum install asciinema -y\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e**以下内容来自官方文档:\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://asciinema.org/docs/usage**\"\u003ehttps://asciinema.org/docs/usage**\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e通过运行\u003c/p\u003e\n\u003cp\u003erec [filename]\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e您可以开始一个新的录制会话。当您退出shell时Ctrl+D或exit完成录制。如果记录的进程不是shell,则记录在进程退出时结束。\u003c/p\u003e\n\u003cp\u003e如果filename省略该参数,则(在要求确认后)将生成的 asciicast 上传到\u0026nbsp;asciinema-server(默认为 asciinema.org),在那里可以观看和共享。\u003c/p\u003e\n\u003cp\u003e如果filename给出参数,则生成的记录(称为\u0026nbsp;asciicast)将保存到本地文件中。稍后可以使用 .asciinema 重播和asciinema play \u003cfilename\u003e/或上传到 asciinema 服务器asciinema upload \u003cfilename\u003e。\u003c/p\u003e\n\u003cp\u003eASCIINEMA_REC=1添加到记录的进程环境变量中。.bashrcshell 的配置文件 ( , )可以使用它来.zshrc更改提示或在录制 shell 时播放声音。\u003c/p\u003e\n\u003cp\u003e可用选项:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e--stdin- 启用标准输入(键盘)录制(见下文)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e--append- 附加到现有录音\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e--raw- 保存原始 STDOUT 输出,没有计时信息或其他元数据\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e--overwrite- 如果录音已存在,则覆盖录音\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-c, --command=\u003ccommand\u003e- 指定要记录的命令,默认为$SHELL\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-e, --env=\u003cvar-names\u003e- 要捕获的环境变量列表,默认为SHELL,TERM\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-t, --title=\u003ctitle\u003e- 指定asciicast的标题\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-i, --idle-time-limit=\u003csec\u003e- 将记录的终端不活动限制为最大\u003csec\u003e秒数\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-y, --yes- 对所有提示回答“是”(例如上传确认)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e-q, --quiet- 保持安静,禁止所有通知/警告(隐含 -y)\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e标准输入记录允许捕获用户在当前记录的 shell 中输入的所有字符。播放器(例如asciinema-player\u0026nbsp;)可以使用它\u0026nbsp;来显示按下的键。因为它基本上是一个按键记录(范围仅限于单个 shell 实例),所以默认情况下它是禁用的,并且必须通过\u0026nbsp;--stdin选项显式启用。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eplay \u0026lt;filename\u0026gt;\u003c/code\u003e\u003c/pre\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e从本地文件播放\u003c/p\u003e\n\u003cp\u003easciinema play /path/to/asciicast.cast\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e从HTTP(S) URL播放\u003c/p\u003e\n\u003cp\u003easciinema play \u003ca rel=\"nofollow noopener noreferrer\" href=\"https://asciinema.org/a/22124.cast\"\u003ehttps://asciinema.org/a/22124.cast\u003c/a\u003e\u003cbr /\u003e\nasciinema play \u003ca rel=\"nofollow noopener noreferrer\" href=\"http://example.com/demo.cast\"\u003ehttp://example.com/demo.cast\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e从 asciicast 页面 URL 播放(需要\u003clink rel=\"alternate\" type=\"application/x-asciicast\" href=\"/my/ascii.cast\"\u003e在页面的 HTML 中)\u003c/p\u003e\n\u003cp\u003easciinema play \u003ca rel=\"nofollow noopener noreferrer\" href=\"https://asciinema.org/a/22124\"\u003ehttps://asciinema.org/a/22124\u003c/a\u003e\u003cbr /\u003e\nasciinema play \u003ca rel=\"nofollow noopener noreferrer\" href=\"http://example.com/blog/post.html\"\u003ehttp://example.com/blog/post.html\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e从标准输入播放\u003c/p\u003e\n\u003cp\u003ecat /path/to/asciicast.cast | asciinema play -\u003cbr /\u003e\nssh user@host cat asciicast.cast | asciinema play -\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e从 IPFS 播放\u003c/p\u003e\n\u003cp\u003easciinema play dweb:/ipfs/QmNe7FsYaHc9SaDEAEXbaagAzNw9cH7YbzN4xV7jV1MCzK/ascii.cast\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e可用选项\u003c/p\u003e\n\u003cp\u003e-i, --idle-time-limit=\u003csec\u003e- 将重播的终端不活动限制为最大\u003csec\u003e秒数\u003cbr /\u003e\n-s, --speed=\u003cfactor\u003e- 播放速度(可以是小数)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e快捷键\u003c/p\u003e\n\u003cp\u003e空格:切换暂停\u003cbr /\u003e\nCtrl+C: exit\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e您可以通过在 处创建配置文件来配置 asciinema\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e$HOME/.config/asciinema/config\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e配置分为 (\u0026nbsp;[api], [record], [play]) 部分。以下是每个部分的所有可用选项的列表\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e[api]\n\n; API服务器的URL,默认值:https://asciinema.org\n; 如果你运行自己的 asciinema-server 实例,那么在这里设置它的地址\n; 也可以通过设置环境变量 ASCIINEMA_API_URL 来覆盖此设置\nurl = https://asciinema.example.com\n\n[record]\n\n;要记录的命令,默认值: $SHELL\ncommand = /bin/bash -l\n\n; 启用 stdin (键盘) 记录, 默认值: no\nstdin = yes\n\n; 要捕获的环境变量列表,默认值: SHELL,TERM\nenv = SHELL,TERM,USER\n\n; 将记录的终端不活动限制为最多 n 秒,默认值: off\nidle_time_limit = 2\n\n; 自动回答所有交互式提示的\"是\",默认值: no\nyes = true\n\n; 安静模式,禁止所有通知/警告,默认值: no\nquiet = true\n\n[play]\n\n; 回放速度(可以是小数),默认值: 1\nspeed = 2\n\n; 将重播的终端不活动限制到最多 n 秒,默认值: off\nidle_time_limit = 1\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"asciicast数据格式\"\u003e\u003cstrong\u003eAsciicast数据格式\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v1.md\"\u003ehttps://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v1.md\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003easciicast 文件是 JSON 文件,包含元数据(例如录制的持续时间或标题)以及录制期间打印到终端标准输出的实际内容。\u003c/p\u003e\n\u003cp\u003easciinema 记录器版本 1.0 至 1.4 使用该格式的版本 1。\u003c/p\u003e\n\u003cp\u003e每个 asciicast 都包含以下属性集:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eversion:设置为 1,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003ewidth:终端宽度(列数),\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eheight:终端高度(行数),\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eduration:asciicast 作为浮点数的总持续时间,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003ecommand:记录的命令,通过-c选项给出rec,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003etitle:asciicast 的标题,通过-t选项给出rec,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eenv:对于调试播放问题有用的环境变量图,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003estdout:\u0026nbsp;最重要的部分是 \"stdout\" 数组。此数组中的每个元素都包含一个浮点数和一个字符串。浮点数标记着从会话开始到当前输出发生时经过的时间(单位:秒),字符串则记录了该时间点的具体输出内容。[5.4321, \"foo\\rbar\\u0007…\"]\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eAsciicast示例\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\n \"version\": 1,\n \"width\": 80,\n \"height\": 24,\n \"duration\": 1.515658,\n \"command\": \"/bin/zsh\",\n \"title\": \"\",\n \"env\": {\n \"TERM\": \"xterm-256color\",\n \"SHELL\": \"/bin/zsh\"\n },\n \"stdout\": [\n [\n 0.248848,\n \"\\u001b[1;31mHello \\u001b[32mWorld!\\u001b[0m\\n\"\n ],\n [\n 1.001376,\n \"I am \\rThis is on the next line.\"\n ]\n ]\n}\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md\"\u003ehttps://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003easciicast v2 文件是以换行符分隔的 JSON文件,其中:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e第一行包含标头(初始终端大小、时间戳和其他元数据),编码为 JSON 对象,\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e以下所有行形成一个事件流,每行代表一个单独的事件,编码为 3 元素 JSON 数组。\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e示例\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\"version\": 2, \"width\": 80, \"height\": 24, \"timestamp\": 1504467315, \"title\": \"Demo\", \"env\": {\"TERM\": \"xterm-256color\", \"SHELL\": \"/bin/zsh\"}}\n[0.248848, \"o\", \"\\u001b[1;31mHello \\u001b[32mWorld!\\u001b[0m\\n\"]\n[1.001376, \"o\", \"That was ok\\rThis is better.\"]\n[1.500000, \"m\", \"\"]\n[2.143733, \"o\", \"Now... \"]\n[6.541828, \"o\", \"Bye!\"]\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"在html中使用asciinema-player\"\u003e\u003cstrong\u003e\u003cem\u003e\u003cem\u003e在HTML中使用asciinema-player\u003c/em\u003e\u003c/strong\u003e\u003c/em\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://github.com/asciinema/asciinema-player\"\u003ehttps://github.com/asciinema/asciinema-player\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e在releases page中下载最新的asciinema-player.min.js和asciinema-player.css文件\u003c/p\u003e\n\u003cp\u003e首先,将 和您的录音文件添加asciinema-player.min.js到asciinema-player.css您.cast网站的资产中。下面的 HTML 代码段假设它们位于 Web 服务器的根目录中。\u003c/p\u003e\n\u003cp\u003e然后将必要的包含添加到您的 HTML 文档中并在空元素内初始化播放器\u003cdiv\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;!DOCTYPE html\u0026gt;\n\u0026lt;html\u0026gt;\n \u0026lt;head\u0026gt;\n ...\n \u0026lt;link rel=\"stylesheet\" type=\"text/css\" href=\"/asciinema-player.css\" /\u0026gt;\n ...\n \u0026lt;/head\u0026gt;\n \u0026lt;body\u0026gt;\n ...\n \u0026lt;div id=\"demo\"\u0026gt;\u0026lt;/div\u0026gt;\n ...\n \u0026lt;script src=\"/asciinema-player.min.js\"\u0026gt;\u0026lt;/script\u0026gt;\n \u0026lt;script\u0026gt;\n AsciinemaPlayer.create('/demo.cast', document.getElementById('demo'));\n \u0026lt;/script\u0026gt;\n \u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"后端go-ssh支持asciicastnbspnbspv1\"\u003e\u003cstrong\u003e后端go-ssh支持asciicast\u0026nbsp;\u0026nbsp;v1\u003c/strong\u003e\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e\u003cstrong\u003e定义AsciiCast结构体\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003etype AsciiCast struct {\u003cbr /\u003e\n Version int \u003ccode\u003ejson:\"version\"\u003c/code\u003e\u003cbr /\u003e\n Width int \u003ccode\u003ejson:\"width\"\u003c/code\u003e\u003cbr /\u003e\n Height int \u003ccode\u003ejson:\"height\"\u003c/code\u003e\u003cbr /\u003e\n // 可选属性\u003cbr /\u003e\n Timestamp float64 \u003ccode\u003ejson:\"timestamp\"\u003c/code\u003e\u003cbr /\u003e\n Duration float64 \u003ccode\u003ejson:\"duration\"\u003c/code\u003e\u003cbr /\u003e\n Command string \u003ccode\u003ejson:\"command\"\u003c/code\u003e\u003cbr /\u003e\n Title string \u003ccode\u003ejson:\"title\"\u003c/code\u003e\u003cbr /\u003e\n Env struct {\u003cbr /\u003e\n Term string \u003ccode\u003ejson:\"TERM\"\u003c/code\u003e\u003cbr /\u003e\n Shell string \u003ccode\u003ejson:\"SHELL\"\u003c/code\u003e\u003cbr /\u003e\n } \u003ccode\u003ejson:\"env\"\u003c/code\u003e\u003cbr /\u003e\n Stdout [][]interface{} \u003ccode\u003ejson:\"stdout\"\u003c/code\u003e\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e修改WSClient,增加如下属性\u003c/p\u003e\n\u003cp\u003etype WSClient struct {\u003cbr /\u003e\n // AsciiCast\u003cbr /\u003e\n asciiCast *AsciiCast\u003cbr /\u003e\n // 会话开始时间\u003cbr /\u003e\n startTime time.Time\u003cbr /\u003e\n // 持续时间\u003cbr /\u003e\n duration float64\u003cbr /\u003e\n // 上次用户交互时间\u003cbr /\u003e\n prevTime float64\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eRead方法中记录持续时间,上次记录时间(间隔时间)\u003c/p\u003e\n\u003cp\u003efunc (c *WSClient) Read(p []byte) (n int, err error) {\u003cbr /\u003e\n // …\u003cbr /\u003e\n c.duration = time.Since(c.startTime).Seconds()\u003cbr /\u003e\n c.prevTime = c.duration\u003cbr /\u003e\n // …\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eWrite方法中记录持续时间,上次记录时间(间隔时间)、记录stdin、stdout、stder添加到asciiCast.Stdout\u003c/p\u003e\n\u003cp\u003efunc (c *WSClient) Write(p []byte) (n int, err error) {\u003cbr /\u003e\n // …\u003cbr /\u003e\n err = c.ws.WriteMessage(websocket.TextMessage, p)\u003cbr /\u003e\n c.duration = time.Since(c.startTime).Seconds()\u003cbr /\u003e\n timeDiff := c.duration - c.prevTime\u003cbr /\u003e\n // 增加间隔时间,\u003cbr /\u003e\n timeDiff += 300.0 / 1000.0\u003cbr /\u003e\n c.asciiCast.Stdout = append(c.asciiCast.Stdout, []interface{}{timeDiff, string(p)}) // …\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eSSHHandler中记录会话开始时间、会话终止时保存AsciiCast文件\u003c/p\u003e\n\u003cp\u003efunc SSHHandler(wsClient *WSClient, user, password, ip, authType, command string, port int) {\u003cbr /\u003e\n // …\u003cbr /\u003e\n wsClient.startTime = time.Now()\u003c/p\u003e\n\u003cp\u003e// …\u003cbr /\u003e\n SaveAsciiCast(wsClient)\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eSaveAsciiCast保存AsciiCast文件\u003c/p\u003e\n\u003cp\u003efunc SaveAsciiCast(c *WSClient) {\u003cbr /\u003e\n c.asciiCast.Duration = c.duration\u003cbr /\u003e\n castData, err := json.MarshalIndent(c.asciiCast, \"\", \" \")\u003cbr /\u003e\n if err != nil {\u003cbr /\u003e\n log.Fatal(err)\u003cbr /\u003e\n }\u003cbr /\u003e\n if err := os.WriteFile(fmt.Sprintf(\"./assets/%s.json\", time.Now().Format(\"20060102150405000\")), castData, 0644); err != nil {\u003cbr /\u003e\n log.Fatal(err)\u003cbr /\u003e\n }\u003cbr /\u003e\n}\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e后端项目完整代码:\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://gitee.com/KubeSec/webssh/tree/master/go-ssh\"\u003ehttps://gitee.com/KubeSec/webssh/tree/master/go-ssh\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"vue中使用asciinema-player\"\u003eVue中使用asciinema-player\u003c/h1\u003e\n\u003cp\u003e更多用法请参考官方文档:\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://github.com/asciinema/asciinema-player\"\u003ehttps://github.com/asciinema/asciinema-player\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e从\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://github.com/asciinema/asciinema-player/releases页面中下载最新的asciinema-player.min.js和asciinema-player.css文件。\"\u003ehttps://github.com/asciinema/asciinema-player/releases页面中下载最新的asciinema-player.min.js和asciinema-player.css文件。\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e把asciinema-player.min.js和asciinema-player.css文件放到项目public目录下\u003c/p\u003e\n\u003cp\u003e在public目录下新建asciinema.html\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;!DOCTYPE html\u0026gt;\n\u0026lt;html lang=\"en\"\u0026gt;\n\u0026lt;head\u0026gt;\n \u0026lt;meta charset=\"UTF-8\"\u0026gt;\n \u0026lt;link rel=\"stylesheet\" type=\"text/css\" href=\"/asciinema-player.css\" /\u0026gt;\n \u0026lt;title\u0026gt;asciinema-player\u0026lt;/title\u0026gt;\n\u0026lt;/head\u0026gt;\n\u0026lt;body\u0026gt;\n \u0026lt;div id=\"demo\"\u0026gt;\u0026lt;/div\u0026gt;\n \u0026lt;script src=\"/asciinema-player.min.js\"\u0026gt;\u0026lt;/script\u0026gt;\n \u0026lt;script\u0026gt;\n AsciinemaPlayer.create('http://127.0.0.1:9191/assets/20230713200759000.json', document.getElementById('demo'));\n \u0026lt;/script\u0026gt;\n\u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在src/views/ssh目录下新建log.vue文件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;template\u0026gt;\n \u0026lt;div style=\"height: 800px; width: 100%\"\u0026gt;\n \u0026lt;iframe src=\"/asciinema.html\" width=\"100%\" height=\"100%\"\u0026gt;\u0026lt;/iframe\u0026gt;\n \u0026lt;/div\u0026gt;\n\u0026lt;/template\u0026gt;\n\n\u0026lt;script\u0026gt;\nexport default {\n data() {\n return {\n }\n },\n methods: {\n }\n}\n\u0026lt;/script\u0026gt;\n\u0026lt;style scoped\u0026gt;\nhtml, body {\n height: 100%;\n}\n\u0026lt;/style\u0026gt;\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/b761d69b-55f7-4a89-a5f0-ff53365a546c.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e在src/router/index.js文件中增加路由\u003c/p\u003e\n\u003cp\u003e{\u003cbr /\u003e\n path: '/ssh-logs',\u003cbr /\u003e\n component: Layout,\u003cbr /\u003e\n children: [\u003cbr /\u003e\n {\u003cbr /\u003e\n path: 'ssh-logs',\u003cbr /\u003e\n name: 'SSH-logs',\u003cbr /\u003e\n component: () =\u0026gt; import('@/views/ssh/log.vue'),\u003cbr /\u003e\n meta: { title: 'SSH-log', icon: 'form' }\u003cbr /\u003e\n }\u003cbr /\u003e\n ]\u003cbr /\u003e\n },\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e启动项目\u003c/p\u003e\n\u003cp\u003enpm install\u003cbr /\u003e\nnpm run dev\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e前端全部代码\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ca rel=\"nofollow noopener noreferrer\" href=\"https://gitee.com/KubeSec/webssh/tree/master/webssh\"\u003ehttps://gitee.com/KubeSec/webssh/tree/master/webssh\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"测试\"\u003e测试\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e在go-ssh项目目录下新建目录assets\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e登录WebSSH连接主机,输入ls、top等指令,输入exit后关闭会话\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/3205f787-0896-4480-862d-4eb76ba50ade.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e查看go-ssh项目assets目录下录制的asciicast文件\u003c/p\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/8fa78b64-2475-4c6d-a021-5dc14b2e7f35.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e修改前端项目public/asciinema.html\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/a374fe70-c04d-4fc8-b4fa-9bbf1b818d7b.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e浏览器访问:http://localhost:9528/#/ssh-logs/ssh-logs 播放录制的视频\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/4252b146-0ce5-4a56-81b4-7de1fd16a872.png\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg class='lazyload' data-src=\"https://article.cdnof.com/2307/a33d13b5-05ab-403d-a426-128b3c9c780a.png\" alt=\"\" /\u003e\u003c/p\u003e"},"seo":{"title":"WebSSH之录屏安全审计(三)","description":"第一篇:Gin+Xterm.js实现WebSSH远程Kubernetes Pod(一)\n第二篇:WebSSH远程管理Linux服务器、Web终端窗口自适应(二)\n支持用户名密码认证\n支持SSH密钥认证\n支持Web终端窗口自适应\n支持录屏审计\n\nAsciinema\nAsciinema是一款开源的终端会话录制工具。\n官网: https://asciinema.org/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","image":"https://article.cdnof.com/2307/712e9cf5-4b4d-442e-b9ee-f27f625234f6.png","url":"https://v2as.com/article/ed4cf1b7-1310-4121-904a-f40bdca53bd0","keywords":["asciinema","asciicast","录屏","player","录制","com","https","json","play","script"]},"viewsCount":1,"promotionList":[{"title":"阿里云服务器","image":"https://article.cdnof.com/promotion/aliyun.jpg","link":"https://qd.rs/aliyun"},{"title":"腾讯云服务器","image":"https://article.cdnof.com/promotion/tencent.jpg","link":"https://qd.rs/tencent"},{"title":"七牛云服务器","image":"https://article.cdnof.com/promotion/qiniu.png","link":"https://qd.rs/qiniu"}],"similarKeywordsList":null},"__N_SSG":true},"page":"/article/[article_id]","query":{"article_id":"ed4cf1b7-1310-4121-904a-f40bdca53bd0"},"buildId":"7EtL49Y65E8zx1NwcIC_o","isFallback":false,"gsp":true,"scriptLoader":[]}</script></body></html>