#dev integrated adapter tomcat7
This commit is contained in:
parent
80f3a3152c
commit
5d275de8b2
12 changed files with 1053 additions and 1 deletions
|
@ -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>
|
||||
|
|
88
integration/tomcat7/adapter/pom.xml
Executable file
88
integration/tomcat7/adapter/pom.xml
Executable 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>
|
|
@ -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";
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue