diff --git a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
index 6ad29c7ebd..a4f7f51bc4 100755
--- a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
+++ b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
@@ -18,4 +18,10 @@ public interface AdapterConstants {
// org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in
// two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
+
+ // Attribute passed in codeToToken request from adapter to Keycloak and saved in ClientSession. Contains ID of HttpSession on adapter
+ public static final String HTTP_SESSION_ID = "http_session_id";
+
+ // Attribute passed in codeToToken request from adapter to Keycloak and saved in ClientSession. Contains hostname of adapter where HttpSession is served
+ public static final String HTTP_SESSION_HOST = "http_session_host";
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
index b89b10d6ff..a14b3f8340 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
@@ -1,40 +1,25 @@
package org.keycloak.representations.adapters.action;
+import java.util.List;
+
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
- protected String user;
- private String session;
+ protected List adapterSessionIds;
protected int notBefore;
public LogoutAction() {
}
- public LogoutAction(String id, int expiration, String resource, String user, String session, int notBefore) {
+ public LogoutAction(String id, int expiration, String resource, List adapterSessionIds, int notBefore) {
super(id, expiration, resource, LOGOUT);
- this.user = user;
- this.session = session;
+ this.adapterSessionIds = adapterSessionIds;
this.notBefore = notBefore;
}
- public String getUser() {
- return user;
- }
-
- public void setUser(String user) {
- this.user = user;
- }
-
- public String getSession() {
- return session;
- }
-
- public void setSession(String session) {
- this.session = session;
- }
public int getNotBefore() {
return notBefore;
@@ -44,6 +29,10 @@ public class LogoutAction extends AdminAction {
this.notBefore = notBefore;
}
+ public List getAdapterSessionIds() {
+ return adapterSessionIds;
+ }
+
@Override
public boolean validate() {
return LOGOUT.equals(action);
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/SessionStats.java b/core/src/main/java/org/keycloak/representations/adapters/action/SessionStats.java
deleted file mode 100755
index f6644335c6..0000000000
--- a/core/src/main/java/org/keycloak/representations/adapters/action/SessionStats.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.keycloak.representations.adapters.action;
-
-import java.util.Map;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class SessionStats {
- protected int activeSessions;
- protected int activeUsers;
- protected Map users;
-
- public int getActiveSessions() {
- return activeSessions;
- }
-
- public void setActiveSessions(int activeSessions) {
- this.activeSessions = activeSessions;
- }
-
- public int getActiveUsers() {
- return activeUsers;
- }
-
- public void setActiveUsers(int activeUsers) {
- this.activeUsers = activeUsers;
- }
-
- public Map getUsers() {
- return users;
- }
-
- public void setUsers(Map users) {
- this.users = users;
- }
-}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/SessionStatsAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/SessionStatsAction.java
deleted file mode 100755
index 1bcb1fafc4..0000000000
--- a/core/src/main/java/org/keycloak/representations/adapters/action/SessionStatsAction.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.keycloak.representations.adapters.action;
-
-/**
- * Query session stats.
- *
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class SessionStatsAction extends AdminAction {
-
- public static final String SESSION_STATS = "SESSION_STATS";
-
- protected boolean listUsers;
-
- public SessionStatsAction() {
- }
-
- public SessionStatsAction(String id, int expiration, String resource) {
- super(id, expiration, resource, SESSION_STATS);
- }
-
- public boolean isListUsers() {
- return listUsers;
- }
-
- public void setListUsers(boolean listUsers) {
- this.listUsers = listUsers;
- }
-
- @Override
- public boolean validate() {
- return SESSION_STATS.equals(action);
- }
-
-}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/UserStatsAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/UserStatsAction.java
deleted file mode 100755
index c7e5b63fc4..0000000000
--- a/core/src/main/java/org/keycloak/representations/adapters/action/UserStatsAction.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.keycloak.representations.adapters.action;
-
-/**
- * Query session stats.
- *
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class UserStatsAction extends AdminAction {
-
- public static final String USER_STATS = "USER_STATS";
- protected String user;
-
- public UserStatsAction() {
- }
-
- public UserStatsAction(String id, int expiration, String resource, String user) {
- super(id, expiration, resource, USER_STATS);
- this.user = user;
- }
-
- public String getUser() {
- return user;
- }
-
- @Override
- public boolean validate() {
- return USER_STATS.equals(action);
- }
-
-}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index fd54ce2b3e..7f62fd18bb 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -17,7 +17,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
- "auth-server-url-for-backend-requests"
+ "auth-server-url-for-backend-requests", "always-refresh-token"
})
public class AdapterConfig extends BaseAdapterConfig {
@@ -39,6 +39,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected int connectionPoolSize = 20;
@JsonProperty("auth-server-url-for-backend-requests")
protected String authServerUrlForBackendRequests;
+ @JsonProperty("always-refresh-token")
+ protected boolean alwaysRefreshToken = false;
public boolean isAllowAnyHostname() {
return allowAnyHostname;
@@ -111,4 +113,12 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
}
+
+ public boolean isAlwaysRefreshToken() {
+ return alwaysRefreshToken;
+ }
+
+ public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
+ this.alwaysRefreshToken = alwaysRefreshToken;
+ }
}
diff --git a/core/src/main/java/org/keycloak/util/HostUtils.java b/core/src/main/java/org/keycloak/util/HostUtils.java
new file mode 100644
index 0000000000..fb1b29fce1
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/HostUtils.java
@@ -0,0 +1,36 @@
+package org.keycloak.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @author Marek Posolda
+ */
+public class HostUtils {
+
+ public static String getHostName() {
+ String jbossHostName = System.getProperty("jboss.host.name");
+ if (jbossHostName != null) {
+ return jbossHostName;
+ } else {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException uhe) {
+ throw new IllegalStateException(uhe);
+ }
+ }
+ }
+
+ public static String getIpAddress() {
+ try {
+ String jbossHostName = System.getProperty("jboss.host.name");
+ if (jbossHostName != null) {
+ return InetAddress.getByName(jbossHostName).getHostAddress();
+ } else {
+ return java.net.InetAddress.getLocalHost().getHostAddress();
+ }
+ } catch (UnknownHostException uhe) {
+ throw new IllegalStateException(uhe);
+ }
+ }
+}
diff --git a/core/src/main/java/org/keycloak/util/UriUtils.java b/core/src/main/java/org/keycloak/util/UriUtils.java
index 60418ea225..873283f61e 100644
--- a/core/src/main/java/org/keycloak/util/UriUtils.java
+++ b/core/src/main/java/org/keycloak/util/UriUtils.java
@@ -1,8 +1,6 @@
package org.keycloak.util;
-import java.net.InetAddress;
import java.net.URI;
-import java.net.UnknownHostException;
/**
* @author Stian Thorgersen
@@ -18,12 +16,4 @@ public class UriUtils {
return u.substring(0, u.indexOf('/', 8));
}
- public static String getHostName() {
- try {
- return InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException uhe) {
- throw new IllegalStateException(uhe);
- }
- }
-
}
diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
index 2a83775252..caa9ed6eea 100755
--- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -13,6 +13,7 @@ import org.keycloak.ServiceUrlConstants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.util.HostUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import org.keycloak.util.UriUtils;
@@ -161,7 +162,7 @@ public class AdminClient {
public static String getBaseUrl(HttpServletRequest request) {
String useHostname = request.getServletContext().getInitParameter("useHostname");
if (useHostname != null && "true".equalsIgnoreCase(useHostname)) {
- return "http://" + UriUtils.getHostName() + ":8080";
+ return "http://" + HostUtils.getHostName() + ":8080";
} else {
return UriUtils.getOrigin(request.getRequestURL().toString());
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index b42090f3e9..6d777478dd 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -131,7 +131,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
attributes.put("account", new AccountBean(user, profileFormData));
break;
case TOTP:
- attributes.put("totp", new TotpBean(user, baseUri));
+ attributes.put("totp", new TotpBean(realm, user, baseUri));
break;
case SOCIAL:
attributes.put("social", new AccountSocialBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
index 75c9fea725..33542e07c8 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/TotpBean.java
@@ -21,6 +21,7 @@
*/
package org.keycloak.account.freemarker.model;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32;
@@ -35,17 +36,19 @@ import java.security.SecureRandom;
*/
public class TotpBean {
- private String totpSecret;
- private String totpSecretEncoded;
- private boolean enabled;
- private String contextUrl;
+ private final String totpSecret;
+ private final String totpSecretEncoded;
+ private final boolean enabled;
+ private final String contextUrl;
+ private final String realmName;
- public TotpBean(UserModel user, URI baseUri) {
+ public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
+ this.realmName = realm.getName();
this.enabled = user.isTotp();
this.contextUrl = baseUri.getPath();
- totpSecret = randomString(20);
- totpSecretEncoded = Base32.encode(totpSecret.getBytes());
+ this.totpSecret = randomString(20);
+ this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
}
private static String randomString(int length) {
@@ -86,7 +89,7 @@ public class TotpBean {
}
public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
- String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8");
+ String contents = URLEncoder.encode("otpauth://totp/" + realmName + "?secret=" + totpSecretEncoded, "utf-8");
return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
index 497e75a7cf..7ac58644a9 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/TotpBean.java
@@ -35,19 +35,19 @@ import java.util.Random;
*/
public class TotpBean {
- private String totpSecret;
- private String totpSecretEncoded;
- private boolean enabled;
- private String contextUrl;
- private String realmName;
+ private final String totpSecret;
+ private final String totpSecretEncoded;
+ private final boolean enabled;
+ private final String contextUrl;
+ private final String realmName;
public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
this.realmName = realm.getName();
this.enabled = user.isTotp();
this.contextUrl = baseUri.getPath();
-
- totpSecret = randomString(20);
- totpSecretEncoded = Base32.encode(totpSecret.getBytes());
+
+ this.totpSecret = randomString(20);
+ this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
}
private static String randomString(int length) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index f1e52923ad..69e61aaf20 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -326,6 +326,16 @@ public class AdapterDeploymentContext {
public void setCorsAllowedHeaders(String corsAllowedHeaders) {
delegate.setCorsAllowedHeaders(corsAllowedHeaders);
}
+
+ @Override
+ public boolean isAlwaysRefreshToken() {
+ return delegate.isAlwaysRefreshToken();
+ }
+
+ @Override
+ public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
+ delegate.setAlwaysRefreshToken(alwaysRefreshToken);
+ }
}
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index db284b19e0..c3761126f8 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -47,6 +47,7 @@ public class KeycloakDeployment {
protected String corsAllowedHeaders;
protected String corsAllowedMethods;
protected boolean exposeToken;
+ protected boolean alwaysRefreshToken;
protected volatile int notBefore;
public KeycloakDeployment() {
@@ -281,4 +282,11 @@ public class KeycloakDeployment {
this.notBefore = notBefore;
}
+ public boolean isAlwaysRefreshToken() {
+ return alwaysRefreshToken;
+ }
+
+ public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
+ this.alwaysRefreshToken = alwaysRefreshToken;
+ }
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 5aa996b428..6e5c29fd86 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -35,7 +35,7 @@ public class KeycloakDeploymentBuilder {
String realmKeyPem = adapterConfig.getRealmKey();
if (realmKeyPem != null) {
- PublicKey realmKey = null;
+ PublicKey realmKey;
try {
realmKey = PemUtils.decodePublicKey(realmKeyPem);
} catch (Exception e) {
@@ -60,9 +60,7 @@ public class KeycloakDeploymentBuilder {
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
-
- if (adapterConfig.isBearerOnly()) {
- }
+ deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@@ -82,7 +80,7 @@ public class KeycloakDeploymentBuilder {
public static KeycloakDeployment build(InputStream is) {
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
- AdapterConfig adapterConfig = null;
+ AdapterConfig adapterConfig;
try {
adapterConfig = mapper.readValue(is, AdapterConfig.class);
} catch (IOException e) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 3932cb0369..c3c2e6e7ba 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong;
public abstract class OAuthRequestAuthenticator {
private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class);
protected KeycloakDeployment deployment;
+ protected RequestAuthenticator reqAuthenticator;
protected int sslRedirectPort;
protected String tokenString;
protected String idTokenString;
@@ -31,7 +32,8 @@ public abstract class OAuthRequestAuthenticator {
protected String refreshToken;
protected String strippedOauthParametersRequestUri;
- public OAuthRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+ public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+ this.reqAuthenticator = requestAuthenticator;
this.facade = facade;
this.deployment = deployment;
this.sslRedirectPort = sslRedirectPort;
@@ -253,7 +255,8 @@ public abstract class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try {
- tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri);
+ String httpSessionId = reqAuthenticator.getHttpSessionId(true);
+ tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus());
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index e2aff3f141..efc5463791 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -7,10 +7,6 @@ import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
-import org.keycloak.representations.adapters.action.SessionStats;
-import org.keycloak.representations.adapters.action.SessionStatsAction;
-import org.keycloak.representations.adapters.action.UserStats;
-import org.keycloak.representations.adapters.action.UserStatsAction;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.StreamUtil;
@@ -60,14 +56,6 @@ public class PreAuthActionsHandler {
if (!resolveDeployment()) return true;
handlePushNotBefore();
return true;
- } else if (requestUri.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
- if (!resolveDeployment()) return true;
- handleGetSessionStats();
- return true;
- } else if (requestUri.endsWith(AdapterConstants.K_GET_USER_STATS)) {
- if (!resolveDeployment()) return true;
- handleGetUserStats();
- return true;
} else if (requestUri.endsWith(AdapterConstants.K_VERSION)) {
handleVersion();
return true;
@@ -123,14 +111,10 @@ public class PreAuthActionsHandler {
}
LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
if (!validateAction(action)) return;
- String user = action.getUser();
- if (user != null) {
- log.debug("logout of session for: " + user);
- userSessionManagement.logoutUser(user);
- } else if (action.getSession() != null) {
- userSessionManagement.logoutKeycloakSession(action.getSession());
+ if (action.getAdapterSessionIds() != null) {
+ userSessionManagement.logoutHttpSessions(action.getAdapterSessionIds());
} else {
- log.debug("logout of all sessions");
+ log.infof("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.setNotBefore(action.getNotBefore());
}
@@ -208,50 +192,6 @@ public class PreAuthActionsHandler {
return true;
}
- protected void handleGetSessionStats() {
- if (log.isTraceEnabled()) {
- log.trace("K_GET_SESSION_STATS sent");
- }
- try {
- JWSInput token = verifyAdminRequest();
- if (token == null) return;
- SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
- if (!validateAction(action)) return;
- SessionStats stats = new SessionStats();
- stats.setActiveSessions(userSessionManagement.getActiveSessions());
- stats.setActiveUsers(userSessionManagement.getActiveUsers().size());
- if (action.isListUsers() && userSessionManagement.getActiveSessions() > 0) {
- Map list = new HashMap();
- for (String user : userSessionManagement.getActiveUsers()) {
- list.put(user, getUserStats(user));
- }
- stats.setUsers(list);
- }
- facade.getResponse().setStatus(200);
- facade.getResponse().setHeader("Content-Type", "application/json");
- JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- protected void handleGetUserStats() {
- if (log.isTraceEnabled()) {
- log.trace("K_GET_USER_STATS sent");
- }
- try {
- JWSInput token = verifyAdminRequest();
- if (token == null) return;
- UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
- if (!validateAction(action)) return;
- String user = action.getUser();
- UserStats stats = getUserStats(user);
- facade.getResponse().setStatus(200);
- facade.getResponse().setHeader("Content-Type", "application/json");
- JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), stats);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
protected void handleVersion() {
try {
facade.getResponse().setStatus(200);
@@ -262,15 +202,4 @@ public class PreAuthActionsHandler {
}
}
- protected UserStats getUserStats(String user) {
- UserStats stats = new UserStats();
- Long loginTime = userSessionManagement.getUserLoginTime(user);
- if (loginTime != null) {
- stats.setLoggedIn(true);
- stats.setWhenLoggedIn(loginTime);
- } else {
- stats.setLoggedIn(false);
- }
- return stats;
- }
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 7dfe62c722..3fc5b3d6e2 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -32,13 +32,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
@Override
public AccessToken getToken() {
- refreshExpiredToken();
+ refreshExpiredToken(true);
return super.getToken();
}
@Override
public String getTokenString() {
- refreshExpiredToken();
+ refreshExpiredToken(true);
return super.getTokenString();
}
@@ -62,12 +62,19 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.deployment = deployment;
}
- public void refreshExpiredToken() {
- if (log.isTraceEnabled()) {
- log.trace("checking whether to refresh.");
+ /**
+ * @param checkActive if true, then we won't send refresh request if current accessToken is still active.
+ * @return true if accessToken is active or was successfully refreshed
+ */
+ public boolean refreshExpiredToken(boolean checkActive) {
+ if (checkActive) {
+ if (log.isTraceEnabled()) {
+ log.trace("checking whether to refresh.");
+ }
+ if (isActive()) return true;
}
- if (isActive()) return;
- if (this.deployment == null || refreshToken == null) return; // Might be serialized in HttpSession?
+
+ if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
if (log.isTraceEnabled()) {
log.trace("Doing refresh");
@@ -77,10 +84,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
response = ServerRequest.invokeRefresh(deployment, refreshToken);
} catch (IOException e) {
log.error("Refresh token failure", e);
- return;
+ return false;
} catch (ServerRequest.HttpFailure httpFailure) {
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
- return;
+ return false;
}
if (log.isTraceEnabled()) {
log.trace("received refresh response");
@@ -100,7 +107,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.token = token;
this.refreshToken = response.getRefreshToken();
this.tokenString = tokenString;
-
+ return true;
}
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
index 3cf2c91f55..0b3123816d 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -111,6 +111,7 @@ public abstract class RequestAuthenticator {
protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal principal);
protected abstract boolean isCached();
+ protected abstract String getHttpSessionId(boolean create);
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
index 985495028d..b20697d354 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java
@@ -11,6 +11,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper;
+import org.keycloak.util.HostUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import org.keycloak.util.StreamUtil;
@@ -84,21 +85,25 @@ public class ServerRequest {
if (is != null) is.close();
}
- public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri) throws HttpFailure, IOException {
+ public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws HttpFailure, IOException {
String codeUrl = deployment.getCodeUrl();
String client_id = deployment.getResourceName();
Map credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient();
- return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, codeUrl, redirectUri, client_id, credentials);
+ return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, codeUrl, redirectUri, client_id, credentials, sessionId);
}
- public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map credentials) throws IOException, HttpFailure {
+ public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map credentials, String sessionId) throws IOException, HttpFailure {
List formparams = new ArrayList();
redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
+ if (sessionId != null) {
+ formparams.add(new BasicNameValuePair(AdapterConstants.HTTP_SESSION_ID, sessionId));
+ formparams.add(new BasicNameValuePair(AdapterConstants.HTTP_SESSION_HOST, HostUtils.getIpAddress()));
+ }
HttpResponse response = null;
HttpPost post = new HttpPost(codeUrl);
if (!publicClient) {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
index ca4653a20b..c89e3ec386 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/UserSessionManagement.java
@@ -1,21 +1,14 @@
package org.keycloak.adapters;
-import java.util.Set;
+import java.util.List;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
public interface UserSessionManagement {
- int getActiveSessions();
-
- Long getUserLoginTime(String username);
-
- Set getActiveUsers();
void logoutAll();
- void logoutUser(String user);
-
- void logoutKeycloakSession(String id);
+ void logoutHttpSessions(List ids);
}
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
index aca4db7154..a1cb95c4eb 100755
--- 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
@@ -18,6 +18,8 @@ import java.security.Principal;
import java.util.Collections;
import java.util.Set;
+import javax.servlet.http.HttpSession;
+
/**
* @author Bill Burke
* @version $Revision: 1 $
@@ -40,7 +42,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
- return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override
protected void saveRequest() {
try {
@@ -64,15 +66,15 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
String username = securityContext.getToken().getSubject();
log.debug("userSessionManage.login: " + username);
- userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
+ userSessionManagement.login(session);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set roles = getRolesFromToken(securityContext);
- for (String role : roles) {
- log.info("Bearer role: " + role);
+ if (log.isDebugEnabled()) {
+ log.debug("Completing bearer authentication. Bearer roles: " + roles);
}
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal);
@@ -123,4 +125,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
}
}
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = request.getSession(create);
+ return session != null ? session.getId() : null;
+ }
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
index c1ac9803fc..ffb806e1ac 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagement.java
@@ -1,17 +1,14 @@
package org.keycloak.adapters.as7;
+import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener;
import org.apache.catalina.realm.GenericPrincipal;
import org.jboss.logging.Logger;
-import org.keycloak.adapters.UserSessionManagement;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+import java.io.IOException;
+import java.util.List;
/**
* Manages relationship to users and sessions so that forced admin logout can be implemented
@@ -19,112 +16,50 @@ import java.util.concurrent.ConcurrentHashMap;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
+public class CatalinaUserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class);
- protected ConcurrentHashMap userSessionMap = new ConcurrentHashMap();
- protected ConcurrentHashMap keycloakSessionMap = new ConcurrentHashMap();
- public static class UserSessions {
- protected String user;
- protected long loggedIn = System.currentTimeMillis();
- protected Map keycloakSessionToHttpSession = new HashMap();
- protected Map httpSessionToKeycloakSession = new HashMap();
- protected Map sessions = new HashMap();
- public long getLoggedIn() {
- return loggedIn;
- }
- }
-
- public synchronized int getActiveSessions() {
- return keycloakSessionMap.size();
- }
-
- /**
- *
- * @param username
- * @return null if user not logged in
- */
- @Override
- public synchronized Long getUserLoginTime(String username) {
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) return null;
- return sessions.getLoggedIn();
- }
-
- @Override
- public synchronized Set getActiveUsers() {
- HashSet set = new HashSet();
- set.addAll(userSessionMap.keySet());
- return set;
- }
-
-
- public synchronized void login(Session session, String username, String keycloakSessionId) {
- String sessionId = session.getId();
-
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) {
- sessions = new UserSessions();
- sessions.user = username;
- userSessionMap.put(username, sessions);
- }
- keycloakSessionMap.put(keycloakSessionId, sessions);
- sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
- sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
- sessions.sessions.put(sessionId, session);
+ public void login(Session session) {
session.addSessionListener(this);
}
- @Override
- public void logoutAll() {
- for (String user : userSessionMap.keySet()) logoutUser(user);
+ public void logoutAll(Manager sessionManager) {
+ Session[] allSessions = sessionManager.findSessions();
+ for (Session session : allSessions) {
+ logoutSession(session);
+ }
}
- @Override
- public void logoutUser(String user) {
- log.debug("logoutUser: " + user);
- UserSessions sessions = null;
- sessions = userSessionMap.remove(user);
- if (sessions == null) {
- log.debug("no session for user: " + user);
+ public void logoutHttpSessions(Manager sessionManager, List sessionIds) {
+ log.debug("logoutHttpSessions: " + sessionIds);
+
+ for (String sessionId : sessionIds) {
+ logoutSession(sessionManager, sessionId);
+ }
+ }
+
+ protected void logoutSession(Manager manager, String httpSessionId) {
+ log.debug("logoutHttpSession: " + httpSessionId);
+
+ Session session;
+ try {
+ session = manager.findSession(httpSessionId);
+ } catch (IOException ioe) {
+ log.warn("IO exception when looking for session " + httpSessionId, ioe);
return;
}
- log.debug("found session for user");
- for (Map.Entry entry : sessions.httpSessionToKeycloakSession.entrySet()) {
- log.debug("invalidating session for user: " + user);
- String sessionId = entry.getKey();
- String keycloakSessionId = entry.getValue();
- Session session = sessions.sessions.get(sessionId);
- session.setPrincipal(null);
- session.setAuthType(null);
- session.getSession().invalidate();
- keycloakSessionMap.remove(keycloakSessionId);
- }
+
+ logoutSession(session);
}
- public synchronized void logoutKeycloakSession(String keycloakSessionId) {
- log.debug("logoutKeycloakSession: " + keycloakSessionId);
- UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
- if (sessions == null) {
- log.debug("no session for keycloak session id: " + keycloakSessionId);
- return;
- }
- String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- if (sessionId == null) {
- log.debug("no session for keycloak session id: " + keycloakSessionId);
-
- }
- sessions.httpSessionToKeycloakSession.remove(sessionId);
- Session session = sessions.sessions.remove(sessionId);
- session.setPrincipal(null);
- session.setAuthType(null);
- session.getSession().invalidate();
- if (sessions.keycloakSessionToHttpSession.size() == 0) {
- userSessionMap.remove(sessions.user);
+ protected void logoutSession(Session session) {
+ try {
+ session.expire();
+ } catch (Exception e) {
+ log.warnf("Session not present or already invalidated.");
}
}
-
public void sessionEvent(SessionEvent event) {
// We only care about session destroyed events
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
@@ -133,28 +68,11 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
// Look up the single session id associated with this session (if any)
Session session = event.getSession();
+ log.debugf("Session %s destroyed", session.getId());
+
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
if (principal == null) return;
session.setPrincipal(null);
session.setAuthType(null);
-
- String username = principal.getUserPrincipal().getName();
- UserSessions userSessions = userSessionMap.get(username);
- if (userSessions == null) {
- return;
- }
- String sessionid = session.getId();
- synchronized (this) {
- String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
- if (keycloakSessionId != null) {
- userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- keycloakSessionMap.remove(keycloakSessionId);
- }
- userSessions.sessions.remove(sessionid);
- if (userSessions.httpSessionToKeycloakSession.size() == 0) {
- userSessionMap.remove(username);
- }
-
- }
}
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagementWrapper.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagementWrapper.java
new file mode 100644
index 0000000000..ff7ee07d5e
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaUserSessionManagementWrapper.java
@@ -0,0 +1,30 @@
+package org.keycloak.adapters.as7;
+
+import java.util.List;
+
+import org.apache.catalina.Manager;
+import org.keycloak.adapters.UserSessionManagement;
+
+/**
+ * @author Marek Posolda
+ */
+public class CatalinaUserSessionManagementWrapper implements UserSessionManagement {
+
+ private final CatalinaUserSessionManagement delegate;
+ private final Manager sessionManager;
+
+ public CatalinaUserSessionManagementWrapper(CatalinaUserSessionManagement delegate, Manager sessionManager) {
+ this.delegate = delegate;
+ this.sessionManager = sessionManager;
+ }
+
+ @Override
+ public void logoutAll() {
+ delegate.logoutAll(sessionManager);
+ }
+
+ @Override
+ public void logoutHttpSessions(List ids) {
+ delegate.logoutHttpSessions(sessionManager, ids);
+ }
+}
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 eb3975630f..9bf2c42636 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
@@ -5,6 +5,7 @@ import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
@@ -127,7 +128,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
log.trace("invoke");
}
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
- PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
+ Manager sessionManager = request.getContext().getManager();
+ CatalinaUserSessionManagementWrapper sessionManagementWrapper = new CatalinaUserSessionManagementWrapper(userSessionManagement, sessionManager);
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(sessionManagementWrapper, deploymentContext, facade);
if (handler.handleRequest()) {
return;
}
@@ -175,18 +178,22 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return;
// just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
- if (session.isActive()) return;
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) 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;
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
- request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ Session catalinaSession = request.getSessionInternal();
+ log.debugf("Cleanup and expire session %s after failed refresh", catalinaSession.getId());
+ catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
request.setUserPrincipal(null);
request.setAuthType(null);
- request.getSessionInternal().setPrincipal(null);
- request.getSessionInternal().setAuthType(null);
+ catalinaSession.setPrincipal(null);
+ catalinaSession.setAuthType(null);
+ catalinaSession.expire();
}
public void keycloakSaveRequest(Request request) throws IOException {
diff --git a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index 32ebfdbf6d..d3ed5367cb 100644
--- a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -227,7 +227,7 @@ public class KeycloakInstalled {
}
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
- AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri);
+ AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
parseAccessToken(tokenResponse);
}
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 0e414ecce5..2420c5308a 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -41,7 +41,8 @@ public class ServletOAuthClient extends AbstractOAuthClient {
}
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
- return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl, false), redirectUri, clientId, credentials);
+ // Don't send sessionId in oauth clients for now
+ return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl, false), redirectUri, clientId, credentials, null);
}
/**
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
index 6a05be844b..8516b1ad8b 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -16,8 +16,11 @@ import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
import java.util.Set;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.servlet.http.HttpSession;
+
/**
* @author Davide Ungari
* @version $Revision: 1 $
@@ -40,7 +43,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
- return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override
protected void saveRequest() {
try {
@@ -63,16 +66,16 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
session.setAuthType("OAUTH");
session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
String username = securityContext.getToken().getSubject();
- log.finer("userSessionManage.login: " + username);
- userSessionManagement.login(session, username, securityContext.getToken().getSessionState());
+ log.finer("userSessionManagement.login: " + username);
+ userSessionManagement.login(session);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set roles = getRolesFromToken(securityContext);
- for (String role : roles) {
- log.info("Bearer role: " + role);
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("Completing bearer authentication. Bearer roles: " + roles);
}
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal);
@@ -123,4 +126,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
}
}
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = request.getSession(create);
+ return session != null ? session.getId() : null;
+ }
}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
index c21bbb8d09..66258ddf05 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagement.java
@@ -1,16 +1,13 @@
package org.keycloak.adapters.tomcat7;
+import org.apache.catalina.Manager;
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;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+import java.io.IOException;
+import java.util.List;
import java.util.logging.Logger;
/**
@@ -19,105 +16,52 @@ import java.util.logging.Logger;
* @author Davide Ungari
* @version $Revision: 1 $
*/
-public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement {
+public class CatalinaUserSessionManagement implements SessionListener {
+
private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
- protected ConcurrentHashMap userSessionMap = new ConcurrentHashMap();
- protected ConcurrentHashMap keycloakSessionMap = new ConcurrentHashMap();
- public static class UserSessions {
- protected String user;
- protected long loggedIn = System.currentTimeMillis();
- protected Map keycloakSessionToHttpSession = new HashMap();
- protected Map httpSessionToKeycloakSession = new HashMap();
- protected Map sessions = new HashMap();
- public long getLoggedIn() {
- return loggedIn;
- }
- }
-
- public synchronized int getActiveSessions() {
- return keycloakSessionMap.size();
- }
-
- /**
- *
- * @param username
- * @return null if user not logged in
- */
- @Override
- public synchronized Long getUserLoginTime(String username) {
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) return null;
- return sessions.getLoggedIn();
- }
-
- @Override
- public synchronized Set getActiveUsers() {
- HashSet set = new HashSet();
- set.addAll(userSessionMap.keySet());
- return set;
- }
-
-
- public synchronized void login(Session session, String username, String keycloakSessionId) {
- String sessionId = session.getId();
-
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) {
- sessions = new UserSessions();
- sessions.user = username;
- userSessionMap.put(username, sessions);
- }
- keycloakSessionMap.put(keycloakSessionId, sessions);
- sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
- sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
- sessions.sessions.put(sessionId, session);
+ public void login(Session session) {
session.addSessionListener(this);
}
- @Override
- public void logoutAll() {
- for (String user : userSessionMap.keySet()) logoutUser(user);
+ public void logoutAll(Manager sessionManager) {
+ Session[] allSessions = sessionManager.findSessions();
+ for (Session session : allSessions) {
+ logoutSession(session);
+ }
}
- @Override
- public void logoutUser(String user) {
- UserSessions sessions = null;
- sessions = userSessionMap.remove(user);
- if (sessions == null) {
+ public void logoutHttpSessions(Manager sessionManager, List sessionIds) {
+ log.fine("logoutHttpSessions: " + sessionIds);
+
+ for (String sessionId : sessionIds) {
+ logoutSession(sessionManager, sessionId);
+ }
+ }
+
+ protected void logoutSession(Manager manager, String httpSessionId) {
+ log.fine("logoutHttpSession: " + httpSessionId);
+
+ Session session;
+ try {
+ session = manager.findSession(httpSessionId);
+ } catch (IOException ioe) {
+ log.warning("IO exception when looking for session " + httpSessionId);
+ ioe.printStackTrace();
return;
}
- for (Map.Entry entry : sessions.httpSessionToKeycloakSession.entrySet()) {
- String sessionId = entry.getKey();
- String keycloakSessionId = entry.getValue();
- Session session = sessions.sessions.get(sessionId);
- session.setPrincipal(null);
- session.setAuthType(null);
- session.getSession().invalidate();
- keycloakSessionMap.remove(keycloakSessionId);
- }
+
+ logoutSession(session);
}
- public synchronized void logoutKeycloakSession(String keycloakSessionId) {
- UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
- if (sessions == null) {
- return;
- }
- String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- if (sessionId == null) {
-
- }
- sessions.httpSessionToKeycloakSession.remove(sessionId);
- Session session = sessions.sessions.remove(sessionId);
- session.setPrincipal(null);
- session.setAuthType(null);
- session.getSession().invalidate();
- if (sessions.keycloakSessionToHttpSession.size() == 0) {
- userSessionMap.remove(sessions.user);
+ protected void logoutSession(Session session) {
+ try {
+ session.expire();
+ } catch (Exception e) {
+ log.warning("Session not present or already invalidated.");
}
}
-
public void sessionEvent(SessionEvent event) {
// We only care about session destroyed events
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
@@ -126,28 +70,11 @@ public class CatalinaUserSessionManagement implements SessionListener, UserSessi
// Look up the single session id associated with this session (if any)
Session session = event.getSession();
+ log.fine("Session " + session.getId() + " destroyed");
+
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
if (principal == null) return;
session.setPrincipal(null);
session.setAuthType(null);
-
- String username = principal.getUserPrincipal().getName();
- UserSessions userSessions = userSessionMap.get(username);
- if (userSessions == null) {
- return;
- }
- String sessionid = session.getId();
- synchronized (this) {
- String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionid);
- if (keycloakSessionId != null) {
- userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- keycloakSessionMap.remove(keycloakSessionId);
- }
- userSessions.sessions.remove(sessionid);
- if (userSessions.httpSessionToKeycloakSession.size() == 0) {
- userSessionMap.remove(username);
- }
-
- }
}
}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagementWrapper.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagementWrapper.java
new file mode 100644
index 0000000000..b1e8828a40
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaUserSessionManagementWrapper.java
@@ -0,0 +1,30 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.List;
+
+import org.apache.catalina.Manager;
+import org.keycloak.adapters.UserSessionManagement;
+
+/**
+ * @author Marek Posolda
+ */
+public class CatalinaUserSessionManagementWrapper implements UserSessionManagement {
+
+ private final CatalinaUserSessionManagement delegate;
+ private final Manager sessionManager;
+
+ public CatalinaUserSessionManagementWrapper(CatalinaUserSessionManagement delegate, Manager sessionManager) {
+ this.delegate = delegate;
+ this.sessionManager = sessionManager;
+ }
+
+ @Override
+ public void logoutAll() {
+ delegate.logoutAll(sessionManager);
+ }
+
+ @Override
+ public void logoutHttpSessions(List ids) {
+ delegate.logoutHttpSessions(sessionManager, ids);
+ }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
index 2fd8be480a..208882c447 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -5,6 +5,7 @@ import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
@@ -133,7 +134,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
- PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
+ Manager sessionManager = request.getContext().getManager();
+ CatalinaUserSessionManagementWrapper sessionManagementWrapper = new CatalinaUserSessionManagementWrapper(userSessionManagement, sessionManager);
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(sessionManagementWrapper, deploymentContext, facade);
if (handler.handleRequest()) {
return;
}
@@ -177,18 +180,22 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return;
// just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
- if (session.isActive()) return;
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) 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;
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
- request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ Session catalinaSession = request.getSessionInternal();
+ log.fine("Cleanup and expire session " + catalinaSession + " after failed refresh");
+ catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
request.setUserPrincipal(null);
request.setAuthType(null);
- request.getSessionInternal().setPrincipal(null);
- request.getSessionInternal().setAuthType(null);
+ catalinaSession.setPrincipal(null);
+ catalinaSession.setAuthType(null);
+ catalinaSession.expire();
}
public void keycloakSaveRequest(Request request) throws IOException {
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 58cbb023d5..472bc90410 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
@@ -92,14 +92,14 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
public boolean isActive() {
// this object may have been serialized, so we need to reset realm config/metadata
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
- if (session.isActive()) {
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
log.debug("session is active");
return true;
}
- log.debug("session is not active try refresh");
- session.refreshExpiredToken();
- if (!session.isActive()) {
+ log.debug("session is not active or refresh is enforced. Try refresh");
+ boolean success = session.refreshExpiredToken(false);
+ if (!success || !session.isActive()) {
log.debug("session is not active return with failure");
return false;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index c46f477d31..5416d3c29e 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -18,7 +18,9 @@ package org.keycloak.adapters.undertow;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.Sessions;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpFacade;
@@ -45,9 +47,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
@Override
protected boolean isCached() {
- final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
- HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
- HttpSession session = req.getSession(false);
+ HttpSession session = getSession(false);
if (session == null) {
log.debug("session was null, returning null");
return false;
@@ -63,10 +63,12 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext( account);
return true;
+ } else {
+ log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+ session.invalidate();
+ return false;
}
- log.debug("Account was not active, returning null");
- session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
- return false;
}
@Override
@@ -80,10 +82,9 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
@Override
protected void login(KeycloakAccount account) {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
- HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
- HttpSession session = req.getSession(true);
+ HttpSession session = getSession(true);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
- userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session.getId(), account.getPrincipal().getName(), account.getKeycloakSecurityContext().getToken().getSessionState());
+ userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
}
@@ -91,4 +92,16 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
protected KeycloakUndertowAccount createAccount(KeycloakPrincipal principal) {
return new KeycloakUndertowAccount(principal);
}
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = getSession(create);
+ return session != null ? session.getId() : null;
+ }
+
+ protected HttpSession getSession(boolean create) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ return req.getSession(create);
+ }
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
index 0bdd434891..839d39e44a 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.session.SessionManager;
import org.keycloak.adapters.UserSessionManagement;
+import java.util.List;
import java.util.Set;
/**
@@ -35,33 +36,13 @@ public class SessionManagementBridge implements UserSessionManagement {
this.sessionManager = sessionManager;
}
- @Override
- public int getActiveSessions() {
- return userSessionManagement.getActiveSessions();
- }
-
- @Override
- public Long getUserLoginTime(String username) {
- return userSessionManagement.getUserLoginTime(username);
- }
-
- @Override
- public Set getActiveUsers() {
- return userSessionManagement.getActiveUsers();
- }
-
@Override
public void logoutAll() {
userSessionManagement.logoutAll(sessionManager);
}
@Override
- public void logoutUser(String user) {
- userSessionManagement.logoutUser(sessionManager, user);
- }
-
- @Override
- public void logoutKeycloakSession(String id) {
- userSessionManagement.logoutKeycloakSession(sessionManager, id);
+ public void logoutHttpSessions(List ids) {
+ userSessionManagement.logoutHttpSessions(sessionManager, ids);
}
}
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
index 79435aef2e..671f2e7c36 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -54,7 +54,7 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
- return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override
protected void saveRequest() {
// todo
@@ -73,9 +73,7 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
protected void login(KeycloakAccount account) {
Session session = Sessions.getOrCreateSession(exchange);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
- String username = account.getPrincipal().getName();
- String keycloakSessionId = account.getKeycloakSecurityContext().getToken().getSessionState();
- userSessionManagement.login(session.getSessionManager(), session.getId(), username, keycloakSessionId);
+ userSessionManagement.login(session.getSessionManager());
}
@@ -90,24 +88,37 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
protected boolean isCached() {
Session session = Sessions.getSession(exchange);
if (session == null) {
- log.info("session was null, returning null");
+ log.debug("session was null, returning null");
return false;
}
KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
if (account == null) {
- log.info("Account was not in session, returning null");
+ log.debug("Account was not in session, returning null");
return false;
}
account.setDeployment(deployment);
if (account.isActive()) {
- log.info("Cached account found");
+ log.debug("Cached account found");
securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext( account);
return true;
+ } else {
+ log.debug("Account was not active, returning false");
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.invalidate(exchange);
+ return false;
+ }
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ if (create) {
+ Session session = Sessions.getOrCreateSession(exchange);
+ return session.getId();
+ } else {
+ Session session = Sessions.getSession(exchange);
+ return session != null ? session.getId() : null;
}
- log.info("Account was not active, returning false");
- session.removeAttribute(KeycloakUndertowAccount.class.getName());
- return false;
}
/**
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
index f39fe6a5bc..cc0a7c540a 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
@@ -27,6 +27,7 @@ import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -40,106 +41,35 @@ import java.util.concurrent.ConcurrentHashMap;
public class UndertowUserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class);
private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
- protected ConcurrentHashMap userSessionMap = new ConcurrentHashMap();
- protected ConcurrentHashMap keycloakSessionMap = new ConcurrentHashMap();
protected volatile boolean registered;
-
- public static class UserSessions {
- protected String user;
- protected long loggedIn = System.currentTimeMillis();
- protected Map keycloakSessionToHttpSession = new HashMap();
- protected Map httpSessionToKeycloakSession = new HashMap();
- public long getLoggedIn() {
- return loggedIn;
- }
- }
-
- public synchronized int getActiveSessions() {
- return keycloakSessionMap.size();
- }
-
- /**
- *
- * @param username
- * @return null if user not logged in
- */
- public synchronized Long getUserLoginTime(String username) {
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) return null;
- return sessions.getLoggedIn();
- }
-
- public synchronized Set getActiveUsers() {
- HashSet set = new HashSet();
- set.addAll(userSessionMap.keySet());
- return set;
- }
-
- public synchronized void login(SessionManager manager, String sessionId, String username, String keycloakSessionId) {
- UserSessions sessions = userSessionMap.get(username);
- if (sessions == null) {
- sessions = new UserSessions();
- sessions.user = username;
- userSessionMap.put(username, sessions);
- }
- sessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
- sessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
- keycloakSessionMap.put(keycloakSessionId, sessions);
+ public void login(SessionManager manager) {
if (!registered) {
manager.registerSessionListener(this);
registered = true;
}
}
- public synchronized void logoutAll(SessionManager manager) {
- for (String user : userSessionMap.keySet()) logoutUser(manager, user);
+ public void logoutAll(SessionManager manager) {
+ Set allSessions = manager.getAllSessions();
+ for (String sessionId : allSessions) logoutSession(manager, sessionId);
}
- public synchronized void logoutUser(SessionManager manager, String user) {
- log.debug("logoutUser: " + user);
- UserSessions sessions = null;
- sessions = userSessionMap.remove(user);
- if (sessions == null) {
- log.debug("no session for user: " + user);
- return;
- }
- log.debug("found session for user");
- for (Map.Entry entry : sessions.httpSessionToKeycloakSession.entrySet()) {
- log.debug("invalidating session for user: " + user);
- String sessionId = entry.getKey();
- String keycloakSessionId = entry.getValue();
- Session session = getSessionById(manager, sessionId);
- try {
- session.invalidate(null);
- } catch (Exception e) {
- log.warn("Session already invalidated.");
- }
- keycloakSessionMap.remove(keycloakSessionId);
+ public void logoutHttpSessions(SessionManager manager, List sessionIds) {
+ log.debug("logoutHttpSessions: " + sessionIds);
+
+ for (String sessionId : sessionIds) {
+ logoutSession(manager, sessionId);
}
}
- public synchronized void logoutKeycloakSession(SessionManager manager, String keycloakSessionId) {
- log.debug("logoutKeycloakSession: " + keycloakSessionId);
- UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId);
- if (sessions == null) {
- log.debug("no session for keycloak session id: " + keycloakSessionId);
- return;
- }
- String sessionId = sessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- if (sessionId == null) {
- log.debug("no session for keycloak session id: " + keycloakSessionId);
-
- }
- sessions.httpSessionToKeycloakSession.remove(sessionId);
- Session session = getSessionById(manager, sessionId);
+ protected void logoutSession(SessionManager manager, String httpSessionId) {
+ log.debug("logoutHttpSession: " + httpSessionId);
+ Session session = getSessionById(manager, httpSessionId);
try {
session.invalidate(null);
} catch (Exception e) {
- log.warn("Session already invalidated.");
- }
- if (sessions.keycloakSessionToHttpSession.size() == 0) {
- userSessionMap.remove(sessions.user);
+ log.warnf("Session %s not present or already invalidated.", httpSessionId);
}
}
@@ -188,23 +118,6 @@ public class UndertowUserSessionManagement implements SessionListener {
// Look up the single session id associated with this session (if any)
String username = getUsernameFromSession(session);
log.debugf("Session destroyed for user: %s, sessionId: %s", username, session.getId());
- if (username == null) return;
- String sessionId = session.getId();
- UserSessions userSessions = userSessionMap.get(username);
- if (userSessions == null) {
- return;
- }
- synchronized (this) {
- String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(sessionId);
- if (keycloakSessionId != null) {
- userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- keycloakSessionMap.remove(keycloakSessionId);
- }
- if (userSessions.httpSessionToKeycloakSession.size() == 0) {
- userSessionMap.remove(username);
- }
-
- }
}
protected String getUsernameFromSession(Session session) {
@@ -217,23 +130,6 @@ public class UndertowUserSessionManagement implements SessionListener {
@Override
public void sessionIdChanged(Session session, String oldSessionId) {
- String username = getUsernameFromSession(session);
- if (username == null) return;
- String sessionId = session.getId();
-
- UserSessions userSessions = userSessionMap.get(username);
- if (userSessions == null) {
- return;
- }
-
- synchronized (this) {
- String keycloakSessionId = userSessions.httpSessionToKeycloakSession.remove(oldSessionId);
- if (keycloakSessionId != null) {
- userSessions.keycloakSessionToHttpSession.remove(keycloakSessionId);
- userSessions.keycloakSessionToHttpSession.put(keycloakSessionId, sessionId);
- userSessions.httpSessionToKeycloakSession.put(sessionId, keycloakSessionId);
- }
- }
}
@Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index fe3bb2ded9..e2e9478337 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -128,6 +128,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
public List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
DBObject query = new QueryBuilder()
.and("clientId").is(client.getId())
+ .and("sessionId").notEquals(null)
.get();
DBObject sort = new BasicDBObject("timestamp", 1).append("id", 1);
@@ -142,7 +143,11 @@ public class MongoUserSessionProvider implements UserSessionProvider {
@Override
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
- return getUserSessions(realm, client).size();
+ DBObject query = new QueryBuilder()
+ .and("clientId").is(client.getId())
+ .and("sessionId").notEquals(null)
+ .get();
+ return mongoStore.countEntities(MongoClientSessionEntity.class, query, invocationContext);
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
index c67960c6ea..e520d8d004 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java
@@ -142,7 +142,7 @@ public class OpenIDConnect implements LoginProtocol {
ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
try {
- new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, null, userSession.getId(), executor, 0);
+ new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, clientSession, executor, 0);
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index b88648e730..007925c028 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -14,6 +14,7 @@ import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@@ -611,6 +612,17 @@ public class OpenIDConnectService {
.build();
}
+ String httpSessionId = formData.getFirst(AdapterConstants.HTTP_SESSION_ID);
+ if (httpSessionId != null) {
+ String httpSessionHost = formData.getFirst(AdapterConstants.HTTP_SESSION_HOST);
+ logger.infof("Http Session '%s' saved in ClientSession for client '%s'. Host is '%s'", httpSessionId, client.getClientId(), httpSessionHost);
+
+ event.detail(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
+ clientSession.setNote(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
+ event.detail(AdapterConstants.HTTP_SESSION_HOST, httpSessionHost);
+ clientSession.setNote(AdapterConstants.HTTP_SESSION_HOST, httpSessionHost);
+ }
+
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
try {
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 105751fa13..2a4576f99e 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -17,22 +17,23 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
-import org.keycloak.representations.adapters.action.SessionStats;
-import org.keycloak.representations.adapters.action.SessionStatsAction;
import org.keycloak.representations.adapters.action.UserStats;
-import org.keycloak.representations.adapters.action.UserStatsAction;
import org.keycloak.services.util.HttpClientBuilder;
import org.keycloak.services.util.ResolveRelative;
+import org.keycloak.util.MultivaluedHashMap;
import org.keycloak.util.StringPropertyReplacer;
import org.keycloak.util.Time;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
/**
* @author Bill Burke
@@ -40,17 +41,7 @@ import java.util.Map;
*/
public class ResourceAdminManager {
protected static Logger logger = Logger.getLogger(ResourceAdminManager.class);
-
- public SessionStats getSessionStats(URI requestUri, KeycloakSession session, RealmModel realm, ApplicationModel application, boolean users) {
- ApacheHttpClient4Executor executor = createExecutor();
-
- try {
- return getSessionStats(requestUri, session, realm, application, users, executor);
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
- }
-
- }
+ private static final String KC_SESSION_HOST = "${kc_session_host}";
public static ApacheHttpClient4Executor createExecutor() {
HttpClient client = new HttpClientBuilder()
@@ -59,49 +50,6 @@ public class ResourceAdminManager {
return new ApacheHttpClient4Executor(client);
}
- public SessionStats getSessionStats(URI requestUri, KeycloakSession session, RealmModel realm, ApplicationModel application, boolean users, ApacheHttpClient4Executor client) {
- String managementUrl = getManagementUrl(requestUri, application);
- if (managementUrl != null) {
- SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName());
- adminAction.setListUsers(users);
- String token = new TokenManager().encodeToken(realm, adminAction);
- logger.debugv("session stats for application: {0} url: {1}", application.getName(), managementUrl);
- ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_GET_SESSION_STATS).build().toString());
- ClientResponse response = null;
- try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(SessionStats.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- try {
- if (response.getStatus() != 200) {
- logger.warn("Failed to get stats: " + response.getStatus());
- return null;
- }
- SessionStats stats = response.getEntity();
-
- // replace with username
- if (users && stats.getUsers() != null) {
- Map newUsers = new HashMap();
- for (Map.Entry entry : stats.getUsers().entrySet()) {
- UserModel user = session.users().getUserById(entry.getKey(), realm);
- if (user == null) continue;
- newUsers.put(user.getUsername(), entry.getValue());
-
- }
- stats.setUsers(newUsers);
- }
- return stats;
- } finally {
- response.releaseConnection();
- }
- } else {
- logger.debug("no management url.");
- return null;
- }
-
- }
-
public static String getManagementUrl(URI requestUri, ApplicationModel application) {
String mgmtUrl = application.getManagementUrl();
if (mgmtUrl == null || mgmtUrl.equals("")) {
@@ -111,95 +59,56 @@ public class ResourceAdminManager {
// this is to support relative admin urls when keycloak and applications are deployed on the same machine
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl);
- // this is for resolving URI like "http://${jboss.home.name}:8080/..." in order to send request to same machine and avoid LB in cluster env
+ // this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
return StringPropertyReplacer.replaceProperties(absoluteURI);
}
- public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user) {
+ public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
+ List userSessions = keycloakSession.sessions().getUserSessions(realm, user);
+ logoutUserSessions(requestUri, realm, userSessions);
+ }
+
+ protected void logoutUserSessions(URI requestUri, RealmModel realm, List userSessions) {
ApacheHttpClient4Executor executor = createExecutor();
try {
- return getUserStats(requestUri, realm, application, user, executor);
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
- }
-
- }
-
-
- public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user, ApacheHttpClient4Executor client) {
- String managementUrl = getManagementUrl(requestUri, application);
- if (managementUrl != null) {
- UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName(), user.getId());
- String token = new TokenManager().encodeToken(realm, adminAction);
- logger.debugv("session stats for application: {0} url: {1}", application.getName(), managementUrl);
- ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_GET_USER_STATS).build().toString());
- ClientResponse response = null;
- try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(UserStats.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ // Map from "app" to clientSessions for this app
+ MultivaluedHashMap clientSessions = new MultivaluedHashMap();
+ for (UserSessionModel userSession : userSessions) {
+ putClientSessions(clientSessions, userSession);
}
- try {
- if (response.getStatus() != 200) {
- logger.warn("Failed to get stats: " + response.getStatus());
- return null;
- }
- UserStats stats = response.getEntity();
- return stats;
- } finally {
- response.releaseConnection();
- }
- } else {
- logger.debug("no management url.");
- return null;
- }
+ logger.debugv("logging out {0} resources ", clientSessions.size());
+ logger.infov("logging out resources: " + clientSessions);
- }
-
- public void logoutUser(URI requestUri, RealmModel realm, String user, UserSessionModel session) {
- ApacheHttpClient4Executor executor = createExecutor();
-
- try {
- List resources;
- if (session != null) {
- resources = new LinkedList();
-
- for (ClientSessionModel clientSession : session.getClientSessions()) {
- ClientModel client = clientSession.getClient();
- if (client instanceof ApplicationModel) {
- resources.add((ApplicationModel) client);
- }
- }
- } else {
- resources = realm.getApplications();
- }
-
- logger.debugv("logging out {0} resources ", resources.size());
- for (ApplicationModel resource : resources) {
- logoutApplication(requestUri, realm, resource, user, session != null ? session.getId() : null, executor, 0);
+ for (Map.Entry> entry : clientSessions.entrySet()) {
+ logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
}
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
}
}
+ private void putClientSessions(MultivaluedHashMap clientSessions, UserSessionModel userSession) {
+ for (ClientSessionModel clientSession : userSession.getClientSessions()) {
+ ClientModel client = clientSession.getClient();
+ if (client instanceof ApplicationModel) {
+ clientSessions.add((ApplicationModel)client, clientSession);
+ }
+ }
+ }
+
public void logoutSession(URI requestUri, RealmModel realm, UserSessionModel session) {
ApacheHttpClient4Executor executor = createExecutor();
try {
- List resources = new LinkedList();
- for (ClientSessionModel clientSession : session.getClientSessions()) {
- ClientModel client = clientSession.getClient();
- if (client instanceof ApplicationModel) {
- resources.add((ApplicationModel) client);
- }
- }
+ // Map from "app" to clientSessions for this app
+ MultivaluedHashMap clientSessions = new MultivaluedHashMap();
+ putClientSessions(clientSessions, session);
- logger.debugv("logging out {0} resources ", resources.size());
- for (ApplicationModel resource : resources) {
- logoutApplication(requestUri, realm, resource, null, session.getId(), executor, 0);
+ logger.debugv("logging out {0} resources ", clientSessions.size());
+ for (Map.Entry> entry : clientSessions.entrySet()) {
+ logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
}
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
@@ -214,46 +123,77 @@ public class ResourceAdminManager {
List resources = realm.getApplications();
logger.debugv("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) {
- logoutApplication(requestUri, realm, resource, null, null, executor, realm.getNotBefore());
+ logoutApplication(requestUri, realm, resource, (List)null, executor, realm.getNotBefore());
}
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
}
}
- public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session) {
+ public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, List userSessions) {
ApacheHttpClient4Executor executor = createExecutor();
try {
resource.setNotBefore(Time.currentTime());
- logoutApplication(requestUri, realm, resource, user, session, executor, resource.getNotBefore());
+
+ List ourAppClientSessions = null;
+ if (userSessions != null) {
+ MultivaluedHashMap clientSessions = new MultivaluedHashMap();
+ for (UserSessionModel userSession : userSessions) {
+ putClientSessions(clientSessions, userSession);
+ }
+ ourAppClientSessions = clientSessions.get(resource);
+ }
+
+ logoutApplication(requestUri, realm, resource, ourAppClientSessions, executor, resource.getNotBefore());
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
}
}
+ public boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, ClientSessionModel clientSession, ApacheHttpClient4Executor client, int notBefore) {
+ return logoutApplication(requestUri, realm, resource, Arrays.asList(clientSession), client, notBefore);
+ }
- public boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session, ApacheHttpClient4Executor client, int notBefore) {
+ protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, List clientSessions, ApacheHttpClient4Executor client, int notBefore) {
String managementUrl = getManagementUrl(requestUri, resource);
if (managementUrl != null) {
- LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, session, notBefore);
- String token = new TokenManager().encodeToken(realm, adminAction);
- logger.debugv("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
- ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
- ClientResponse response;
- try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(UserStats.class);
- } catch (Exception e) {
- logger.warn("Logout for application '" + resource.getName() + "' failed", e);
- return false;
+
+ // Key is host, value is list of http sessions for this host
+ MultivaluedHashMap adapterSessionIds = null;
+ if (clientSessions != null && clientSessions.size() > 0) {
+ adapterSessionIds = new MultivaluedHashMap();
+ for (ClientSessionModel clientSession : clientSessions) {
+ String adapterSessionId = clientSession.getNote(AdapterConstants.HTTP_SESSION_ID);
+ if (adapterSessionId != null) {
+ String host = clientSession.getNote(AdapterConstants.HTTP_SESSION_HOST);
+ adapterSessionIds.add(host, adapterSessionId);
+ }
+ }
}
- try {
- boolean success = response.getStatus() == 204;
- logger.debug("logout success.");
- return success;
- } finally {
- response.releaseConnection();
+
+ if (managementUrl.contains(KC_SESSION_HOST) && adapterSessionIds != null) {
+ boolean allPassed = true;
+ // Send logout separately to each host (needed for single-sign-out in cluster for non-distributable apps - KEYCLOAK-748)
+ for (Map.Entry> entry : adapterSessionIds.entrySet()) {
+ String host = entry.getKey();
+ List sessionIds = entry.getValue();
+ String currentHostMgmtUrl = managementUrl.replace(KC_SESSION_HOST, host);
+ allPassed = logoutApplicationOnHost(realm, resource, sessionIds, client, notBefore, currentHostMgmtUrl) && allPassed;
+ }
+
+ return allPassed;
+ } else {
+ // Send single logout request
+ List allSessionIds = null;
+ if (adapterSessionIds != null) {
+ allSessionIds = new ArrayList();
+ for (List currentIds : adapterSessionIds.values()) {
+ allSessionIds.addAll(currentIds);
+ }
+ }
+ return logoutApplicationOnHost(realm, resource, allSessionIds, client, notBefore, managementUrl);
}
} else {
logger.debugv("Can't logout {0}: no management url", resource.getName());
@@ -261,6 +201,27 @@ public class ResourceAdminManager {
}
}
+ protected boolean logoutApplicationOnHost(RealmModel realm, ApplicationModel resource, List adapterSessionIds, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
+ LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore);
+ String token = new TokenManager().encodeToken(realm, adminAction);
+ logger.infov("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getName(), managementUrl);
+ ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
+ ClientResponse response;
+ try {
+ response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(UserStats.class);
+ } catch (Exception e) {
+ logger.warn("Logout for application '" + resource.getName() + "' failed", e);
+ return false;
+ }
+ try {
+ boolean success = response.getStatus() == 204;
+ logger.debug("logout success.");
+ return success;
+ } finally {
+ response.releaseConnection();
+ }
+ }
+
public void pushRealmRevocationPolicy(URI requestUri, RealmModel realm) {
ApacheHttpClient4Executor executor = createExecutor();
@@ -290,9 +251,9 @@ public class ResourceAdminManager {
if (managementUrl != null) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore);
String token = new TokenManager().encodeToken(realm, adminAction);
- logger.debugv("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
+ logger.infov("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString());
- ClientResponse response = null;
+ ClientResponse response;
try {
response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
} catch (Exception e) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index 237bf448b6..e8041614ef 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -13,8 +13,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.representations.adapters.action.SessionStats;
-import org.keycloak.representations.adapters.action.UserStats;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
@@ -27,7 +25,6 @@ import org.keycloak.util.JsonSerialization;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -285,35 +282,6 @@ public class ApplicationResource {
new ResourceAdminManager().pushApplicationRevocationPolicy(uriInfo.getRequestUri(), realm, application);
}
- /**
- * If the application has an admin URL, query it directly for session stats.
- *
- * @param users whether to include users logged in.
- * @return
- */
- @Path("session-stats")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public SessionStats getSessionStats(@QueryParam("users") @DefaultValue("false") boolean users) {
- logger.info("session-stats");
- auth.requireView();
- if (application.getManagementUrl() == null || application.getManagementUrl().trim().equals("")) {
- logger.info("sending empty stats");
- SessionStats stats = new SessionStats();
- if (users) stats.setUsers(new HashMap());
- return stats;
- }
- SessionStats stats = new ResourceAdminManager().getSessionStats(uriInfo.getRequestUri(), session, realm, application, users);
- if (stats == null) {
- logger.info("app returned null stats");
- } else {
- logger.info("activeUsers: " + stats.getActiveUsers());
- logger.info("activeSessions: " + stats.getActiveSessions());
- }
- return stats;
- }
-
/**
* Number of user sessions associated with this application
*
@@ -363,7 +331,7 @@ public class ApplicationResource {
@POST
public void logoutAll() {
auth.requireManage();
- new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null, null);
+ new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null);
}
/**
@@ -378,7 +346,9 @@ public class ApplicationResource {
if (user == null) {
throw new NotFoundException("User not found");
}
- new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, user.getId(), null);
+
+ List userSessions = session.sessions().getUserSessions(realm, user);
+ new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, userSessions);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 5cd0ac591d..9c6047ac9f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -19,7 +19,6 @@ import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.LDAPConnectionTestManager;
@@ -323,28 +322,6 @@ public class RealmAdminResource {
return data;
}
- /**
- * Any application that has an admin URL will be asked directly how many sessions they have active and what users
- * are involved with those sessions.
- *
- * @return
- */
- @Path("session-stats")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Map getSessionStats() {
- logger.info("session-stats");
- auth.requireView();
- Map stats = new HashMap();
- for (ApplicationModel applicationModel : realm.getApplications()) {
- if (applicationModel.getManagementUrl() == null) continue;
- SessionStats appStats = new ResourceAdminManager().getSessionStats(uriInfo.getRequestUri(), this.session, realm, applicationModel, false);
- stats.put(applicationModel.getName(), appStats);
- }
- return stats;
- }
-
/**
* View the events provider and how it is configured.
*
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index eea2808d54..5eff07bf63 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -24,7 +24,6 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.OpenIDConnect;
import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.adapters.action.UserStats;
import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
@@ -217,36 +216,6 @@ public class UsersResource {
return ModelToRepresentation.toRepresentation(user);
}
- /**
- * For each application with an admin URL, query them for the set of users logged in. This not as reliable
- * as getSessions().
- *
- * @See getSessions
- *
- * @param username
- * @return
- */
- @Path("{username}/session-stats")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Map getSessionStats(final @PathParam("username") String username) {
- logger.info("session-stats");
- auth.requireView();
- UserModel user = session.users().getUserByUsername(username, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- Map stats = new HashMap();
- for (ApplicationModel applicationModel : realm.getApplications()) {
- if (applicationModel.getManagementUrl() == null) continue;
- UserStats appStats = new ResourceAdminManager().getUserStats(uriInfo.getRequestUri(), realm, applicationModel, user);
- if (appStats == null) continue;
- if (appStats.isLoggedIn()) stats.put(applicationModel.getName(), appStats);
- }
- return stats;
- }
-
/**
* List set of sessions associated with this user.
*
@@ -258,7 +227,6 @@ public class UsersResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List getSessions(final @PathParam("username") String username) {
- logger.info("sessions");
auth.requireView();
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) {
@@ -345,8 +313,8 @@ public class UsersResource {
if (user == null) {
throw new NotFoundException("User not found");
}
+ new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user, session);
session.sessions().removeUserSessions(realm, user);
- new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), null);
}
/**
diff --git a/testsuite/docker-cluster/shared-files/deploy-examples.sh b/testsuite/docker-cluster/shared-files/deploy-examples.sh
index 10f9643798..f71f697a9a 100644
--- a/testsuite/docker-cluster/shared-files/deploy-examples.sh
+++ b/testsuite/docker-cluster/shared-files/deploy-examples.sh
@@ -23,15 +23,20 @@ done;
# Configure admin-access.war
sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml
+# Enforce refreshing token for product-portal and customer-portal war
+# sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' customer-portal.war/WEB-INF/keycloak.json;
+# sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' product-portal.war/WEB-INF/keycloak.json;
+
# Configure other examples
for I in *.war/WEB-INF/keycloak.json; do
- sed -i -e 's/\"\/auth\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;
+ sed -i -e 's/\"auth-server-url\".*: \"\/auth\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;
done;
# Enable distributable for customer-portal
sed -i -e 's/<\/module-name>/&\n /' customer-portal.war/WEB-INF/web.xml
# Configure testrealm.json - Enable adminUrl to access adapters on local machine
-sed -i -e 's/\"adminUrl\": \"/&http:\/\/\$\{jboss.host.name\}:8080/' /keycloak-docker-cluster/examples/testrealm.json
+sed -i -e 's/\"adminUrl\": \"\/customer-portal/\"adminUrl\": \"http:\/\/\$\{jboss.host.name\}:8080\/customer-portal/' /keycloak-docker-cluster/examples/testrealm.json
+sed -i -e 's/\"adminUrl\": \"\/product-portal/\"adminUrl\": \"http:\/\/\$\{kc_session_host\}:8080\/product-portal/' /keycloak-docker-cluster/examples/testrealm.json
diff --git a/testsuite/docker-cluster/shared-files/keycloak-run-node.sh b/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
index 55edc8267f..7d350aa762 100644
--- a/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
+++ b/testsuite/docker-cluster/shared-files/keycloak-run-node.sh
@@ -24,6 +24,8 @@ function prepareHost
# Enable Infinispan provider
sed -i "s|keycloak.userSessions.provider:mem|keycloak.userSessions.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
+ sed -i "s|keycloak.realm.cache.provider:mem|keycloak.realm.cache.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
+ sed -i "s|keycloak.user.cache.provider:mem|keycloak.user.cache.provider:infinispan|" $JBOSS_HOME/standalone/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
# Deploy and configure examples
/keycloak-docker-cluster/shared-files/deploy-examples.sh
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 28bd4b61b1..5443a7e742 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -23,10 +23,8 @@ package org.keycloak.testsuite.adapter;
import org.junit.Assert;
import org.junit.ClassRule;
-import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runners.MethodSorters;
import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.Version;
@@ -34,13 +32,13 @@ import org.keycloak.adapters.AdapterConstants;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OpenIDConnectService;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -48,6 +46,7 @@ import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.testutils.KeycloakServer;
@@ -67,6 +66,7 @@ import java.net.URI;
import java.net.URL;
import java.security.PublicKey;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests Undertow Adapter
@@ -158,17 +158,16 @@ public class AdapterTest {
Client client = ClientBuilder.newClient();
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
- Map stats = adminTarget.path("session-stats").request()
+ Map stats = adminTarget.path("application-session-stats").request()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
- .get(new GenericType