#dev integrated adapter tomcat7

This commit is contained in:
Davide Ungari 2014-04-18 08:32:42 +02:00
parent 80f3a3152c
commit 5d275de8b2
12 changed files with 1053 additions and 1 deletions

View file

@ -19,12 +19,12 @@
<module>servlet-oauth-client</module>
<module>jboss-adapter-core</module>
<module>as7-eap6/adapter</module>
<module>tomcat7/adapter</module>
<module>undertow</module>
<module>wildfly-adapter</module>
<module>wildfly-subsystem</module>
<module>as7-eap-subsystem</module>
<module>js</module>
<module>installed</module>
<!-- <module>as7-eap6/jboss-modules</module> -->
</modules>
</project>

View file

@ -0,0 +1,88 @@
<?xml version="1.0"?>
<project>
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-alpha-3</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-tomcat7-adapter</artifactId>
<name>Keycloak Tomcat7 Integration</name>
<description />
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jboss-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${keycloak.apache.httpcomponents.version}</version>
</dependency>
<dependency>
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.52</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>7.0.52</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,13 @@
package org.keycloak.adapters.tomcat7;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface Actions {
public static final String J_OAUTH_ADMIN_FORCED_LOGOUT = "j_oauth_admin_forced_logout";
public static final String J_OAUTH_LOGOUT = "j_oauth_logout";
public static final String J_OAUTH_RESOLVE_ACCESS_CODE = "j_oauth_resolve_access_code";
public static final String J_OAUTH_REMOTE_LOGOUT = "j_oauth_remote_logout";
public static final String J_OAUTH_TOKEN_GRANT = "j_oauth_token_grant";
}

View file

