org.wildfly.security.http.util.sso.DefaultSingleSignOnSession Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.http.util.sso;
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security.http.HttpScopeNotification.SessionNotificationType.INVALIDATED;
import static org.wildfly.security.http.util.sso.ElytronMessages.log;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.cache.CachedIdentity;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.Scope;
/**
* {@link SingleSignOnSession} that delegates its persistence strategy to a {@link SingleSignOnManager}.
* {@link SingleSignOn} entries are created lazily in response to {@link #put(SecurityIdentity)}.
*
* This implementation supports single logout in order to invalidate local sessions for each participant of a single sign-on session, where participants
* represent the applications with active sessions associated with a given single sign-on session.
* @author Paul Ferraro
*/
public class DefaultSingleSignOnSession implements SingleSignOnSession {
private static final String LOGOUT_REQUEST_PARAMETER = "ely_logout_message";
private static final String SESSION_INVALIDATING_ATTRIBUTE = DefaultSingleSignOnSessionFactory.class.getName() + ".INVALIDATING";
private static final Boolean SINGLE_SIGN_ON_KEY = Boolean.TRUE;
private final HttpServerRequest request;
// Serves as a lazy initializable atomic reference
private final ConcurrentMap map = new ConcurrentHashMap<>(1);
private final SingleSignOnSessionContext context;
private final Function ssoFactory;
public DefaultSingleSignOnSession(SingleSignOnSessionContext context, HttpServerRequest request, String mechanismName) {
this.context = checkNotNullParam("context", context);
this.request = checkNotNullParam("request", request);
checkNotNullParam("mechanismName", mechanismName);
this.ssoFactory = identity -> context.getSingleSignOnManager().create(mechanismName, identity);
}
public DefaultSingleSignOnSession(SingleSignOnSessionContext context, HttpServerRequest request, SingleSignOn sso) {
this.context = checkNotNullParam("context", context);
this.map.put(SINGLE_SIGN_ON_KEY, sso);
this.request = checkNotNullParam("request", request);
checkNotNullParam("sso", sso);
this.ssoFactory = identity -> sso;
}
@Override
public String getId() {
SingleSignOn sso = this.map.get(SINGLE_SIGN_ON_KEY);
return (sso != null) ? sso.getId() : null;
}
@Override
public CachedIdentity get() {
SingleSignOn sso = this.map.get(SINGLE_SIGN_ON_KEY);
return (sso != null) ? getCachedIdentity(sso) : null;
}
@Override
public void put(SecurityIdentity identity) {
SingleSignOn sso = this.map.computeIfAbsent(SINGLE_SIGN_ON_KEY, key -> this.ssoFactory.apply(identity));
sso.setIdentity(identity);
HttpScope scope = this.request.getScope(Scope.SESSION);
if (!scope.exists()) {
scope.create();
}
URI uri = this.request.getRequestURI();
String sessionId = scope.getID();
String applicationId = this.request.getScope(Scope.APPLICATION).getID();
if (sso.addParticipant(applicationId, sessionId, uri)) {
String id = sso.getId();
log.debugf("Updating local sessions for SSO [%s]. New local session [%s]. Local sessions: [%s]", id, sessionId, sso.getParticipants());
scope.registerForNotification(notification -> {
HttpScope sessionScope = notification.getScope(Scope.SESSION);
Map> logoutTargets = Collections.emptyMap();
try (SingleSignOn target = this.context.getSingleSignOnManager().find(id)) {
if (target != null) {
Map.Entry localParticipant = target.removeParticipant(applicationId);
if (localParticipant != null) {
log.debugf("Removed local session [%s] from SSO [%s]", localParticipant.getKey(), target.getId());
}
if (sessionScope.getAttachment(SESSION_INVALIDATING_ATTRIBUTE) == null) {
Map> participants = target.getParticipants();
if (participants.isEmpty()) {
log.debugf("Destroying SSO [%s]. SSO is not associated with participants", target.getId());
target.invalidate();
} else if (notification.isOfType(INVALIDATED)) {
logoutTargets = participants;
}
}
}
}
if (!logoutTargets.isEmpty()) {
logoutTargets.forEach((participantId, participant) -> {
String remoteSessionId = participant.getKey();
URI remoteURI = participant.getValue();
try {
URL participantUrl = remoteURI.toURL();
HttpURLConnection connection = (HttpURLConnection) participantUrl.openConnection();
this.context.configureLogoutConnection(connection);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setAllowUserInteraction(false);
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
StringBuilder parameterBuilder = new StringBuilder();
parameterBuilder.append(LOGOUT_REQUEST_PARAMETER).append("=").append(this.context.createLogoutParameter(remoteSessionId));
connection.setRequestProperty("Content-Length", Integer.toString(parameterBuilder.length()));
try (
OutputStream outputStream = connection.getOutputStream();
DataOutputStream wr = new DataOutputStream(outputStream);
) {
wr.writeBytes(parameterBuilder.toString());
}
connection.getInputStream().close();
} catch (Exception cause) {
log.warnHttpMechSsoFailedLogoutParticipant(remoteURI.toString(), cause);
}
});
try (SingleSignOn target = this.context.getSingleSignOnManager().find(id)) {
if (target != null) {
// If all logout requests were successful, then there should be no participants, and we can invalidate the SSO
if (!target.getParticipants().isEmpty()) {
log.debugf("Destroying SSO [%s]. Participant list not empty.", target.getId());
} else {
log.debugf("Destroying SSO [%s]. SSO is no longer associated with any participants", target.getId());
}
target.invalidate();
}
}
}
});
}
}
@Override
public CachedIdentity remove() {
SingleSignOn sso = this.map.get(SINGLE_SIGN_ON_KEY);
if (sso == null) return null;
sso.invalidate();
HttpScope scope = this.request.getScope(Scope.SESSION);
if (scope.exists()) {
invalidateLocalSession(scope);
}
return getCachedIdentity(sso);
}
@Override
public boolean logout() {
String logoutMessage = this.request.getFirstParameterValue(LOGOUT_REQUEST_PARAMETER);
if (logoutMessage == null) {
return false;
}
try {
String localSessionId = this.context.verifyLogoutParameter(logoutMessage);
HttpScope scope = this.request.getScope(Scope.SESSION, localSessionId);
if (!scope.exists()) {
return false;
}
log.debugf("Invalidating local session [%s] from SSO [%s]", localSessionId, this.getId());
invalidateLocalSession(scope);
} catch (Exception e) {
log.errorHttpMechSsoFailedInvalidateLocalSession(e);
}
this.request.authenticationInProgress(response -> response.setStatusCode(200));
return true;
}
@Override
public void close() {
Optional.ofNullable(this.map.remove(SINGLE_SIGN_ON_KEY)).ifPresent(SingleSignOn::close);
}
void invalidateLocalSession(HttpScope scope) {
scope.setAttachment(SESSION_INVALIDATING_ATTRIBUTE, true);
scope.invalidate();
log.debugf("Local session [%s] invalidated for SSO [%s]", scope.getID(), this.getId());
}
private static CachedIdentity getCachedIdentity(SingleSignOn sso) {
String mechanism = sso.getMechanism();
SecurityIdentity identity = sso.getIdentity();
return (identity != null) ? new CachedIdentity(mechanism, identity) : new CachedIdentity(mechanism, new NamePrincipal(sso.getName()));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy