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