将 guacamole嵌入到自己的应用当中
部署guacamole
选用了docker部署,需要部署三个应用,mysql服务, guacd服务 guacamole服务。
以下环境为centos7.6操作
关闭防火墙或者开启必要接口
firewall-cmd --zone=public --add-port=9090/tcp --permanent
firewall-cmd --zone=public --add-port=4822/tcp --permanent
firewall-cmd --zone=public --add-port=3306/tcp --permanent
firewall-cmd --reload
以是我部署时用到的,可以自行删除非必要的端口
或者直接关闭防火墙
部署docker
yum -y update
yum remove docker docker-common docker-selinux docker-engine
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum -y install docker-ce-18.03.1.ce
systemctl start docker
systemctl enable docker
部署mysql服务
我自己执行的命令如下(不是必须如此做),先映射了两个目录mariadb/conf mariadb/data
设定了密码,执行了如下操作。可以通过局域网ip访问到了。相关的文档都传到csdn的共享里了
docker run \
--name mariadb \
-d \
-p 3306:3306 \
-v /opt/data/mariadb/conf/my.cnf:/etc/mysql/my.cnf \
-v /opt/data/mariadb/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD='123456' \
--restart=always \
mariadb:10.4.10
还可以简单执行一个docker命令来部署mysql服务,无论怎样,保证可以访问到,可以登录进去。
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql
获取一下镜像
docker pull guacamole/guacamole
docker pull guacamole/guacd
执行如下命令
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > initdb.sql
会在执行命令目录下生成一个sql文件。我通过navicat连接上了mariadb,然后导入了这个文件,创建的数据库名字为
guacamole,编码是utf8 utf8_general_ci
部署guard
docker run --name guacd -d -p 4822:4822 --privileged=true guacamole/guacd
部署guacamole
docker run --name guacamole \
--link guacd \
-e MYSQL_DATABASE=guacamole \
-e MYSQL_USER=root \
-e MYSQL_HOSTNAME=192.168.1.61 \
-e MYSQL_PASSWORD='123456' \
--privileged=true \
-d -p 9090:8080 guacamole/guacamole
三个部署完,就可以通过映射出来的9090访问界面了
http://ip:9090/guacamole 可以看到页面
默认用户名密码 guacadmin guacadmin
首先需要测试一下配置一个windows试试 看看能不能远程,windows用的rdp配置如下,点击设置-》连接-》新建连接
可以修改下登录里的标题,需要修改docker里的文件
/home/guacamole/tomcat/webapps/guacamole/translations/zh.json
还可以修改css等。
如果是用docker cp的方式修改的文件需要注意权限,否则不生效。
需要docker exec -it -u root guacamole bash 进入容器
进入/home/guacamole/tomcat/webapps/guacamole/translations
执行chouwn guacamole:guacamole zh.json
然后docker restart guacamole 才能生效
接下来就是通过api获取token,只要有了token就可以访问界面上的network里的所有连接后边跟上?token=xxxxxxxx
获取token的java代码其他的一样 就是发送一个http请求
// Guacamole API 认证信息
private static final String GUAC_USERNAME = "guacadmin";
private static final String GUAC_PASSWORD = "guacadmin";
// Guacamole 服务器信息
private static final String GUAC_HOST = "xx.xx.xx.xx";
private static final String GUAC_PORT = "9090";
// Guacamole API 基础 URL
private static final String GUAC_API_BASE_URL = "http://" + GUAC_HOST + ":" + GUAC_PORT + "/guacamole/api";
public String getToken() throws IOException {
String authURL = GUAC_API_BASE_URL + "/tokens";
URL url = new URL(authURL);
String authString = GUAC_USERNAME + ":" + GUAC_PASSWORD;
String encodedAuthString = Base64.getEncoder().encodeToString(authString.getBytes());
String authData = "username=" + GUAC_USERNAME + "&password=" + GUAC_PASSWORD;
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Basic " + encodedAuthString);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.getOutputStream().write(authData.getBytes());
int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new RuntimeException("Failed : HTTP error code : " + responseCode);
}
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder responseBuilder = new StringBuilder();
String output;
while ((output = br.readLine()) != null) {
responseBuilder.append(output);
}
conn.disconnect();
JsonObject responseJson = JsonParser.parseString(responseBuilder.toString()).getAsJsonObject();
return responseJson.get("authToken").getAsString();
}
这样就有token了,也可以用postman请求出来这个token
增加连接
String token = getToken();
String apiAdd = GUAC_API_BASE_URL + “/session/data/mysql/connections?token=” + token;
然后发送post请求,json格式的参数就可以增加成功
json 的格式,可以通过界面里设置保存的时候看到,调试开发工具里的network可以,拷贝一个示例,只需要传入额时候改下name, hostname, password,username就可以了
{
"name": "123",
"identifier": "6",
"parentIdentifier": "ROOT",
"protocol": "ssh",
"attributes": {
"guacd-encryption": "",
"failover-only": "",
"weight": "",
"max-connections": "12",
"guacd-hostname": null,
"guacd-port": "",
"max-connections-per-user": "1"
},
"activeConnections": 0,
"lastActive": 1677653601000,
"parameters": {
"hostname": "xx.xx.xx.xx",
"password": "123456",
"port": "22",
"username": "root",
"color-scheme": "",
"font-size": "",
"scrollback": "",
"read-only": "",
"disable-copy": "",
"disable-paste": "",
"timezone": null,
"server-alive-interval": "",
"backspace": "",
"terminal-type": "",
"create-typescript-path": "",
"recording-exclude-output": "",
"recording-exclude-mouse": "",
"recording-include-keys": "",
"create-recording-path": "",
"enable-sftp": "",
"sftp-disable-download": "",
"sftp-disable-upload": "",
"wol-send-packet": "",
"wol-udp-port": "",
"wol-wait-time": ""
}
}
再来一个windows的示例吧,改name, hostname, password username就可以了,用的rdp协议
{
"name": "0637FGCZ49J13ARJ",
"identifier": "29",
"parentIdentifier": "ROOT",
"protocol": "rdp",
"attributes": {
"guacd-encryption": "",
"failover-only": "",
"weight": "",
"max-connections": "10",
"guacd-hostname": null,
"guacd-port": "",
"max-connections-per-user": "5"
},
"activeConnections": 0,
"lastActive": 1677668267000,
"parameters": {
"hostname": "xx.xx.xx.xx",
"password": "123456",
"security": "nla",
"ignore-cert": "true",
"port": "3389",
"username": "admin",
"disable-auth": "",
"gateway-port": "",
"server-layout": "",
"timezone": null,
"enable-touch": "",
"console": "",
"width": "",
"height": "",
"dpi": "",
"color-depth": "",
"force-lossless": "",
"resize-method": "",
"read-only": "",
"normalize-clipboard": "",
"disable-copy": "",
"disable-paste": "",
"console-audio": "",
"disable-audio": "",
"enable-audio-input": "",
"enable-printing": "",
"enable-drive": "",
"disable-download": "",
"disable-upload": "",
"create-drive-path": "",
"enable-wallpaper": "",
"enable-theming": "",
"enable-font-smoothing": "",
"enable-full-window-drag": "",
"enable-desktop-composition": "",
"enable-menu-animations": "",
"disable-bitmap-caching": "",
"disable-offscreen-caching": "",
"disable-glyph-caching": "",
"preconnection-id": "",
"recording-exclude-output": "",
"recording-exclude-mouse": "",
"recording-exclude-touch": "",
"recording-include-keys": "",
"create-recording-path": "",
"enable-sftp": "",
"sftp-port": "",
"sftp-server-alive-interval": "",
"sftp-disable-download": "",
"sftp-disable-upload": "",
"wol-send-packet": "",
"wol-udp-port": "",
"wol-wait-time": ""
}
}
查询identifier
根据名字获取identifier。这个很简单,循环里可以写自己的路基主要就是看那个路径
= GUAC_API_BASE_URL + “/session/data/mysql/connectionGroups/ROOT/tree?token=” + tok
public String getConnections(String name) throws IOException {
String token = getToken();
String res = "none";
String queryURL = GUAC_API_BASE_URL + "/session/data/mysql/connectionGroups/ROOT/tree?token=" + token;
String json = HttpUtils.getApi(queryURL);
Gson gson = new Gson();
GuacamoleDTO guacamoleDTO = gson.fromJson(json, GuacamoleDTO.class);
List<ChildConnections> list = guacamoleDTO.getChildConnections();
for (ChildConnections childConnections : list) {
if (name.equals(childConnections.getName())) {
res = childConnections.getIdentifier();
break;
}
}
return res;
}
删除连接
String token = getToken();
String deleteURL = GUAC_API_BASE_URL + "/session/data/mysql/connections/" + identify + "?token=" + token
// 以上url中的关键是identify 这个是个标识,这个就是查询获得的
http的method是DELETE
附上java的代码示例吧
public static void deleteQuery(String url) {
String result = null;
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpDelete httpDelete = new HttpDelete(url);
httpDelete.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpclient.execute(httpDelete);
try {
// 获取响应实体
HttpEntity entity = response.getEntity();
// 打印响应状态
System.out.println(response.getStatusLine());
if (entity != null) {
result = EntityUtils.toString(entity, "UTF-8");
}
} finally {
response.close();
}
} catch (Exception e) {
System.out.println("executing httpDelete error: " + e.getMessage());
} finally {
try {
httpclient.close();
} catch (IOException e) {
System.out.println("executing httpDelete error: " + e.getMessage());
}
}
}
public static String postWithJson(String url, String json) {
String result = null;
// 创建默认的httpClient实例
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
// 创建httppost
HttpPost httppost = new HttpPost(url);
httppost.addHeader("Content-type", "application/json; charset=utf-8");
System.out.println("executing request " + httppost.getURI());
// 向POST请求中添加消息实体
StringEntity se = new StringEntity(json, "UTF-8");
httppost.setEntity(se);
System.out.println("request parameters " + json);
// 执行post请求
CloseableHttpResponse response = httpclient.execute(httppost);
try {
// 获取响应实体
HttpEntity entity = response.getEntity();
// 打印响应状态
System.out.println(response.getStatusLine());
if (entity != null) {
result = EntityUtils.toString(entity, "UTF-8");
// 打印响应内容
System.out.println("--------------------------------------");
System.out.println("Response content: " + EntityUtils.toString(entity, "UTF-8"));
System.out.println("--------------------------------------");
}
} finally {
response.close();
}
} catch (Exception e) {
System.out.println("executing httpPostWithJSON error: " + e.getMessage());
} finally {
// 关闭连接,释放资源
try {
httpclient.close();
} catch (IOException e) {
System.out.println("executing httpPostWithJSON error: " + e.getMessage());
}
}
return result;
}
前台怎么嵌入呢
后台通过http获取到的就是identifier,这个打印出来就是个数字。但是从首页上去点击链接访问,发现url是client/MjYAYwBteXNxbA client后有一个编码后的字符串。这个是前端的一个操作。通过查看代码guacamole-client的源码,总结出来下边js的代码
// 其中12 表示identifier 就是链接里的那个
// c表示的是connections,这个是固定的不用改,查看源码可以看到
// mysql 表示数据源, 这个也是固定的 还有的会用到mysql-shard 通过查看network可以看到这些
window.btoa(['12', 'c', 'mysql'].join('\0')).replace(/[+/=]/g,
(str) => ({
'+': '-',
'/': '_',
'=': ''
})[str])
以上转换完就是那串加密后的字符了 然后拼接上/client/ 就可以给前台嵌入到iframe里了