package org.pac4j.saml.storage; import org.opensaml.core.xml.XMLObject; import org.pac4j.core.context.WebContext; import org.pac4j.core.util.CommonHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Hashtable; /** * Class implements storage of SAML messages and uses HttpSession as underlying dataStore. As the XMLObjects * can't be serialized (which could lead to problems during failover), the messages are transformed into SAMLObject * which internally marshalls the content into XML during serialization. * * Messages are populated to a Hashtable and stored inside HttpSession. The Hashtable is lazily initialized * during first attempt to create or retrieve a message. * * @author Vladimir Schäfer */ public class HttpSessionStorage implements SAMLMessageStorage { /** * Class logger. */ protected final Logger log = LoggerFactory.getLogger(getClass()); /** * The web context to storage data. */ private final WebContext context; /** * Internal storage for messages, corresponding to the object in session. */ private Hashtable<String, XMLObject> internalMessages; /** * Session key for storage of the hashtable. */ private static final String SAML_STORAGE_KEY = "_springSamlStorageKey"; /** * Creates the storage object. The session is manipulated only once caller tries to store * or retrieve a message. * * In case request doesn't already have a started session, it will be created. * * @param context context to load/store internalMessages from */ public HttpSessionStorage(final WebContext context) { CommonHelper.assertNotNull("context", context); this.context = context; } /** * Stores a request message into the repository. RequestAbstractType must have an ID * set. Any previous message with the same ID will be overwritten. * * @param messageID ID of message * @param message message to be stored */ @Override public void storeMessage(final String messageID, final XMLObject message) { log.debug("Storing message {} to session {}", messageID, context.getSessionIdentifier()); final Hashtable<String, XMLObject> messages = getMessages(); messages.put(messageID, message); updateSession(messages); } /** * Returns previously stored message with the given ID or null, if there is no message * stored. * <p> * Message is stored in String format and must be unmarshalled into XMLObject. Call to this * method may thus be expensive. * <p> * Messages are automatically cleared upon successful reception, as we presume that there * are never multiple ongoing SAML exchanges for the same session. This saves memory used by * the session. * * @param messageID ID of message to retrieve * @return message found or null */ @Override public XMLObject retrieveMessage(final String messageID) { final Hashtable<String, XMLObject> messages = getMessages(); final XMLObject o = messages.get(messageID); if (o == null) { log.debug("Message {} not found in session {}", messageID, context.getSessionIdentifier()); return null; } log.debug("Message {} found in session {}, clearing", messageID, context.getSessionIdentifier()); messages.clear(); updateSession(messages); return o; } /** * Provides message storage hashtable. Table is lazily initialized when user tries to store or retrieve * the first message. * * @return message storage */ private Hashtable<String, XMLObject> getMessages() { if (internalMessages == null) { internalMessages = initializeSession(); } return internalMessages; } /** * Call to the method tries to load internalMessages hashtable object from the session, if the object doesn't exist * it will be created and stored. * <p> * Method synchronizes on session object to prevent two threads from overwriting each others hashtable. */ @SuppressWarnings("unchecked") private Hashtable<String, XMLObject> initializeSession() { Hashtable<String, XMLObject> messages = (Hashtable<String, XMLObject>) context.getSessionAttribute(SAML_STORAGE_KEY); if (messages == null) { synchronized (context) { messages = (Hashtable<String, XMLObject>) context.getSessionAttribute(SAML_STORAGE_KEY); if (messages == null) { messages = new Hashtable<>(); updateSession(messages); } } } return messages; } /** * Updates session with the internalMessages key. Some application servers require session value to be updated * in order to replicate the session across nodes or persist it correctly. */ private void updateSession(final Hashtable<String, XMLObject> messages) { context.setSessionAttribute(SAML_STORAGE_KEY, messages); } }