Skip to content

Commit

Permalink
Hawtio and Keycloak integration
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda committed Jan 30, 2015
1 parent 6a1d41d commit 1f25c04
Show file tree
Hide file tree
Showing 21 changed files with 854 additions and 51 deletions.
4 changes: 4 additions & 0 deletions docs/Configuration.md
Expand Up @@ -324,6 +324,10 @@ At last enable the jaas module in jetty. This is done by adding the following li
# Enable security via jaas, and configure it
--module=jaas

#### Keycloak integration

Hawtio can now be integrated with [Keycloak](http://www.keycloak.org) for SSO authentication. See details [here](https://github.com/mposolda/hawtio/blob/master/sample-keycloak-integration/README.md) .

## Configuration Properties

The following table contains the various configuration settings for the various hawtio plugins.
Expand Down
4 changes: 3 additions & 1 deletion hawtio-system/src/main/java/io/hawt/web/LoginServlet.java
Expand Up @@ -95,7 +95,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S

if (principals != null) {
for (Principal principal : principals) {
if (principal.getClass().getSimpleName().equals("UserPrincipal")) {
// Should be name of class configurable like for Role principal?
String principalClass = principal.getClass().getSimpleName();
if (principalClass.equals("UserPrincipal") || principalClass.equals("KeycloakPrincipal")) {
username = principal.getName();
LOG.debug("Authorizing user {}", username);
}
Expand Down
133 changes: 133 additions & 0 deletions hawtio-system/src/main/java/io/hawt/web/keycloak/KeycloakServlet.java
@@ -0,0 +1,133 @@
package io.hawt.web.keycloak;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.hawt.system.ConfigManager;
import io.hawt.util.IOHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Servlet, which aims to return:
* - whether keycloak is enabled (true/false) if path '/enabled' is used
* - keycloak.json to be used by keycloak JS adapter on frontend if path '/client-config' is used
*
*/
public class KeycloakServlet extends HttpServlet {

private static final transient Logger LOG = LoggerFactory.getLogger(KeycloakServlet.class);

public static final String KEYCLOAK_CLIENT_CONFIG = "keycloakClientConfig";
public static final String KEYCLOAK_ENABLED = "keycloakEnabled";

private String keycloakConfig = null;
private boolean keycloakEnabled;


@Override
public void init() throws ServletException {
ConfigManager config = (ConfigManager) getServletContext().getAttribute("ConfigManager");

String keycloakEnabledCfg = config.get(KEYCLOAK_ENABLED, "false");
keycloakEnabled = Boolean.parseBoolean(keycloakEnabledCfg);
LOG.info("Keycloak is " + (this.keycloakEnabled ? "enabled" : "disabled"));
if (!keycloakEnabled) {
return;
}

String keycloakConfigFile = config.get(KEYCLOAK_CLIENT_CONFIG, null);
if (keycloakConfigFile == null || keycloakConfigFile.length() == 0) {
keycloakConfigFile = defaultKeycloakConfigLocation();
}

LOG.info("Will load keycloak config from location: " + keycloakConfigFile);

InputStream is = loadFile(keycloakConfigFile);
if (is == null) {
LOG.warn("Keycloak client configuration not found!");
} else {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String keycloakConfig = IOHelper.readFully(reader);

// Minify for perf purposes
this.keycloakConfig = keycloakConfig.replaceAll(" ", "").replaceAll(System.lineSeparator(), "");
} catch (IOException ioe) {
LOG.warn("Couldn't read keycloak configuration file", ioe);
} finally {
IOHelper.close(is, "keycloakInputStream", LOG);
}
}
}

/**
* Will try to guess the config location based on the server where hawtio is running. Used just if keycloakClientConfig is not provided
* @return config to be used by default
*/
protected String defaultKeycloakConfigLocation() {
String karafBase = System.getProperty("karaf.base");
if (karafBase != null) {
return karafBase + "/etc/keycloak.json";
}

String jettyHome = System.getProperty("jetty.home");
if (jettyHome != null) {
return jettyHome + "/etc/keycloak.json";
}

String tomcatHome = System.getProperty("catalina.home");
if (tomcatHome != null) {
return tomcatHome + "/conf/keycloak.json";
}

// Fallback to classpath inside hawtio.war
return "classpath:keycloak.json";
}

protected InputStream loadFile(String keycloakConfigFile) {
if (keycloakConfigFile.startsWith("classpath:")) {
String classPathLocation = keycloakConfigFile.substring(10);
return getClass().getClassLoader().getResourceAsStream(classPathLocation);
} else {
try {
return new FileInputStream(keycloakConfigFile);
} catch (FileNotFoundException fnfe) {
LOG.warn("Couldn't find keycloak config file on location: " + keycloakConfigFile);
return null;
}
}
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pathInfo = request.getPathInfo();
if ("/enabled".equals(pathInfo)) {
renderJSONResponse(response, String.valueOf(keycloakEnabled));
} else if ("/client-config".equals(pathInfo)) {
if (keycloakConfig == null) {
response.sendError(404, "Keycloak client configuration not found");
} else {
renderJSONResponse(response, keycloakConfig);
}
}
}

private void renderJSONResponse(HttpServletResponse response, String text) throws ServletException, IOException {
response.setContentType("application/json");
PrintWriter writer = response.getWriter();
writer.println(text);
writer.flush();
writer.close();
}
}
3 changes: 2 additions & 1 deletion hawtio-web/bower.json
Expand Up @@ -27,6 +27,7 @@
"d3": "3.0.0",
"Font-Awesome": "3.2.1",
"elastic.js": "1.1.1",
"underscore": "1.7.0"
"underscore": "1.7.0",
"keycloak": "1.1.0"
}
}
59 changes: 59 additions & 0 deletions hawtio-web/src/main/d.ts/keycloak.d.ts
@@ -0,0 +1,59 @@
declare module KeycloakModule {

export interface Promise {
success(callback: Function): Promise;
error(callback: Function): Promise;
}

export interface InitOptions {
checkLoginIframe?: boolean;
checkLoginIframeInterval?: number;
onLoad?: string;
}

export interface LoginOptions {
prompt?: String;
loginHint?: String;
}

export interface RedirectUriOptions {
redirectUri?: String;
}

export interface IKeycloak {
init(options?: InitOptions): Promise;
login(options?: LoginOptions): Promise;
createLoginUrl(options?: LoginOptions): string;
logout(options?: RedirectUriOptions): Promise;
createLogoutUrl(options?: RedirectUriOptions): string;
createAccountUrl(options?: RedirectUriOptions): string;
accountManagement(): Promise;
hasRealmRole(role: string): boolean;
hasResourceRole(role: string, resource?: string): boolean;
loadUserProfile(): Promise;
isTokenExpired(minValidity: number): boolean;
updateToken(minValidity: number): Promise;

realm: string;
clientId: string;
authServerUrl: string;

token: string;
tokenParsed: any;
refreshToken: string;
refreshTokenParsed: any;
idToken: string;
authenticated: boolean;
subject: string;

onAuthSuccess: Function;
onAuthError: Function;
onAuthRefreshSuccess: Function;
onAuthRefreshError: Function;
onAuthLogout: Function;
}
}

declare var Keycloak: {
new(config?: any): KeycloakModule.IKeycloak;
};
23 changes: 23 additions & 0 deletions hawtio-web/src/main/webapp/WEB-INF/web.xml
Expand Up @@ -43,6 +43,20 @@
<env-entry-value>io.hawt.web.tomcat.TomcatAuthenticationContainerDiscovery</env-entry-value>
</env-entry>

<env-entry>
<description>Enable/disable Keycloak integration. Value is really a boolean</description>
<env-entry-name>hawtio/keycloakEnabled</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>false</env-entry-value>
</env-entry>

<env-entry>
<description>Keycloak config file used for frontend. Will use default location if not provided</description>
<env-entry-name>hawtio/keycloakClientConfig</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>

<env-entry>
<description>Whether or not to check and see if branding should be enabled, value is really a boolean</description>
<env-entry-name>hawtio/useBranding</env-entry-name>
Expand Down Expand Up @@ -216,6 +230,15 @@
<url-pattern>/auth/logout/*</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>keycloak</servlet-name>
<servlet-class>io.hawt.web.keycloak.KeycloakServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>keycloak</servlet-name>
<url-pattern>/keycloak/*</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>plugin</servlet-name>
<servlet-class>io.hawt.web.PluginServlet</servlet-class>
Expand Down
1 change: 1 addition & 0 deletions hawtio-web/src/main/webapp/app/baseIncludes.ts
Expand Up @@ -23,6 +23,7 @@
/// <reference path="../../d.ts/google.d.ts" />
/// <reference path="../../d.ts/hawtio-plugin-loader.d.ts" />
/// <reference path="../../d.ts/jolokia-1.0.d.ts" />
/// <reference path="../../d.ts/keycloak.d.ts" />
/// <reference path="../../d.ts/logger.d.ts" />
/// <reference path="../../d.ts/marked.d.ts" />
/// <reference path="../../d.ts/schemas.d.ts" />
Expand Down
2 changes: 1 addition & 1 deletion hawtio-web/src/main/webapp/app/core/html/login.html
@@ -1,7 +1,7 @@
<div ng-controller="Core.LoginController">


<div class="login-wrapper">
<div class="login-wrapper" ng-hide="keycloakEnabled">
<div class="login-form">
<form name="login" class="form-horizontal no-bottom-margin" ng-submit="doLogin()">
<fieldset>
Expand Down

0 comments on commit 1f25c04

Please sign in to comment.