adapter cors support
This commit is contained in:
1 parent
0f03670c06
commit
d6ee6aef9f
48 files changed
+867
-125
No files matched your search
Executable
+123
@@ -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;
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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;
|
||||
}
|
||||
|
||||
+4
-8
@@ -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;
|
||||
}
|
||||
|
||||
Executable
+56
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+13
-9
@@ -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);
|
||||
|
||||
+3
-3
@@ -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());
|
||||
|
||||
+51
-10
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user