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