commit
d7b616773a
3 changed files with 199 additions and 87 deletions
|
@ -20,7 +20,12 @@ import org.keycloak.adapters.RefreshableKeycloakSession;
|
|||
import org.keycloak.adapters.ResourceMetadata;
|
||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
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.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.adapters.config.RealmConfiguration;
|
||||
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||
|
@ -35,7 +40,9 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -99,6 +106,21 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
|
|||
return; // we failed to verify the request
|
||||
}
|
||||
pushNotBefore(input, response);
|
||||
return;
|
||||
} else if (requestURI.endsWith(AdapterConstants.K_GET_SESSION_STATS)) {
|
||||
JWSInput input = verifyAdminRequest(request, response);
|
||||
if (input == null) {
|
||||
return; // we failed to verify the request
|
||||
}
|
||||
getSessionStats(input, response);
|
||||
return;
|
||||
} else if (requestURI.endsWith(AdapterConstants.K_GET_USER_STATS)) {
|
||||
JWSInput input = verifyAdminRequest(request, response);
|
||||
if (input == null) {
|
||||
return; // we failed to verify the request
|
||||
}
|
||||
getUserStats(input, response);
|
||||
return;
|
||||
}
|
||||
checkKeycloakSession(request);
|
||||
super.invoke(request, response);
|
||||
|
@ -136,63 +158,112 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
|
|||
String token = StreamUtil.readString(request.getInputStream());
|
||||
if (token == null) {
|
||||
log.warn("admin request failed, no token");
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "no token");
|
||||
response.sendError(403, "no token");
|
||||
return null;
|
||||
}
|
||||
|
||||
JWSInput input = new JWSInput(token);
|
||||
boolean verified = false;
|
||||
try {
|
||||
verified = RSAProvider.verify(input, resourceMetadata.getRealmKey());
|
||||
verified = RSAProvider.verify(input, realmConfiguration.getMetadata().getRealmKey());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
if (!verified) {
|
||||
log.warn("admin request failed, unable to verify token");
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "verification failed");
|
||||
response.sendError(403, "verification failed");
|
||||
return null;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException {
|
||||
try {
|
||||
log.info("->> pushNotBefore: ");
|
||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
||||
if (action.isExpired()) {
|
||||
log.warn("admin request failed, expired token");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token");
|
||||
return;
|
||||
}
|
||||
if (!resourceMetadata.getResourceName().equals(action.getResource())) {
|
||||
log.warn("Resource name does not match");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match");
|
||||
return;
|
||||
|
||||
}
|
||||
realmConfiguration.setNotBefore(action.getNotBefore());
|
||||
} catch (Exception e) {
|
||||
log.warn("failed to logout", e);
|
||||
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to logout");
|
||||
protected boolean validateAction(HttpServletResponse response, AdminAction action) throws IOException {
|
||||
if (!action.validate()) {
|
||||
log.warn("admin request failed, not validated" + action.getAction());
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not validated");
|
||||
return false;
|
||||
}
|
||||
if (action.isExpired()) {
|
||||
log.warn("admin request failed, expired token");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token");
|
||||
return false;
|
||||
}
|
||||
if (!resourceMetadata.getResourceName().equals(action.getResource())) {
|
||||
log.warn("Resource name does not match");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match");
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void pushNotBefore(JWSInput token, HttpServletResponse response) throws IOException {
|
||||
log.info("->> pushNotBefore: ");
|
||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
||||
if (!validateAction(response, action)) {
|
||||
return;
|
||||
}
|
||||
realmConfiguration.setNotBefore(action.getNotBefore());
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
protected void getSessionStats(JWSInput token, HttpServletResponse response) throws IOException {
|
||||
log.info("->> getSessionStats: ");
|
||||
SessionStatsAction action = JsonSerialization.readValue(token.getContent(), SessionStatsAction.class);
|
||||
if (!validateAction(response, 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);
|
||||
}
|
||||
response.setStatus(200);
|
||||
response.setContentType("application/json");
|
||||
JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
|
||||
|
||||
}
|
||||
|
||||
protected void getUserStats(JWSInput token, HttpServletResponse response) throws IOException {
|
||||
log.info("->> getUserStats: ");
|
||||
UserStatsAction action = JsonSerialization.readValue(token.getContent(), UserStatsAction.class);
|
||||
if (!validateAction(response, action)) {
|
||||
return;
|
||||
}
|
||||
String user = action.getUser();
|
||||
UserStats stats = getUserStats(user);
|
||||
response.setStatus(200);
|
||||
response.setContentType("application/json");
|
||||
JsonSerialization.writeValueToStream(response.getOutputStream(), stats);
|
||||
}
|
||||
|
||||
|
||||
protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException {
|
||||
try {
|
||||
log.debug("->> remoteLogout: ");
|
||||
LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||
if (action.isExpired()) {
|
||||
log.warn("admin request failed, expired token");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Expired token");
|
||||
if (!validateAction(response, action)) {
|
||||
return;
|
||||
}
|
||||
if (!resourceMetadata.getResourceName().equals(action.getResource())) {
|
||||
log.warn("Resource name does not match");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name does not match");
|
||||
return;
|
||||
|
||||
}
|
||||
String user = action.getUser();
|
||||
if (user != null) {
|
||||
log.debug("logout of session for: " + user);
|
||||
|
|
|
@ -8,8 +8,10 @@ import org.jboss.logging.Logger;
|
|||
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -20,19 +22,63 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
*/
|
||||
public class UserSessionManagement implements SessionListener {
|
||||
private static final Logger log = Logger.getLogger(UserSessionManagement.class);
|
||||
protected ConcurrentHashMap<String, Map<String, Session>> userSessionMap = new ConcurrentHashMap<String, Map<String, Session>>();
|
||||
protected ConcurrentHashMap<String, UserSessions> userSessionMap = new ConcurrentHashMap<String, UserSessions>();
|
||||
|
||||
public static class UserSessions {
|
||||
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
|
||||
protected long loggedIn = System.currentTimeMillis();
|
||||
|
||||
|
||||
public Map<String, Session> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
public long getLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumUserLogins() {
|
||||
return userSessionMap.size();
|
||||
}
|
||||
|
||||
public int getActiveSessions() {
|
||||
int active = 0;
|
||||
synchronized (userSessionMap) {
|
||||
for (UserSessions sessions : userSessionMap.values()) {
|
||||
active += sessions.getSessions().size();
|
||||
}
|
||||
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param username
|
||||
* @return null if user not logged in
|
||||
*/
|
||||
public Long getUserLoginTime(String username) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions == null) return null;
|
||||
return sessions.getLoggedIn();
|
||||
}
|
||||
|
||||
public Set<String> getActiveUsers() {
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
set.addAll(userSessionMap.keySet());
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
protected void login(Session session, String username) {
|
||||
Map<String, Session> map = userSessionMap.get(username);
|
||||
if (map == null) {
|
||||
final Map<String, Session> value = new HashMap<String, Session>();
|
||||
map = userSessionMap.putIfAbsent(username, value);
|
||||
if (map == null) {
|
||||
map = value;
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions userSessions = userSessionMap.get(username);
|
||||
if (userSessions == null) {
|
||||
userSessions = new UserSessions();
|
||||
userSessionMap.put(username, userSessions);
|
||||
}
|
||||
}
|
||||
synchronized (map) {
|
||||
map.put(session.getId(), session);
|
||||
userSessions.getSessions().put(session.getId(), session);
|
||||
}
|
||||
session.addSessionListener(this);
|
||||
}
|
||||
|
@ -43,32 +89,24 @@ public class UserSessionManagement implements SessionListener {
|
|||
for (String user : users) logout(user);
|
||||
}
|
||||
|
||||
public void logoutAllBut(String but) {
|
||||
List<String> users = new ArrayList<String>();
|
||||
users.addAll(userSessionMap.keySet());
|
||||
for (String user : users) {
|
||||
if (!but.equals(user)) logout(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void logout(String user) {
|
||||
log.debug("logoutUser: " + user);
|
||||
Map<String, Session> map = userSessionMap.remove(user);
|
||||
if (map == null) {
|
||||
UserSessions sessions = null;
|
||||
synchronized (userSessionMap) {
|
||||
sessions = userSessionMap.remove(user);
|
||||
|
||||
}
|
||||
if (sessions == null) {
|
||||
log.debug("no session for user: " + user);
|
||||
return;
|
||||
|
||||
}
|
||||
log.debug("found session for user");
|
||||
synchronized (map) {
|
||||
for (Session session : map.values()) {
|
||||
log.debug("invalidating session for user: " + user);
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
}
|
||||
for (Session session : sessions.getSessions().values()) {
|
||||
session.setPrincipal(null);
|
||||
session.setAuthType(null);
|
||||
session.getSession().invalidate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sessionEvent(SessionEvent event) {
|
||||
|
@ -85,13 +123,14 @@ public class UserSessionManagement implements SessionListener {
|
|||
session.setAuthType(null);
|
||||
|
||||
String username = principal.getUserPrincipal().getName();
|
||||
Map<String, Session> map = userSessionMap.get(username);
|
||||
if (map == null) return;
|
||||
synchronized (map) {
|
||||
map.remove(session.getId());
|
||||
if (map.isEmpty()) userSessionMap.remove(username);
|
||||
synchronized (userSessionMap) {
|
||||
UserSessions sessions = userSessionMap.get(username);
|
||||
if (sessions != null) {
|
||||
sessions.getSessions().remove(session.getId());
|
||||
if (sessions.getSessions().isEmpty()) {
|
||||
userSessionMap.remove(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,27 +69,6 @@ public class ServletAdminActionsHandler implements HttpHandler {
|
|||
this.realmConfig = realmConfig;
|
||||
}
|
||||
|
||||
protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String token = StreamUtil.readString(request.getInputStream());
|
||||
if (token == null) {
|
||||
log.warn("admin request failed, no token");
|
||||
response.sendError(StatusCodes.FORBIDDEN, "no token");
|
||||
return null;
|
||||
}
|
||||
|
||||
JWSInput input = new JWSInput(token);
|
||||
boolean verified = false;
|
||||
try {
|
||||
verified = RSAProvider.verify(input, realmConfig.getMetadata().getRealmKey());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
if (!verified) {
|
||||
log.warn("admin request failed, unable to verify token");
|
||||
response.sendError(StatusCodes.FORBIDDEN, "verification failed");
|
||||
return null;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -135,6 +114,29 @@ public class ServletAdminActionsHandler implements HttpHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
protected JWSInput verifyAdminRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String token = StreamUtil.readString(request.getInputStream());
|
||||
if (token == null) {
|
||||
log.warn("admin request failed, no token");
|
||||
response.sendError(StatusCodes.FORBIDDEN, "no token");
|
||||
return null;
|
||||
}
|
||||
|
||||
JWSInput input = new JWSInput(token);
|
||||
boolean verified = false;
|
||||
try {
|
||||
verified = RSAProvider.verify(input, realmConfig.getMetadata().getRealmKey());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
if (!verified) {
|
||||
log.warn("admin request failed, unable to verify token");
|
||||
response.sendError(StatusCodes.FORBIDDEN, "verification failed");
|
||||
return null;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
protected boolean validateAction(HttpServletResponse response, AdminAction action) throws IOException {
|
||||
if (!action.validate()) {
|
||||
log.warn("admin request failed, not validated" + action.getAction());
|
||||
|
|
Loading…
Reference in a new issue