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