@ -0,0 +1,49 @@
package org.keycloak.adapters.tomcat7;
import java.io.IOException;
import java.util.logging.Logger;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import org.apache.catalina.Container;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
/**
* Pre-installed actions that must be authenticated
* <p/>
* Actions include:
* <p/>
* CORS Origin Check and Response headers
* k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
*
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class AuthenticatedActionsValve extends ValveBase {
private static final Logger log = Logger.getLogger(""+AuthenticatedActionsValve.class);
protected KeycloakDeployment deployment;
public AuthenticatedActionsValve(KeycloakDeployment deployment, Valve next, Container container, ObjectName objectName) {
this.deployment = deployment;
if (next == null) throw new RuntimeException("WTF is next null?!");
setNext(next);
setContainer(container);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.finer("AuthenticatedActionsValve.invoke" + request.getRequestURI());
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
if (handler.handledRequest()) {
return;
}
getNext().invoke(request, response);
}
}

View file

@ -0,0 +1,181 @@
package org.keycloak.adapters.tomcat7;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.security.cert.X509Certificate;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpFacade;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CatalinaHttpFacade implements HttpFacade {
protected org.apache.catalina.connector.Request request;
protected HttpServletResponse response;
protected RequestFacade requestFacade = new RequestFacade();
protected ResponseFacade responseFacade = new ResponseFacade();
protected class RequestFacade implements Request {
@Override
public String getURI() {
StringBuffer buf = request.getRequestURL();
if (request.getQueryString() != null) {
buf.append('?').append(request.getQueryString());
}
return buf.toString();
}
@Override
public boolean isSecure() {
return request.isSecure();
}
@Override
public String getQueryParamValue(String paramName) {
return request.getParameter(paramName);
}
@Override
public Cookie getCookie(String cookieName) {
if (request.getCookies() == null) return null;
javax.servlet.http.Cookie cookie = null;
for (javax.servlet.http.Cookie c : request.getCookies()) {
if (c.getName().equals(cookieName)) {
cookie = c;
break;
}
}
if (cookie == null) return null;
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
}
@Override
public List<String> getHeaders(String name) {
Enumeration<String> headers = request.getHeaders(name);
if (headers == null) return null;
List<String> list = new ArrayList<String>();
while (headers.hasMoreElements()) {
list.add(headers.nextElement());
}
return list;
}
@Override
public InputStream getInputStream() {
try {
return request.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String getMethod() {
return request.getMethod();
}
@Override
public String getHeader(String name) {
return request.getHeader(name);
}
}
protected class ResponseFacade implements Response {
protected boolean ended;
@Override
public void setStatus(int status) {
response.setStatus(status);
}
@Override
public void addHeader(String name, String value) {
response.addHeader(name, value);
}
@Override
public void setHeader(String name, String value) {
response.setHeader(name, value);
}
@Override
public void resetCookie(String name, String path) {
setCookie(name, "", null, path, 0, false, false);
}
@Override
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value);
if (domain != null) cookie.setDomain(domain);
if (path != null) cookie.setPath(path);
if (secure) cookie.setSecure(true);
if (httpOnly) cookie.setHttpOnly(httpOnly);
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
@Override
public OutputStream getOutputStream() {
try {
return response.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void sendError(int code, String message) {
try {
response.sendError(code, message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void end() {
ended = true;
}
public boolean isEnded() {
return ended;
}
}
public CatalinaHttpFacade(org.apache.catalina.connector.Request request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
@Override
public Request getRequest() {
return requestFacade;
}
@Override
public Response getResponse() {
return responseFacade;
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
}
@Override
public X509Certificate[] getCertificateChain() {
throw new IllegalStateException("Not supported yet");
}
public boolean isEnded() {
return responseFacade.isEnded();
}
}

View file

@ -0,0 +1,123 @@
package org.keycloak.adapters.tomcat7;
import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.connector.Request;
import org.apache.catalina.realm.GenericPrincipal;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.representations.AccessToken;
/**
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class CatalinaRequestAuthenticator extends RequestAuthenticator {
private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class);
protected KeycloakAuthenticatorValve valve;
protected CatalinaUserSessionManagement userSessionManagement;
protected Request request;
public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
CatalinaHttpFacade facade,
Request request) {
super(facade, deployment, request.getConnector().getRedirectPort());
this.valve = valve;
this.userSessionManagement = userSessionManagement;
this.request = request;
}
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
@Override
protected void saveRequest() {
try {
valve.keycloakSaveRequest(request);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
}
@Override
protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) {
Set<String> roles = getRolesFromToken(securityContext);
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
Session session = request.getSessionInternal(true);
session.setPrincipal(principal);
session.setAuthType("OAUTH");
session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
String username = securityContext.getToken().getSubject();
log.finer("userSessionManage.login: " + username);
userSessionManagement.login(session, username);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) {
Set<String> roles = getRolesFromToken(securityContext);
for (String role : roles) {
log.info("Bearer role: " + role);
}
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal);
request.setAuthType("KEYCLOAK");
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
protected Set<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
Set<String> roles = null;
if (deployment.isUseResourceRoleMappings()) {
AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
if (access != null) roles = access.getRoles();
} else {
AccessToken.Access access = session.getToken().getRealmAccess();
if (access != null) roles = access.getRoles();
}
if (roles == null) roles = Collections.emptySet();
return roles;
}
@Override
protected boolean isCached() {
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
return false;
log.finer("remote logged in already");
GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
request.setUserPrincipal(principal);
request.setAuthType("KEYCLOAK");
Session session = request.getSessionInternal();
if (session != null) {
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
if (securityContext != null) {
securityContext.setDeployment(deployment);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
}
restoreRequest();
return true;
}
protected void restoreRequest() {
if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
if (valve.keycloakRestoreRequest(request)) {
log.finer("restoreRequest");
} else {
log.finer("Restore of original request failed");
throw new RuntimeException("Restore of original request failed");
}
}
}
}

View file

@ -0,0 +1,131 @@
package org.keycloak.adapters.tomcat7;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
import org.apache.catalina.Realm;
import org.apache.catalina.realm.GenericPrincipal;
import org.keycloak.KeycloakSecurityContext;
/**
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class CatalinaSecurityContextHelper {
public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet, final KeycloakSecurityContext securityContext) {
// KeycloakAccount account = new KeycloakAccount() {
// @Override
// public Principal getPrincipal() {
// return identity;
// }
//
// @Override
// public Set<String> getRoles() {
// return roleSet;
// }
//
// @Override
// public KeycloakSecurityContext getKeycloakSecurityContext() {
// return securityContext;
// }
// };
Subject subject = new Subject();
Set<Principal> principals = subject.getPrincipals();
principals.add(identity);
Group[] roleSets = getRoleSets(roleSet);
for (int g = 0; g < roleSets.length; g++) {
Group group = roleSets[g];
String name = group.getName();
Group subjectGroup = createGroup(name, principals);
// if (subjectGroup instanceof NestableGroup) {
// /* A NestableGroup only allows Groups to be added to it so we
// need to add a SimpleGroup to subjectRoles to contain the roles
// */
// SimpleGroup tmp = new SimpleGroup("Roles");
// subjectGroup.addMember(tmp);
// subjectGroup = tmp;
// }
// Copy the group members to the Subject group
Enumeration<? extends Principal> members = group.members();
while (members.hasMoreElements()) {
Principal role = (Principal) members.nextElement();
subjectGroup.addMember(role);
}
}
Principal userPrincipal = getPrincipal(subject);
List<String> rolesAsStringList = new ArrayList<String>();
rolesAsStringList.addAll(roleSet);
GenericPrincipal principal = new GenericPrincipal(userPrincipal.getName(), null, rolesAsStringList, userPrincipal, null);
return principal;
}
/**
* Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
* considered or the single subject inside the CallerPrincipal group.
*
* @param subject
* @return the authenticated subject
*/
protected Principal getPrincipal(Subject subject) {
Principal principal = null;
Principal callerPrincipal = null;
if (subject != null) {
Set<Principal> principals = subject.getPrincipals();
if (principals != null && !principals.isEmpty()) {
for (Principal p : principals) {
if (!(p instanceof Group) && principal == null) {
principal = p;
}
// if (p instanceof Group) {
// Group g = Group.class.cast(p);
// if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
// Enumeration<? extends Principal> e = g.members();
// if (e.hasMoreElements())
// callerPrincipal = e.nextElement();
// }
// }
}
}
}
return callerPrincipal == null ? principal : callerPrincipal;
}
protected Group createGroup(String name, Set<Principal> principals) {
Group roles = null;
Iterator<Principal> iter = principals.iterator();
while (iter.hasNext()) {
Object next = iter.next();
if ((next instanceof Group) == false)
continue;
Group grp = (Group) next;
if (grp.getName().equals(name)) {
roles = grp;
break;
}
}
// If we did not find a group create one
if (roles == null) {
roles = new SimpleGroup(name);
principals.add(roles);
}
return roles;
}
protected Group[] getRoleSets(Collection<String> roleSet) {
SimpleGroup roles = new SimpleGroup("Roles");
Group[] roleSets = {roles};
for (String role : roleSet) {
roles.addMember(new SimplePrincipal(role));
}
return roleSets;
}
}

