| <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Chapter 25. Custom authentication</title><link rel="stylesheet" type="text/css" href="gug.css" /><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot" /><link rel="home" href="index.html" title="Guacamole Manual" /><link rel="up" href="developers-guide.html" title="Part II. Developer's Guide" /><link rel="prev" href="custom-protocols.html" title="Chapter 24. Adding new protocols" /><link rel="next" href="event-listeners.html" title="Chapter 26. Event listeners" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/> |
| </head><body> |
| <!-- CONTENT --> |
| |
| <div id="page"><div id="content"> |
| <div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Chapter 25. Custom authentication</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><th width="60%" align="center">Part II. Developer's Guide</th><td width="20%" align="right"> <a accesskey="n" href="event-listeners.html">Next</a></td></tr></table><hr /></div><div xml:lang="en" class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><a id="custom-auth"></a>Chapter 25. Custom authentication</h2></div></div></div><div class="toc"><p><strong>Table of Contents</strong></p><dl class="toc"><dt><span class="section"><a href="custom-auth.html#custom-auth-model">Guacamole's authentication model</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-skeleton">A Guacamole extension skeleton</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-building">Building the extension</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-config">Configuration and authentication</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-more-config">Parsing the configuration</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-installing">Installing the extension</a></span></dt></dl></div><a id="idm46227492881392" class="indexterm"></a><p>Guacamole's authentication layer is designed to be extendable such that users can |
| integrate Guacamole into existing authentication systems without having to resort to writing |
| their own web application around the Guacamole API.</p><p>The web application comes with a default authentication mechanism which uses an XML file |
| to associate users with connections. Extensions for Guacamole that provide LDAP-based |
| authentication or database-based authentication have also been developed.</p><p>To demonstrate the principles involved, we will implement a very simple authentication |
| extension which associates a single user/password pair with a single connection, with all |
| this information saved in properties inside the <code class="filename">guacamole.properties</code> |
| file.</p><p>In general, all other authentication extensions for Guacamole will use the principles |
| demonstrated here. This tutorial demonstrates the simplest way to create an authentication |
| extension for Guacamole - an authentication extension that does not support management of |
| users and connections via the web interface.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-model"></a>Guacamole's authentication model</h2></div></div></div><p>When you view any page in Guacamole, whether that be the login screen or the client |
| interface, the page makes an authentication attempt with the web application, sending |
| all available credentials. After entering your username and password, the exact same |
| process occurs, except the web application receives the username and password as |
| well.</p><p>The web application handles this authentication attempt by collecting all credentials |
| available and passing them to designated classes called "authentication providers". |
| Given the set of credentials, authentication providers return a context object that |
| provides restricted access to other users and connections, if any.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-skeleton"></a>A Guacamole extension skeleton</h2></div></div></div><p>For simplicity's sake, and because this is how things are done upstream in the |
| Guacamole project, we will use Maven to build our extension.</p><p>The bare minimum required for a Guacamole authentication extension is a |
| <code class="filename">pom.xml</code> file listing guacamole-ext as a dependency, a single |
| .java file implementing our stub of an authentication provider, and a |
| <code class="filename">guac-manifest.json</code> file describing the extension and pointing |
| to our authentication provider class.</p><p>In our stub, we won't actually do any authentication yet; we'll just universally |
| reject all authentication attempts by returning <code class="varname">null</code> for any |
| credentials given. You can verify that this is what happens by checking the server |
| logs.</p><div class="example"><a id="idm46227492660160"></a><p class="title"><strong>Example 25.1. Barebones <code class="filename">pom.xml</code> required for a simple authentication |
| extension.</strong></p><div class="example-contents"><pre class="programlisting"><project xmlns="http://maven.apache.org/POM/4.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 |
| http://maven.apache.org/maven-v4_0_0.xsd"> |
| |
| <modelVersion>4.0.0</modelVersion> |
| <groupId>org.apache.guacamole</groupId> |
| <artifactId>guacamole-auth-tutorial</artifactId> |
| <packaging>jar</packaging> |
| <version>1.3.0</version> |
| <name>guacamole-auth-tutorial</name> |
| |
| <properties> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| </properties> |
| |
| <build> |
| <plugins> |
| |
| <!-- Written for 1.6 --> |
| <plugin> |
| <groupId>org.apache.maven.plugins</groupId> |
| <artifactId>maven-compiler-plugin</artifactId> |
| <version>3.3</version> |
| <configuration> |
| <source>1.6</source> |
| <target>1.6</target> |
| </configuration> |
| </plugin> |
| |
| </plugins> |
| </build> |
| |
| <dependencies> |
| |
| <!-- Guacamole Extension API --> |
| <dependency> |
| <groupId>org.apache.guacamole</groupId> |
| <artifactId>guacamole-ext</artifactId> |
| <version>1.3.0</version> |
| <scope>provided</scope> |
| </dependency> |
| |
| </dependencies> |
| |
| </project></pre></div></div><br class="example-break" /><p>We won't need to update this <code class="filename">pom.xml</code> throughout the rest of the |
| tutorial. Even after adding new files, Maven will just find them and compile as |
| necessary.</p><p>Naturally, we need the actual authentication extension skeleton code. While you can |
| put this in whatever file and package you want, for the sake of this tutorial, we will |
| assume you are using |
| <code class="classname">org.apache.guacamole.auth.TutorialAuthenticationProvider</code>.</p><div class="example"><a id="idm46227492879152"></a><p class="title"><strong>Example 25.2. A skeleton <code class="classname">TutorialAuthenticationProvider</code></strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth; |
| |
| import java.util.Map; |
| import org.apache.guacamole.GuacamoleException; |
| import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider; |
| import org.apache.guacamole.net.auth.Credentials; |
| import org.apache.guacamole.protocol.GuacamoleConfiguration; |
| |
| /** |
| * Authentication provider implementation intended to demonstrate basic use |
| * of Guacamole's extension API. The credentials and connection information for |
| * a single user are stored directly in guacamole.properties. |
| */ |
| public class TutorialAuthenticationProvider extends SimpleAuthenticationProvider { |
| |
| @Override |
| public String getIdentifier() { |
| return "tutorial"; |
| } |
| |
| @Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Do nothing ... yet |
| return null; |
| |
| } |
| |
| }</pre></div></div><br class="example-break" /><p>To conform with Maven, this skeleton file must be placed within |
| <code class="filename">src/main/java/org/apache/guacamole/auth</code> as |
| <code class="filename">TutorialAuthenticationProvider.java</code>.</p><p>Notice how simple the authentication provider is. The |
| <code class="classname">SimpleAuthenticationProvider</code> base class simplifies the |
| <code class="classname">AuthenticationProvider</code> interface, requiring nothing more than |
| a unique identifier (we will use "tutorial") and a single |
| <code class="methodname">getAuthorizedConfigurations()</code> implementation, which must |
| return a <code class="classname">Map</code> of <code class="classname">GuacamoleConfiguration</code> |
| each associated with some arbitrary unique ID. This unique ID will be presented to the |
| user in the connection list after they log in.</p><p>For now, <code class="methodname">getAuthorizedConfigurations()</code> will just return |
| <code class="varname">null</code>. This will cause Guacamole to report an invalid login for |
| every attempt. Note that there is a difference in semantics between returning an empty |
| map and returning <code class="varname">null</code>, as the former indicates the credentials are |
| authorized but simply have no associated configurations, while the latter indicates the |
| credentials are not authorized at all.</p><p>The only remaining piece for the overall skeleton to be complete is a |
| <code class="filename">guac-manifest.json</code> file. <span class="emphasis"><em>This file is absolutely |
| required for all Guacamole extensions.</em></span> The |
| <code class="filename">guac-manifest.json</code> format is described in more detail in <a class="xref" href="guacamole-ext.html" title="Chapter 23. guacamole-ext">Chapter 23, <em>guacamole-ext</em></a>. It provides |
| for quite a few properties, but for our authentication extension we are mainly |
| interested in the Guacamole version sanity check (to make sure an extension built for |
| the API of Guacamole version X is not accidentally used against version Y) and telling |
| Guacamole where to find our authentication provider class.</p><p>The Guacamole extension format requires that <code class="filename">guac-manifest.json</code> |
| be placed in the root directory of the extension <code class="filename">.jar</code> file. To |
| accomplish this with Maven, we place it within the |
| <code class="filename">src/main/resources</code> directory. Maven will automatically pick it |
| up during the build and include it within the <code class="filename">.jar</code>.</p><div class="example"><a id="idm46227495767088"></a><p class="title"><strong>Example 25.3. The required <code class="filename">guac-manifest.json</code></strong></p><div class="example-contents"><pre class="programlisting">{ |
| |
| "guacamoleVersion" : "1.3.0", |
| |
| "name" : "Tutorial Authentication Extension", |
| "namespace" : "guac-auth-tutorial", |
| |
| "authProviders" : [ |
| "org.apache.guacamole.auth.TutorialAuthenticationProvider" |
| ] |
| |
| }</pre></div></div><br class="example-break" /></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-building"></a>Building the extension</h2></div></div></div><p>Once all three of the above files are in place, the extension will build, and can even |
| be installed within Guacamole (see <a class="xref" href="custom-auth.html#custom-auth-installing" title="Installing the extension">the section called “Installing the extension”</a> at the end of this chapter), even though it is |
| just a skeleton at this point. It won't do anything yet other than reject all |
| authentication attempts, but it's good to at least try building the extension to make |
| sure nothing is missing and that all steps have been followed correctly so far:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> mvn package |
| <code class="computeroutput">[INFO] Scanning for projects... |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Building guacamole-auth-tutorial 1.3.0 |
| [INFO] ------------------------------------------------------------------------ |
| ... |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] BUILD SUCCESS |
| [INFO] ------------------------------------------------------------------------ |
| [INFO] Total time: 2.345 s |
| [INFO] Finished at: 2015-12-16T13:39:00-08:00 |
| [INFO] Final Memory: 14M/138M |
| [INFO] ------------------------------------------------------------------------</code> |
| <code class="prompt">$</code></pre></div><p>Assuming you see the "<code class="computeroutput">BUILD SUCCESS</code>" message when you |
| build the extension, there will be a new file, |
| <code class="filename">target/guacamole-auth-tutorial-1.3.0.jar</code>, which can be |
| installed within Guacamole and tested. If you changed the name or version of the project |
| in the <code class="filename">pom.xml</code> file, the name of this new <code class="filename">.jar</code> |
| file will be different, but it can still be found within |
| <code class="filename">target/</code>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-config"></a>Configuration and authentication</h2></div></div></div><p>Once we receive credentials, we need to validate those credentials against the |
| associated properties in <code class="filename">guacamole.properties</code> (our source of |
| authentication information for the sake of this tutorial).</p><p>We will define four properties:</p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="property">tutorial-user</span></span></dt><dd><p>The name of the only user we accept.</p></dd><dt><span class="term"><span class="property">tutorial-password</span></span></dt><dd><p>The password we require for the user specified to be |
| authenticated.</p></dd><dt><span class="term"><span class="property">tutorial-protocol</span></span></dt><dd><p>The protocol of the configuration this user is authorized to use, |
| which will be sent to guacd when the user logs in and selects their |
| connection.</p></dd><dt><span class="term"><span class="property">tutorial-parameters</span></span></dt><dd><p>A comma-delimited list of |
| <code class="code"><em class="replaceable"><code>name</code></em>=<em class="replaceable"><code>value</code></em></code> |
| pairs. For the sake of simplicity, we'll assume there will never be any |
| commas in the values.</p></dd></dl></div><p>If the username and password match what is stored in the file, we read the |
| configuration information, store it in a <code class="classname">GuacamoleConfiguration</code>, |
| and return the configuration within a set, telling Guacamole that this user is |
| authorized but only to access the configurations returned.</p><p>Upstream, we always place the properties of authentication providers in their own |
| class, and so we will also do that here in this tutorial, as it keeps things |
| organized.</p><div class="example"><a id="idm46227492361696"></a><p class="title"><strong>Example 25.4. <code class="filename">TutorialProperties.java</code>, a class containing property |
| definitions</strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth; |
| |
| import org.apache.guacamole.properties.StringGuacamoleProperty; |
| |
| /** |
| * Utility class containing all properties used by the custom authentication |
| * tutorial. The properties defined here must be specified within |
| * guacamole.properties to configure the tutorial authentication provider. |
| */ |
| public class TutorialGuacamoleProperties { |
| |
| /** |
| * This class should not be instantiated. |
| */ |
| private TutorialGuacamoleProperties() {} |
| |
| /** |
| * The only user to allow. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_USER = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-user"; } |
| |
| }; |
| |
| /** |
| * The password required for the specified user. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PASSWORD = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-password"; } |
| |
| }; |
| |
| |
| /** |
| * The protocol to use when connecting. |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PROTOCOL = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-protocol"; } |
| |
| }; |
| |
| |
| /** |
| * All parameters associated with the connection, as a comma-delimited |
| * list of name="value" |
| */ |
| public static final StringGuacamoleProperty TUTORIAL_PARAMETERS = |
| new StringGuacamoleProperty() { |
| |
| @Override |
| public String getName() { return "tutorial-parameters"; } |
| |
| }; |
| |
| }</pre></div></div><br class="example-break" /><p>Normally, we would define a new type of <code class="classname">GuacamoleProperty</code> to |
| handle the parsing of the parameters required by <code class="varname">TUTORIAL_PARAMETERS</code>, |
| but for the sake of simplicity, parsing of this parameter will be embedded in the |
| authentication function later.</p><p>You will need to modify your existing <code class="filename">guacamole.properties</code> file, |
| adding each of the above properties to describe one of your available |
| connections.</p><div class="example"><a id="idm46227492355888"></a><p class="title"><strong>Example 25.5. Properties describing a user and connection, as required by this tutorial</strong></p><div class="example-contents"><pre class="programlisting"># Username and password |
| tutorial-user: <em class="replaceable"><code>tutorial</code></em> |
| tutorial-password: <em class="replaceable"><code>password</code></em> |
| |
| # Connection information |
| tutorial-protocol: <em class="replaceable"><code>vnc</code></em> |
| tutorial-parameters: <em class="replaceable"><code>hostname=localhost, port=5900</code></em></pre></div></div><br class="example-break" /><p>Once these properties and their accessor class are in place, it's simple enough to |
| read the properties within <code class="methodname">getAuthorizedConfigurations()</code> and |
| authenticate the user based on their username and password.</p><div class="example"><a id="idm46227492351872"></a><p class="title"><strong>Example 25.6. Checking the credentials against the properties</strong></p><div class="example-contents"><pre class="programlisting">@Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Get the Guacamole server environment |
| Environment environment = new LocalEnvironment(); |
| |
| // Get username from guacamole.properties |
| String username = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_USER |
| ); |
| |
| // If wrong username, fail |
| if (!username.equals(credentials.getUsername())) |
| return null; |
| |
| // Get password from guacamole.properties |
| String password = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PASSWORD |
| ); |
| |
| // If wrong password, fail |
| if (!password.equals(credentials.getPassword())) |
| return null; |
| |
| // Successful login. Return configurations (STUB) |
| return new HashMap<String, GuacamoleConfiguration>(); |
| |
| }</pre></div></div><br class="example-break" /><p>As is, the authentication provider will work in its current state in that the correct |
| username and password will authenticate the user, while an incorrect username or |
| password will not, but we still aren't returning an actual map of configurations. We |
| need to construct the configuration based on the properties in the |
| <code class="filename">guacamole.properties</code> file after the user has been |
| authenticated, and return that configuration to the web application.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-more-config"></a>Parsing the configuration</h2></div></div></div><p>The only remaining task before we have a fully-functioning authentication provider is |
| to actually parse the configuration from the <code class="filename">guacamole.properties</code> |
| file.</p><div class="example"><a id="idm46227492346816"></a><p class="title"><strong>Example 25.7. Parsing and returning a <code class="classname">GuacamoleConfiguration</code></strong></p><div class="example-contents"><pre class="programlisting">@Override |
| public Map<String, GuacamoleConfiguration> |
| getAuthorizedConfigurations(Credentials credentials) |
| throws GuacamoleException { |
| |
| // Get the Guacamole server environment |
| Environment environment = new LocalEnvironment(); |
| |
| // Get username from guacamole.properties |
| String username = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_USER |
| ); |
| |
| // If wrong username, fail |
| if (!username.equals(credentials.getUsername())) |
| return null; |
| |
| // Get password from guacamole.properties |
| String password = environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PASSWORD |
| ); |
| |
| // If wrong password, fail |
| if (!password.equals(credentials.getPassword())) |
| return null; |
| |
| // Successful login. Return configurations. |
| Map<String, GuacamoleConfiguration> configs = |
| new HashMap<String, GuacamoleConfiguration>(); |
| |
| // Create new configuration |
| GuacamoleConfiguration config = new GuacamoleConfiguration(); |
| |
| // Set protocol specified in properties |
| config.setProtocol(environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PROTOCOL |
| )); |
| |
| // Set all parameters, splitting at commas |
| for (String parameterValue : environment.getRequiredProperty( |
| TutorialGuacamoleProperties.TUTORIAL_PARAMETERS |
| ).split(",\\s*")) { |
| |
| // Find the equals sign |
| int equals = parameterValue.indexOf('='); |
| if (equals == -1) |
| throw new GuacamoleServerException("Required equals sign missing"); |
| |
| // Get name and value from parameter string |
| String name = parameterValue.substring(0, equals); |
| String value = parameterValue.substring(equals+1); |
| |
| // Set parameter as specified |
| config.setParameter(name, value); |
| |
| } |
| |
| configs.put("Tutorial Connection", config); |
| return configs; |
| |
| }</pre></div></div><br class="example-break" /><p>The extension is now complete and can be built as described earlier in <a class="xref" href="custom-auth.html#custom-auth-building" title="Building the extension">the section called “Building the extension”</a>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-installing"></a>Installing the extension</h2></div></div></div><p>Guacamole extensions are self-contained <code class="filename">.jar</code> files which are |
| installed by being placed within <code class="filename">GUACAMOLE_HOME/extensions</code>, and |
| this extension is no different. As described in <a class="xref" href="configuring-guacamole.html" title="Chapter 5. Configuring Guacamole">Chapter 5, <em>Configuring Guacamole</em></a>, |
| <code class="varname">GUACAMOLE_HOME</code> is a placeholder used to refer to the directory |
| that Guacamole uses to locate its configuration files and extensions. Typically, this |
| will be the <code class="filename">.guacamole</code> directory within the home directory of the |
| user running Tomcat.</p><p>To install your extension, ensure that the required properties have been added to your |
| <code class="filename">guacamole.properties</code>, copy the |
| <code class="filename">target/guacamole-auth-tutorial-1.3.0.jar</code> file into |
| <code class="filename">GUACAMOLE_HOME/extensions</code> and restart Tomcat. Guacamole will |
| automatically load your extension, logging an informative message that it has done |
| so:</p><div class="informalexample"><pre class="screen">Extension "Tutorial Authentication Extension" loaded.</pre></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="developers-guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="event-listeners.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 24. Adding new protocols </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 26. Event listeners</td></tr></table></div> |
| |
| </div></div> |
| <!-- Google Analytics --> |
| <script type="text/javascript"> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-75289145-1', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body></html> |