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