赞
踩
本文主要介绍的是如何docker安装Guacamole,进行浏览器远程桌面的访问。
Apache Guacamole 是一个无客户端远程桌面网关。它支持标准协议,如 VNC、RDP 和 SSH。我们称之为无客户端,因为不需要插件或客户端软件。
用户使用他们的网络浏览器连接到 Guacamole 服务器。用 JavaScript 编写的 Guacamole 客户端由 Guacamole 服务器内的网络服务器提供给用户。加载后,此客户端使用 Guacamole 协议通过 HTTP 连接回服务器。部署到 Guacamole 服务器的 Web 应用程序读取 Guacamole 协议并将其转发到 guacd,即原生的 Guacamole 代理。这个代理实际上解释了 Guacamole 协议的内容,代表用户连接到任意数量的远程桌面服务器。Guacamole 协议与 guacd 结合提供了协议不可知性:Guacamole 客户端和 Web 应用程序都不需要知道实际使用的是什么远程桌面协议
Web 应用程序根本不了解任何远程桌面协议。它不包含对 VNC 或 RDP 或由 Guacamole堆栈支持的任何其他协议的支持。它其实只懂Guacamole协议,这是一个用于远程显示渲染和事件传输的协议。虽然具有这些属性的协议自然具有与远程桌面协议相同的能力,但远程桌面协议和 Guacamole 协议背后的设计原则是不同的:Guacamole 协议并非旨在实现特定桌面环境的功能。
作为远程显示和交互协议,Guacamole 实现了现有远程桌面协议的超集。因此,向 Guacamole 添加对特定远程桌面协议(如RDP)的支持涉及编写一个中间层,在远程桌面协议和 Guacamole 协议之间进行“转换”。实现这样的转换与实现任何本地客户端没有什么不同,只是这个特定的实现呈现到远程显示器而不是本地显示器。
处理这种翻译的中间层是 guacd
##guacd
guacd 是 Guacamole 的核心,它动态加载对远程桌面协议(称为“客户端插件”)的支持,并根据从 Web 应用程序收到的指令将它们连接到远程桌面。 guacd 是一个守护进程,它与 Guacamole 一起安装并在后台运行,监听来自 Web 应用程序的 TCP 连接。guacd 也不理解任何特定的远程桌面协议,而是实现了足够的 Guacamole 协议来确定需要加载哪些协议支持以及必须将哪些参数传递给它。加载客户端插件后,它会独立于 guacd 运行,并完全控制自身与 Web 应用程序之间的通信,直到客户端插件终止。 guacd 和所有客户端插件都依赖于一个公共库 libguac,这使得通过 Guacamole
协议进行的通信更容易也更抽象一些。
UltraVNC是一款功能强大、易于使用且免费的远程 PC 访问软件,它可以在您自己的屏幕上显示另一台计算机的屏幕(通过互联网或网络)。该程序允许您使用鼠标和键盘远程控制另一台 PC。这意味着您可以在远程计算机上工作,就好像您坐在它前面一样,就在您当前的位置。
VNC,即远程帧缓冲协议 (RFB),允许通过 Internet 远程查看和控制桌面。VNC 服务器必须运行在共享桌面的计算机上,VNC 客户端必须运行在将访问共享桌面的计算机上
本文编写环境: window11专业版 + VMware + Ubuntu官网最新版镜像虚拟机( 22.04 LTS)
docker安装教程: https://www.runoob.com/docker/ubuntu-docker-install.html
官网使用docker安装: https://guacamole.apache.org/doc/gug/guacamole-docker.html
docker推荐使用官方安装脚本自动安装
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
输入以上命令即可
接下来就可以输入docker的命令进行测试了
安装大体可以分为4部分:
docker pull guacamole/guacd
docker pull guacamole/guacamole
docker pull mysql
通过docker images
命令可以看到目前已经有了3个镜像
docker run --name guacd -d guacamole/guacd
guacd 将在其默认端口 4822 上进行侦听
docker run -itd --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
通过 docker ps 命令查看mysql
和guacd
是否安装成功
由于Guacamole
需要使用数据库来进行身份验证,所以我们还需要对mysql数据写入一个供guacamole
访问的用户和数据库
guacamole
提供一个生成自己所需的sql文件供我们使用,使用以下的命令:
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > initdb.sql
此命令会在你当前的文件夹下生成一个initdb.sql
文件,比如你在桌面目录下执行的,那么你桌面上就会生成这样的一个文件
生成此脚本后,我们需要完成:
guacamole_db
.guacamole_user
.现在我们进入数据库:
//进入mysql容器 docker exec -it mysql /bin/bash //进入数据库 mysql -uroot -p123456 //创建数据库 CREATE DATABASE guacamole_db; //创建供Guacamole访问的用户,并赋予权限 //这里因为我们的mysql是用的容器,如果用@'localhost'到后面我们用宿主机进入后台的时候,是无法访问的,我们应该用'%'这个权限 //如果你的mysql是用的宿主机的mysql,那么可以试试localhost CREATE USER 'guacamole_user'@'%' IDENTIFIED BY 'some_password'; GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'%'; //刷新用户 FLUSH PRIVILEGES; //退出数据库 exit //退出mysql容器内部 exit //执行把宿主机的sql文件copy到mysql容器内部 //docker cp 要拷贝的文件路径 容器名:要拷贝到容器里面对应的路径 //我这里是因为我的文件就在桌面,如果你在其他目录下,填写路径就可以比如: /home/initdb.sql docker cp initdb.sql mysql:/ //这个时候进入mysql容器查看文件 docker exec -it mysql bash //此时应该就有这个sql文件了 ls //执行mysql执行sql脚本 //语法: mysql –u用户名 –p密码 –D数据库 < [sql脚本文件路径全名] mysql -uroot -p123456 -Dguacamole_db<initdb.sql //查看脚本是否执行成功 //进入数据库 mysql -uroot -p123456 //查看数据库 show databases; //进入guacamole_db数据库 use guacamole_db; //查看guacamole_db表 show tables; //如果看到十几个表就证明执行成功了 //退出mysql和容器 exit exit
//请先不要执行,先看参数解析
docker run --name guacamole --restart=always --link guacd:guacd --link mysql:mysql -e MYSQL_DATABASE=guacamole_db -e MYSQL_USER=guacamole_user -e MYSQL_PASSWORD=some_password -d -p 9090:8080 guacamole/guacamole
--link guacd:guacd
使Guacamole容器和guacd容器关联,这个地方冒号前面的guacd是你创建这个容器时指定的name参数--link mysql:mysql
使Guacamole容器和mysql容器管理,这个地方冒号前面的guacd是你创建这个容器时指定的name参数-e MYSQL_DATABASE=guacamole_db
上面初始化数据库时,创建的guacamole_db数据库-e MYSQL_USER=guacamole_user
上面初始化用户时,创建的guacamole_user用户-e MYSQL_PASSWORD=song_password
上面初始化用户密码时,创建的some_password密码-p 9090:8080
绑定宿主机的9090端口如果上面的操作中,创建的用户名、数据库名、密码不同的话请自行更改
其他额外的一些参数,如果严格按照上文的步骤是不需要的:
MYSQL_HOSTNAME
用于 Guacamole 身份验证的数据库的主机名。如果您不使用 Docker 来提供 MySQL 数据库,则这是必需的
MYSQL_PORT
Guacamole 在连接到 MySQL 时应该使用的端口。此环境变量是可选的。如果未提供,将使用标准 MySQL 端口 3306
此时可以执行docker ps
查看三个容器是否都正常执行
如果都启动成功了,那么就可以在宿主机上打开浏览器输入http://localhost:9090/guacamole,来进入后台管理页面,默认账号密码都是guacadmin
重启之后我们需要重启启动容器
docker ps -a
docker start mysql
docker start guacd
docker start guacamole
docker ps -a
如果mysql没有问题,那么我们就需要看看guacamole
容器的报错日志
docker logs 容器id
看看日志里面有没有什么报错,比如无法连接数据库之类的问题
解决:如果在mysql创建用户的时候,权限使用的localhost的话,使用docker-mysql容器连接的话,是代表不了localhost的,需要使用%
##使用DRP方式测试远程连接
云服务器配置:win10服务器版本 非window版本可以试试SSH连接
点击设置
创建一个RDP连接
编辑连接:
参数:
网络:
认证:
直接到最下面保存即可
来到首页,找到我们刚刚新建的连接,双击即可,等待连接成功
云服务器环境:window10服务器版本 非window版本可以试试SSH连接
创建一个VNC连接
编辑连接:
参数:
网络:
认证:
直接到最下面保存即可
这个时候我们只是创建了一个连接,我们还需要在被远程的机器上安装UltraVNC工具,下文有一章会介绍如何安装使用
测试window版本:win10 + 专业版本
创建一个DRP连接
编辑连接:
参数:
网络:
认证:
直接到最下面保存即可
这个地方如果你要访问的机器没有用户名和密码全部空着即可
如果访问不通,请把被访问机器里面的防火墙关掉或者把3389这个端口开通
打开被访问机器里面关于运行被远程控制等选项
win10家庭版不支持远程,所以无法使用DRP和VNC方式
如果有绑定微软账号,那么用户名其实是你微软账号的用户名,密码同理,如果你电脑开通了PIN登录,但是你需要输入的密码还是微软账号的密码
如果我们附加没有第二台机器供我们测试,我们可以测试用虚拟机里面的机器远程连接我们本机,PRD的方式会在连接成功后的几秒显示有冲突而退出PRD,VNC方式可以套娃一直连接
测试window版本:win10 + 专业版本
创建一个VNC连接
编辑连接:
参数:
网络:
认证:
直接到最下面保存即可
这个地方如果你要访问的机器没有用户名和密码全部空着即可
如果访问不通,请把被访问机器里面的防火墙关掉或者把3389这个端口开通
打开被访问机器里面关于运行被远程控制等选项
win10家庭版不支持远程,所以无法使用DRP和VNC方式
如果有绑定微软账号,那么用户名其实是你微软账号的用户名,密码同理,如果你电脑开通了PIN登录,但是你需要输入的密码还是微软账号的密码
如果我们附近没有第二台机器供我们测试,我们可以测试用虚拟机里面的机器远程连接我们本机,PRD的方式会在连接成功后的几秒显示有冲突而退出PRD,VNC方式可以套娃一直连接
官网下载合适的版本即可: https://uvnc.com/
一直next即可
安装之后,他会在本地启一个uvnc_service 服务
我们打开安装 UltraVNC Server 软件,我们底部任务栏会出现他的图标,在他的图标上右边打开Admin properties
更改完之后直接apply-ok即可
https://guacamole.apache.org/api-documentation/
我们集成的话需要上面链接里面的 Java (guacamole-common)
JavaScript (guacamole-common-js)
两个包
Java (guacamole-common)
这个使用maven下载即可
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>1.4.0</version>
</dependency>
JavaScript (guacamole-common-js)
这个包也是有maven,但是我本机下不下来,我看其他人也有这种情况,所以此包在下文中采用下载包的形式
https://search.maven.org/artifact/org.apache.guacamole/guacamole-common-js/1.4.0/pom
选择下载zip包解压,即可
###Servlet项目集成
先新建一个Servlet项目
放入maven Guacamole依赖
<!-- Main Guacamole library -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>1.4.0</version>
</dependency>
TutorialGuacamoleTunnelServlet
类all.js
和all.min.js
这两个选择一个使用即可)index.html
文件TutorialGuacamoleTunnelServlet
文件内容:public class TutorialGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet { @Override protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { //此方法为http的连接方式,如果是websocker方式,请继承相关的类`GuacamoleWebSocketTunnelEndpoint` // Create our configuration GuacamoleConfiguration config = new GuacamoleConfiguration(); config.setProtocol("rdp"); config.setParameter("hostname", "自己测试的远程ip"); config.setParameter("port", "测试连接端口"); config.setParameter("username", "用户名"); config.setParameter("password", "密码"); config.setParameter("ignore-cert", "true"); // Connect to guacd - everything is hard-coded here. GuacamoleSocket socket = new ConfiguredGuacamoleSocket( new InetGuacamoleSocket("运行guacd的机器ip", 4822), config ); // Return a new tunnel which uses the connected socket return new SimpleGuacamoleTunnel(socket); } }
web.xml
文件内容<!-- Basic config --> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <!-- Guacamole Tunnel Servlet --> <servlet> <description>Tunnel servlet.</description> <servlet-name>Tunnel</servlet-name> <servlet-class> <!-- TutorialGuacamoleTunnelServlet类的具体路径,具体到类名 --> com.example.e.类名 </servlet-class> </servlet> <servlet-mapping> <servlet-name>Tunnel</servlet-name> <url-pattern>/tunnel</url-pattern> </servlet-mapping>
index.html
文件内容<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript" src="guacamole-common-js/all.min.js"></script> <!-- Display --> <div id="display"></div> <script type="text/javascript"> /* <![CDATA[ */ // Get display div from document var display = document.getElementById("display"); // Instantiate client, using an HTTP tunnel for communications. var guac = new Guacamole.Client( new Guacamole.HTTPTunnel("tunnel") ); // Add client to display div display.appendChild(guac.getDisplay().getElement()); // Error handler guac.onerror = function (error) { alert(error); }; // Connect guac.connect(); // Disconnect on close window.onunload = function () { guac.disconnect(); } // Mouse var mouse = new Guacamole.Mouse(guac.getDisplay().getElement()); mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = function (mouseState) { guac.sendMouseState(mouseState); }; // Keyboard var keyboard = new Guacamole.Keyboard(document); keyboard.onkeydown = function (keysym) { guac.sendKeyEvent(1, keysym); }; keyboard.onkeyup = function (keysym) { guac.sendKeyEvent(0, keysym); }; /* ]]> */</script> </body> </html>
springboot + vue项目请自行创建
<!-- 远程控制-->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 添加servlet支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
因为api依赖servlet的支持,所以需要额外添加servlet的包,Servlet集成的时候,新建项目自带这个包
TutorialGuacamoleTunnelServlet
类,放入合适的包中即可//配置请求路径 /tunnel @WebServlet(name = "Tunnel", urlPatterns = "/tunnel") public class TutorialGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet { @Override protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { GuacamoleConfiguration config = new GuacamoleConfiguration(); //config.setProtocol("vnc"); //根据header参数来进行参数化 config.setProtocol(request.getHeader("loginType")); config.setParameter("hostname", request.getHeader("hostName")); config.setParameter("port", request.getHeader("port")); config.setParameter("username", request.getHeader("userName")); config.setParameter("password", request.getHeader("password")); config.setParameter("ignore-cert", request.getHeader("ignore")); GuacamoleSocket socket = new ConfiguredGuacamoleSocket( new InetGuacamoleSocket("192.168.150.128", 4822), config ); return new SimpleGuacamoleTunnel(socket); } }
//扫描这个目录下的servlet,这个不需要具体到类名,到包名即可
@ServletComponentScan("com.framework.servlet")
spring security
所以需要对这个url进行解除访问限制//只允许登录用户访问,这个地方需要这个接口认证token,然后认证token,后面会讲
.antMatchers("/tunnel/**").authenticated()
//允许任何人访问
.antMatchers("/tunnel/**").permitAll()
这两个选一个即可
{
path: "/remote-page",
//比如我准备展示远程桌面的页面放入了这个路径下面
component: () => import("@/views/business/tunnel/index"),
},
//配置内容请自行填写
const loginForm = {
hostName: "0.0.0.0"
ignore: "true"
loginType: "rdp"
password: "123"
port: "3389"
userName: "pass"
}
let openUrl = window.location.origin + "/remote-page?";
for (const [key, value] of Object.entries(loginForm)) {
openUrl += "&" + key + "=" + value;
}
window.open(openUrl, "_blank");
<template> <div> <!-- Display --> <div id="display"></div> </div> </template> <script> //如果你后端配置的是需要token认证的,请使用你们项目里面自己带的获取token的方法 import { getToken } from "@/utils/auth"; export default { data() { return { params: {}, }; }, mounted() { this.initUrlParams(); this.initGuacamole(); }, methods: { // 初始化url initUrlParams() { this.params = this.getQueryObject(window.location.href); //添加token认证 this.params["Authorization"] = "Bearer " + getToken(); }, // 初始化视频 initGuacamole() { var display = document.getElementById("display"); var guac = new Guacamole.Client( new Guacamole.HTTPTunnel("tunnel", true, this.params) ); display.appendChild(guac.getDisplay().getElement()); guac.onerror = (error) => { if (error.code === 519) { this.$message.error( "连接失败,请检查连接参数,10秒后自动关闭本页面!" ); setTimeout(() => { window.close(); }, 10000); } else { this.$message.error(error); } }; guac.connect(); window.onunload = function () { guac.disconnect(); }; var mouse = new Guacamole.Mouse(guac.getDisplay().getElement()); mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = function (mouseState) { guac.sendMouseState(mouseState); }; var keyboard = new Guacamole.Keyboard(document); keyboard.onkeydown = function (keysym) { guac.sendKeyEvent(1, keysym); }; keyboard.onkeyup = function (keysym) { guac.sendKeyEvent(0, keysym); }; }, //解析url参数 getQueryObject(url){ url = url == null ? window.location.href : url const search = url.substring(url.lastIndexOf('?') + 1) const obj = {} const reg = /([^?&=]+)=([^?&=]*)/g search.replace(reg, (rs, $1, $2) => { const name = decodeURIComponent($1) let val = decodeURIComponent($2) val = String(val) obj[name] = val return rs }) return obj } }, }; </script> <style lang="scss" scoped></style>
guacamole-common-js
原包使用import导入的话会提示找不到Guacamole对象,所以我更改了下源码,建议大家先不改,先试试好不好报错,再来决定需要不需要改
这里我使用的是all.js
把里面的第一行代码
var Guacamole = Guacamole || {};
=>
window.Guacamole = Guacamole || {};
在main.js
里面引入
//请自行更改路径
import "@/utils/guacamole-common-js/all.js";
如果你不想更改源码并且也报错的话,那么可以直接放在public目录下,然后在index.html里面引入
<script src="<%= BASE_URL %>all.js"></script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。