adapter cors support

This commit is contained in:
Bill Burke committed 2013-10-30 08:28:23 -04:00
1 parent 0f03670c06
commit d6ee6aef9f
48 files changed
+867 -125

No files matched your search

@@ -0,0 +1,123 @@
package org.keycloak.adapters.as7;
import org.apache.catalina.Container;
import org.apache.catalina.Session;
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.jboss.logging.Logger;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.SkeletonKeySession;
import org.keycloak.adapters.as7.config.ManagedResourceConfig;
import org.keycloak.representations.SkeletonKeyToken;
import javax.management.ObjectName;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Set;
/**
* Pre-installed actions that must be authenticated
*
* Actions include:
*
* CORS Origin Check and Response headers
* K_QUERY_BEARER_TOKEN: Get bearer token from server for Javascripts CORS requests
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AuthenticatedActionsValve extends ValveBase {
private static final Logger log = Logger.getLogger(AuthenticatedActionsValve.class);
protected ManagedResourceConfig config;
public AuthenticatedActionsValve(ManagedResourceConfig config, Valve next, Container container, ObjectName controller) {
this.config = config;
if (next == null) throw new RuntimeException("WTF is next null?!");
setNext(next);
setContainer(container);
setController(controller);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
SkeletonKeySession session = getSkeletonKeySession(request);
if (corsRequest(request, response, session)) return;
String requestUri = request.getRequestURI();
if (requestUri.endsWith("K_QUERY_BEARER_TOKEN")) {
queryBearerToken(request, response, session);
return;
}
getNext().invoke(request, response);
}
public SkeletonKeySession getSkeletonKeySession(Request request) {
SkeletonKeySession skSession = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
if (skSession != null) return skSession;
Session session = request.getSessionInternal();
if (session != null) {
return (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
}
return null;
}
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
log.debugv("queryBearerToken {0}",request.getRequestURI());
if (abortTokenResponse(request, response, session)) return;
response.setContentType("text/plain");
response.getOutputStream().write(session.getTokenString().getBytes());
response.getOutputStream().flush();
}
protected boolean abortTokenResponse(Request request, Response response, SkeletonKeySession session) throws IOException {
if (session == null) {
log.debugv("session was null, sending back 401: {0}",request.getRequestURI());
response.sendError(401);
return true;
}
if (!config.isExposeToken()) {
response.setStatus(200);
return true;
}
if (!config.isCors() && request.getHeader("Origin") != null) {
response.setStatus(200);
return true;
}
return false;
}
protected boolean corsRequest(Request request, Response response, SkeletonKeySession session) throws IOException {
if (!config.isCors()) return false;
log.debugv("CORS enabled + request.getRequestURI()");
String origin = request.getHeader("Origin");
log.debugv("Origin: {0} uri: {1}", origin, request.getRequestURI());
if (session != null && origin != null) {
SkeletonKeyToken token = session.getToken();
Set<String> allowedOrigins = token.getAllowedOrigins();
if (log.isDebugEnabled()) {
for (String a : allowedOrigins) log.debug(" " + a);
}
if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
if (allowedOrigins == null) {
log.debugv("allowedOrigins was null in token");
}
if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
log.debugv("allowedOrigins did not contain origin");
}
response.sendError(403);
return true;
}
log.debugv("returning origin: {0}", origin);
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
} else {
log.debugv("session or origin was null: {0}", request.getRequestURI());
}
return false;
}
}
@@ -49,11 +49,17 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
managedResourceConfigLoader.init(false);
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(remoteSkeletonKeyConfig, getNext(), getContainer(), getController());
setNext(actions);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
log.debugv("{0} {1}", request.getMethod(), request.getRequestURI());
if (remoteSkeletonKeyConfig.isCors() && new CorsPreflightChecker(remoteSkeletonKeyConfig).checkCorsPreflight(request, response)) {
return;
}
super.invoke(request, response);
} finally {
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
@@ -63,7 +69,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
@Override
protected boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
try {
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, !remoteSkeletonKeyConfig.isCancelPropagation(), true, remoteSkeletonKeyConfig.isUseResourceRoleMappings());
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, remoteSkeletonKeyConfig.isUseResourceRoleMappings());
if (bearer.login(request, response)) {
return true;
}
@@ -29,13 +29,11 @@ public class CatalinaBearerTokenAuthenticator {
protected String tokenString;
protected SkeletonKeyToken token;
private Principal principal;
protected boolean propagateToken;
protected boolean useResourceRoleMappings;
public CatalinaBearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean propagateToken, boolean challenge, boolean useResourceRoleMappings) {
public CatalinaBearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean challenge, boolean useResourceRoleMappings) {
this.resourceMetadata = resourceMetadata;
this.challenge = challenge;
this.propagateToken = propagateToken;
this.useResourceRoleMappings = useResourceRoleMappings;
}
@@ -109,11 +107,9 @@ public class CatalinaBearerTokenAuthenticator {
principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skeletonKeyPrincipal, roles);
request.setUserPrincipal(principal);
request.setAuthType("OAUTH_BEARER");
if (propagateToken) {
SkeletonKeySession skSession = new SkeletonKeySession(tokenString, resourceMetadata);
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
}
SkeletonKeySession skSession = new SkeletonKeySession(tokenString, token, resourceMetadata);
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
return true;
}
@@ -0,0 +1,56 @@
package org.keycloak.adapters.as7;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.jboss.logging.Logger;
import org.keycloak.adapters.as7.config.ManagedResourceConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CorsPreflightChecker {
private static final Logger log = Logger.getLogger(CorsPreflightChecker.class);
protected ManagedResourceConfig config;
public CorsPreflightChecker(ManagedResourceConfig config) {
this.config = config;
}
public boolean checkCorsPreflight(Request request, Response response) {
log.debugv("checkCorsPreflight {0}", request.getRequestURI());
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
log.debug("checkCorsPreflight: not options ");
return false;
}
if (request.getHeader("Origin") == null) {
log.debug("checkCorsPreflight: no origin header");
return false;
}
log.debug("Preflight request returning");
response.setStatus(200);
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 (config.getCorsAllowedMethods() != null) {
requestMethods = config.getCorsAllowedMethods();
}
response.setHeader("Access-Control-Allow-Methods", requestMethods);
}
String allowHeaders = request.getHeader("Access-Control-Request-Headers");
if (allowHeaders != null) {
if (config.getCorsAllowedHeaders() != null) {
allowHeaders = config.getCorsAllowedHeaders();
}
response.setHeader("Access-Control-Allow-Headers", allowHeaders);
}
if (config.getCorsMaxAge() > -1) {
response.setHeader("Access-Control-Max-Age", Integer.toString(config.getCorsMaxAge()));
}
return true;
}
}
@@ -561,7 +561,7 @@ public class OAuthAuthenticationServerValve extends FormAuthenticator implements
public boolean bearer(Request request, HttpServletResponse response, boolean propagate) throws IOException {
if (request.getHeader("Authorization") != null) {
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, false, false);
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, false, false);
try {
if (bearer.login(request, response)) {
return true;
@@ -582,7 +582,7 @@ public class OAuthAuthenticationServerValve extends FormAuthenticator implements
if (gp != null) {
SkeletonKeyToken token = buildToken(gp);
String stringToken = buildTokenString(realmPrivateKey, token);
SkeletonKeySession skSession = new SkeletonKeySession(stringToken, resourceMetadata);
SkeletonKeySession skSession = new SkeletonKeySession(stringToken, token, resourceMetadata);
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
request.getSessionInternal(true).setNote(SkeletonKeySession.class.getName(), skSession);
@@ -70,6 +70,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
managedResourceConfigLoader.init(true);
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
realmConfiguration = new RealmConfiguration();
String authUrl = remoteSkeletonKeyConfig.getAuthUrl();
if (authUrl == null) {
@@ -91,11 +92,16 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
realmConfiguration.setClient(client);
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
realmConfiguration.setCodeUrl(client.target(tokenUrl));
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(remoteSkeletonKeyConfig, getNext(), getContainer(), getController());
setNext(actions);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
if (remoteSkeletonKeyConfig.isCors() && new CorsPreflightChecker(remoteSkeletonKeyConfig).checkCorsPreflight(request, response)) {
return;
}
String requestURI = request.getDecodedRequestURI();
if (requestURI.endsWith("j_admin_request")) {
adminRequest(request, response);
@@ -163,7 +169,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException {
try {
log.info("->> remoteLogout: ");
log.debug("->> remoteLogout: ");
LogoutAction action = JsonSerialization.fromBytes(LogoutAction.class, token.getContent());
if (action.isExpired()) {
log.warn("admin request failed, expired token");
@@ -183,10 +189,10 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
}
String user = action.getUser();
if (user != null) {
log.info("logout of session for: " + user);
log.debug("logout of session for: " + user);
userSessionManagement.logout(user);
} else {
log.info("logout of all sessions");
log.debug("logout of all sessions");
userSessionManagement.logoutAll();
}
} catch (Exception e) {
@@ -197,7 +203,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
}
protected boolean bearer(boolean challenge, Request request, HttpServletResponse response) throws LoginException, IOException {
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(realmConfiguration.getMetadata(), !remoteSkeletonKeyConfig.isCancelPropagation(), challenge, remoteSkeletonKeyConfig.isUseResourceRoleMappings());
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(realmConfiguration.getMetadata(), challenge, remoteSkeletonKeyConfig.isUseResourceRoleMappings());
if (bearer.login(request, response)) {
return true;
}
@@ -212,7 +218,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
request.setUserPrincipal(principal);
request.setAuthType("OAUTH");
Session session = request.getSessionInternal();
if (session != null && !remoteSkeletonKeyConfig.isCancelPropagation()) {
if (session != null) {
SkeletonKeySession skSession = (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
if (skSession != null) {
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
@@ -256,10 +262,8 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
Session session = request.getSessionInternal(true);
session.setPrincipal(principal);
session.setAuthType("OAUTH");
if (!remoteSkeletonKeyConfig.isCancelPropagation()) {
SkeletonKeySession skSession = new SkeletonKeySession(oauth.getTokenString(), realmConfiguration.getMetadata());
session.setNote(SkeletonKeySession.class.getName(), skSession);
}
SkeletonKeySession skSession = new SkeletonKeySession(oauth.getTokenString(), token, realmConfiguration.getMetadata());
session.setNote(SkeletonKeySession.class.getName(), skSession);
String username = token.getPrincipal();
log.debug("userSessionManage.login: " + username);
@@ -76,7 +76,7 @@ public class ServletOAuthLogin {
protected void sendRedirect(String url) {
try {
log.info("Sending redirect to: " + url);
log.debugv("Sending redirect to: {0}", url);
response.sendRedirect(url);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -175,7 +175,7 @@ public class ServletOAuthLogin {
return false;
}
// reset the cookie
log.info("** reseting application state cookie");
log.debug("** reseting application state cookie");
Cookie reset = new Cookie(realmInfo.getStateCookieName(), "");
reset.setPath(getDefaultCookiePath());
reset.setMaxAge(0);
@@ -257,7 +257,7 @@ public class ServletOAuthLogin {
tokenString = tokenResponse.getToken();
try {
token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata());
log.info("Token Verification succeeded!");
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");
sendError(Response.Status.FORBIDDEN.getStatusCode());
@@ -5,6 +5,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -49,8 +50,16 @@ public class ManagedResourceConfig {
protected Map<String, String> credentials = new HashMap<String, String>();
@JsonProperty("connection-pool-size")
protected int connectionPoolSize;
@JsonProperty("cancel-propagation")
protected boolean cancelPropagation;
@JsonProperty("enable-cors")
protected boolean cors;
@JsonProperty("cors-max-age")
protected int corsMaxAge = -1;
@JsonProperty("cors-allowed-headers")
protected String corsAllowedHeaders;
@JsonProperty("cors-allowed-methods")
protected String corsAllowedMethods;
@JsonProperty("expose-token")
protected boolean exposeToken;
public boolean isUseResourceRoleMappings() {
return useResourceRoleMappings;
@@ -184,14 +193,6 @@ public class ManagedResourceConfig {
this.connectionPoolSize = connectionPoolSize;
}
public boolean isCancelPropagation() {
return cancelPropagation;
}
public void setCancelPropagation(boolean cancelPropagation) {
this.cancelPropagation = cancelPropagation;
}
public String getAdminRole() {
return adminRole;
}
@@ -199,4 +200,44 @@ public class ManagedResourceConfig {
public void setAdminRole(String adminRole) {
this.adminRole = adminRole;
}
public boolean isCors() {
return cors;
}
public void setCors(boolean cors) {
this.cors = cors;
}
public int getCorsMaxAge() {
return corsMaxAge;
}
public void setCorsMaxAge(int corsMaxAge) {
this.corsMaxAge = corsMaxAge;
}
public String getCorsAllowedHeaders() {
return corsAllowedHeaders;
}
public void setCorsAllowedHeaders(String corsAllowedHeaders) {
this.corsAllowedHeaders = corsAllowedHeaders;
}
public String getCorsAllowedMethods() {
return corsAllowedMethods;
}
public void setCorsAllowedMethods(String corsAllowedMethods) {
this.corsAllowedMethods = corsAllowedMethods;
}
public boolean isExposeToken() {
return exposeToken;
}
public void setExposeToken(boolean exposeToken) {
this.exposeToken = exposeToken;
}
}