View file

@ -0,0 +1,137 @@
package org.keycloak.adapters.tomcat7;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener;
import org.apache.catalina.realm.GenericPrincipal;
import org.keycloak.adapters.UserSessionManagement;
/**
* Manages relationship to users and sessions so that forced admin logout can be implemented
*
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
public static class UserSessions {
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
protected long loggedIn = System.currentTimeMillis();
public Map<String, Session> getSessions() {
return sessions;
}
public long getLoggedIn() {
return loggedIn;
}
}
@Override
public int getActiveSessions() {
int active = 0;
synchronized (userSessionMap) {
for (UserSessions sessions : userSessionMap.values()) {
active += sessions.getSessions().size();
}
}
return active;
}
/**
*
* @param username
* @return null if user not logged in
*/
@Override
public Long getUserLoginTime(String username) {
UserSessions sessions = userSessionMap.get(username);
if (sessions == null) return null;
return sessions.getLoggedIn();
}
@Override
public Set<String> getActiveUsers() {
HashSet<String> set = new HashSet<String>();
set.addAll(userSessionMap.keySet());
return set;
}
protected void login(Session session, String username) {
synchronized (userSessionMap) {
UserSessions userSessions = userSessionMap.get(username);
if (userSessions == null) {
userSessions = new UserSessions();
userSessionMap.put(username, userSessions);
}
userSessions.getSessions().put(session.getId(), session);
}
session.addSessionListener(this);
}
@Override
public void logoutAll() {
List<String> users = new ArrayList<String>();
users.addAll(userSessionMap.keySet());
for (String user : users) logout(user);
}
@Override
public void logout(String user) {
log.finer("logoutUser: " + user);
UserSessions sessions = null;
synchronized (userSessionMap) {
sessions = userSessionMap.remove(user);
}
if (sessions == null) {
log.finer("no session for user: " + user);
return;
}
log.finer("found session for user");
for (Session session : sessions.getSessions().values()) {
session.setPrincipal(null);
session.setAuthType(null);
session.getSession().invalidate();
}
}
public void sessionEvent(SessionEvent event) {
// We only care about session destroyed events
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
&& (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType())))
return;
// Look up the single session id associated with this session (if any)
Session session = event.getSession();
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
if (principal == null) return;
session.setPrincipal(null);
session.setAuthType(null);
String username = principal.getUserPrincipal().getName();
synchronized (userSessionMap) {
UserSessions sessions = userSessionMap.get(username);
if (sessions != null) {
sessions.getSessions().remove(session.getId());
if (sessions.getSessions().isEmpty()) {
userSessionMap.remove(username);
}
}
}
}
}

