1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.shiro.mgt; 20 21 import org.apache.shiro.session.Session; 22 import org.apache.shiro.subject.PrincipalCollection; 23 import org.apache.shiro.subject.Subject; 24 import org.apache.shiro.subject.support.DefaultSubjectContext; 25 import org.apache.shiro.subject.support.DelegatingSubject; 26 import org.apache.shiro.util.CollectionUtils; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import java.lang.reflect.Field; 31 32 /** 33 * Default {@code SubjectDAO} implementation that stores Subject state in the Subject's Session by default (but this 34 * can be disabled - see below). The Subject instance 35 * can be re-created at a later time by first acquiring the associated Session (typically from a 36 * {@link org.apache.shiro.session.mgt.SessionManager SessionManager}) via a session ID or session key and then 37 * building a {@code Subject} instance from {@code Session} attributes. 38 * <h2>Controlling how Sessions are used</h2> 39 * Whether or not a {@code Subject}'s {@code Session} is used or not to persist its own state is controlled on a 40 * <em>per-Subject</em> basis as determined by the configured 41 * {@link #setSessionStorageEvaluator(SessionStorageEvaluator) sessionStorageEvaluator}. 42 * The default {@code Evaluator} is a {@link DefaultSessionStorageEvaluator}, which supports enabling or disabling 43 * session usage for Subject persistence at a global level for all subjects (and defaults to allowing sessions to be 44 * used). 45 * <h3>Disabling Session Persistence Entirely</h3> 46 * Because the default {@code SessionStorageEvaluator} instance is a {@link DefaultSessionStorageEvaluator}, you 47 * can disable Session usage for Subject state entirely by configuring that instance directly, e.g.: 48 * <pre> 49 * ((DefaultSessionStorageEvaluator)sessionDAO.getSessionStorageEvaluator()).setSessionStorageEnabled(false); 50 * </pre> 51 * or, for example, in {@code shiro.ini}: 52 * <pre> 53 * securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false 54 * </pre> 55 * but <b>note:</b> ONLY do this your 56 * application is 100% stateless and you <em>DO NOT</em> need subjects to be remembered across remote 57 * invocations, or in a web environment across HTTP requests. 58 * <h3>Supporting Both Stateful and Stateless Subject paradigms</h3> 59 * Perhaps your application needs to support a hybrid approach of both stateful and stateless Subjects: 60 * <ul> 61 * <li>Stateful: Stateful subjects might represent web end-users that need their identity and authentication 62 * state to be remembered from page to page.</li> 63 * <li>Stateless: Stateless subjects might represent API clients (e.g. REST clients) that authenticate on every 64 * request, and therefore don't need authentication state to be stored across requests in a session.</li> 65 * </ul> 66 * To support the hybrid <em>per-Subject</em> approach, you will need to create your own implementation of the 67 * {@link SessionStorageEvaluator} interface and configure it via the 68 * {@link #setSessionStorageEvaluator(SessionStorageEvaluator)} method, or, with {@code shiro.ini}: 69 * <pre> 70 * myEvaluator = com.my.CustomSessionStorageEvaluator 71 * securityManager.subjectDAO.sessionStorageEvaluator = $myEvaluator 72 * </pre> 73 * <p/> 74 * Unless overridden, the default evaluator is a {@link DefaultSessionStorageEvaluator}, which enables session usage for 75 * Subject state by default. 76 * 77 * @see #isSessionStorageEnabled(org.apache.shiro.subject.Subject) 78 * @see SessionStorageEvaluator 79 * @see DefaultSessionStorageEvaluator 80 * @since 1.2 81 */ 82 public class DefaultSubjectDAO implements SubjectDAO { 83 84 private static final Logger log = LoggerFactory.getLogger(DefaultSubjectDAO.class); 85 86 /** 87 * Evaluator that determines if a Subject's session may be used to store the Subject's own state. 88 */ 89 private SessionStorageEvaluator sessionStorageEvaluator; 90 91 public DefaultSubjectDAO() { 92 //default implementation allows enabling/disabling session usages at a global level for all subjects: 93 this.sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); 94 } 95 96 /** 97 * Determines if the subject's session will be used to persist subject state or not. This implementation 98 * merely delegates to the internal {@link SessionStorageEvaluator} (a 99 * {@code DefaultSessionStorageEvaluator} by default). 100 * 101 * @param subject the subject to inspect to determine if the subject's session will be used to persist subject 102 * state or not. 103 * @return {@code true} if the subject's session will be used to persist subject state, {@code false} otherwise. 104 * @see #setSessionStorageEvaluator(SessionStorageEvaluator) 105 * @see DefaultSessionStorageEvaluator 106 */ 107 protected boolean isSessionStorageEnabled(Subject subject) { 108 return getSessionStorageEvaluator().isSessionStorageEnabled(subject); 109 } 110 111 /** 112 * Returns the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in 113 * the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}. 114 * 115 * @return the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in 116 * the Subject's session. 117 * @see DefaultSessionStorageEvaluator 118 */ 119 public SessionStorageEvaluator getSessionStorageEvaluator() { 120 return sessionStorageEvaluator; 121 } 122 123 /** 124 * Sets the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in 125 * the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}. 126 * 127 * @param sessionStorageEvaluator the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s 128 * state may be persisted in the Subject's session. 129 * @see DefaultSessionStorageEvaluator 130 */ 131 public void setSessionStorageEvaluator(SessionStorageEvaluator sessionStorageEvaluator) { 132 this.sessionStorageEvaluator = sessionStorageEvaluator; 133 } 134 135 /** 136 * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only 137 * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}. If session storage is not enabled 138 * for the specific {@code Subject}, this method does nothing. 139 * <p/> 140 * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created). 141 * 142 * @param subject the Subject instance for which its state will be created or updated. 143 * @return the same {@code Subject} passed in (a new Subject instance is not created). 144 */ 145 public Subject save(Subject subject) { 146 if (isSessionStorageEnabled(subject)) { 147 saveToSession(subject); 148 } else { 149 log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " + 150 "authentication state are expected to be initialized on every request or invocation.", subject); 151 } 152 153 return subject; 154 } 155 156 /** 157 * Saves the subject's state (it's principals and authentication state) to its 158 * {@link org.apache.shiro.subject.Subject#getSession() session}. The session can be retrieved at a later time 159 * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate 160 * the {@code Subject} instance. 161 * 162 * @param subject the subject for which state will be persisted to its session. 163 */ 164 protected void saveToSession(Subject subject) { 165 //performs merge logic, only updating the Subject's session if it does not match the current state: 166 mergePrincipals(subject); 167 mergeAuthenticationState(subject); 168 } 169 170 /** 171 * Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in 172 * any available session. Only updates the Subject's session if the session does not match the current principals 173 * state. 174 * 175 * @param subject the Subject for which principals will potentially be merged into the Subject's session. 176 */ 177 protected void mergePrincipals(Subject subject) { 178 //merge PrincipalCollection state: 179 180 PrincipalCollection currentPrincipals = null; 181 182 //SHIRO-380: added if/else block - need to retain original (source) principals 183 //This technique (reflection) is only temporary - a proper long term solution needs to be found, 184 //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible 185 // 186 //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 + 187 if (subject.isRunAs() && subject instanceof DelegatingSubject) { 188 try { 189 Field field = DelegatingSubject.class.getDeclaredField("principals"); 190 field.setAccessible(true); 191 currentPrincipals = (PrincipalCollection)field.get(subject); 192 } catch (Exception e) { 193 throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e); 194 } 195 } 196 if (currentPrincipals == null || currentPrincipals.isEmpty()) { 197 currentPrincipals = subject.getPrincipals(); 198 } 199 200 Session session = subject.getSession(false); 201 202 if (session == null) { 203 if (!CollectionUtils.isEmpty(currentPrincipals)) { 204 session = subject.getSession(); 205 session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); 206 } 207 //otherwise no session and no principals - nothing to save 208 } else { 209 PrincipalCollection existingPrincipals = 210 (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); 211 212 if (CollectionUtils.isEmpty(currentPrincipals)) { 213 if (!CollectionUtils.isEmpty(existingPrincipals)) { 214 session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); 215 } 216 //otherwise both are null or empty - no need to update the session 217 } else { 218 if (!currentPrincipals.equals(existingPrincipals)) { 219 session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); 220 } 221 //otherwise they're the same - no need to update the session 222 } 223 } 224 } 225 226 /** 227 * Merges the Subject's current authentication state with whatever may be in 228 * any available session. Only updates the Subject's session if the session does not match the current 229 * authentication state. 230 * 231 * @param subject the Subject for which principals will potentially be merged into the Subject's session. 232 */ 233 protected void mergeAuthenticationState(Subject subject) { 234 235 Session session = subject.getSession(false); 236 237 if (session == null) { 238 if (subject.isAuthenticated()) { 239 session = subject.getSession(); 240 session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE); 241 } 242 //otherwise no session and not authenticated - nothing to save 243 } else { 244 Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY); 245 246 if (subject.isAuthenticated()) { 247 if (existingAuthc == null || !existingAuthc) { 248 session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE); 249 } 250 //otherwise authc state matches - no need to update the session 251 } else { 252 if (existingAuthc != null) { 253 //existing doesn't match the current state - remove it: 254 session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY); 255 } 256 //otherwise not in the session and not authenticated - no need to update the session 257 } 258 } 259 } 260 261 /** 262 * Removes any existing subject state from the Subject's session (if the session exists). If the session 263 * does not exist, this method does not do anything. 264 * 265 * @param subject the subject for which any existing subject state will be removed from its session. 266 */ 267 protected void removeFromSession(Subject subject) { 268 Session session = subject.getSession(false); 269 if (session != null) { 270 session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY); 271 session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); 272 } 273 } 274 275 /** 276 * Removes any existing subject state from the subject's session (if the session exists). 277 * 278 * @param subject the Subject instance for which any persistent state should be deleted. 279 */ 280 public void delete(Subject subject) { 281 removeFromSession(subject); 282 } 283 }