diff --git a/hawtio-system/src/main/java/io/hawt/web/keycloak/KeycloakServlet.java b/hawtio-system/src/main/java/io/hawt/web/keycloak/KeycloakServlet.java index fbabb870fc..54942af4e9 100644 --- a/hawtio-system/src/main/java/io/hawt/web/keycloak/KeycloakServlet.java +++ b/hawtio-system/src/main/java/io/hawt/web/keycloak/KeycloakServlet.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import io.hawt.system.ConfigManager; import io.hawt.util.IOHelper; @@ -22,6 +23,7 @@ * 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 + * - validate if current JAAS logged subject is same like SSO user logged through keycloak if path '/validate-subject-matches' is used * */ public class KeycloakServlet extends HttpServlet { @@ -137,6 +139,31 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t } else { renderJSONResponse(response, keycloakConfig); } + } else if ("/validate-subject-matches".equals(pathInfo)) { + String keycloakUser = request.getParameter("keycloakUser"); + if (keycloakUser == null || keycloakUser.length() == 0) { + LOG.warn("Parameter 'keycloakUser' not found"); + } + boolean valid = validateKeycloakUser(request, keycloakUser); + renderJSONResponse(response, String.valueOf(valid)); + } + } + + protected boolean validateKeycloakUser(HttpServletRequest request, String keycloakUser) { + HttpSession session = request.getSession(false); + + // No session available. No existing subject logged + if (session == null) { + return true; + } + + String username = (String) session.getAttribute("user"); + if (username != null && !username.equals(keycloakUser)) { + LOG.debug("Non matching username found. JAAS username: " + username + ", keycloakUsername: " + keycloakUser + ". Invalidating session"); + session.invalidate(); + return false; + } else { + return true; } } diff --git a/hawtio-web/src/main/webapp/app/core/js/app.ts b/hawtio-web/src/main/webapp/app/core/js/app.ts index 10a51d09a0..2e66a7a336 100644 --- a/hawtio-web/src/main/webapp/app/core/js/app.ts +++ b/hawtio-web/src/main/webapp/app/core/js/app.ts @@ -78,7 +78,7 @@ module Core { * @param {*} jolokiaUrl * @param {*} branding */ - export var AppController = _module.controller("Core.AppController", ["$scope", "$location", "workspace", "jolokia", "jolokiaStatus", "$document", "pageTitle", "localStorage", "userDetails", "lastLocation", "jolokiaUrl", "branding", "ConnectOptions", "$timeout", "locationChangeStartTasks", "$route", ($scope, $location:ng.ILocationService, workspace, jolokia, jolokiaStatus, $document, pageTitle:Core.PageTitle, localStorage, userDetails, lastLocation:{ url:string }, jolokiaUrl, branding, ConnectOptions:Core.ConnectOptions, $timeout:ng.ITimeoutService, locationChangeStartTasks:Core.ParameterizedTasks, $route:ng.route.IRouteService) => { + export var AppController = _module.controller("Core.AppController", ["$scope", "$location", "$window", "workspace", "jolokia", "jolokiaStatus", "$document", "pageTitle", "localStorage", "userDetails", "lastLocation", "jolokiaUrl", "branding", "ConnectOptions", "$timeout", "locationChangeStartTasks", "$route", "keycloakContext", ($scope, $location:ng.ILocationService, $window:ng.IWindowService, workspace, jolokia, jolokiaStatus, $document, pageTitle:Core.PageTitle, localStorage, userDetails, lastLocation:{ url:string }, jolokiaUrl, branding, ConnectOptions:Core.ConnectOptions, $timeout:ng.ITimeoutService, locationChangeStartTasks:Core.ParameterizedTasks, $route:ng.route.IRouteService, keycloakContext:Core.KeycloakContext) => { $scope.collapse = ''; $scope.match = null; @@ -90,6 +90,7 @@ module Core { $scope.connectFailure = {}; $scope.showPrefs = false; + $scope.keycloakEnabled = keycloakContext.enabled; $scope.logoClass = () => { if (branding.logoOnly) { @@ -289,6 +290,17 @@ module Core { function defaultPage() { return Perspective.defaultPage($location, workspace, jolokia, localStorage); } + + $scope.redirectToKeycloakAccountMgmt = () => { + keycloakContext.keycloak.accountManagement(); + }; + + $scope.redirectToKeycloakAdminConsole = () => { + var realm: string = encodeURIComponent(keycloakContext.keycloak.realm); + var redirectURI: string = keycloakContext.keycloak.authServerUrl + '/admin/' + realm + '/console/index.html'; + $window.location.href = redirectURI; + }; + }]); } diff --git a/hawtio-web/src/main/webapp/app/core/js/keycloakLogin.ts b/hawtio-web/src/main/webapp/app/core/js/keycloakLogin.ts index 678f42494f..c8116b0cc7 100644 --- a/hawtio-web/src/main/webapp/app/core/js/keycloakLogin.ts +++ b/hawtio-web/src/main/webapp/app/core/js/keycloakLogin.ts @@ -6,7 +6,7 @@ module Core { var log = Logger.get('Keycloak'); - // log.setLevel(Logger.DEBUG); + log.setLevel(Logger.DEBUG); var checkKeycloakEnabled = function(callback: Function) { var keycloakEnabledUrl: string = "keycloak/enabled"; @@ -15,7 +15,7 @@ module Core { $.ajax(keycloakEnabledUrl, { type: "GET", success: function (response) { - log.debug("Got user response for check if keycloak is enabled: ", response); + log.debug("Got response for check if keycloak is enabled: ", response); var keycloakEnabled: boolean = response; var keycloakContext: KeycloakContext = createKeycloakContext(keycloakEnabled); @@ -50,6 +50,7 @@ module Core { return keycloakContext; }; + var initKeycloakIfNeeded = function(keycloakContext: KeycloakContext, nextTask: Function) { if (keycloakContext.enabled) { log.debug('Keycloak authentication required. Initializing Keycloak'); @@ -64,9 +65,14 @@ module Core { } keycloak.init(kcInitOptions).success(function () { - log.debug("Keycloak authentication finished! Continue next task"); - // Continue next registered task and bootstrap Angular - nextTask(); + var keycloakUsername: string = keycloak.tokenParsed.preferred_username; + log.debug("Keycloak authenticated with Subject " + keycloakUsername + ". Validating subject matches"); + + validateSubjectMatches(keycloakUsername, function() { + log.debug("Keycloak authentication finished! Continue next task"); + // Continue next registered task and bootstrap Angular + nextTask(); + }); }).error(function () { log.warn("Keycloak authentication failed!"); notification('error', 'Failed to log in to Keycloak'); @@ -78,6 +84,28 @@ module Core { } }; + + /** + * Validate if subject authenticated through Keycloak matches with SSO + */ + var validateSubjectMatches = function(keycloakUser: string, callback: Function) { + var keycloakValidateUrl: string = "keycloak/validate-subject-matches?keycloakUser=" + encodeURIComponent(keycloakUser); + + // Send ajax request to KeycloakServlet to figure out if keycloak integration is enabled + $.ajax(keycloakValidateUrl, { + type: "GET", + success: function (response) { + log.debug("Got response for validate subject matches: ", response); + callback(); + }, + error: function (xhr, textStatus, error) { + // Just fallback to false if we couldn't figure userDetails + log.debug("Failed to validate subject matches: ", error); + callback(); + } + }); + } + /** * Prebootstrap task, which handles Keycloak OAuth flow. It will first check if keycloak is enabled and then possibly init keycloak. * It will continue with Angular bootstrap just when Keycloak authentication is successfully finished diff --git a/hawtio-web/src/main/webapp/index.html b/hawtio-web/src/main/webapp/index.html index 9b7afff763..849a26742a 100644 --- a/hawtio-web/src/main/webapp/index.html +++ b/hawtio-web/src/main/webapp/index.html @@ -187,6 +187,18 @@ +
  • + + Manage my account + +
  • + +
  • + + Keycloak admin console + +
  • + diff --git a/sample-keycloak-integration/README.md b/sample-keycloak-integration/README.md index d4003b74b9..74978a8be9 100644 --- a/sample-keycloak-integration/README.md +++ b/sample-keycloak-integration/README.md @@ -74,7 +74,7 @@ features:uninstall hawtio-core features:removeurl mvn:io.hawt/hawtio-karaf/1.2-redhat-379/xml/features ``` -* Install new hawtio with keycloak integration (Replace with the correct version where is Keycloak integration available. It should be 1.4.47 or 1.5-redhat-048 or newer) +* Install new hawtio with keycloak integration (Replace with the correct version where is Keycloak integration available. It should be 1.4.47 or newer) ``` features:chooseurl hawtio 1.4.47 diff --git a/sample-keycloak-integration/demorealm.json b/sample-keycloak-integration/demorealm.json index 98f7edba0e..ff35029915 100644 --- a/sample-keycloak-integration/demorealm.json +++ b/sample-keycloak-integration/demorealm.json @@ -40,6 +40,9 @@ "users" : [ { "username" : "john", + "firstName" : "John", + "lastName" : "Anthony", + "email" : "john@hawt.io", "enabled" : true, "credentials" : [ { @@ -54,6 +57,9 @@ }, { "username" : "mary", + "firstName" : "Mary", + "lastName" : "Kelly", + "email" : "mary@hawt.io", "enabled" : true, "credentials" : [ { @@ -67,6 +73,9 @@ }, { "username" : "root", + "firstName" : "Root", + "lastName" : "Root", + "email" : "root@hawt.io", "enabled" : true, "credentials" : [ { @@ -76,7 +85,8 @@ ], "realmRoles" : [ "jmxAdmin" ], "applicationRoles": { - "account" : [ "view-profile", "manage-account" ] + "account" : [ "view-profile", "manage-account" ], + "realm-management" : [ "realm-admin" ] } } ], @@ -84,6 +94,7 @@ { "name" : "hawtio-client", "surrogateAuthRequired" : false, + "fullScopeAllowed" : false, "enabled" : true, "redirectUris" : [ "http://localhost:8080/hawtio/*", "http://localhost:8181/hawtio/*", "http://localhost:8081/hawtio/*" ], "webOrigins" : [ "http://localhost:8080", "http://localhost:8181", "http://localhost:8081" ],