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.*;
022import org.apache.shiro.authz.Authorizer;
023import org.apache.shiro.realm.Realm;
024import org.apache.shiro.session.InvalidSessionException;
025import org.apache.shiro.session.Session;
026import org.apache.shiro.session.mgt.DefaultSessionContext;
027import org.apache.shiro.session.mgt.DefaultSessionKey;
028import org.apache.shiro.session.mgt.SessionContext;
029import org.apache.shiro.session.mgt.SessionKey;
030import org.apache.shiro.subject.PrincipalCollection;
031import org.apache.shiro.subject.Subject;
032import org.apache.shiro.subject.SubjectContext;
033import org.apache.shiro.subject.support.DefaultSubjectContext;
034import org.apache.shiro.util.CollectionUtils;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import java.io.Serializable;
039import 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 */
071public 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}