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.DefaultSecurityManager;
20 
21 import hunt.shiro.mgt.DefaultSubjectFactory;
22 import hunt.shiro.mgt.DefaultSubjectDAO;
23 import hunt.shiro.mgt.RememberMeManager;
24 import hunt.shiro.mgt.SessionsSecurityManager;
25 import hunt.shiro.mgt.SubjectFactory;
26 import hunt.shiro.mgt.SubjectDAO;
27 
28 import hunt.shiro.Exceptions;
29 import hunt.shiro.authc.AuthenticationInfo;
30 import hunt.shiro.authc.AuthenticationToken;
31 import hunt.shiro.authc.Authenticator;
32 import hunt.shiro.authc.LogoutAware;
33 import hunt.shiro.authz.Authorizer;
34 import hunt.shiro.realm.Realm;
35 import hunt.shiro.session.Session;
36 import hunt.shiro.session.mgt.DefaultSessionContext;
37 import hunt.shiro.session.mgt.DefaultSessionKey;
38 import hunt.shiro.session.mgt.SessionContext;
39 import hunt.shiro.session.mgt.SessionKey;
40 import hunt.shiro.subject.PrincipalCollection;
41 import hunt.shiro.subject.Subject;
42 import hunt.shiro.subject.SubjectContext;
43 import hunt.shiro.subject.support.DefaultSubjectContext;
44 import hunt.shiro.util.CollectionUtils;
45 import hunt.logging.ConsoleLogger;
46 
47 import hunt.Exceptions;
48 import hunt.collection;
49 import hunt.util.Common;
50 
51 import std.conv;
52 
53 
54 
55 /**
56  * The Shiro framework's default concrete implementation of the {@link SecurityManager} interface,
57  * based around a collection of {@link hunt.shiro.realm.Realm}s.  This implementation delegates its
58  * authentication, authorization, and session operations to wrapped {@link Authenticator}, {@link Authorizer}, and
59  * {@link hunt.shiro.session.mgt.SessionManager SessionManager} instances respectively via superclass
60  * implementation.
61  * <p/>
62  * To greatly reduce and simplify configuration, this implementation (and its superclasses) will
63  * create suitable defaults for all of its required dependencies, <em>except</em> the required one or more
64  * {@link Realm Realm}s.  Because {@code Realm} implementations usually interact with an application's data model,
65  * they are almost always application specific;  you will want to specify at least one custom
66  * {@code Realm} implementation that 'knows' about your application's data/security model
67  * (via {@link #setRealm} or one of the overloaded constructors).  All other attributes in this class hierarchy
68  * will have suitable defaults for most enterprise applications.
69  * <p/>
70  * <b>RememberMe notice</b>: This class supports the ability to configure a
71  * {@link #setRememberMeManager RememberMeManager}
72  * for {@code RememberMe} identity services for login/logout, BUT, a default instance <em>will not</em> be created
73  * for this attribute at startup.
74  * <p/>
75  * Because RememberMe services are inherently client tier-specific and
76  * therefore aplication-dependent, if you want {@code RememberMe} services enabled, you will have to specify an
77  * instance yourself via the {@link #setRememberMeManager(RememberMeManager) setRememberMeManager}
78  * mutator.  However if you're reading this JavaDoc with the
79  * expectation of operating in a Web environment, take a look at the
80  * {@code hunt.shiro.web.DefaultWebSecurityManager} implementation, which
81  * <em>does</em> support {@code RememberMe} services by default at startup.
82  *
83  */
84 class DefaultSecurityManager : SessionsSecurityManager {
85 
86     protected RememberMeManager rememberMeManager;
87     protected SubjectDAO subjectDAO;
88     protected SubjectFactory subjectFactory;
89 
90     /**
91      * Default no-arg constructor.
92      */
93     this() {
94         super();
95         this.subjectFactory = new DefaultSubjectFactory();
96         this.subjectDAO = new DefaultSubjectDAO();
97     }
98 
99     /**
100      * Supporting constructor for a single-realm application.
101      *
102      * @param singleRealm the single realm used by this SecurityManager.
103      */
104     this(Realm singleRealm) {
105         this();
106         setRealm(singleRealm);
107     }
108 
109     /**
110      * Supporting constructor for multiple {@link #setRealms realms}.
111      *
112      * @param realms the realm instances backing this SecurityManager.
113      */
114     this(Realm[] realms) {
115         this();
116         setRealms(realms);
117     }
118 
119     /**
120      * Returns the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
121      *
122      * @return the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
123      */
124     SubjectFactory getSubjectFactory() {
125         return subjectFactory;
126     }
127 
128     /**
129      * Sets the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
130      *
131      * @param subjectFactory the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
132      */
133     void setSubjectFactory(SubjectFactory subjectFactory) {
134         this.subjectFactory = subjectFactory;
135     }
136 
137     /**
138      * Returns the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
139      * Subject identity is discovered (eg after RememberMe services).  Unless configured otherwise, the default
140      * implementation is a {@link DefaultSubjectDAO}.
141      *
142      * @return the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
143      *         Subject identity is discovered (eg after RememberMe services).
144      * @see DefaultSubjectDAO
145      */
146     SubjectDAO getSubjectDAO() {
147         return subjectDAO;
148     }
149 
150     /**
151      * Sets the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
152      * Subject identity is discovered (eg after RememberMe services). Unless configured otherwise, the default
153      * implementation is a {@link DefaultSubjectDAO}.
154      *
155      * @param subjectDAO the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
156      *                   Subject identity is discovered (eg after RememberMe services).
157      * @see DefaultSubjectDAO
158      */
159     void setSubjectDAO(SubjectDAO subjectDAO) {
160         this.subjectDAO = subjectDAO;
161     }
162 
163     RememberMeManager getRememberMeManager() {
164         return rememberMeManager;
165     }
166 
167     void setRememberMeManager(RememberMeManager rememberMeManager) {
168         this.rememberMeManager = rememberMeManager;
169     }
170 
171     protected SubjectContext createSubjectContext() {
172         return new DefaultSubjectContext();
173     }
174 
175     /**
176      * Creates a {@code Subject} instance for the user represented by the given method arguments.
177      *
178      * @param token    the {@code AuthenticationToken} submitted for the successful authentication.
179      * @param info     the {@code AuthenticationInfo} of a newly authenticated user.
180      * @param existing the existing {@code Subject} instance that initiated the authentication attempt
181      * @return the {@code Subject} instance that represents the context and session data for the newly
182      *         authenticated subject.
183      */
184     protected Subject createSubject(AuthenticationToken token,
185             AuthenticationInfo info, Subject existing) {
186         SubjectContext context = createSubjectContext();
187         context.setAuthenticated(true);
188         context.setAuthenticationToken(token);
189         context.setAuthenticationInfo(info);
190         if (existing !is null) {
191             context.setSubject(existing);
192         }
193         return createSubject(context);
194     }
195 
196     /**
197      * Binds a {@code Subject} instance created after authentication to the application for later use.
198      * <p/>
199      * As of Shiro 1.2, this method has been deprecated in favor of {@link #save(hunt.shiro.subject.Subject)},
200      * which this implementation now calls.
201      *
202      * @param subject the {@code Subject} instance created after authentication to be bound to the application
203      *                for later use.
204      * @see #save(hunt.shiro.subject.Subject)
205      * deprecated("") in favor of {@link #save(hunt.shiro.subject.Subject) save(subject)}.
206      */
207     deprecated("") protected void bind(Subject subject) {
208         save(subject);
209     }
210 
211     protected void rememberMeSuccessfulLogin(AuthenticationToken token,
212             AuthenticationInfo info, Subject subject) {
213         RememberMeManager rmm = getRememberMeManager();
214         if (rmm !is null) {
215             try {
216                 rmm.onSuccessfulLogin(subject, token, info);
217             } catch (Exception e) {
218                 version (HUNT_SHIRO_DEBUG) {
219                     string msg = "Delegate RememberMeManager instance of type [" ~ typeid(cast(Object) rmm)
220                         .name ~ "] threw an exception during onSuccessfulLogin.  RememberMe services will not be "
221                         ~ "performed for account [" ~ typeid(cast(Object) info).name ~ "].";
222                     warning(msg, e);
223                 }
224             }
225         } else {
226             version (HUNT_SHIRO_DEBUG) {
227                 tracef("This " ~ typeid(this)
228                         .name ~ " instance does not have a " ~ "[" ~ typeid(RememberMeManager)
229                         .toString() ~ "] instance configured.  RememberMe services " ~ "will not be performed for account [" ~ (
230                             cast(Object) info).toString() ~ "].");
231             }
232         }
233     }
234 
235     protected void rememberMeFailedLogin(AuthenticationToken token,
236             AuthenticationException ex, Subject subject) {
237         RememberMeManager rmm = getRememberMeManager();
238         if (rmm !is null) {
239             try {
240                 rmm.onFailedLogin(subject, token, ex);
241             } catch (Exception e) {
242                 version (HUNT_SHIRO_DEBUG) {
243                     string msg = "Delegate RememberMeManager instance of type [" ~ typeid(cast(Object) rmm)
244                         .name ~ "] threw an exception during onFailedLogin for AuthenticationToken [" ~ typeid(
245                                 cast(Object) token).name ~ "].";
246                     warning(msg, e);
247                 }
248             }
249         }
250     }
251 
252     protected void rememberMeLogout(Subject subject) {
253         RememberMeManager rmm = getRememberMeManager();
254         if (rmm !is null) {
255             try {
256                 rmm.onLogout(subject);
257             } catch (Exception e) {
258                 version (HUNT_SHIRO_DEBUG) {
259                     PrincipalCollection pc = (subject !is null ? subject.getPrincipals() : null);
260                     string msg = "Delegate RememberMeManager instance of type [" ~ typeid(rmm).toString()
261                         ~ "] threw an exception during onLogout for subject with principals [" ~ pc.to!string()
262                         ~ "]";
263                     warning(msg, e);
264                 }
265             }
266         }
267     }
268 
269     /**
270      * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
271      * {@code Subject} instance representing the authenticated account's identity.
272      * <p/>
273      * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
274      * subsequent access before being returned to the caller.
275      *
276      * @param token the authenticationToken to process for the login attempt.
277      * @return a Subject representing the authenticated user.
278      * @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
279      */
280     Subject login(Subject subject, AuthenticationToken token) {
281         AuthenticationInfo info;
282         try {
283             info = authenticate(token);
284         } catch (AuthenticationException ae) {
285             try {
286                 onFailedLogin(token, ae, subject);
287             } catch (Exception e) {
288                 version (HUNT_SHIRO_DEBUG) {
289                     infof("onFailedLogin method threw an "
290                             ~ "exception.  Logging and propagating original AuthenticationException.",
291                             e);
292                 }
293             }
294             throw ae; //propagate
295         }
296 
297         Subject loggedIn = createSubject(token, info, subject);
298         // Always create a new subject whithout refering to the existing one.
299         // Subject loggedIn = createSubject(token, info, null);
300 
301         onSuccessfulLogin(token, info, loggedIn);
302 
303         return loggedIn;
304     }
305 
306     protected void onSuccessfulLogin(AuthenticationToken token,
307             AuthenticationInfo info, Subject subject) {
308         rememberMeSuccessfulLogin(token, info, subject);
309     }
310 
311     protected void onFailedLogin(AuthenticationToken token,
312             AuthenticationException ae, Subject subject) {
313         rememberMeFailedLogin(token, ae, subject);
314     }
315 
316     protected void beforeLogout(Subject subject) {
317         rememberMeLogout(subject);
318     }
319 
320     protected SubjectContext copy(SubjectContext subjectContext) {
321         // warning((cast(Object)subjectContext).toString());
322         return new DefaultSubjectContext(subjectContext);
323     }
324 
325     /**
326      * This implementation functions as follows:
327      * <p/>
328      * <ol>
329      * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire
330      * data that may not have already been available to it (such as a referenced session or remembered principals).</li>
331      * <li>Calls {@link #doCreateSubject(hunt.shiro.subject.SubjectContext)} to actually perform the
332      * {@code Subject} instance creation.</li>
333      * <li>calls {@link #save(hunt.shiro.subject.Subject) save(subject)} to ensure the constructed
334      * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li>
335      * <li>returns the constructed {@code Subject} instance.</li>
336      * </ol>
337      *
338      * @param subjectContext any data needed to direct how the Subject should be constructed.
339      * @return the {@code Subject} instance reflecting the specified contextual data.
340      * @see #ensureSecurityManager(hunt.shiro.subject.SubjectContext)
341      * @see #resolveSession(hunt.shiro.subject.SubjectContext)
342      * @see #resolvePrincipals(hunt.shiro.subject.SubjectContext)
343      * @see #doCreateSubject(hunt.shiro.subject.SubjectContext)
344      * @see #save(hunt.shiro.subject.Subject)
345      */
346     Subject createSubject(SubjectContext subjectContext) {
347         //create a copy so we don't modify the argument's backing map:
348         SubjectContext context = copy(subjectContext);
349 
350         //ensure that the context has a SecurityManager instance, and if not, add one:
351         context = ensureSecurityManager(context);
352 
353         //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
354         //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
355         //process is often environment specific - better to shield the SF from these details:
356         context = resolveSession(context);
357 
358         //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
359         //if possible before handing off to the SubjectFactory:
360         context = resolvePrincipals(context);
361 
362         Subject subject = doCreateSubject(context);
363 
364         //save this subject for future reference if necessary:
365         //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
366         //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
367         //Added in 1.2:
368         save(subject);
369 
370         return subject;
371     }
372 
373     /**
374      * Actually creates a {@code Subject} instance by delegating to the internal
375      * {@link #getSubjectFactory() subjectFactory}.  By the time this method is invoked, all possible
376      * {@code SubjectContext} data (session, principals, et. al.) has been made accessible using all known heuristics
377      * and will be accessible to the {@code subjectFactory} via the {@code subjectContext.resolve*} methods.
378      *
379      * @param context the populated context (data map) to be used by the {@code SubjectFactory} when creating a
380      *                {@code Subject} instance.
381      * @return a {@code Subject} instance reflecting the data in the specified {@code SubjectContext} data map.
382      * @see #getSubjectFactory()
383      * @see SubjectFactory#createSubject(hunt.shiro.subject.SubjectContext)
384      */
385     protected Subject doCreateSubject(SubjectContext context) {
386         return getSubjectFactory().createSubject(context);
387     }
388 
389     /**
390      * Saves the subject's state to a persistent location for future reference if necessary.
391      * <p/>
392      * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
393      * {@link SubjectDAO#save(hunt.shiro.subject.Subject) subjectDAO.save(subject)}.
394      *
395      * @param subject the subject for which state will potentially be persisted
396      * @see SubjectDAO#save(hunt.shiro.subject.Subject)
397      */
398     protected void save(Subject subject) {
399         this.subjectDAO.save(subject);
400     }
401 
402     /**
403      * Removes (or 'unbinds') the Subject's state from the application, typically called during {@link #logout}..
404      * <p/>
405      * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
406      * {@link SubjectDAO#remove(hunt.shiro.subject.Subject) remove(subject)}.
407      *
408      * @param subject the subject for which state will be removed
409      * @see SubjectDAO#remove(hunt.shiro.subject.Subject)
410      */
411     protected void remove(Subject subject) {
412         this.subjectDAO.remove(subject);
413     }
414 
415     /**
416      * Determines if there is a {@code SecurityManager} instance in the context, and if not, adds 'this' to the
417      * context.  This ensures the SubjectFactory instance will have access to a SecurityManager during Subject
418      * construction if necessary.
419      *
420      * @param context the subject context data that may contain a SecurityManager instance.
421      * @return The SubjectContext to use to pass to a {@link SubjectFactory} for subject creation.
422      */
423     //@SuppressWarnings({"unchecked"})
424     protected SubjectContext ensureSecurityManager(SubjectContext context) {
425         if (context.resolveSecurityManager() !is null) {
426             version (HUNT_SHIRO_DEBUG) {
427                 tracef("Context already contains a SecurityManager instance.  Returning.");
428             }
429             return context;
430         }
431         version (HUNT_SHIRO_DEBUG) {
432             warning("No SecurityManager found in context.  Adding self reference.");
433         }
434         context.setSecurityManager(this);
435         return context;
436     }
437 
438     /**
439      * Attempts to resolve any associated session based on the context and returns a
440      * context that represents this resolved {@code Session} to ensure it may be referenced if necessary by the
441      * invoked {@link SubjectFactory} that performs actual {@link Subject} construction.
442      * <p/>
443      * If there is a {@code Session} already in the context because that is what the caller wants to be used for
444      * {@code Subject} construction, or if no session is resolved, this method effectively does nothing
445      * returns the context method argument unaltered.
446      *
447      * @param context the subject context data that may resolve a Session instance.
448      * @return The context to use to pass to a {@link SubjectFactory} for subject creation.
449      */
450     //@SuppressWarnings({"unchecked"})
451     protected SubjectContext resolveSession(SubjectContext context) {
452         if (context.resolveSession() !is null) {
453             version (HUNT_SHIRO_DEBUG)
454                 tracef("Context already contains a session.  Returning.");
455             return context;
456         }
457         try {
458             //Context couldn't resolve it directly, let's see if we can since we have direct access to 
459             //the session manager:
460             Session session = resolveContextSession(context);
461             if (session !is null) {
462                 context.setSession(session);
463             }
464         } catch (InvalidSessionException e) {
465             warningf("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous "
466                     ~ "(session-less) Subject instance.", e);
467         }
468         return context;
469     }
470 
471     protected Session resolveContextSession(SubjectContext context) {
472         SessionKey key = getSessionKey(context);
473         if (key !is null) {
474             return getSession(key);
475         }
476         return null;
477     }
478 
479     protected SessionKey getSessionKey(SubjectContext context) {
480         string sessionId = context.getSessionId();
481         if (sessionId !is null) {
482             return new DefaultSessionKey(sessionId);
483         }
484         return null;
485     }
486 
487     private static bool isEmpty(PrincipalCollection pc) {
488         return pc is null || pc.isEmpty();
489     }
490 
491     /**
492      * Attempts to resolve an identity (a {@link PrincipalCollection}) for the context using heuristics.  This
493      * implementation functions as follows:
494      * <ol>
495      * <li>Check the context to see if it can already {@link SubjectContext#resolvePrincipals resolve an identity}.  If
496      * so, this method does nothing and returns the method argument unaltered.</li>
497      * <li>Check for a RememberMe identity by calling {@link #getRememberedIdentity}.  If that method returns a
498      * non-null value, place the remembered {@link PrincipalCollection} in the context.</li>
499      * </ol>
500      *
501      * @param context the subject context data that may provide (directly or indirectly through one of its values) a
502      *                {@link PrincipalCollection} identity.
503      * @return The Subject context to use to pass to a {@link SubjectFactory} for subject creation.
504      */
505     //@SuppressWarnings({"unchecked"})
506     protected SubjectContext resolvePrincipals(SubjectContext context) {
507 
508         PrincipalCollection principals = context.resolvePrincipals();
509 
510         if (isEmpty(principals)) {
511             version (HUNT_SHIRO_DEBUG)
512                 tracef("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");
513 
514             principals = getRememberedIdentity(context);
515 
516             if (!isEmpty(principals)) {
517                 version (HUNT_SHIRO_DEBUG)
518                     tracef("Found remembered PrincipalCollection.  Adding to the context to be used "
519                             ~ "for subject construction by the SubjectFactory.");
520 
521                 context.setPrincipals(principals);
522 
523                 // The following call was removed (commented out) in Shiro 1.2 because it uses the session as an
524                 // implementation strategy.  Session use for Shiro's own needs should be controlled in a single place
525                 // to be more manageable for end-users: there are a number of stateless (e.g. REST) applications that
526                 // use Shiro that need to ensure that sessions are only used when desirable.  If Shiro's internal
527                 // implementations used Subject sessions (setting attributes) whenever we wanted, it would be much
528                 // harder for end-users to control when/where that occurs.
529                 //
530                 // Because of this, the SubjectDAO was created as the single point of control, and session state logic
531                 // has been moved to the DefaultSubjectDAO implementation.
532 
533                 // Removed in Shiro 1.2.  SHIRO-157 is still satisfied by the new DefaultSubjectDAO implementation
534                 // introduced in 1.2
535                 // Satisfies SHIRO-157:
536                 // bindPrincipalsToSession(principals, context);
537 
538             } else {
539                 version (HUNT_SHIRO_DEBUG)
540                     tracef("No remembered identity found.  Returning original context.");
541             }
542         }
543 
544         return context;
545     }
546 
547     protected SessionContext createSessionContext(SubjectContext subjectContext) {
548         Map!(string, Object) contextMap = cast(Map!(string, Object)) subjectContext;
549         DefaultSessionContext sessionContext = new DefaultSessionContext();
550         if (!CollectionUtils.isEmpty(contextMap)) {
551             sessionContext.putAll(contextMap);
552         }
553         string sessionId = subjectContext.getSessionId();
554         if (sessionId !is null) {
555             sessionContext.setSessionId(sessionId);
556         }
557         string host = subjectContext.resolveHost();
558         if (host !is null) {
559             sessionContext.setHost(host);
560         }
561         return sessionContext;
562     }
563 
564     void logout(Subject subject) {
565 
566         if (subject is null) {
567             throw new IllegalArgumentException("Subject method argument cannot be null.");
568         }
569 
570         beforeLogout(subject);
571 
572         PrincipalCollection principals = subject.getPrincipals();
573         if (principals !is null && !principals.isEmpty()) {
574             version (HUNT_SHIRO_DEBUG) {
575                 tracef("Logging out subject with primary principal %s",
576                         principals.getPrimaryPrincipal());
577             }
578             Authenticator authc = getAuthenticator();
579             LogoutAware la = cast(LogoutAware) authc;
580             if (la !is null) {
581                 la.onLogout(principals);
582             }
583         }
584 
585         try {
586             remove(subject);
587         } catch (Exception e) {
588             string msg = "Unable to cleanly unbind Subject.  Ignoring (logging out).";
589             warning(msg);
590             version (HUNT_SHIRO_DEBUG) {
591                 warning(e);
592             }
593         }
594 
595         try {
596             stopSession(subject);
597         } catch (Exception e) {
598             version (HUNT_SHIRO_DEBUG) {
599                 string msg = "Unable to cleanly stop Session for Subject [" ~ subject.getPrincipal()
600                     .toString() ~ "] " ~ "Ignoring (logging out).";
601                 tracef(msg, e);
602             }
603         }
604     }
605 
606     protected void stopSession(Subject subject) {
607         Session s = subject.getSession(false);
608         if (s !is null) {
609             s.stop();
610         }
611     }
612 
613     /**
614      * Unbinds or removes the Subject's state from the application, typically called during {@link #logout}.
615      * <p/>
616      * This has been deprecated in Shiro 1.2 in favor of the {@link #remove(hunt.shiro.subject.Subject) remove}
617      * method.  The implementation has been updated to invoke that method.
618      *
619      * @param subject the subject to unbind from the application as it will no longer be used.
620      * deprecated("") in Shiro 1.2 in favor of {@link #remove(hunt.shiro.subject.Subject)}
621      */
622     deprecated("") //@SuppressWarnings({"UnusedDeclaration"})
623     protected void unbind(Subject subject) {
624         remove(subject);
625     }
626 
627     protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
628         RememberMeManager rmm = getRememberMeManager();
629         if (rmm !is null) {
630             try {
631                 return rmm.getRememberedPrincipals(subjectContext);
632             } catch (Exception e) {
633                 version (HUNT_SHIRO_DEBUG) {
634                     string msg = "Delegate RememberMeManager instance of type [" ~ typeid(cast(Object) rmm)
635                         .name ~ "] threw an exception during getRememberedPrincipals().";
636                     warning(msg, e);
637                 }
638             }
639         }
640         return null;
641     }
642 }