001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * François Maturel 018 */ 019package org.nuxeo.ecm.platform.ui.web.keycloak; 020 021import java.lang.reflect.Method; 022import java.security.Principal; 023import java.util.List; 024 025import javax.servlet.http.HttpServletResponse; 026import javax.servlet.http.HttpSession; 027 028import org.apache.catalina.authenticator.FormAuthenticator; 029import org.apache.catalina.connector.Request; 030import org.apache.catalina.realm.GenericPrincipal; 031import org.apache.tomcat.util.descriptor.web.LoginConfig; 032import org.keycloak.KeycloakPrincipal; 033import org.keycloak.adapters.AdapterTokenStore; 034import org.keycloak.adapters.AuthChallenge; 035import org.keycloak.adapters.AuthOutcome; 036import org.keycloak.adapters.KeycloakDeployment; 037import org.keycloak.adapters.OAuthRequestAuthenticator; 038import org.keycloak.adapters.RefreshableKeycloakSecurityContext; 039import org.keycloak.adapters.RequestAuthenticator; 040import org.keycloak.adapters.tomcat.CatalinaCookieTokenStore; 041import org.keycloak.adapters.tomcat.CatalinaHttpFacade; 042import org.keycloak.adapters.tomcat.CatalinaSessionTokenStore; 043import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement; 044import org.keycloak.adapters.tomcat.GenericPrincipalFactory; 045import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve; 046import org.keycloak.enums.TokenStore; 047import org.keycloak.representations.AccessToken; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051/** 052 * @since 7.4 053 */ 054 055public class KeycloakRequestAuthenticator extends RequestAuthenticator { 056 057 private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakRequestAuthenticator.class); 058 059 public static final String KEYCLOAK_ACCESS_TOKEN = "KEYCLOAK_ACCESS_TOKEN"; 060 061 private CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement(); 062 063 protected Request request; 064 065 protected HttpServletResponse response; 066 067 protected LoginConfig loginConfig; 068 069 public KeycloakRequestAuthenticator(Request request, HttpServletResponse response, CatalinaHttpFacade facade, 070 KeycloakDeployment deployment) { 071 super(facade, deployment); 072 this.request = request; 073 this.response = response; 074 tokenStore = getTokenStore(); 075 sslRedirectPort = request.getConnector().getRedirectPort(); 076 } 077 078 @Override 079 public AuthOutcome authenticate() { 080 AuthOutcome outcome = super.authenticate(); 081 if (outcome == AuthOutcome.AUTHENTICATED) { 082 return AuthOutcome.AUTHENTICATED; 083 } 084 AuthChallenge challenge = getChallenge(); 085 if (challenge != null) { 086 if (loginConfig == null) { 087 loginConfig = request.getContext().getLoginConfig(); 088 } 089 if (challenge.errorPage()) { 090 if (forwardToErrorPageInternal(request, response, loginConfig)) { 091 return AuthOutcome.FAILED; 092 } 093 } 094 challenge.challenge(facade); 095 } 096 return AuthOutcome.FAILED; 097 } 098 099 protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) { 100 if (loginConfig == null) { 101 return false; 102 } 103 LoginConfig config = (LoginConfig) loginConfig; 104 if (config.getErrorPage() == null) { 105 return false; 106 } 107 try { 108 Method method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, 109 HttpServletResponse.class, LoginConfig.class); 110 method.setAccessible(true); 111 method.invoke(this, request, response, config); 112 } catch (Exception e) { 113 String message = "Error occurred during Keycloak authentication"; 114 LOGGER.error(message, e); 115 throw new RuntimeException(message, e); 116 } 117 return true; 118 } 119 120 protected GenericPrincipalFactory createPrincipalFactory() { 121 return new GenericPrincipalFactory() { 122 @Override 123 protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) { 124 return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null); 125 } 126 }; 127 } 128 129 protected AdapterTokenStore getTokenStore() { 130 final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; 131 132 AdapterTokenStore store = (AdapterTokenStore) request.getNote(TOKEN_STORE_NOTE); 133 if (store != null) { 134 return store; 135 } 136 137 if (deployment.getTokenStore() == TokenStore.SESSION) { 138 store = new CatalinaSessionTokenStore(request, deployment, userSessionManagement, createPrincipalFactory(), 139 new KeycloakAuthenticatorValve()); 140 } else { 141 store = new CatalinaCookieTokenStore(request, facade, deployment, createPrincipalFactory()); 142 } 143 144 request.setNote(TOKEN_STORE_NOTE, store); 145 return store; 146 } 147 148 @Override 149 protected OAuthRequestAuthenticator createOAuthAuthenticator() { 150 return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore); 151 } 152 153 @Override 154 protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) { 155 final AccessToken token = skp.getKeycloakSecurityContext().getToken(); 156 request.setAttribute(KEYCLOAK_ACCESS_TOKEN, token); 157 } 158 159 @Override 160 protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp, String method) { 161 completeOAuthAuthentication(skp); 162 } 163 164 @Override 165 protected String getHttpSessionId(boolean create) { 166 HttpSession session = request.getSession(create); 167 return session != null ? session.getId() : null; 168 } 169 170}