001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.mgt;
020
021import org.apache.shiro.authc.AuthenticationException;
022import org.apache.shiro.authc.AuthenticationInfo;
023import org.apache.shiro.authc.AuthenticationToken;
024import org.apache.shiro.authc.Authenticator;
025import org.apache.shiro.authc.LogoutAware;
026import org.apache.shiro.authz.Authorizer;
027import org.apache.shiro.realm.Realm;
028import org.apache.shiro.session.InvalidSessionException;
029import org.apache.shiro.session.Session;
030import org.apache.shiro.session.mgt.DefaultSessionContext;
031import org.apache.shiro.session.mgt.DefaultSessionKey;
032import org.apache.shiro.session.mgt.SessionContext;
033import org.apache.shiro.session.mgt.SessionKey;
034import org.apache.shiro.subject.PrincipalCollection;
035import org.apache.shiro.subject.Subject;
036import org.apache.shiro.subject.SubjectContext;
037import org.apache.shiro.subject.support.DefaultSubjectContext;
038import org.apache.shiro.util.CollectionUtils;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042import java.io.Serializable;
043import java.util.Collection;
044
045/**
046 * The Shiro framework's default concrete implementation of the {@link SecurityManager} interface,
047 * based around a collection of {@link org.apache.shiro.realm.Realm}s.  This implementation delegates its
048 * authentication, authorization, and session operations to wrapped {@link Authenticator}, {@link Authorizer}, and
049 * {@link org.apache.shiro.session.mgt.SessionManager SessionManager} instances respectively via superclass
050 * implementation.
051 * <p/>
052 * To greatly reduce and simplify configuration, this implementation (and its superclasses) will
053 * create suitable defaults for all of its required dependencies, <em>except</em> the required one or more
054 * {@link Realm Realm}s.  Because {@code Realm} implementations usually interact with an application's data model,
055 * they are almost always application specific;  you will want to specify at least one custom
056 * {@code Realm} implementation that 'knows' about your application's data/security model
057 * (via {@link #setRealm} or one of the overloaded constructors).  All other attributes in this class hierarchy
058 * will have suitable defaults for most enterprise applications.
059 * <p/>
060 * <b>RememberMe notice</b>: This class supports the ability to configure a
061 * {@link #setRememberMeManager RememberMeManager}
062 * for {@code RememberMe} identity services for login/logout, BUT, a default instance <em>will not</em> be created
063 * for this attribute at startup.
064 * <p/>
065 * Because RememberMe services are inherently client tier-specific and
066 * therefore application-dependent, if you want {@code RememberMe} services enabled, you will have to specify an
067 * instance yourself via the {@link #setRememberMeManager(RememberMeManager) setRememberMeManager}
068 * mutator.  However if you're reading this JavaDoc with the
069 * expectation of operating in a Web environment, take a look at the
070 * {@code org.apache.shiro.web.DefaultWebSecurityManager} implementation, which
071 * <em>does</em> support {@code RememberMe} services by default at startup.
072 *
073 * @since 0.2
074 */
075@SuppressWarnings("checkstyle:MethodCount")
076public class DefaultSecurityManager extends SessionsSecurityManager {
077
078    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSecurityManager.class);
079
080    protected RememberMeManager rememberMeManager;
081    protected SubjectDAO subjectDAO;
082    protected SubjectFactory subjectFactory;
083
084    /**
085     * Default no-arg constructor.
086     */
087    public DefaultSecurityManager() {
088        super();
089        this.subjectFactory = new DefaultSubjectFactory();
090        this.subjectDAO = new DefaultSubjectDAO();
091    }
092
093    /**
094     * Supporting constructor for a single-realm application.
095     *
096     * @param singleRealm the single realm used by this SecurityManager.
097     */
098    public DefaultSecurityManager(Realm singleRealm) {
099        this();
100        setRealm(singleRealm);
101    }
102
103    /**
104     * Supporting constructor for multiple {@link #setRealms realms}.
105     *
106     * @param realms the realm instances backing this SecurityManager.
107     */
108    public DefaultSecurityManager(Collection<Realm> realms) {
109        this();
110        setRealms(realms);
111    }
112
113    /**
114     * Returns the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
115     *
116     * @return the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
117     */
118    public SubjectFactory getSubjectFactory() {
119        return subjectFactory;
120    }
121
122    /**
123     * Sets the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
124     *
125     * @param subjectFactory    the {@code SubjectFactory} responsible for creating
126     *                          {@link Subject} instances exposed to the application.
127     */
128    public void setSubjectFactory(SubjectFactory subjectFactory) {
129        this.subjectFactory = subjectFactory;
130    }
131
132    /**
133     * Returns the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
134     * Subject identity is discovered (e.g. after RememberMe services).  Unless configured otherwise, the default
135     * implementation is a {@link DefaultSubjectDAO}.
136     *
137     * @return the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
138     * Subject identity is discovered (e.g. after RememberMe services).
139     * @see DefaultSubjectDAO
140     * @since 1.2
141     */
142    public SubjectDAO getSubjectDAO() {
143        return subjectDAO;
144    }
145
146    /**
147     * Sets the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
148     * Subject identity is discovered (e.g. after RememberMe services). Unless configured otherwise, the default
149     * implementation is a {@link DefaultSubjectDAO}.
150     *
151     * @param subjectDAO the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
152     *                   Subject identity is discovered (e.g. after RememberMe services).
153     * @see DefaultSubjectDAO
154     * @since 1.2
155     */
156    public void setSubjectDAO(SubjectDAO subjectDAO) {
157        this.subjectDAO = subjectDAO;
158    }
159
160    public RememberMeManager getRememberMeManager() {
161        return rememberMeManager;
162    }
163
164    public void setRememberMeManager(RememberMeManager rememberMeManager) {
165        this.rememberMeManager = rememberMeManager;
166    }
167
168    protected SubjectContext createSubjectContext() {
169        return new DefaultSubjectContext();
170    }
171
172    /**
173     * Creates a {@code Subject} instance for the user represented by the given method arguments.
174     *
175     * @param token    the {@code AuthenticationToken} submitted for the successful authentication.
176     * @param info     the {@code AuthenticationInfo} of a newly authenticated user.
177     * @param existing the existing {@code Subject} instance that initiated the authentication attempt
178     * @return the {@code Subject} instance that represents the context and session data for the newly
179     * authenticated subject.
180     */
181    protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
182        SubjectContext context = createSubjectContext();
183        context.setAuthenticated(true);
184        context.setAuthenticationToken(token);
185        context.setAuthenticationInfo(info);
186        context.setSecurityManager(this);
187        if (existing != null) {
188            context.setSubject(existing);
189        }
190        return createSubject(context);
191    }
192
193    /**
194     * Binds a {@code Subject} instance created after authentication to the application for later use.
195     * <p/>
196     * As of Shiro 1.2, this method has been deprecated in favor of {@link #save(org.apache.shiro.subject.Subject)},
197     * which this implementation now calls.
198     *
199     * @param subject the {@code Subject} instance created after authentication to be bound to the application
200     *                for later use.
201     * @see #save(org.apache.shiro.subject.Subject)
202     * @deprecated in favor of {@link #save(org.apache.shiro.subject.Subject) save(subject)}.
203     */
204    @Deprecated
205    protected void bind(Subject subject) {
206        save(subject);
207    }
208
209    protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
210        RememberMeManager rmm = getRememberMeManager();
211        if (rmm != null) {
212            try {
213                rmm.onSuccessfulLogin(subject, token, info);
214            } catch (Exception e) {
215                if (LOGGER.isWarnEnabled()) {
216                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName()
217                            + "] threw an exception during onSuccessfulLogin.  RememberMe services will not be "
218                            + "performed for account [" + info + "].";
219                    LOGGER.warn(msg, e);
220                }
221            }
222        } else {
223            if (LOGGER.isTraceEnabled()) {
224                LOGGER.trace("This " + getClass().getName() + " instance does not have a "
225                        + "[" + RememberMeManager.class.getName() + "] instance configured.  RememberMe services "
226                        + "will not be performed for account [" + info + "].");
227            }
228        }
229    }
230
231    protected void rememberMeFailedLogin(AuthenticationToken token, AuthenticationException ex, Subject subject) {
232        RememberMeManager rmm = getRememberMeManager();
233        if (rmm != null) {
234            try {
235                rmm.onFailedLogin(subject, token, ex);
236            } catch (Exception e) {
237                if (LOGGER.isWarnEnabled()) {
238                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName()
239                            + "] threw an exception during onFailedLogin for AuthenticationToken ["
240                            + token + "].";
241                    LOGGER.warn(msg, e);
242                }
243            }
244        }
245    }
246
247    protected void rememberMeLogout(Subject subject) {
248        RememberMeManager rmm = getRememberMeManager();
249        if (rmm != null) {
250            try {
251                rmm.onLogout(subject);
252            } catch (Exception e) {
253                if (LOGGER.isWarnEnabled()) {
254                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName()
255                            + "] threw an exception during onLogout for subject with principals ["
256                            + (subject != null ? subject.getPrincipals() : null) + "]";
257                    LOGGER.warn(msg, e);
258                }
259            }
260        }
261    }
262
263    /**
264     * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
265     * {@code Subject} instance representing the authenticated account's identity.
266     * <p/>
267     * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
268     * subsequent access before being returned to the caller.
269     *
270     * @param token the authenticationToken to process for the login attempt.
271     * @return a Subject representing the authenticated user.
272     * @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
273     */
274    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
275        AuthenticationInfo info;
276        try {
277            info = authenticate(token);
278        } catch (AuthenticationException ae) {
279            try {
280                onFailedLogin(token, ae, subject);
281            } catch (Exception e) {
282                if (LOGGER.isInfoEnabled()) {
283                    LOGGER.info("onFailedLogin method threw an "
284                            + "exception.  Logging and propagating original AuthenticationException.", e);
285                }
286            }
287            //propagate
288            throw ae;
289        }
290
291        Subject loggedIn = createSubject(token, info, subject);
292
293        onSuccessfulLogin(token, info, loggedIn);
294
295        return loggedIn;
296    }
297
298    protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
299        rememberMeSuccessfulLogin(token, info, subject);
300    }
301
302    protected void onFailedLogin(AuthenticationToken token, AuthenticationException ae, Subject subject) {
303        rememberMeFailedLogin(token, ae, subject);
304    }
305
306    protected void beforeLogout(Subject subject) {
307        rememberMeLogout(subject);
308    }
309
310    protected SubjectContext copy(SubjectContext subjectContext) {
311        return new DefaultSubjectContext(subjectContext);
312    }
313
314    /**
315     * This implementation functions as follows:
316     * <p/>
317     * <ol>
318     * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire
319     * data that may not have already been available to it (such as a referenced session or remembered principals).</li>
320     * <li>Calls {@link #doCreateSubject(org.apache.shiro.subject.SubjectContext)} to actually perform the
321     * {@code Subject} instance creation.</li>
322     * <li>calls {@link #save(org.apache.shiro.subject.Subject) save(subject)} to ensure the constructed
323     * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li>
324     * <li>returns the constructed {@code Subject} instance.</li>
325     * </ol>
326     *
327     * @param subjectContext any data needed to direct how the Subject should be constructed.
328     * @return the {@code Subject} instance reflecting the specified contextual data.
329     * @see #ensureSecurityManager(org.apache.shiro.subject.SubjectContext)
330     * @see #resolveSession(org.apache.shiro.subject.SubjectContext)
331     * @see #resolvePrincipals(org.apache.shiro.subject.SubjectContext)
332     * @see #doCreateSubject(org.apache.shiro.subject.SubjectContext)
333     * @see #save(org.apache.shiro.subject.Subject)
334     * @since 1.0
335     */
336    public Subject createSubject(SubjectContext subjectContext) {
337        //create a copy so we don't modify the argument's backing map:
338        SubjectContext context = copy(subjectContext);
339
340        //ensure that the context has a SecurityManager instance, and if not, add one:
341        context = ensureSecurityManager(context);
342
343        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
344        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
345        //process is often environment specific - better to shield the SF from these details:
346        context = resolveSession(context);
347
348        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
349        //if possible before handing off to the SubjectFactory:
350        context = resolvePrincipals(context);
351
352        Subject subject = doCreateSubject(context);
353
354        //save this subject for future reference if necessary:
355        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
356        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
357        //Added in 1.2:
358        if (context.isSessionCreationEnabled()) {
359            save(subject);
360        }
361
362        return subject;
363    }
364
365    /**
366     * Actually creates a {@code Subject} instance by delegating to the internal
367     * {@link #getSubjectFactory() subjectFactory}.  By the time this method is invoked, all possible
368     * {@code SubjectContext} data (session, principals, et al.) has been made accessible using all known heuristics
369     * and will be accessible to the {@code subjectFactory} via the {@code subjectContext.resolve*} methods.
370     *
371     * @param context the populated context (data map) to be used by the {@code SubjectFactory} when creating a
372     *                {@code Subject} instance.
373     * @return a {@code Subject} instance reflecting the data in the specified {@code SubjectContext} data map.
374     * @see #getSubjectFactory()
375     * @see SubjectFactory#createSubject(org.apache.shiro.subject.SubjectContext)
376     * @since 1.2
377     */
378    protected Subject doCreateSubject(SubjectContext context) {
379        return getSubjectFactory().createSubject(context);
380    }
381
382    /**
383     * Saves the subject's state to a persistent location for future reference if necessary.
384     * <p/>
385     * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
386     * {@link SubjectDAO#save(org.apache.shiro.subject.Subject) subjectDAO.save(subject)}.
387     *
388     * @param subject the subject for which state will potentially be persisted
389     * @see SubjectDAO#save(org.apache.shiro.subject.Subject)
390     * @since 1.2
391     */
392    protected void save(Subject subject) {
393        this.subjectDAO.save(subject);
394    }
395
396    /**
397     * Removes (or 'unbinds') the Subject's state from the application, typically called during {@link #logout}..
398     * <p/>
399     * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
400     * {@link SubjectDAO#delete(org.apache.shiro.subject.Subject) delete(subject)}.
401     *
402     * @param subject the subject for which state will be removed
403     * @see SubjectDAO#delete(org.apache.shiro.subject.Subject)
404     * @since 1.2
405     */
406    protected void delete(Subject subject) {
407        this.subjectDAO.delete(subject);
408    }
409
410    /**
411     * Determines if there is a {@code SecurityManager} instance in the context, and if not, adds 'this' to the
412     * context.  This ensures the SubjectFactory instance will have access to a SecurityManager during Subject
413     * construction if necessary.
414     *
415     * @param context the subject context data that may contain a SecurityManager instance.
416     * @return The SubjectContext to use to pass to a {@link SubjectFactory} for subject creation.
417     * @since 1.0
418     */
419    @SuppressWarnings({"unchecked"})
420    protected SubjectContext ensureSecurityManager(SubjectContext context) {
421        if (context.resolveSecurityManager() != null) {
422            LOGGER.trace("Context already contains a SecurityManager instance.  Returning.");
423            return context;
424        }
425        LOGGER.trace("No SecurityManager found in context.  Adding self reference.");
426        context.setSecurityManager(this);
427        return context;
428    }
429
430    /**
431     * Attempts to resolve any associated session based on the context and returns a
432     * context that represents this resolved {@code Session} to ensure it may be referenced if necessary by the
433     * invoked {@link SubjectFactory} that performs actual {@link Subject} construction.
434     * <p/>
435     * If there is a {@code Session} already in the context because that is what the caller wants to be used for
436     * {@code Subject} construction, or if no session is resolved, this method effectively does nothing
437     * returns the context method argument unaltered.
438     *
439     * @param context the subject context data that may resolve a Session instance.
440     * @return The context to use to pass to a {@link SubjectFactory} for subject creation.
441     * @since 1.0
442     */
443    @SuppressWarnings({"unchecked"})
444    protected SubjectContext resolveSession(SubjectContext context) {
445        if (context.resolveSession() != null) {
446            LOGGER.debug("Context already contains a session.  Returning.");
447            return context;
448        }
449        try {
450            //Context couldn't resolve it directly, let's see if we can since we have direct access to
451            //the session manager:
452            Session session = resolveContextSession(context);
453            if (session != null) {
454                context.setSession(session);
455            }
456        } catch (InvalidSessionException e) {
457            LOGGER.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous "
458                    + "(session-less) Subject instance.", e);
459        }
460        return context;
461    }
462
463    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
464        SessionKey key = getSessionKey(context);
465        if (key != null) {
466            return getSession(key);
467        }
468        return null;
469    }
470
471    protected SessionKey getSessionKey(SubjectContext context) {
472        Serializable sessionId = context.getSessionId();
473        if (sessionId != null) {
474            return new DefaultSessionKey(sessionId);
475        }
476        return null;
477    }
478
479    private static boolean isEmpty(PrincipalCollection pc) {
480        return pc == null || pc.isEmpty();
481    }
482
483    /**
484     * Attempts to resolve an identity (a {@link PrincipalCollection}) for the context using heuristics.  This
485     * implementation functions as follows:
486     * <ol>
487     * <li>Check the context to see if it can already {@link SubjectContext#resolvePrincipals resolve an identity}.  If
488     * so, this method does nothing and returns the method argument unaltered.</li>
489     * <li>Check for a RememberMe identity by calling {@link #getRememberedIdentity}.  If that method returns a
490     * non-null value, place the remembered {@link PrincipalCollection} in the context.</li>
491     * </ol>
492     *
493     * @param context the subject context data that may provide (directly or indirectly through one of its values) a
494     *                {@link PrincipalCollection} identity.
495     * @return The Subject context to use to pass to a {@link SubjectFactory} for subject creation.
496     * @since 1.0
497     */
498    @SuppressWarnings({"unchecked"})
499    protected SubjectContext resolvePrincipals(SubjectContext context) {
500
501        PrincipalCollection principals = context.resolvePrincipals();
502
503        if (isEmpty(principals)) {
504            LOGGER.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");
505
506            principals = getRememberedIdentity(context);
507
508            if (!isEmpty(principals)) {
509                LOGGER.debug("Found remembered PrincipalCollection.  Adding to the context to be used "
510                        + "for subject construction by the SubjectFactory.");
511
512                context.setPrincipals(principals);
513
514                // The following call was removed (commented out) in Shiro 1.2 because it uses the session as an
515                // implementation strategy.  Session use for Shiro's own needs should be controlled in a single place
516                // to be more manageable for end-users: there are a number of stateless (e.g. REST) applications that
517                // use Shiro that need to ensure that sessions are only used when desirable.  If Shiro's internal
518                // implementations used Subject sessions (setting attributes) whenever we wanted, it would be much
519                // harder for end-users to control when/where that occurs.
520                //
521                // Because of this, the SubjectDAO was created as the single point of control, and session state logic
522                // has been moved to the DefaultSubjectDAO implementation.
523
524                // Removed in Shiro 1.2.  SHIRO-157 is still satisfied by the new DefaultSubjectDAO implementation
525                // introduced in 1.2
526                // Satisfies SHIRO-157:
527                // bindPrincipalsToSession(principals, context);
528
529            } else {
530                LOGGER.trace("No remembered identity found.  Returning original context.");
531            }
532        }
533
534        return context;
535    }
536
537    protected SessionContext createSessionContext(SubjectContext subjectContext) {
538        DefaultSessionContext sessionContext = new DefaultSessionContext();
539        if (!CollectionUtils.isEmpty(subjectContext)) {
540            sessionContext.putAll(subjectContext);
541        }
542        Serializable sessionId = subjectContext.getSessionId();
543        if (sessionId != null) {
544            sessionContext.setSessionId(sessionId);
545        }
546        String host = subjectContext.resolveHost();
547        if (host != null) {
548            sessionContext.setHost(host);
549        }
550        return sessionContext;
551    }
552
553    public void logout(Subject subject) {
554
555        if (subject == null) {
556            throw new IllegalArgumentException("Subject method argument cannot be null.");
557        }
558
559        beforeLogout(subject);
560
561        PrincipalCollection principals = subject.getPrincipals();
562        if (principals != null && !principals.isEmpty()) {
563            if (LOGGER.isDebugEnabled()) {
564                LOGGER.debug("Logging out subject with primary principal {}", principals.getPrimaryPrincipal());
565            }
566            Authenticator authc = getAuthenticator();
567            if (authc instanceof LogoutAware) {
568                ((LogoutAware) authc).onLogout(principals);
569            }
570        }
571
572        try {
573            delete(subject);
574        } catch (Exception e) {
575            if (LOGGER.isDebugEnabled()) {
576                String msg = "Unable to cleanly unbind Subject.  Ignoring (logging out).";
577                LOGGER.debug(msg, e);
578            }
579        } finally {
580            try {
581                stopSession(subject);
582            } catch (Exception e) {
583                if (LOGGER.isDebugEnabled()) {
584                    String msg = "Unable to cleanly stop Session for Subject [" + subject.getPrincipal() + "] "
585                            + "Ignoring (logging out).";
586                    LOGGER.debug(msg, e);
587                }
588            }
589        }
590    }
591
592    protected void stopSession(Subject subject) {
593        Session s = subject.getSession(false);
594        if (s != null) {
595            s.stop();
596        }
597    }
598
599    /**
600     * Unbinds or removes the Subject's state from the application, typically called during {@link #logout}.
601     * <p/>
602     * This has been deprecated in Shiro 1.2 in favor of the {@link #delete(org.apache.shiro.subject.Subject) delete}
603     * method.  The implementation has been updated to invoke that method.
604     *
605     * @param subject the subject to unbind from the application as it will no longer be used.
606     * @deprecated in Shiro 1.2 in favor of {@link #delete(org.apache.shiro.subject.Subject)}
607     */
608    @Deprecated
609    @SuppressWarnings({"UnusedDeclaration"})
610    protected void unbind(Subject subject) {
611        delete(subject);
612    }
613
614    protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
615        RememberMeManager rmm = getRememberMeManager();
616        if (rmm != null) {
617            try {
618                return rmm.getRememberedPrincipals(subjectContext);
619            } catch (Exception e) {
620                if (LOGGER.isWarnEnabled()) {
621                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName()
622                            + "] threw an exception during getRememberedPrincipals().";
623                    LOGGER.warn(msg, e);
624                }
625            }
626        }
627        return null;
628    }
629}