blob: 490125f9afacde929da5480bb36325984d4c4860 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.shiro.mgt;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default {@code SubjectDAO} implementation that stores Subject state in the Subject's Session by default (but this
* can be disabled - see below). The Subject instance
* can be re-created at a later time by first acquiring the associated Session (typically from a
* {@link org.apache.shiro.session.mgt.SessionManager SessionManager}) via a session ID or session key and then
* building a {@code Subject} instance from {@code Session} attributes.
* <h2>Controlling how Sessions are used</h2>
* Whether or not a {@code Subject}'s {@code Session} is used or not to persist its own state is controlled on a
* <em>per-Subject</em> basis as determined by the configured
* {@link #setSessionStorageEvaluator(SessionStorageEvaluator) sessionStorageEvaluator}.
* The default {@code Evaluator} is a {@link DefaultSessionStorageEvaluator}, which supports enabling or disabling
* session usage for Subject persistence at a global level for all subjects (and defaults to allowing sessions to be
* used).
* <h3>Disabling Session Persistence Entirely</h3>
* Because the default {@code SessionStorageEvaluator} instance is a {@link DefaultSessionStorageEvaluator}, you
* can disable Session usage for Subject state entirely by configuring that instance directly, e.g.:
* <pre>
* ((DefaultSessionStorageEvaluator)sessionDAO.getSessionStorageEvaluator()).setSessionStorageEnabled(false);
* </pre>
* or, for example, in {@code shiro.ini}:
* <pre>
* securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
* </pre>
* but <b>note:</b> ONLY do this your
* application is 100% stateless and you <em>DO NOT</em> need subjects to be remembered across remote
* invocations, or in a web environment across HTTP requests.
* <h3>Supporting Both Stateful and Stateless Subject paradigms</h3>
* Perhaps your application needs to support a hybrid approach of both stateful and stateless Subjects:
* <ul>
* <li>Stateful: Stateful subjects might represent web end-users that need their identity and authentication
* state to be remembered from page to page.</li>
* <li>Stateless: Stateless subjects might represent API clients (e.g. REST clients) that authenticate on every
* request, and therefore don't need authentication state to be stored across requests in a session.</li>
* </ul>
* To support the hybrid <em>per-Subject</em> approach, you will need to create your own implementation of the
* {@link SessionStorageEvaluator} interface and configure it via the
* {@link #setSessionStorageEvaluator(SessionStorageEvaluator)} method, or, with {@code shiro.ini}:
* <pre>
* myEvaluator = com.my.CustomSessionStorageEvaluator
* securityManager.subjectDAO.sessionStorageEvaluator = $myEvaluator
* </pre>
* <p/>
* Unless overridden, the default evaluator is a {@link DefaultSessionStorageEvaluator}, which enables session usage for
* Subject state by default.
*
* @see #isSessionStorageEnabled(org.apache.shiro.subject.Subject)
* @see SessionStorageEvaluator
* @see DefaultSessionStorageEvaluator
* @since 1.2
*/
public class DefaultSubjectDAO implements SubjectDAO {
private static final Logger log = LoggerFactory.getLogger(DefaultSubjectDAO.class);
/**
* Evaluator that determines if a Subject's session may be used to store the Subject's own state.
*/
private SessionStorageEvaluator sessionStorageEvaluator;
public DefaultSubjectDAO() {
//default implementation allows enabling/disabling session usages at a global level for all subjects:
this.sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
}
/**
* Determines if the subject's session will be used to persist subject state or not. This implementation
* merely delegates to the internal {@link SessionStorageEvaluator} (a
* {@code DefaultSessionStorageEvaluator} by default).
*
* @param subject the subject to inspect to determine if the subject's session will be used to persist subject
* state or not.
* @return {@code true} if the subject's session will be used to persist subject state, {@code false} otherwise.
* @see #setSessionStorageEvaluator(SessionStorageEvaluator)
* @see DefaultSessionStorageEvaluator
*/
protected boolean isSessionStorageEnabled(Subject subject) {
return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
}
/**
* Returns the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
* the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}.
*
* @return the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
* the Subject's session.
* @see DefaultSessionStorageEvaluator
*/
public SessionStorageEvaluator getSessionStorageEvaluator() {
return sessionStorageEvaluator;
}
/**
* Sets the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
* the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}.
*
* @param sessionStorageEvaluator the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s
* state may be persisted in the Subject's session.
* @see DefaultSessionStorageEvaluator
*/
public void setSessionStorageEvaluator(SessionStorageEvaluator sessionStorageEvaluator) {
this.sessionStorageEvaluator = sessionStorageEvaluator;
}
/**
* Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only
* if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}. If session storage is not enabled
* for the specific {@code Subject}, this method does nothing.
* <p/>
* In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created).
*
* @param subject the Subject instance for which its state will be created or updated.
* @return the same {@code Subject} passed in (a new Subject instance is not created).
*/
public Subject save(Subject subject) {
if (isSessionStorageEnabled(subject)) {
saveToSession(subject);
} else {
log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
"authentication state are expected to be initialized on every request or invocation.", subject);
}
return subject;
}
/**
* Saves the subject's state (it's principals and authentication state) to its
* {@link org.apache.shiro.subject.Subject#getSession() session}. The session can be retrieved at a later time
* (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
* the {@code Subject} instance.
*
* @param subject the subject for which state will be persisted to its session.
*/
protected void saveToSession(Subject subject) {
//performs merge logic, only updating the Subject's session if it does not match the current state:
mergePrincipals(subject);
mergeAuthenticationState(subject);
}
/**
* Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in
* any available session. Only updates the Subject's session if the session does not match the current principals
* state.
*
* @param subject the Subject for which principals will potentially be merged into the Subject's session.
*/
protected void mergePrincipals(Subject subject) {
//merge PrincipalCollection state:
PrincipalCollection currentPrincipals = subject.getPrincipals();
Session session = subject.getSession(false);
if (session == null) {
if (!CollectionUtils.isEmpty(currentPrincipals)) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
// otherwise no session and no principals - nothing to save
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (CollectionUtils.isEmpty(currentPrincipals)) {
if (!CollectionUtils.isEmpty(existingPrincipals)) {
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
// otherwise both are null or empty - no need to update the session
} else {
if (!currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
/*
PrincipalCollection previousPrincipals = null;
if (subject.isRunAs()) {
previousPrincipals = subject.getPreviousPrincipals();
}
currentPrincipals = CollectionUtils.isEmpty(previousPrincipals) ? currentPrincipals : previousPrincipals;
if (currentPrincipals != null && !currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
*/
// otherwise they're the same - no need to update the session
}
}
}
/**
* Merges the Subject's current authentication state with whatever may be in
* any available session. Only updates the Subject's session if the session does not match the current
* authentication state.
*
* @param subject the Subject for which principals will potentially be merged into the Subject's session.
*/
protected void mergeAuthenticationState(Subject subject) {
Session session = subject.getSession(false);
if (session == null) {
if (subject.isAuthenticated()) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
//otherwise no session and not authenticated - nothing to save
} else {
Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
if (subject.isAuthenticated()) {
if (existingAuthc == null || !existingAuthc) {
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
//otherwise authc state matches - no need to update the session
} else {
if (existingAuthc != null) {
//existing doesn't match the current state - remove it:
session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
}
//otherwise not in the session and not authenticated - no need to update the session
}
}
}
/**
* Removes any existing subject state from the Subject's session (if the session exists). If the session
* does not exist, this method does not do anything.
*
* @param subject the subject for which any existing subject state will be removed from its session.
*/
protected void removeFromSession(Subject subject) {
Session session = subject.getSession(false);
if (session != null) {
session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
}
/**
* Removes any existing subject state from the subject's session (if the session exists).
*
* @param subject the Subject instance for which any persistent state should be deleted.
*/
public void delete(Subject subject) {
removeFromSession(subject);
}
}