/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: * François Maturel */ package org.nuxeo.ecm.platform.ui.web.keycloak; import java.lang.reflect.Method; import java.security.Principal; import java.util.List; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.authenticator.FormAuthenticator; import org.apache.catalina.connector.Request; import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.realm.GenericPrincipal; import org.keycloak.KeycloakPrincipal; import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AuthChallenge; import org.keycloak.adapters.AuthOutcome; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.OAuthRequestAuthenticator; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RequestAuthenticator; import org.keycloak.adapters.tomcat.CatalinaCookieTokenStore; import org.keycloak.adapters.tomcat.CatalinaHttpFacade; import org.keycloak.adapters.tomcat.CatalinaSessionTokenStore; import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement; import org.keycloak.adapters.tomcat.GenericPrincipalFactory; import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve; import org.keycloak.enums.TokenStore; import org.keycloak.representations.AccessToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @since 7.4 */ public class KeycloakRequestAuthenticator extends RequestAuthenticator { private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakRequestAuthenticator.class); public static final String KEYCLOAK_ACCESS_TOKEN = "KEYCLOAK_ACCESS_TOKEN"; private CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement(); protected Request request; protected HttpServletResponse response; protected LoginConfig loginConfig; public KeycloakRequestAuthenticator(Request request, HttpServletResponse response, CatalinaHttpFacade facade, KeycloakDeployment deployment) { super(facade, deployment); this.request = request; this.response = response; tokenStore = getTokenStore(); sslRedirectPort = request.getConnector().getRedirectPort(); } @Override public AuthOutcome authenticate() { AuthOutcome outcome = super.authenticate(); if (outcome == AuthOutcome.AUTHENTICATED) { return AuthOutcome.AUTHENTICATED; } AuthChallenge challenge = getChallenge(); if (challenge != null) { if (loginConfig == null) { loginConfig = request.getContext().getLoginConfig(); } if (challenge.errorPage()) { if (forwardToErrorPageInternal(request, response, loginConfig)) { return AuthOutcome.FAILED; } } challenge.challenge(facade); } return AuthOutcome.FAILED; } protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) { if (loginConfig == null) { return false; } LoginConfig config = (LoginConfig) loginConfig; if (config.getErrorPage() == null) { return false; } try { Method method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, HttpServletResponse.class, LoginConfig.class); method.setAccessible(true); method.invoke(this, request, response, config); } catch (Exception e) { String message = "Error occurred during Keycloak authentication"; LOGGER.error(message, e); throw new RuntimeException(message, e); } return true; } protected GenericPrincipalFactory createPrincipalFactory() { return new GenericPrincipalFactory() { @Override protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) { return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null); } }; } protected AdapterTokenStore getTokenStore() { final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; AdapterTokenStore store = (AdapterTokenStore) request.getNote(TOKEN_STORE_NOTE); if (store != null) { return store; } if (deployment.getTokenStore() == TokenStore.SESSION) { store = new CatalinaSessionTokenStore(request, deployment, userSessionManagement, createPrincipalFactory(), new KeycloakAuthenticatorValve()); } else { store = new CatalinaCookieTokenStore(request, facade, deployment, createPrincipalFactory()); } request.setNote(TOKEN_STORE_NOTE, store); return store; } @Override protected OAuthRequestAuthenticator createOAuthAuthenticator() { return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); } @Override protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) { final AccessToken token = skp.getKeycloakSecurityContext().getToken(); request.setAttribute(KEYCLOAK_ACCESS_TOKEN, token); } @Override protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp, String method) { completeOAuthAuthentication(skp); } @Override protected String getHttpSessionId(boolean create) { HttpSession session = request.getSession(create); return session != null ? session.getId() : null; } }