javaweb集成guacamole在html页面中展示

上一篇幅是介绍guacamole的安装,接下来说说使用

项目需求,需要在页面中集成远程桌面,要去掉基础认证。整体的方案有两种,都在这里说一下吧。

一、不需要guacamole客户端,在自己项目中实现socket通道。与页面进行连接(建议使用第二种)

1、环境准备

启动guacd服务

service guacd start

2、在自己的java项目中引入guacamole-common的包,版本与自己的guacd版本一致。

        
            org.apache.guacamole
            guacamole-common
            1.0.0
        

3、自定义servlet实现GuacamoleHTTPTunnelServlet,官方截图如下

javaweb集成guacamole在html页面中展示_第1张图片

引入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来模拟一个

javaweb集成guacamole在html页面中展示_第2张图片

ajax代码如上所示。

token拿到之后我们要封装到connect中去,这里我在api中是没有看到任何说明指出connect怎么来拼接,没办法了反正是开源的,down一下代码来读一读吧

下载github上代码

javaweb集成guacamole在html页面中展示_第3张图片

拿到代码就好办了我们来看一下。

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连接就可以了,有什么问题欢迎大家指出。

你可能感兴趣的:(guacamole,java,spring,boot)