View file

@ -0,0 +1,59 @@
package org.keycloak.adapters.tomcat7;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.keycloak.adapters.KeycloakDeployment;
/**
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class CorsPreflightChecker {
private static final Logger log = Logger.getLogger(""+CorsPreflightChecker.class);
protected KeycloakDeployment deployment;
public CorsPreflightChecker(KeycloakDeployment deployment) {
this.deployment = deployment;
}
public boolean checkCorsPreflight(Request request, Response response) {
log.finer("checkCorsPreflight " + request.getRequestURI());
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
log.finer("checkCorsPreflight: not options ");
return false;
}
if (request.getHeader("Origin") == null) {
log.finer("checkCorsPreflight: no origin header");
return false;
}
log.finer("Preflight request returning");
response.setStatus(HttpServletResponse.SC_OK);
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
String requestMethods = request.getHeader("Access-Control-Request-Method");
if (requestMethods != null) {
if (deployment.getCorsAllowedMethods() != null) {
requestMethods = deployment.getCorsAllowedMethods();
}
response.setHeader("Access-Control-Allow-Methods", requestMethods);
}
String allowHeaders = request.getHeader("Access-Control-Request-Headers");
if (allowHeaders != null) {
if (deployment.getCorsAllowedHeaders() != null) {
allowHeaders = deployment.getCorsAllowedHeaders();
}
response.setHeader("Access-Control-Allow-Headers", allowHeaders);
}
if (deployment.getCorsMaxAge() > -1) {
response.setHeader("Access-Control-Max-Age", Integer.toString(deployment.getCorsMaxAge()));
}
return true;
}
}

View file

@ -0,0 +1,178 @@
package org.keycloak.adapters.tomcat7;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
/**
* Web deployment whose security is managed by a remote OAuth Skeleton Key
* authentication server
* <p/>
* Redirects browser to remote authentication server if not logged in. Also
* allows OAuth Bearer Token requests that contain a Skeleton Key bearer tokens.
*
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected KeycloakDeployment deployment;
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType() == Lifecycle.START_EVENT) {
try {
startDeployment();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
public void startDeployment() throws LifecycleException {
super.start();
StandardContext standardContext = (StandardContext) context;
standardContext.addLifecycleListener(this);
cache = false;
}
public void initInternal() {
this.deployment = KeycloakDeploymentBuilder.build(getConfigInputStream(context));
log.info("deployment realm:" + deployment.getRealm() + " resource:" + deployment.getResourceName());
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deployment, getNext(), getContainer(), getObjectName());
setNext(actions);
}
private static InputStream getJSONFromServletContext(ServletContext servletContext) {
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
if (json == null) {
return null;
}
log.info("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME);
log.info(json);
return new ByteArrayInputStream(json.getBytes());
}
private static InputStream getConfigInputStream(Context context) {
InputStream is = getJSONFromServletContext(context.getServletContext());
if (is == null) {
String path = context.getServletContext().getInitParameter("keycloak.config.file");
if (path == null) {
log.info("**** using /WEB-INF/keycloak.json");
is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
log.severe("NOT FOUND /WEB-INF/keycloak.json");
throw new RuntimeException(e);
}
}
}
return is;
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment,
new CatalinaHttpFacade(request, response));
if (handler.handleRequest()) {
return;
}
checkKeycloakSession(request);
super.invoke(request, response);
} finally {
}
}
@Override
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
return false;
}
return true;
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
return false;
}
/**
* Checks that access token is still valid. Will attempt refresh of token if
* it is not.
*
* @param request
*/
protected void checkKeycloakSession(Request request) {
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
return;
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal()
.getNote(KeycloakSecurityContext.class.getName());
if (session == null)
return;
// just in case session got serialized
session.setDeployment(deployment);
if (session.isActive())
return;
// FYI: A refresh requires same scope, so same roles will be set.
// Otherwise, refresh will fail and token will
// not be updated
session.refreshExpiredToken();
if (session.isActive())
return;
request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
request.setUserPrincipal(null);
request.setAuthType(null);
request.getSessionInternal().setPrincipal(null);
request.getSessionInternal().setAuthType(null);
}
public void keycloakSaveRequest(Request request) throws IOException {
saveRequest(request, request.getSessionInternal(true));
}
public boolean keycloakRestoreRequest(Request request) {
try {
return restoreRequest(request, request.getSessionInternal());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,42 @@
package org.keycloak.adapters.tomcat7;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
public class SimpleGroup extends SimplePrincipal implements Group {
private static final long serialVersionUID = 3273437693505893786L;
private final Set<Principal> members = new HashSet<Principal>();
/**
* Creates a new group with the given name.
* @param name Group name.
*/
public SimpleGroup(final String name) {
super(name);
}
public boolean addMember(final Principal user) {
return this.members.add(user);
}
public boolean isMember(final Principal member) {
return this.members.contains(member);
}
public Enumeration<? extends Principal> members() {
return Collections.enumeration(this.members);
}
public boolean removeMember(final Principal user) {
return this.members.remove(user);
}
public String toString() {
return super.toString() + ": " + members.toString();
}
}

View file

@ -0,0 +1,51 @@
package org.keycloak.adapters.tomcat7;
import java.io.Serializable;
import java.security.Principal;
/**
* Simple security principal implementation.
*
* @author Marvin S. Addison
* @version $Revision: 22071 $
* @since 3.1.11
*
*/
public class SimplePrincipal implements Principal, Serializable {
/** SimplePrincipal.java */
private static final long serialVersionUID = -5645357206342793145L;
/** The unique identifier for this principal. */
private final String name;
/**
* Creates a new principal with the given name.
* @param name Principal name.
*/
public SimplePrincipal(final String name) {
this.name = name;
}
public final String getName() {
return this.name;
}
public String toString() {
return getName();
}
public boolean equals(final Object o) {
if (o == null) {
return false;
} else if (!(o instanceof SimplePrincipal)) {
return false;
} else {
return getName().equals(((SimplePrincipal)o).getName());
}
}
public int hashCode() {
return 37 * getName().hashCode();
}
}