keycloak-uncached
Changes
integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java 62(+27 -35)
integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 132(+57 -75)
integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java 8(+4 -4)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java 133(+0 -133)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java 136(+136 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java 118(+118 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java 108(+23 -85)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java 301(+0 -301)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java 4(+0 -4)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java 133(+0 -133)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java 10(+3 -7)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java 8(+4 -4)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java 60(+60 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletOAuthAuthenticator.java 23(+0 -23)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java 40(+20 -20)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java 127(+127 -0)
Details
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java
new file mode 100755
index 0000000..4682808
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthChallenge.java
@@ -0,0 +1,14 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AuthChallenge {
+ /**
+ *
+ * @param exchange
+ * @return challenge sent
+ */
+ boolean challenge(HttpFacade exchange);
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthOutcome.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthOutcome.java
new file mode 100755
index 0000000..e19dfa0
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AuthOutcome.java
@@ -0,0 +1,11 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public enum AuthOutcome {
+ NOT_ATTEMPTED,
+ FAILED,
+ AUTHENTICATED
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
new file mode 100755
index 0000000..25b9a7f
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/HttpFacade.java
@@ -0,0 +1,87 @@
+package org.keycloak.adapters;
+
+import javax.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Bridge between core adapter and HTTP Engine
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface HttpFacade {
+
+ public class Cookie {
+ protected String name;
+ protected String value;
+ protected int version;
+ protected String domain;
+ protected String path;
+
+ public Cookie(String name, String value, int version, String domain, String path) {
+ this.name = name;
+ this.value = value;
+ this.version = version;
+ this.domain = domain;
+ this.path = path;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public String getPath() {
+ return path;
+ }
+ }
+
+ interface Request {
+ /**
+ * Full request URI with query params
+ *
+ * @return
+ */
+ String getURI();
+
+ /**
+ * HTTPS?
+ *
+ * @return
+ */
+ boolean isSecure();
+
+ String getQueryParamValue(String param);
+ Cookie getCookie(String cookieName);
+ List<String> getHeaders(String name);
+ }
+
+ interface Response {
+ void setStatus(int status);
+ void addHeader(String name, String value);
+ void setHeader(String name, String value);
+ void resetCookie(String name, String path);
+ void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly);
+
+ /**
+ * If the response is finished, end it.
+ *
+ */
+ void end();
+ }
+
+ Request getRequest();
+ Response getResponse();
+ X509Certificate[] getCertificateChain();
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
new file mode 100755
index 0000000..f0d697c
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -0,0 +1,95 @@
+package org.keycloak.adapters;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class RequestAuthenticator {
+ protected Logger log = Logger.getLogger(RequestAuthenticator.class);
+
+ protected HttpFacade facade;
+ protected KeycloakDeployment deployment;
+ protected AuthChallenge challenge;
+ protected int sslRedirectPort;
+
+ public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+ this.facade = facade;
+ this.deployment = deployment;
+ this.sslRedirectPort = sslRedirectPort;
+ }
+
+ public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment) {
+ this.facade = facade;
+ this.deployment = deployment;
+ }
+
+ public AuthChallenge getChallenge() {
+ return challenge;
+ }
+
+ public AuthOutcome authenticate() {
+ log.info("--> authenticate()");
+ BearerTokenRequestAuthenticator bearer = createBearerTokenAuthenticator();
+ AuthOutcome outcome = bearer.authenticate(facade);
+ if (outcome == AuthOutcome.FAILED) {
+ challenge = bearer.getChallenge();
+ return AuthOutcome.FAILED;
+ } else if (outcome == AuthOutcome.AUTHENTICATED) {
+ completeAuthentication(bearer);
+ return AuthOutcome.AUTHENTICATED;
+ } else if (deployment.isBearerOnly()) {
+ challenge = bearer.getChallenge();
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+
+ if (isCached()) {
+ return AuthOutcome.AUTHENTICATED;
+ }
+
+ OAuthRequestAuthenticator oauth = createOAuthAuthenticator();
+ outcome = oauth.authenticate();
+ if (outcome == AuthOutcome.FAILED) {
+ challenge = oauth.getChallenge();
+ return AuthOutcome.FAILED;
+ } else if (outcome == AuthOutcome.NOT_ATTEMPTED) {
+ challenge = oauth.getChallenge();
+ return AuthOutcome.NOT_ATTEMPTED;
+
+ }
+ completeAuthentication(oauth);
+
+ // redirect to strip out access code and state query parameters
+ facade.getResponse().setHeader("Location", oauth.getStrippedOauthParametersRequestUri());
+ facade.getResponse().setStatus(302);
+ facade.getResponse().end();
+
+ log.info("AUTHENTICATED");
+ return AuthOutcome.AUTHENTICATED;
+ }
+
+ protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
+
+ protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
+ return new BearerTokenRequestAuthenticator(deployment);
+ }
+
+ protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
+ final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), null);
+ RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
+ completeOAuthAuthentication(principal, session);
+ }
+
+ protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session);
+ protected abstract void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session);
+ protected abstract boolean isCached();
+
+ protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
+ final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), bearer.getSurrogate());
+ RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);
+ completeBearerAuthentication(principal, session);
+ }
+
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
new file mode 100755
index 0000000..62adcb6
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaHttpFacade.java
@@ -0,0 +1,136 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.keycloak.adapters.HttpFacade;
+
+import javax.security.cert.X509Certificate;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * @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;
+ }
+ }
+
+ 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 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 X509Certificate[] getCertificateChain() {
+ throw new IllegalStateException("Not supported yet");
+ }
+
+ public boolean isEnded() {
+ return responseFacade.isEnded();
+ }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
new file mode 100755
index 0000000..143df34
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -0,0 +1,118 @@
+package org.keycloak.adapters.as7;
+
+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;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaRequestAuthenticator extends RequestAuthenticator {
+ protected KeycloakAuthenticatorValve valve;
+ protected UserSessionManagement userSessionManagement;
+ protected Request request;
+
+ public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
+ KeycloakAuthenticatorValve valve, UserSessionManagement 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);
+ Session session = request.getSessionInternal(true);
+ session.setPrincipal(principal);
+ session.setAuthType("OAUTH");
+ session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+ String username = securityContext.getToken().getSubject();
+ log.debug("userSessionManage.login: " + username);
+ userSessionManagement.login(session, username);
+ }
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) {
+ Set<String> roles = getRolesFromToken(securityContext);
+ Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles);
+ 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.debug("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.debug("restoreRequest");
+ } else {
+ log.debug("Restore of original request failed");
+ throw new RuntimeException("Restore of original request failed");
+ }
+ }
+ }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
index ab380aa..2012c66 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java
@@ -17,9 +17,11 @@ import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.KeycloakPrincipal;
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.RefreshableKeycloakSession;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
@@ -154,29 +156,21 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
@Override
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
- try {
- if (bearer(false, request, response)) return true;
- else if (checkLoggedIn(request, response)) {
- if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
- if (restoreRequest(request, request.getSessionInternal())) {
- log.debug("restoreRequest");
- return (true);
- } else {
- log.debug("Restore of original request failed");
- response.sendError(HttpServletResponse.SC_BAD_REQUEST);
- return (false);
- }
- } else {
- return true;
- }
+ 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;
}
-
- // initiate or continue oauth2 protocol
- if (!deployment.isBearerOnly()) oauth(request, response);
- } catch (LoginException e) {
+ return true;
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ challenge.challenge(facade);
}
return false;
- }
+ }
protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = StreamUtil.readString(request.getInputStream());
@@ -303,15 +297,6 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
- protected boolean bearer(boolean challenge, Request request, HttpServletResponse response) throws LoginException, IOException {
- boolean useResourceRoleMappings = deployment.isUseResourceRoleMappings();
- CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(deployment, challenge);
- if (bearer.login(request, response)) {
- return true;
- }
- return false;
- }
-
/**
* Checks that access token is still valid. Will attempt refresh of token if it is not.
*
@@ -319,7 +304,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
*/
protected void checkKeycloakSession(Request request) {
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
- RefreshableKeycloakSession session = (RefreshableKeycloakSession)request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+ RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext)request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (session == null) return;
// just in case session got serialized
session.setDeployment(deployment);
@@ -337,62 +322,15 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
request.getSessionInternal().setAuthType(null);
}
- protected boolean checkLoggedIn(Request request, HttpServletResponse response) {
- if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
- return false;
- log.debug("remote logged in already");
- GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
- request.setUserPrincipal(principal);
- request.setAuthType("KEYCLOAK");
- Session session = request.getSessionInternal();
- if (session != null) {
- KeycloakSecurityContext skSession = (KeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
- if (skSession != null) {
- request.setAttribute(KeycloakSecurityContext.class.getName(), skSession);
- }
- }
- return true;
+ public void keycloakSaveRequest(Request request) throws IOException {
+ saveRequest(request, request.getSessionInternal(true));
}
- /**
- * This method always set the HTTP response, so do not continue after invoking
- */
- protected void oauth(Request request, HttpServletResponse response) throws IOException {
- ServletOAuthLogin oauth = new ServletOAuthLogin(deployment, request, response, request.getConnector().getRedirectPort());
- String code = oauth.getCode();
- if (code == null) {
- String error = oauth.getError();
- if (error != null) {
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, "OAuth " + error);
- return;
- } else {
- saveRequest(request, request.getSessionInternal(true));
- oauth.loginRedirect();
- }
- return;
- } else {
- if (!oauth.resolveCode(code)) return;
-
- AccessToken token = oauth.getToken();
- Set<String> roles = new HashSet<String>();
- if (deployment.isUseResourceRoleMappings()) {
- AccessToken.Access access = token.getResourceAccess(deployment.getResourceName());
- if (access != null) roles.addAll(access.getRoles());
- } else {
- AccessToken.Access access = token.getRealmAccess();
- if (access != null) roles.addAll(access.getRoles());
- }
- KeycloakPrincipal skp = new KeycloakPrincipal(token.getSubject(), null);
- GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(context.getRealm(), skp, roles);
- Session session = request.getSessionInternal(true);
- session.setPrincipal(principal);
- session.setAuthType("OAUTH");
- KeycloakSecurityContext skSession = new RefreshableKeycloakSession(deployment, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
- session.setNote(KeycloakSecurityContext.class.getName(), skSession);
-
- String username = token.getSubject();
- log.debug("userSessionManage.login: " + username);
- userSessionManagement.login(session, username);
+ public boolean keycloakRestoreRequest(Request request) {
+ try {
+ return restoreRequest(request, request.getSessionInternal());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java
index 5e829bf..6d23569 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java
@@ -38,10 +38,6 @@ public class UserSessionManagement implements SessionListener {
}
}
- public int getNumUserLogins() {
- return userSessionMap.size();
- }
-
public int getActiveSessions() {
int active = 0;
synchronized (userSessionMap) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
index 7b630c1..9b0a911 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -65,22 +65,18 @@ public class KeycloakServletExtension implements ServletExtension {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
PreflightCorsHandler.Wrapper preflight = new PreflightCorsHandler.Wrapper(deployment);
UserSessionManagement userSessionManagement = new UserSessionManagement(deployment);
- ServletKeycloakAuthenticationMechanism auth = null;
- auth = new ServletKeycloakAuthenticationMechanism(
- userSessionManagement,
- deployment,
- deploymentInfo.getConfidentialPortManager());
+ final ServletKeycloakAuthMech mech = new ServletKeycloakAuthMech(deployment, userSessionManagement, deploymentInfo.getConfidentialPortManager());
+
AuthenticatedActionsHandler.Wrapper actions = new AuthenticatedActionsHandler.Wrapper(deployment);
// setup handlers
deploymentInfo.addInitialHandlerChainWrapper(preflight); // cors preflight
deploymentInfo.addOuterHandlerChainWrapper(new ServletAdminActionsHandler.Wrapper(deployment, userSessionManagement));
- final ServletKeycloakAuthenticationMechanism theAuth = auth;
deploymentInfo.addAuthenticationMechanism("KEYCLOAK", new AuthenticationMechanismFactory() {
@Override
public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
- return theAuth;
+ return mech;
}
}); // authentication
deploymentInfo.addInnerHandlerChainWrapper(actions); // handles authenticated actions and cors.
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
index ab5e514..f66cd01 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -4,7 +4,7 @@ import io.undertow.security.idm.Account;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.KeycloakDeployment;
-import org.keycloak.adapters.RefreshableKeycloakSession;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import java.io.Serializable;
@@ -18,11 +18,11 @@ import java.util.Set;
*/
public class KeycloakUndertowAccount implements Account, Serializable {
protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
- protected RefreshableKeycloakSession session;
+ protected RefreshableKeycloakSecurityContext session;
protected KeycloakPrincipal principal;
protected Set<String> accountRoles;
- public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSession session, KeycloakDeployment deployment) {
+ public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session, KeycloakDeployment deployment) {
this.principal = principal;
this.session = session;
setRoles(session.getToken(), deployment);
@@ -59,7 +59,7 @@ public class KeycloakUndertowAccount implements Account, Serializable {
return session.getTokenString();
}
- public RefreshableKeycloakSession getSession() {
+ public RefreshableKeycloakSecurityContext getSession() {
return session;
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
new file mode 100755
index 0000000..8160592
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -0,0 +1,60 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.api.ConfidentialPortManager;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletKeycloakAuthMech implements AuthenticationMechanism {
+ public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
+
+ protected KeycloakDeployment deployment;
+ protected UserSessionManagement userSessionManagement;
+ protected ConfidentialPortManager portManager;
+
+ public ServletKeycloakAuthMech(KeycloakDeployment deployment, UserSessionManagement userSessionManagement, ConfidentialPortManager portManager) {
+ this.deployment = deployment;
+ this.userSessionManagement = userSessionManagement;
+ this.portManager = portManager;
+ }
+
+ @Override
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+ ServletRequestAuthenticator authenticator = new ServletRequestAuthenticator(facade, deployment,
+ portManager.getConfidentialPort(exchange), securityContext, exchange, userSessionManagement);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, challenge);
+ }
+
+ if (outcome == AuthOutcome.FAILED) {
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ @Override
+ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+ if (challenge != null) {
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+ if (challenge.challenge(facade)) {
+ return new ChallengeResult(true, exchange.getResponseCode());
+ }
+ }
+ return new ChallengeResult(false);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
new file mode 100755
index 0000000..d08864d
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -0,0 +1,127 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.Cookie;
+import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.HttpString;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.util.KeycloakUriBuilder;
+
+import javax.security.cert.X509Certificate;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowHttpFacade implements HttpFacade {
+ protected HttpServerExchange exchange;
+ protected RequestFacade requestFacade = new RequestFacade();
+ protected ResponseFacade responseFacade = new ResponseFacade();
+
+ protected class RequestFacade implements Request {
+ @Override
+ public String getURI() {
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString());
+ if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+ return uriBuilder.build().toString();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return exchange.getProtocol().toString().equalsIgnoreCase("https");
+ }
+
+ @Override
+ public String getQueryParamValue(String param) {
+ Map<String,Deque<String>> queryParameters = exchange.getQueryParameters();
+ if (queryParameters == null) return null;
+ Deque<String> strings = queryParameters.get(param);
+ if (strings == null) return null;
+ return strings.getFirst();
+ }
+
+ @Override
+ public Cookie getCookie(String cookieName) {
+ Map<String, io.undertow.server.handlers.Cookie> requestCookies = exchange.getRequestCookies();
+ if (requestCookies == null) return null;
+ io.undertow.server.handlers.Cookie cookie = requestCookies.get(cookieName);
+ 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) {
+ return exchange.getRequestHeaders().get(name);
+ }
+ }
+
+ protected class ResponseFacade implements Response {
+ @Override
+ public void setStatus(int status) {
+ exchange.setResponseCode(status);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ exchange.getResponseHeaders().add(new HttpString(name), value);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ exchange.getResponseHeaders().put(new HttpString(name), value);
+ }
+
+ @Override
+ public void resetCookie(String name, String path) {
+ CookieImpl cookie = new CookieImpl(name, "");
+ cookie.setMaxAge(0);
+ cookie.setPath(path);
+ exchange.setResponseCookie(cookie);
+ }
+
+ @Override
+ public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+ CookieImpl cookie = new CookieImpl(name, value);
+ cookie.setPath(path);
+ cookie.setDomain(domain);
+ cookie.setMaxAge(maxAge);
+ cookie.setSecure(secure);
+ cookie.setHttpOnly(httpOnly);
+ exchange.setResponseCookie(cookie);
+ }
+
+ @Override
+ public void end() {
+ exchange.endExchange();
+ }
+ }
+
+ public UndertowHttpFacade(HttpServerExchange exchange) {
+ this.exchange = exchange;
+ }
+
+ @Override
+ public Request getRequest() {
+ return requestFacade;
+ }
+
+ @Override
+ public Response getResponse() {
+ return responseFacade;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain() {
+ X509Certificate[] chain = new X509Certificate[0];
+ try {
+ chain = exchange.getConnection().getSslSessionInfo().getPeerCertificateChain();
+ } catch (Exception ignore) {
+
+ }
+ return chain;
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
new file mode 100755
index 0000000..14b4f72
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -0,0 +1,63 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowRequestAuthenticator extends RequestAuthenticator {
+ protected SecurityContext securityContext;
+ protected HttpServerExchange exchange;
+
+
+ public UndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, SecurityContext securityContext, HttpServerExchange exchange) {
+ super(facade, deployment, sslRedirectPort);
+ this.securityContext = securityContext;
+ this.exchange = exchange;
+ }
+
+ protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
+ @Override
+ protected void saveRequest() {
+ // todo
+ }
+ };
+ }
+
+ @Override
+ protected void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) {
+ KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, deployment);
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ login(account);
+ }
+
+ protected void login(KeycloakUndertowAccount account) {
+
+ }
+
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) {
+ KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal, session, deployment);
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ propagateKeycloakContext(account);
+ }
+
+ @Override
+ protected boolean isCached() {
+ return false;
+ }
+}