Merge pull request #750 from mposolda/stateless-session-mgmt

Stateless session mgmt
This commit is contained in:
Marek Posolda 2014-10-08 23:32:51 +02:00
commit a00d36eea2
38 changed files with 426 additions and 880 deletions

View file

@ -18,4 +18,7 @@ public interface AdapterConstants {
// org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in // org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in
// two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
// Attribute passed in codeToToken request from adapter to Keycloak and saved in ClientSession.
public static final String HTTP_SESSION_ID = "http_session_id";
} }

View file

@ -1,40 +1,25 @@
package org.keycloak.representations.adapters.action; package org.keycloak.representations.adapters.action;
import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class LogoutAction extends AdminAction { public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT"; public static final String LOGOUT = "LOGOUT";
protected String user; protected List<String> adapterSessionIds;
private String session;
protected int notBefore; protected int notBefore;
public LogoutAction() { 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<String> adapterSessionIds, int notBefore) {
super(id, expiration, resource, LOGOUT); super(id, expiration, resource, LOGOUT);
this.user = user; this.adapterSessionIds = adapterSessionIds;
this.session = session;
this.notBefore = notBefore; 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() { public int getNotBefore() {
return notBefore; return notBefore;
@ -44,6 +29,10 @@ public class LogoutAction extends AdminAction {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public List<String> getAdapterSessionIds() {
return adapterSessionIds;
}
@Override @Override
public boolean validate() { public boolean validate() {
return LOGOUT.equals(action); return LOGOUT.equals(action);

View file

@ -1,37 +0,0 @@
package org.keycloak.representations.adapters.action;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SessionStats {
protected int activeSessions;
protected int activeUsers;
protected Map<String, UserStats> 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<String, UserStats> getUsers() {
return users;
}
public void setUsers(Map<String, UserStats> users) {
this.users = users;
}
}

View file

@ -1,35 +0,0 @@
package org.keycloak.representations.adapters.action;
/**
* Query session stats.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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);
}
}

View file

@ -1,31 +0,0 @@
package org.keycloak.representations.adapters.action;
/**
* Query session stats.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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);
}
}

View file

@ -17,7 +17,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"connection-pool-size", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-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 { public class AdapterConfig extends BaseAdapterConfig {
@ -39,6 +39,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected int connectionPoolSize = 20; protected int connectionPoolSize = 20;
@JsonProperty("auth-server-url-for-backend-requests") @JsonProperty("auth-server-url-for-backend-requests")
protected String authServerUrlForBackendRequests; protected String authServerUrlForBackendRequests;
@JsonProperty("always-refresh-token")
protected boolean alwaysRefreshToken = false;
public boolean isAllowAnyHostname() { public boolean isAllowAnyHostname() {
return allowAnyHostname; return allowAnyHostname;
@ -111,4 +113,12 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) { public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
this.authServerUrlForBackendRequests = authServerUrlForBackendRequests; this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
} }
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
this.alwaysRefreshToken = alwaysRefreshToken;
}
} }

View file

@ -326,6 +326,16 @@ public class AdapterDeploymentContext {
public void setCorsAllowedHeaders(String corsAllowedHeaders) { public void setCorsAllowedHeaders(String corsAllowedHeaders) {
delegate.setCorsAllowedHeaders(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) { protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {

View file

@ -47,6 +47,7 @@ public class KeycloakDeployment {
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
protected String corsAllowedMethods; protected String corsAllowedMethods;
protected boolean exposeToken; protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected volatile int notBefore; protected volatile int notBefore;
public KeycloakDeployment() { public KeycloakDeployment() {
@ -281,4 +282,11 @@ public class KeycloakDeployment {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
this.alwaysRefreshToken = alwaysRefreshToken;
}
} }

View file

@ -35,7 +35,7 @@ public class KeycloakDeploymentBuilder {
String realmKeyPem = adapterConfig.getRealmKey(); String realmKeyPem = adapterConfig.getRealmKey();
if (realmKeyPem != null) { if (realmKeyPem != null) {
PublicKey realmKey = null; PublicKey realmKey;
try { try {
realmKey = PemUtils.decodePublicKey(realmKeyPem); realmKey = PemUtils.decodePublicKey(realmKeyPem);
} catch (Exception e) { } catch (Exception e) {
@ -60,9 +60,7 @@ public class KeycloakDeploymentBuilder {
} }
deployment.setBearerOnly(adapterConfig.isBearerOnly()); deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
if (adapterConfig.isBearerOnly()) {
}
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { 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"); 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) { public static KeycloakDeployment build(InputStream is) {
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory()); ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
AdapterConfig adapterConfig = null; AdapterConfig adapterConfig;
try { try {
adapterConfig = mapper.readValue(is, AdapterConfig.class); adapterConfig = mapper.readValue(is, AdapterConfig.class);
} catch (IOException e) { } catch (IOException e) {

View file

@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong;
public abstract class OAuthRequestAuthenticator { public abstract class OAuthRequestAuthenticator {
private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class); private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class);
protected KeycloakDeployment deployment; protected KeycloakDeployment deployment;
protected RequestAuthenticator reqAuthenticator;
protected int sslRedirectPort; protected int sslRedirectPort;
protected String tokenString; protected String tokenString;
protected String idTokenString; protected String idTokenString;
@ -31,7 +32,8 @@ public abstract class OAuthRequestAuthenticator {
protected String refreshToken; protected String refreshToken;
protected String strippedOauthParametersRequestUri; 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.facade = facade;
this.deployment = deployment; this.deployment = deployment;
this.sslRedirectPort = sslRedirectPort; this.sslRedirectPort = sslRedirectPort;
@ -253,7 +255,8 @@ public abstract class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null; AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect(); strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try { try {
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri); String httpSessionId = reqAuthenticator.getHttpSessionId(true);
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) { } catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token"); log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus()); log.error("status from server: " + failure.getStatus());

View file

@ -7,10 +7,6 @@ import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction; 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.JsonSerialization;
import org.keycloak.util.StreamUtil; import org.keycloak.util.StreamUtil;
@ -60,14 +56,6 @@ public class PreAuthActionsHandler {
if (!resolveDeployment()) return true; if (!resolveDeployment()) return true;
handlePushNotBefore(); handlePushNotBefore();
return true; 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)) { } else if (requestUri.endsWith(AdapterConstants.K_VERSION)) {
handleVersion(); handleVersion();
return true; return true;
@ -123,14 +111,10 @@ public class PreAuthActionsHandler {
} }
LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class); LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
if (!validateAction(action)) return; if (!validateAction(action)) return;
String user = action.getUser(); if (action.getAdapterSessionIds() != null) {
if (user != null) { userSessionManagement.logoutHttpSessions(action.getAdapterSessionIds());
log.debug("logout of session for: " + user);
userSessionManagement.logoutUser(user);
} else if (action.getSession() != null) {
userSessionManagement.logoutKeycloakSession(action.getSession());
} else { } else {
log.debug("logout of all sessions"); log.infof("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) { if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.setNotBefore(action.getNotBefore()); deployment.setNotBefore(action.getNotBefore());
} }
@ -208,50 +192,6 @@ public class PreAuthActionsHandler {
return true; 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<String, UserStats> list = new HashMap<String, UserStats>();
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() { protected void handleVersion() {
try { try {
facade.getResponse().setStatus(200); 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;
}
} }

View file

@ -32,13 +32,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
@Override @Override
public AccessToken getToken() { public AccessToken getToken() {
refreshExpiredToken(); refreshExpiredToken(true);
return super.getToken(); return super.getToken();
} }
@Override @Override
public String getTokenString() { public String getTokenString() {
refreshExpiredToken(); refreshExpiredToken(true);
return super.getTokenString(); return super.getTokenString();
} }
@ -62,12 +62,19 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.deployment = deployment; this.deployment = deployment;
} }
public void refreshExpiredToken() { /**
* @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()) { if (log.isTraceEnabled()) {
log.trace("checking whether to refresh."); log.trace("checking whether to refresh.");
} }
if (isActive()) return; if (isActive()) return true;
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()) { if (log.isTraceEnabled()) {
log.trace("Doing refresh"); log.trace("Doing refresh");
@ -77,10 +84,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
response = ServerRequest.invokeRefresh(deployment, refreshToken); response = ServerRequest.invokeRefresh(deployment, refreshToken);
} catch (IOException e) { } catch (IOException e) {
log.error("Refresh token failure", e); log.error("Refresh token failure", e);
return; return false;
} catch (ServerRequest.HttpFailure httpFailure) { } catch (ServerRequest.HttpFailure httpFailure) {
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError()); log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
return; return false;
} }
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("received refresh response"); log.trace("received refresh response");
@ -100,7 +107,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.token = token; this.token = token;
this.refreshToken = response.getRefreshToken(); this.refreshToken = response.getRefreshToken();
this.tokenString = tokenString; this.tokenString = tokenString;
return true;
} }

View file

@ -111,6 +111,7 @@ public abstract class RequestAuthenticator {
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal); protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal); protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract boolean isCached(); protected abstract boolean isCached();
protected abstract String getHttpSessionId(boolean create);
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) { protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);

View file

@ -84,21 +84,24 @@ public class ServerRequest {
if (is != null) is.close(); 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 codeUrl = deployment.getCodeUrl();
String client_id = deployment.getResourceName(); String client_id = deployment.getResourceName();
Map<String, String> credentials = deployment.getResourceCredentials(); Map<String, String> credentials = deployment.getResourceCredentials();
HttpClient client = deployment.getClient(); 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<String, String> credentials) throws IOException, HttpFailure { public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials, String sessionId) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); List<NameValuePair> formparams = new ArrayList<NameValuePair>();
redirectUri = stripOauthParametersFromRedirect(redirectUri); redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code")); formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code)); formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri)); formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
if (sessionId != null) {
formparams.add(new BasicNameValuePair(AdapterConstants.HTTP_SESSION_ID, sessionId));
}
HttpResponse response = null; HttpResponse response = null;
HttpPost post = new HttpPost(codeUrl); HttpPost post = new HttpPost(codeUrl);
if (!publicClient) { if (!publicClient) {

View file

@ -1,21 +1,14 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import java.util.Set; import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface UserSessionManagement { public interface UserSessionManagement {
int getActiveSessions();
Long getUserLoginTime(String username);
Set<String> getActiveUsers();
void logoutAll(); void logoutAll();
void logoutUser(String user); void logoutHttpSessions(List<String> ids);
void logoutKeycloakSession(String id);
} }

View file

@ -18,6 +18,8 @@ import java.security.Principal;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpSession;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
@ -40,7 +42,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override @Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() { protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) { return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override @Override
protected void saveRequest() { protected void saveRequest() {
try { try {
@ -64,15 +66,15 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
session.setNote(KeycloakSecurityContext.class.getName(), securityContext); session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
String username = securityContext.getToken().getSubject(); String username = securityContext.getToken().getSubject();
log.debug("userSessionManage.login: " + username); log.debug("userSessionManage.login: " + username);
userSessionManagement.login(session, username, securityContext.getToken().getSessionState()); userSessionManagement.login(session);
} }
@Override @Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) { protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
for (String role : roles) { if (log.isDebugEnabled()) {
log.info("Bearer role: " + role); log.debug("Completing bearer authentication. Bearer roles: " + roles);
} }
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext); Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal); 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;
}
} }

View file

@ -1,17 +1,14 @@
package org.keycloak.adapters.as7; package org.keycloak.adapters.as7;
import org.apache.catalina.Manager;
import org.apache.catalina.Session; import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent; import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener; import org.apache.catalina.SessionListener;
import org.apache.catalina.realm.GenericPrincipal; import org.apache.catalina.realm.GenericPrincipal;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.UserSessionManagement;
import java.util.HashMap; import java.io.IOException;
import java.util.HashSet; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Manages relationship to users and sessions so that forced admin logout can be implemented * Manages relationship to users and sessions so that forced admin logout can be implemented
@ -19,112 +16,50 @@ import java.util.concurrent.ConcurrentHashMap;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement { public class CatalinaUserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class); private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class);
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
public static class UserSessions { public void login(Session session) {
protected String user;
protected long loggedIn = System.currentTimeMillis();
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
protected Map<String, Session> sessions = new HashMap<String, Session>();
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<String> getActiveUsers() {
HashSet<String> set = new HashSet<String>();
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);
session.addSessionListener(this); session.addSessionListener(this);
} }
@Override public void logoutAll(Manager sessionManager) {
public void logoutAll() { Session[] allSessions = sessionManager.findSessions();
for (String user : userSessionMap.keySet()) logoutUser(user); for (Session session : allSessions) {
logoutSession(session);
}
} }
@Override public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
public void logoutUser(String user) { log.debug("logoutHttpSessions: " + sessionIds);
log.debug("logoutUser: " + user);
UserSessions sessions = null; for (String sessionId : sessionIds) {
sessions = userSessionMap.remove(user); logoutSession(sessionManager, sessionId);
if (sessions == null) { }
log.debug("no session for user: " + user); }
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; return;
} }
log.debug("found session for user");
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) { logoutSession(session);
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);
}
} }
public synchronized void logoutKeycloakSession(String keycloakSessionId) { protected void logoutSession(Session session) {
log.debug("logoutKeycloakSession: " + keycloakSessionId); try {
UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId); session.expire();
if (sessions == null) { } catch (Exception e) {
log.debug("no session for keycloak session id: " + keycloakSessionId); log.warnf("Session not present or already invalidated.");
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);
} }
} }
public void sessionEvent(SessionEvent event) { public void sessionEvent(SessionEvent event) {
// We only care about session destroyed events // We only care about session destroyed events
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()) 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) // Look up the single session id associated with this session (if any)
Session session = event.getSession(); Session session = event.getSession();
log.debugf("Session %s destroyed", session.getId());
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal(); GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
if (principal == null) return; if (principal == null) return;
session.setPrincipal(null); session.setPrincipal(null);
session.setAuthType(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);
}
}
} }
} }

View file

@ -0,0 +1,30 @@
package org.keycloak.adapters.as7;
import java.util.List;
import org.apache.catalina.Manager;
import org.keycloak.adapters.UserSessionManagement;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> ids) {
delegate.logoutHttpSessions(sessionManager, ids);
}
}

View file

@ -5,6 +5,7 @@ import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Session; import org.apache.catalina.Session;
import org.apache.catalina.authenticator.FormAuthenticator; import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Request;
@ -127,7 +128,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
log.trace("invoke"); log.trace("invoke");
} }
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response); 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()) { if (handler.handleRequest()) {
return; return;
} }
@ -175,18 +178,22 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return; if (session == null) return;
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); 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 // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated // not be updated
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (session.isActive()) return; 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.setUserPrincipal(null);
request.setAuthType(null); request.setAuthType(null);
request.getSessionInternal().setPrincipal(null); catalinaSession.setPrincipal(null);
request.getSessionInternal().setAuthType(null); catalinaSession.setAuthType(null);
catalinaSession.expire();
} }
public void keycloakSaveRequest(Request request) throws IOException { public void keycloakSaveRequest(Request request) throws IOException {

View file

@ -227,7 +227,7 @@ public class KeycloakInstalled {
} }
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException { 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); parseAccessToken(tokenResponse);
} }

View file

@ -41,7 +41,8 @@ public class ServletOAuthClient extends AbstractOAuthClient {
} }
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure { 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);
} }
/** /**

View file

@ -16,8 +16,11 @@ import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.servlet.http.HttpSession;
/** /**
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a> * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
@ -40,7 +43,7 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override @Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() { protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) { return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override @Override
protected void saveRequest() { protected void saveRequest() {
try { try {
@ -63,16 +66,16 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
session.setAuthType("OAUTH"); session.setAuthType("OAUTH");
session.setNote(KeycloakSecurityContext.class.getName(), securityContext); session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
String username = securityContext.getToken().getSubject(); String username = securityContext.getToken().getSubject();
log.finer("userSessionManage.login: " + username); log.finer("userSessionManagement.login: " + username);
userSessionManagement.login(session, username, securityContext.getToken().getSessionState()); userSessionManagement.login(session);
} }
@Override @Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) { protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
for (String role : roles) { if (log.isLoggable(Level.FINE)) {
log.info("Bearer role: " + role); log.fine("Completing bearer authentication. Bearer roles: " + roles);
} }
Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext); Principal generalPrincipal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), principal, roles, securityContext);
request.setUserPrincipal(generalPrincipal); 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;
}
} }

View file

@ -1,16 +1,13 @@
package org.keycloak.adapters.tomcat7; package org.keycloak.adapters.tomcat7;
import org.apache.catalina.Manager;
import org.apache.catalina.Session; import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent; import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener; import org.apache.catalina.SessionListener;
import org.apache.catalina.realm.GenericPrincipal; import org.apache.catalina.realm.GenericPrincipal;
import org.keycloak.adapters.UserSessionManagement;
import java.util.HashMap; import java.io.IOException;
import java.util.HashSet; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -19,105 +16,52 @@ import java.util.logging.Logger;
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a> * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CatalinaUserSessionManagement implements SessionListener, UserSessionManagement { public class CatalinaUserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class); private static final Logger log = Logger.getLogger(""+CatalinaUserSessionManagement.class);
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
public static class UserSessions { public void login(Session session) {
protected String user;
protected long loggedIn = System.currentTimeMillis();
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
protected Map<String, Session> sessions = new HashMap<String, Session>();
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<String> getActiveUsers() {
HashSet<String> set = new HashSet<String>();
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);
session.addSessionListener(this); session.addSessionListener(this);
} }
@Override public void logoutAll(Manager sessionManager) {
public void logoutAll() { Session[] allSessions = sessionManager.findSessions();
for (String user : userSessionMap.keySet()) logoutUser(user); for (Session session : allSessions) {
logoutSession(session);
}
} }
@Override public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
public void logoutUser(String user) { log.fine("logoutHttpSessions: " + sessionIds);
UserSessions sessions = null;
sessions = userSessionMap.remove(user); for (String sessionId : sessionIds) {
if (sessions == null) { 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; return;
} }
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
String sessionId = entry.getKey(); logoutSession(session);
String keycloakSessionId = entry.getValue();
Session session = sessions.sessions.get(sessionId);
session.setPrincipal(null);
session.setAuthType(null);
session.getSession().invalidate();
keycloakSessionMap.remove(keycloakSessionId);
}
} }
public synchronized void logoutKeycloakSession(String keycloakSessionId) { protected void logoutSession(Session session) {
UserSessions sessions = keycloakSessionMap.remove(keycloakSessionId); try {
if (sessions == null) { session.expire();
return; } catch (Exception e) {
} log.warning("Session not present or already invalidated.");
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);
} }
} }
public void sessionEvent(SessionEvent event) { public void sessionEvent(SessionEvent event) {
// We only care about session destroyed events // We only care about session destroyed events
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()) 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) // Look up the single session id associated with this session (if any)
Session session = event.getSession(); Session session = event.getSession();
log.fine("Session " + session.getId() + " destroyed");
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal(); GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
if (principal == null) return; if (principal == null) return;
session.setPrincipal(null); session.setPrincipal(null);
session.setAuthType(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);
}
}
} }
} }

View file

@ -0,0 +1,30 @@
package org.keycloak.adapters.tomcat7;
import java.util.List;
import org.apache.catalina.Manager;
import org.keycloak.adapters.UserSessionManagement;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> ids) {
delegate.logoutHttpSessions(sessionManager, ids);
}
}

View file

@ -5,6 +5,7 @@ import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Session; import org.apache.catalina.Session;
import org.apache.catalina.authenticator.FormAuthenticator; import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request; 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 { public void invoke(Request request, Response response) throws IOException, ServletException {
try { try {
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response); 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()) { if (handler.handleRequest()) {
return; return;
} }
@ -177,18 +180,22 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return; if (session == null) return;
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); 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 // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated // not be updated
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (session.isActive()) return; 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.setUserPrincipal(null);
request.setAuthType(null); request.setAuthType(null);
request.getSessionInternal().setPrincipal(null); catalinaSession.setPrincipal(null);
request.getSessionInternal().setAuthType(null); catalinaSession.setAuthType(null);
catalinaSession.expire();
} }
public void keycloakSaveRequest(Request request) throws IOException { public void keycloakSaveRequest(Request request) throws IOException {

View file

@ -92,14 +92,14 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
public boolean isActive() { public boolean isActive() {
// this object may have been serialized, so we need to reset realm config/metadata // this object may have been serialized, so we need to reset realm config/metadata
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.isActive()) { if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
log.debug("session is active"); log.debug("session is active");
return true; return true;
} }
log.debug("session is not active try refresh"); log.debug("session is not active or refresh is enforced. Try refresh");
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (!session.isActive()) { if (!success || !session.isActive()) {
log.debug("session is not active return with failure"); log.debug("session is not active return with failure");
return false; return false;

View file

@ -18,7 +18,9 @@ package org.keycloak.adapters.undertow;
import io.undertow.security.api.SecurityContext; import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.handlers.ServletRequestContext;
import io.undertow.util.Sessions;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpFacade; import org.keycloak.adapters.HttpFacade;
@ -45,9 +47,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
@Override @Override
protected boolean isCached() { protected boolean isCached() {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpSession session = getSession(false);
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
HttpSession session = req.getSession(false);
if (session == null) { if (session == null) {
log.debug("session was null, returning null"); log.debug("session was null, returning null");
return false; return false;
@ -63,11 +63,13 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
securityContext.authenticationComplete(account, "KEYCLOAK", false); securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext( account); propagateKeycloakContext( account);
return true; return true;
} } else {
log.debug("Account was not active, returning null"); log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
session.setAttribute(KeycloakUndertowAccount.class.getName(), null); session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
session.invalidate();
return false; return false;
} }
}
@Override @Override
protected void propagateKeycloakContext(KeycloakUndertowAccount account) { protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
@ -80,10 +82,9 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
@Override @Override
protected void login(KeycloakAccount account) { protected void login(KeycloakAccount account) {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest(); HttpSession session = getSession(true);
HttpSession session = req.getSession(true);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account); 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<RefreshableKeycloakSecurityContext> principal) { protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
return new KeycloakUndertowAccount(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);
}
} }

View file

@ -19,6 +19,7 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.session.SessionManager; import io.undertow.server.session.SessionManager;
import org.keycloak.adapters.UserSessionManagement; import org.keycloak.adapters.UserSessionManagement;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
@ -35,33 +36,13 @@ public class SessionManagementBridge implements UserSessionManagement {
this.sessionManager = sessionManager; this.sessionManager = sessionManager;
} }
@Override
public int getActiveSessions() {
return userSessionManagement.getActiveSessions();
}
@Override
public Long getUserLoginTime(String username) {
return userSessionManagement.getUserLoginTime(username);
}
@Override
public Set<String> getActiveUsers() {
return userSessionManagement.getActiveUsers();
}
@Override @Override
public void logoutAll() { public void logoutAll() {
userSessionManagement.logoutAll(sessionManager); userSessionManagement.logoutAll(sessionManager);
} }
@Override @Override
public void logoutUser(String user) { public void logoutHttpSessions(List<String> ids) {
userSessionManagement.logoutUser(sessionManager, user); userSessionManagement.logoutHttpSessions(sessionManager, ids);
}
@Override
public void logoutKeycloakSession(String id) {
userSessionManagement.logoutKeycloakSession(sessionManager, id);
} }
} }

View file

@ -54,7 +54,7 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
@Override @Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() { protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(facade, deployment, sslRedirectPort) { return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
@Override @Override
protected void saveRequest() { protected void saveRequest() {
// todo // todo
@ -73,9 +73,7 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
protected void login(KeycloakAccount account) { protected void login(KeycloakAccount account) {
Session session = Sessions.getOrCreateSession(exchange); Session session = Sessions.getOrCreateSession(exchange);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account); session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
String username = account.getPrincipal().getName(); userSessionManagement.login(session.getSessionManager());
String keycloakSessionId = account.getKeycloakSecurityContext().getToken().getSessionState();
userSessionManagement.login(session.getSessionManager(), session.getId(), username, keycloakSessionId);
} }
@ -90,25 +88,38 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
protected boolean isCached() { protected boolean isCached() {
Session session = Sessions.getSession(exchange); Session session = Sessions.getSession(exchange);
if (session == null) { if (session == null) {
log.info("session was null, returning null"); log.debug("session was null, returning null");
return false; return false;
} }
KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName()); KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
if (account == null) { if (account == null) {
log.info("Account was not in session, returning null"); log.debug("Account was not in session, returning null");
return false; return false;
} }
account.setDeployment(deployment); account.setDeployment(deployment);
if (account.isActive()) { if (account.isActive()) {
log.info("Cached account found"); log.debug("Cached account found");
securityContext.authenticationComplete(account, "KEYCLOAK", false); securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext( account); propagateKeycloakContext( account);
return true; return true;
} } else {
log.info("Account was not active, returning false"); log.debug("Account was not active, returning false");
session.removeAttribute(KeycloakUndertowAccount.class.getName()); session.removeAttribute(KeycloakUndertowAccount.class.getName());
session.invalidate(exchange);
return false; 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;
}
}
/** /**
* Subclasses need to be able to create their own version of the KeycloakUndertowAccount * Subclasses need to be able to create their own version of the KeycloakUndertowAccount

View file

@ -27,6 +27,7 @@ import org.jboss.logging.Logger;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -40,106 +41,35 @@ import java.util.concurrent.ConcurrentHashMap;
public class UndertowUserSessionManagement implements SessionListener { public class UndertowUserSessionManagement implements SessionListener {
private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class); private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class);
private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession"; private static final String AUTH_SESSION_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession";
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
protected ConcurrentHashMap<String, UserSessions> keycloakSessionMap = new ConcurrentHashMap<String, UserSessions>();
protected volatile boolean registered; protected volatile boolean registered;
public void login(SessionManager manager) {
public static class UserSessions {
protected String user;
protected long loggedIn = System.currentTimeMillis();
protected Map<String, String> keycloakSessionToHttpSession = new HashMap<String, String>();
protected Map<String, String> httpSessionToKeycloakSession = new HashMap<String, String>();
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<String> getActiveUsers() {
HashSet<String> set = new HashSet<String>();
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);
if (!registered) { if (!registered) {
manager.registerSessionListener(this); manager.registerSessionListener(this);
registered = true; registered = true;
} }
} }
public synchronized void logoutAll(SessionManager manager) { public void logoutAll(SessionManager manager) {
for (String user : userSessionMap.keySet()) logoutUser(manager, user); Set<String> allSessions = manager.getAllSessions();
for (String sessionId : allSessions) logoutSession(manager, sessionId);
} }
public synchronized void logoutUser(SessionManager manager, String user) { public void logoutHttpSessions(SessionManager manager, List<String> sessionIds) {
log.debug("logoutUser: " + user); log.debug("logoutHttpSessions: " + sessionIds);
UserSessions sessions = null;
sessions = userSessionMap.remove(user); for (String sessionId : sessionIds) {
if (sessions == null) { logoutSession(manager, sessionId);
log.debug("no session for user: " + user);
return;
} }
log.debug("found session for user"); }
for (Map.Entry<String, String> entry : sessions.httpSessionToKeycloakSession.entrySet()) {
log.debug("invalidating session for user: " + user); protected void logoutSession(SessionManager manager, String httpSessionId) {
String sessionId = entry.getKey(); log.debug("logoutHttpSession: " + httpSessionId);
String keycloakSessionId = entry.getValue(); Session session = getSessionById(manager, httpSessionId);
Session session = getSessionById(manager, sessionId);
try { try {
session.invalidate(null); session.invalidate(null);
} catch (Exception e) { } catch (Exception e) {
log.warn("Session already invalidated."); log.warnf("Session %s not present or already invalidated.", httpSessionId);
}
keycloakSessionMap.remove(keycloakSessionId);
}
}
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);
try {
session.invalidate(null);
} catch (Exception e) {
log.warn("Session already invalidated.");
}
if (sessions.keycloakSessionToHttpSession.size() == 0) {
userSessionMap.remove(sessions.user);
} }
} }
@ -188,23 +118,6 @@ public class UndertowUserSessionManagement implements SessionListener {
// Look up the single session id associated with this session (if any) // Look up the single session id associated with this session (if any)
String username = getUsernameFromSession(session); String username = getUsernameFromSession(session);
log.debugf("Session destroyed for user: %s, sessionId: %s", username, session.getId()); 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) { protected String getUsernameFromSession(Session session) {
@ -217,23 +130,6 @@ public class UndertowUserSessionManagement implements SessionListener {
@Override @Override
public void sessionIdChanged(Session session, String oldSessionId) { 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 @Override

View file

@ -142,7 +142,7 @@ public class OpenIDConnect implements LoginProtocol {
ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor(); ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
try { try {
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, null, userSession.getId(), executor, 0); new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, app, clientSession, executor, 0);
} finally { } finally {
executor.getHttpClient().getConnectionManager().shutdown(); executor.getHttpClient().getConnectionManager().shutdown();
} }

View file

@ -14,6 +14,7 @@ import org.keycloak.Config;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -611,6 +612,13 @@ public class OpenIDConnectService {
.build(); .build();
} }
String httpSessionId = formData.getFirst(AdapterConstants.HTTP_SESSION_ID);
if (httpSessionId != null) {
logger.debugf("Http Session '%s' saved in ClientSession for client '%s'", httpSessionId, client.getClientId());
event.detail(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
clientSession.setNote(AdapterConstants.HTTP_SESSION_ID, httpSessionId);
}
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession); AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
try { try {

View file

@ -17,10 +17,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction; 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.UserStats;
import org.keycloak.representations.adapters.action.UserStatsAction;
import org.keycloak.services.util.HttpClientBuilder; import org.keycloak.services.util.HttpClientBuilder;
import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.util.ResolveRelative;
import org.keycloak.util.StringPropertyReplacer; import org.keycloak.util.StringPropertyReplacer;
@ -29,8 +26,9 @@ import org.keycloak.util.Time;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -41,17 +39,6 @@ import java.util.Map;
public class ResourceAdminManager { public class ResourceAdminManager {
protected static Logger logger = Logger.getLogger(ResourceAdminManager.class); 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();
}
}
public static ApacheHttpClient4Executor createExecutor() { public static ApacheHttpClient4Executor createExecutor() {
HttpClient client = new HttpClientBuilder() HttpClient client = new HttpClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default .disableTrustManager() // todo fix this, should have a trust manager or a good default
@ -59,49 +46,6 @@ public class ResourceAdminManager {
return new ApacheHttpClient4Executor(client); 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<SessionStats> 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<String, UserStats> newUsers = new HashMap<String, UserStats>();
for (Map.Entry<String, UserStats> 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) { public static String getManagementUrl(URI requestUri, ApplicationModel application) {
String mgmtUrl = application.getManagementUrl(); String mgmtUrl = application.getManagementUrl();
if (mgmtUrl == null || mgmtUrl.equals("")) { if (mgmtUrl == null || mgmtUrl.equals("")) {
@ -111,77 +55,47 @@ public class ResourceAdminManager {
// this is to support relative admin urls when keycloak and applications are deployed on the same machine // this is to support relative admin urls when keycloak and applications are deployed on the same machine
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl); 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); 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<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
logoutUserSessions(requestUri, realm, userSessions);
}
protected void logoutUserSessions(URI requestUri, RealmModel realm, List<UserSessionModel> userSessions) {
ApacheHttpClient4Executor executor = createExecutor(); ApacheHttpClient4Executor executor = createExecutor();
try { try {
return getUserStats(requestUri, realm, application, user, executor); // Map from "app" to clientSessions for this app
Map<ApplicationModel, List<ClientSessionModel>> clientSessions = new HashMap<ApplicationModel, List<ClientSessionModel>>();
for (UserSessionModel userSession : userSessions) {
putClientSessions(clientSessions, userSession);
}
logger.debugv("logging out {0} resources ", clientSessions.size());
logger.infov("logging out resources: " + clientSessions);
for (Map.Entry<ApplicationModel, List<ClientSessionModel>> entry : clientSessions.entrySet()) {
logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
}
} finally { } finally {
executor.getHttpClient().getConnectionManager().shutdown(); executor.getHttpClient().getConnectionManager().shutdown();
} }
} }
private void putClientSessions(Map<ApplicationModel, List<ClientSessionModel>> clientSessions, UserSessionModel userSession) {
public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user, ApacheHttpClient4Executor client) { for (ClientSessionModel clientSession : userSession.getClientSessions()) {
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<UserStats> response = null;
try {
response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(UserStats.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
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;
}
}
public void logoutUser(URI requestUri, RealmModel realm, String user, UserSessionModel session) {
ApacheHttpClient4Executor executor = createExecutor();
try {
List<ApplicationModel> resources;
if (session != null) {
resources = new LinkedList<ApplicationModel>();
for (ClientSessionModel clientSession : session.getClientSessions()) {
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
if (client instanceof ApplicationModel) { if (client instanceof ApplicationModel) {
resources.add((ApplicationModel) client); List<ClientSessionModel> curClientSessions = clientSessions.get(client);
if (curClientSessions == null) {
curClientSessions = new ArrayList<ClientSessionModel>();
clientSessions.put((ApplicationModel)client, curClientSessions);
} }
curClientSessions.add(clientSession);
} }
} 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);
}
} finally {
executor.getHttpClient().getConnectionManager().shutdown();
} }
} }
@ -189,17 +103,12 @@ public class ResourceAdminManager {
ApacheHttpClient4Executor executor = createExecutor(); ApacheHttpClient4Executor executor = createExecutor();
try { try {
List<ApplicationModel> resources = new LinkedList<ApplicationModel>(); Map<ApplicationModel, List<ClientSessionModel>> clientSessions = new HashMap<ApplicationModel, List<ClientSessionModel>>();
for (ClientSessionModel clientSession : session.getClientSessions()) { putClientSessions(clientSessions, session);
ClientModel client = clientSession.getClient();
if (client instanceof ApplicationModel) {
resources.add((ApplicationModel) client);
}
}
logger.debugv("logging out {0} resources ", resources.size()); logger.debugv("logging out {0} resources ", clientSessions.size());
for (ApplicationModel resource : resources) { for (Map.Entry<ApplicationModel, List<ClientSessionModel>> entry : clientSessions.entrySet()) {
logoutApplication(requestUri, realm, resource, null, session.getId(), executor, 0); logoutApplication(requestUri, realm, entry.getKey(), entry.getValue(), executor, 0);
} }
} finally { } finally {
executor.getHttpClient().getConnectionManager().shutdown(); executor.getHttpClient().getConnectionManager().shutdown();
@ -214,32 +123,57 @@ public class ResourceAdminManager {
List<ApplicationModel> resources = realm.getApplications(); List<ApplicationModel> resources = realm.getApplications();
logger.debugv("logging out {0} resources ", resources.size()); logger.debugv("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) { for (ApplicationModel resource : resources) {
logoutApplication(requestUri, realm, resource, null, null, executor, realm.getNotBefore()); logoutApplication(requestUri, realm, resource, (List<ClientSessionModel>)null, executor, realm.getNotBefore());
} }
} finally { } finally {
executor.getHttpClient().getConnectionManager().shutdown(); 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<UserSessionModel> userSessions) {
ApacheHttpClient4Executor executor = createExecutor(); ApacheHttpClient4Executor executor = createExecutor();
try { try {
resource.setNotBefore(Time.currentTime()); resource.setNotBefore(Time.currentTime());
logoutApplication(requestUri, realm, resource, user, session, executor, resource.getNotBefore());
List<ClientSessionModel> ourAppClientSessions = null;
if (userSessions != null) {
Map<ApplicationModel, List<ClientSessionModel>> clientSessions = new HashMap<ApplicationModel, List<ClientSessionModel>>();
for (UserSessionModel userSession : userSessions) {
putClientSessions(clientSessions, userSession);
}
ourAppClientSessions = clientSessions.get(resource);
}
logoutApplication(requestUri, realm, resource, ourAppClientSessions, executor, resource.getNotBefore());
} finally { } finally {
executor.getHttpClient().getConnectionManager().shutdown(); 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<ClientSessionModel> clientSessions, ApacheHttpClient4Executor client, int notBefore) {
String managementUrl = getManagementUrl(requestUri, resource); String managementUrl = getManagementUrl(requestUri, resource);
if (managementUrl != null) { if (managementUrl != null) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, session, notBefore);
List<String> adapterSessionIds = null;
if (clientSessions != null && clientSessions.size() > 0) {
adapterSessionIds = new ArrayList<String>();
for (ClientSessionModel clientSession : clientSessions) {
String adapterSessionId = clientSession.getNote(AdapterConstants.HTTP_SESSION_ID);
if (adapterSessionId != null) {
adapterSessionIds.add(adapterSessionId);
}
}
}
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore);
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(realm, adminAction);
logger.debugv("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl); logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getName(), managementUrl);
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString()); ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
ClientResponse response; ClientResponse response;
try { try {
@ -292,7 +226,7 @@ public class ResourceAdminManager {
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(realm, adminAction);
logger.debugv("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl); logger.debugv("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString()); ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString());
ClientResponse response = null; ClientResponse response;
try { try {
response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(); response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
} catch (Exception e) { } catch (Exception e) {

View file

@ -13,8 +13,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel; 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.ApplicationRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation;
@ -27,7 +25,6 @@ import org.keycloak.util.JsonSerialization;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
@ -285,35 +282,6 @@ public class ApplicationResource {
new ResourceAdminManager().pushApplicationRevocationPolicy(uriInfo.getRequestUri(), realm, application); 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<String, UserStats>());
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 * Number of user sessions associated with this application
* *
@ -363,7 +331,7 @@ public class ApplicationResource {
@POST @POST
public void logoutAll() { public void logoutAll() {
auth.requireManage(); 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) { if (user == null) {
throw new NotFoundException("User not found"); throw new NotFoundException("User not found");
} }
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, user.getId(), null);
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, userSessions);
} }

View file

@ -19,7 +19,6 @@ import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.LDAPConnectionTestManager; import org.keycloak.services.managers.LDAPConnectionTestManager;
@ -283,28 +282,6 @@ public class RealmAdminResource {
return stats; return stats;
} }
/**
* 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<String, SessionStats> getSessionStats() {
logger.info("session-stats");
auth.requireView();
Map<String, SessionStats> stats = new HashMap<String, SessionStats>();
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. * View the events provider and how it is configured.
* *

View file

@ -24,7 +24,6 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.OpenIDConnect; import org.keycloak.protocol.oidc.OpenIDConnect;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.adapters.action.UserStats;
import org.keycloak.representations.idm.ApplicationMappingsRepresentation; import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation;
@ -217,36 +216,6 @@ public class UsersResource {
return ModelToRepresentation.toRepresentation(user); 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<String, UserStats> 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<String, UserStats> stats = new HashMap<String, UserStats>();
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. * List set of sessions associated with this user.
* *
@ -258,7 +227,6 @@ public class UsersResource {
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public List<UserSessionRepresentation> getSessions(final @PathParam("username") String username) { public List<UserSessionRepresentation> getSessions(final @PathParam("username") String username) {
logger.info("sessions");
auth.requireView(); auth.requireView();
UserModel user = session.users().getUserByUsername(username, realm); UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) { if (user == null) {
@ -345,8 +313,8 @@ public class UsersResource {
if (user == null) { if (user == null) {
throw new NotFoundException("User not found"); throw new NotFoundException("User not found");
} }
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user, session);
session.sessions().removeUserSessions(realm, user); session.sessions().removeUserSessions(realm, user);
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), null);
} }
/** /**

View file

@ -23,6 +23,10 @@ done;
# Configure admin-access.war # Configure admin-access.war
sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml 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 # Configure other examples
for I in *.war/WEB-INF/keycloak.json; do 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\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;

View file

@ -23,10 +23,8 @@ package org.keycloak.testsuite.adapter;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.Version; import org.keycloak.Version;
@ -40,7 +38,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OpenIDConnectService; import org.keycloak.protocol.oidc.OpenIDConnectService;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
@ -158,17 +155,16 @@ public class AdapterTest {
Client client = ClientBuilder.newClient(); Client client = ClientBuilder.newClient();
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth"); UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo"); WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
Map<String, SessionStats> stats = adminTarget.path("session-stats").request() Map<String, Integer> stats = adminTarget.path("application-session-stats").request()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken) .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
.get(new GenericType<Map<String, SessionStats>>() { .get(new GenericType<Map<String, Integer>>() {
}); });
Integer custSessionsCount = stats.get("customer-portal");
SessionStats custStats = stats.get("customer-portal"); Assert.assertNotNull(custSessionsCount);
Assert.assertNotNull(custStats); Assert.assertTrue(1 == custSessionsCount);
Assert.assertEquals(1, custStats.getActiveSessions()); Integer prodStatsCount = stats.get("product-portal");
SessionStats prodStats = stats.get("product-portal"); Assert.assertNotNull(prodStatsCount);
Assert.assertNotNull(prodStats); Assert.assertTrue(1 == prodStatsCount);
Assert.assertEquals(1, prodStats.getActiveSessions());
client.close(); client.close();
@ -299,7 +295,7 @@ public class AdapterTest {
realm = session.realms().getRealmByName("demo"); realm = session.realms().getRealmByName("demo");
// need to cleanup so other tests don't fail, so invalidate http sessions on remote clients. // need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm); UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
new ResourceAdminManager().logoutUser(null, realm, user.getId(), null); new ResourceAdminManager().logoutUser(null, realm, user, session);
realm.setSsoSessionIdleTimeout(originalIdle); realm.setSsoSessionIdleTimeout(originalIdle);
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); session.close();