Liferay Keycloak integration - SSO and SLO implementation
Liferay Keycloak - Single Sign On / Single Logout
Overview
Previous article ( https://lifedev-solutions.blogspot.com/2019/10/liferay-keycloak-integration-using.html ) describes steps to configure Keycloak SSO Authorization in Liferay using OpenID Connect Provider.
However, this approach has several limitations:
- it can't be used as a default login mechanism: user needs to click "Sign In", chose "OpenId Connect", select "OpenId Connect Provider", and only then he is redirected to the Keycloak sign-in form;
- After logging out from Liferay user stays logged in in Keycloak.
This article will show how to overcome these issues.
However, this approach has several limitations:
- it can't be used as a default login mechanism: user needs to click "Sign In", chose "OpenId Connect", select "OpenId Connect Provider", and only then he is redirected to the Keycloak sign-in form;
- After logging out from Liferay user stays logged in in Keycloak.
This article will show how to overcome these issues.
SSO with Servlet Filter
For automatic redirection to Keycloak's Sign In form the servlet filter may be implemented:
@Component( immediate = true, property = { "servlet-context-name=", "servlet-filter-name=Keycloak Login Filter", "url-pattern=/c/portal/login" }, service = Filter.class) public class KeycloakLoginFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; //Get OpenId Providers Collection<String> openIdConnectProviderNames = _openIdConnectProviderRegistry.getOpenIdConnectProviderNames(); if (openIdConnectProviderNames == null || openIdConnectProviderNames.size() == 0) { filterChain.doFilter(servletRequest, servletResponse); return; } String openIdConnectProviderName = openIdConnectProviderNames.iterator().next(); _openIdConnectServiceHandler.requestAuthentication(openIdConnectProviderName, request, response); } catch (Exception e) { _log.error("Error in KeycloakLoginFilter: " + e.getMessage(), e); } finally { filterChain.doFilter(servletRequest, servletResponse); } } @Override public void destroy() { } @Reference private OpenIdConnectProviderRegistry _openIdConnectProviderRegistry; @Reference private OpenIdConnectServiceHandler _openIdConnectServiceHandler; private static final Log _log = LogFactoryUtil.getLog(KeycloakLoginFilter.class); }
It intercepts all requests to "/c/portal/login" URL and invokes the OpenID authentication, which redirects user to Keycloak Sign In form.
Check also the article about Servlet Filters.
Check also the article about Servlet Filters.
SLO with Logout Post Action
To make user automatically logged out from Keycloak after logging out in Liferay - a custom Login Post Action may be implemented:
@Component( immediate = true, property = "key=logout.events.post", service = LifecycleAction.class) public class KeycloakLogoutPostAction implements LifecycleAction { @Override public void processLifecycleEvent(LifecycleEvent lifecycleEvent) throws ActionException { try { HttpServletRequest request = lifecycleEvent.getRequest(); HttpServletResponse response = lifecycleEvent.getResponse(); Collection<String> openIdConnectProviderNames = _openIdConnectProviderRegistry.getOpenIdConnectProviderNames(); if (openIdConnectProviderNames == null || openIdConnectProviderNames.size() == 0) { _log.warn("No OpenID Connect Providers found."); return; } String openIdConnectProviderName = openIdConnectProviderNames.iterator().next(); OpenIdConnectProvider openIdConnectProvider = _openIdConnectProviderRegistry.getOpenIdConnectProvider(openIdConnectProviderName); Object oidcProviderMetadata = openIdConnectProvider.getOIDCProviderMetadata(); String oidcJson = oidcProviderMetadata.toString(); JSONObject oidcJsonObject = JSONFactoryUtil.createJSONObject(oidcJson); Object authEndpoint = oidcJsonObject.get("authorization_endpoint"); String authEndpointUrl = authEndpoint.toString(); String logoutEndpoint = StringUtil.replaceLast(authEndpointUrl, "/auth", "/logout"); String redirectUri = getRedirectUrl(request); String logoutUrl = logoutEndpoint + "?redirect_uri=" + redirectUri; response.sendRedirect(logoutUrl); } catch (Exception e) { _log.error("Error in KeycloakLogoutPostAction: " + e.getMessage(), e); } } private String getRedirectUrl(HttpServletRequest request) { String portalURL = _portal.getPortalURL(request); long companyId = _portal.getCompanyId(request); PortletPreferences preferences = _prefsProps.getPreferences(companyId); String logoutPath = _prefsProps.getString(preferences, PropsKeys.DEFAULT_LOGOUT_PAGE_PATH); return portalURL + logoutPath; } @Reference private Portal _portal; @Reference private PrefsProps _prefsProps; @Reference private OpenIdConnectProviderRegistry _openIdConnectProviderRegistry; private static final Log _log = LogFactoryUtil.getLog(KeycloakLogoutPostAction.class); }
This action is invoked after used has been logged out from Liferay. It performs a call to Keycloak's logout endpoint and then redirects back to a portal page.
Enjoy 😉
Getting null , I am using liferay 7.4.3.11
ReplyDelete