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