上一篇幅是介绍guacamole的安装,接下来说说使用
项目需求,需要在页面中集成远程桌面,要去掉基础认证。整体的方案有两种,都在这里说一下吧。
一、不需要guacamole客户端,在自己项目中实现socket通道。与页面进行连接(建议使用第二种)
1、环境准备
启动guacd服务
service guacd start
2、在自己的java项目中引入guacamole-common的包,版本与自己的guacd版本一致。
org.apache.guacamole
guacamole-common
1.0.0
3、自定义servlet实现GuacamoleHTTPTunnelServlet,官方截图如下
引入jar,之后创建MyGuacamoleHTTPTunnelServlet
package com.xx.guacalome;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
import org.springframework.beans.factory.annotation.Value;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
/**
* @description:
* @Author: huangsan
* @Date: 2020/5/27 11:01 上午
*/
@WebServlet(urlPatterns = "/tunnel")
public class MyGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
@Value("${guacamole.guacd.host}")
private String guacdHost;
@Value("${guacamole.guacd.port}")
private String guacdPort;
@Value("${guacamole.target.protocol}")
private String targetProtocol;
@Value("${guacamole.target.host}")
private String targetHost;
@Value("${guacamole.target.port}")
private String targetPort;
@Value("${guacamole.target.username}")
private String targetUsername;
@Value("${guacamole.target.password}")
private String targetPassword;
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest httpServletRequest) throws GuacamoleException {
System.out.println("-----------远程桌面调用成功");
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(targetProtocol);
config.setParameter("hostname", targetHost);
config.setParameter("port", targetPort);
config.setParameter("username", targetUsername);
config.setParameter("password", targetPassword);
return new SimpleGuacamoleTunnel(
new ConfiguredGuacamoleSocket(new InetGuacamoleSocket(this.guacdHost, Integer.parseInt(this.guacdPort)), config));
}
}
在yml配置文件中配置我们需要的value
guacamole:
guacd:
host: 10.0.30.50
port: 4822
target:
protocol: rdp
host: 10.0.30.224
port: 3389
username: admin
password: 123456
启动类上配置@ServletComponentScan注解扫描servlet
启动项目即可,我们后台的通道就搭建完成
4、配置guacamole-common-js
创建前端html页面,在页面中定义展示区域的div。
all.min.js获取的方法,在maven中依赖
org.apache.guacamole
guacamole-common-js
1.0.0
然后去maven仓库中找到这个包,解压之后就可以找到相关jar。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Guacamole show
guacamole client支持httptunnel或者websockettunnel 这里我们就配置一个httptunnel。
通道地址指向我们项目中暴露的servlet地址即可。
以上内容在guacamole的API中说的很详细,在页面中还支持展示更多内容,具体请参考html与js如下:
http://guacamole.apache.org/doc/gug/guacamole-common-js.html
http://guacamole.apache.org/doc/1.0.0/guacamole-common-js/
二、依赖于guacamole的客户端和服务端来搞事情(以上的方法,需要在自己的项目中去引入其他的依赖,还不如利用其客户端当为服务一样使用)
1、环境准备
需要启动guacd的客户端和服务端,一个tomcat一个是service guacd start,就不赘述了。
2、与上面前端服务一样,创建html,指定展示区域div
3、编写js,如下(到这里就直接可以使用了,参数按照我这个来拼接就可以了,456节就是对这些参数的分析,如果是直接用的话就不用看下去了,当然想知道是什么的可以继续看下)
guac.onerror = function (error) {
alert(error);
};
$.ajax({
type: "POST",
async: false,
url: "http://10.0.30.50:8888/g/api/tokens",
data: "username=hhb&password=hhb123",
}).done(function (result) {
guac.connect("token=" + result.authToken + '&GUAC_DATA_SOURCE=default&GUAC_ID=test1&GUAC_TYPE=c&GUAC_WIDTH=900');
}).fail(function (result) {
console.error("token error: ", result.status, result.statusText)
});
window.onunload = function () {
guac.disconnect();
}
// Mouse 鼠标事件
let mouse = new Guacamole.Mouse(guac.getDisplay().getElement());
mouse.onmousedown =
mouse.onmouseup =
mouse.onmousemove = function(mouseState) {
guac.sendMouseState(mouseState);
};
// Keyboard 键盘事件
let keyboard = new Guacamole.Keyboard(document);
keyboard.onkeydown = function (keysym) {
guac.sendKeyEvent(1, keysym);
};
keyboard.onkeyup = function (keysym) {
guac.sendKeyEvent(0, keysym);
};
4、讲解一下js内容,首先要解决认证的问题
官网的描述是可以在connect中传递任意数据,那么我们将获取的的token等信息就可以在connect的时候进行传递。
那就先来获取token,我们看客户端登陆是如何获取token的,之后用ajax来模拟一个
ajax代码如上所示。
token拿到之后我们要封装到connect中去,这里我在api中是没有看到任何说明指出connect怎么来拼接,没办法了反正是开源的,down一下代码来读一读吧
下载github上代码
拿到代码就好办了我们来看一下。
5、在源码中找到我们想要的
---我们如果自己创建的话是创建一个tunnel的servlet,所以我们全局搜索“/tunnel”。
---在TunnelModule类中我么看到了configureServlets方法,从这里我们看出有两种方式一种http一种websocket
@Override
protected void configureServlets() {
bind(TunnelRequestService.class);
// Set up HTTP tunnel
serve("/tunnel").with(RestrictedGuacamoleHTTPTunnelServlet.class);
// Try to load each WebSocket tunnel in sequence
for (String classname : WEBSOCKET_MODULES) {
if (loadWebSocketModule(classname)) {
logger.debug("WebSocket module loaded: {}", classname);
return;
}
}
// Warn of lack of WebSocket
logger.info("WebSocket support NOT present. Only HTTP will be used.");
}
---进入http的创建中我们看到
@Singleton
public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
// Attempt to create HTTP tunnel
GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
// If successful, warn of lack of WebSocket
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
return tunnel;
}
}
发现了一个和我们之前的实现思路一样的继承GuacamoleHTTPTunnelServlet
---我们看到对HTTPTunnelRequest进行了封装参数是我么的HttpServletRequest
---点进去发现其实就是把我们的httpservlet的参数拿出来而已
public HTTPTunnelRequest(HttpServletRequest request) {
// For each parameter
for (Map.Entry mapEntry : ((Map)
request.getParameterMap()).entrySet()) {
// Get parameter name and corresponding values
String parameterName = mapEntry.getKey();
List parameterValues = Arrays.asList(mapEntry.getValue());
// Store copy of all values in our own map
parameterMap.put(
parameterName,
new ArrayList(parameterValues)
);
}
}
---看来我们只要把参数给http请求就可以了,那我们再来看看都需要哪些参数点到tunnelRequestService.createTunnel方法中
public GuacamoleTunnel createTunnel(TunnelRequest request)
throws GuacamoleException {
// Parse request parameters
String authToken = request.getAuthenticationToken();
String id = request.getIdentifier();
TunnelRequest.Type type = request.getType();
String authProviderIdentifier = request.getAuthenticationProviderIdentifier();
GuacamoleClientInformation info = getClientInformation(request);
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
UserContext userContext = session.getUserContext(authProviderIdentifier);
try {
// Create connected tunnel using provided connection ID and client information
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
// Notify listeners to allow connection to be vetoed
fireTunnelConnectEvent(session.getAuthenticatedUser(),
session.getAuthenticatedUser().getCredentials(), tunnel);
// Associate tunnel with session
return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id);
}
// Ensure any associated session is invalidated if unauthorized
catch (GuacamoleUnauthorizedException e) {
// If there is an associated auth token, invalidate it
if (authenticationService.destroyGuacamoleSession(authToken))
logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
// Continue with exception processing
throw e;
}
}
---重点来看
String authToken = request.getAuthenticationToken(); String id = request.getIdentifier(); TunnelRequest.Type type = request.getType(); String authProviderIdentifier = request.getAuthenticationProviderIdentifier(); GuacamoleClientInformation info = getClientInformation(request);
首先前四个request的方法我们可以点进去看看,无非就是获取参数,但是id\type\authProviderIdentifier这三个参数都是必须的
public String getAuthenticationProviderIdentifier()
throws GuacamoleException {
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
}
---既然知道了参数的key,那我们就可以在前端来模拟了,具体都有哪些参数可以参考
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel;
import java.util.List;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleException;
/**
* A request object which provides only the functions absolutely required to
* retrieve and connect to a tunnel.
*/
public abstract class TunnelRequest {
/**
* The name of the request parameter containing the user's authentication
* token.
*/
public static final String AUTH_TOKEN_PARAMETER = "token";
/**
* The name of the parameter containing the identifier of the
* AuthenticationProvider associated with the UserContext containing the
* object to which a tunnel is being requested.
*/
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
/**
* The name of the parameter specifying the type of object to which a
* tunnel is being requested. Currently, this may be "c" for a Guacamole
* connection, or "g" for a Guacamole connection group.
*/
public static final String TYPE_PARAMETER = "GUAC_TYPE";
/**
* The name of the parameter containing the unique identifier of the object
* to which a tunnel is being requested.
*/
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
/**
* The name of the parameter containing the desired display width, in
* pixels.
*/
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
/**
* The name of the parameter containing the desired display height, in
* pixels.
*/
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
/**
* The name of the parameter containing the desired display resolution, in
* DPI.
*/
public static final String DPI_PARAMETER = "GUAC_DPI";
/**
* The name of the parameter specifying one supported audio mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
/**
* The name of the parameter specifying one supported video mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
/**
* The name of the parameter specifying one supported image mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
/**
* All supported object types that can be used as the destination of a
* tunnel.
*/
public static enum Type {
/**
* A Guacamole connection.
*/
CONNECTION("c"),
/**
* A Guacamole connection group.
*/
CONNECTION_GROUP("g");
/**
* The parameter value which denotes a destination object of this type.
*/
final String PARAMETER_VALUE;
/**
* Defines a Type having the given corresponding parameter value.
*
* @param value
* The parameter value which denotes a destination object of this
* type.
*/
Type(String value) {
PARAMETER_VALUE = value;
}
};
//下面没用的都删掉了
}
那我们来分析一下我们需要传的参数
"token=" + result.authToken + '&GUAC_DATA_SOURCE=default&GUAC_ID=test1&GUAC_TYPE=c&GUAC_WIDTH=900'
---token:不用说,为什么不是必须的参数,参考api的快速连接部分。
---GUAC_DATA_SOURCE:在获取token的时候就传递回来了,我这里是default
{"authToken":"x","username":"hhb","dataSource":"default","availableDataSources":["quickconnect","default"]}
---GUAC_ID:就是我们user-mapping中的connect的name
---GUAC_TYPE:看源码发现是个枚举类型,这里我们没引入组的概念(1.1.0版本有),这里直接传c
public static enum Type {
/**
* A Guacamole connection.
*/
CONNECTION("c"),
/**
* A Guacamole connection group.
*/
CONNECTION_GROUP("g");
/**
* The parameter value which denotes a destination object of this type.
*/
final String PARAMETER_VALUE;
/**
* Defines a Type having the given corresponding parameter value.
*
* @param value
* The parameter value which denotes a destination object of this
* type.
*/
Type(String value) {
PARAMETER_VALUE = value;
}
};
---GUAC_WIDTH:这属于随便的参数了,看个人需要我就指定了个宽度,你们可以自由继续拼装。
5、我们知道了connect中需要传递的一些参数,那就直接拼接出来即可,直接调用创建隧道。
6、总结:这种方法好处就是不用自己在项目中引入guacamole的内容了,不好的地方是我们需要解决跨域问题,虽然guacamole是个tomcat,但是我无论怎么配置跨域都没解决,没办法最后使用nginx来解决了。
以上就是web集成guacamole的两种方式了,其实思路就是一种,创建隧道与guacamole-common-js连接就可以了,有什么问题欢迎大家指出。