package de.ahus1.keycloak.dropwizard; import com.google.common.base.Preconditions; import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.AuthenticationException; import io.dropwizard.auth.Authenticator; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Request; import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.jetty.JettyAdapterSessionStore; import org.keycloak.adapters.jetty.core.JettyCookieTokenStore; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.enums.TokenStore; import org.keycloak.representations.adapters.config.AdapterConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Priority; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.SecurityContext; import java.io.IOException; import java.security.Principal; import java.util.Optional; @Priority(Priorities.AUTHENTICATION) public class KeycloakAuthFilter<P extends Principal> extends AuthFilter<HttpServletRequest, P> { private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakAuthFilter.class); public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE"; protected AdapterDeploymentContext deploymentContext; private AdapterConfig adapterConfig; public void initializeKeycloak() { KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig); deploymentContext = new AdapterDeploymentContext(kd); } private KeycloakAuthFilter(AdapterConfig adapterConfig) { this.adapterConfig = adapterConfig; } @Override public void filter(final ContainerRequestContext requestContext) throws IOException { validateRequest(requestContext); HttpServletRequest request = (HttpServletRequest) requestContext.getProperty(HttpServletRequest.class.getName()); final Optional<P> principal; try { principal = authenticator.authenticate(request); if (principal.isPresent()) { requestContext.setSecurityContext(new SecurityContext() { @Override public Principal getUserPrincipal() { return principal.get(); } @Override public boolean isUserInRole(String role) { return authorizer.authorize(principal.get(), role); } @Override public boolean isSecure() { return requestContext.getSecurityContext().isSecure(); } @Override public String getAuthenticationScheme() { return SecurityContext.BASIC_AUTH; } }); return; } } catch (AuthenticationException e) { LOGGER.warn("Error authenticating credentials", e); throw new InternalServerErrorException(); } // TODO: re-enable / check if 302 has been returned // throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm)); } public void validateRequest(final ContainerRequestContext requestContext) { if(requestContext.getSecurityContext().getUserPrincipal() != null) { // the user is already authenticated, further processing is not necessary return; } Request request = Request.getBaseRequest((ServletRequest) requestContext.getProperty(HttpServletRequest.class.getName())); JaxrsHttpFacade facade = new JaxrsHttpFacade(requestContext, requestContext.getSecurityContext()); request.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); if (deployment == null || !deployment.isConfigured()) { return; } AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment); tokenStore.checkCurrentToken(); JettyRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore); AuthOutcome outcome = authenticator.authenticate(); if (outcome == AuthOutcome.AUTHENTICATED) { return; } AuthChallenge challenge = authenticator.getChallenge(); if (challenge != null) { challenge.challenge(facade); if (!adapterConfig.isBearerOnly()) { // create session and set cookie for client facade.getResponse().setCookie("JSESSIONID", request.getSession().getId(), "/", null, -1, false, false); } facade.getResponse().end(); } } protected JettyRequestAuthenticator createRequestAuthenticator(HttpServletRequest request, JaxrsHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) { Request r = Request.getBaseRequest(request); return new JettyRequestAuthenticator(facade, deployment, tokenStore, -1, r); } public static AdapterTokenStore getTokenStore(HttpServletRequest request, HttpFacade facade, KeycloakDeployment resolvedDeployment) { AdapterTokenStore store = (AdapterTokenStore) request.getAttribute(TOKEN_STORE_NOTE); if (store != null) { return store; } Request r = Request.getBaseRequest(request); if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) { store = new JettySessionTokenStore(r, resolvedDeployment, new JettyAdapterSessionStore(r)); } else { store = new JettyCookieTokenStore(r, facade, resolvedDeployment); } request.setAttribute(TOKEN_STORE_NOTE, store); return store; } /** * Builder for {@link KeycloakAuthFilter}. * <p>An {@link Authenticator} must be provided during the building process.</p> * * @param <P> the type of the principal */ public static class Builder<P extends Principal> extends AuthFilterBuilder<HttpServletRequest, P, KeycloakAuthFilter<P>> { private AdapterConfig adapterConfig; @Override protected KeycloakAuthFilter<P> newInstance() { return new KeycloakAuthFilter<>(adapterConfig); } public Builder<P> setConfig(AdapterConfig adapterConfig) { this.adapterConfig = adapterConfig; return this; } @Override public KeycloakAuthFilter<P> buildAuthFilter() { Preconditions.checkArgument(adapterConfig != null, "Keycloak config is not set"); KeycloakAuthFilter<P> filter = super.buildAuthFilter(); filter.initializeKeycloak(); return filter; } } }