[shiro] 02/03: Imported Upstream version 1.2.2

Emmanuel Bourg ebourg-guest at alioth.debian.org
Wed Oct 16 15:04:03 UTC 2013


This is an automated email from the git hooks/post-receive script.

ebourg-guest pushed a commit to branch master
in repository shiro.

commit fd3ba291bffc803b8c060af4d5a6241e87ad1a62
Author: Emmanuel Bourg <ebourg at apache.org>
Date:   Wed Oct 16 17:03:21 2013 +0200

    Imported Upstream version 1.2.2
---
 LICENSE                                            |  201 ++++
 NOTICE                                             |   15 +
 README                                             |   18 +
 RELEASE-NOTES                                      |  106 ++
 all/pom.xml                                        |  124 +++
 core/pom.xml                                       |  111 +++
 .../main/java/org/apache/shiro/SecurityUtils.java  |  127 +++
 .../main/java/org/apache/shiro/ShiroException.java |   66 ++
 .../shiro/UnavailableSecurityManagerException.java |   36 +
 .../org/apache/shiro/aop/AnnotationHandler.java    |   84 ++
 .../shiro/aop/AnnotationMethodInterceptor.java     |  150 +++
 .../org/apache/shiro/aop/AnnotationResolver.java   |   42 +
 .../shiro/aop/DefaultAnnotationResolver.java       |   64 ++
 .../org/apache/shiro/aop/MethodInterceptor.java    |   41 +
 .../apache/shiro/aop/MethodInterceptorSupport.java |   50 +
 .../org/apache/shiro/aop/MethodInvocation.java     |   64 ++
 .../java/org/apache/shiro/aop/package-info.java    |   26 +
 .../apache/shiro/authc/AbstractAuthenticator.java  |  259 +++++
 .../main/java/org/apache/shiro/authc/Account.java  |   42 +
 .../org/apache/shiro/authc/AccountException.java   |   64 ++
 .../shiro/authc/AuthenticationException.java       |   66 ++
 .../org/apache/shiro/authc/AuthenticationInfo.java |   82 ++
 .../apache/shiro/authc/AuthenticationListener.java |   55 ++
 .../apache/shiro/authc/AuthenticationToken.java    |   94 ++
 .../java/org/apache/shiro/authc/Authenticator.java |   67 ++
 .../shiro/authc/ConcurrentAccessException.java     |   75 ++
 .../apache/shiro/authc/CredentialsException.java   |   64 ++
 .../shiro/authc/DisabledAccountException.java      |   64 ++
 .../shiro/authc/ExcessiveAttemptsException.java    |   66 ++
 .../shiro/authc/ExpiredCredentialsException.java   |   68 ++
 .../shiro/authc/HostAuthenticationToken.java       |   44 +
 .../shiro/authc/IncorrectCredentialsException.java |   71 ++
 .../apache/shiro/authc/LockedAccountException.java |   69 ++
 .../java/org/apache/shiro/authc/LogoutAware.java   |   41 +
 .../shiro/authc/MergableAuthenticationInfo.java    |   47 +
 .../shiro/authc/RememberMeAuthenticationToken.java |   43 +
 .../shiro/authc/SaltedAuthenticationInfo.java      |   48 +
 .../java/org/apache/shiro/authc/SimpleAccount.java |  494 ++++++++++
 .../shiro/authc/SimpleAuthenticationInfo.java      |  282 ++++++
 .../shiro/authc/UnknownAccountException.java       |   67 ++
 .../apache/shiro/authc/UsernamePasswordToken.java  |  369 +++++++
 .../credential/AllowAllCredentialsMatcher.java     |   43 +
 .../shiro/authc/credential/CredentialsMatcher.java |   54 ++
 .../authc/credential/DefaultPasswordService.java   |  200 ++++
 .../authc/credential/HashedCredentialsMatcher.java |  459 +++++++++
 .../authc/credential/HashingPasswordService.java   |   91 ++
 .../authc/credential/Md2CredentialsMatcher.java    |   47 +
 .../authc/credential/Md5CredentialsMatcher.java    |   46 +
 .../shiro/authc/credential/PasswordMatcher.java    |  110 +++
 .../shiro/authc/credential/PasswordService.java    |  147 +++
 .../authc/credential/Sha1CredentialsMatcher.java   |   46 +
 .../authc/credential/Sha256CredentialsMatcher.java |   40 +
 .../authc/credential/Sha384CredentialsMatcher.java |   40 +
 .../authc/credential/Sha512CredentialsMatcher.java |   40 +
 .../authc/credential/SimpleCredentialsMatcher.java |  131 +++
 .../shiro/authc/credential/package-info.java       |   24 +
 .../java/org/apache/shiro/authc/package-info.java  |   29 +
 .../authc/pam/AbstractAuthenticationStrategy.java  |   96 ++
 .../shiro/authc/pam/AllSuccessfulStrategy.java     |  104 ++
 .../authc/pam/AtLeastOneSuccessfulStrategy.java    |   61 ++
 .../shiro/authc/pam/AuthenticationStrategy.java    |  115 +++
 .../shiro/authc/pam/FirstSuccessfulStrategy.java   |   61 ++
 .../shiro/authc/pam/ModularRealmAuthenticator.java |  295 ++++++
 .../shiro/authc/pam/UnsupportedTokenException.java |   68 ++
 .../org/apache/shiro/authc/pam/package-info.java   |   32 +
 .../apache/shiro/authz/AuthorizationException.java |   66 ++
 .../org/apache/shiro/authz/AuthorizationInfo.java  |   92 ++
 .../java/org/apache/shiro/authz/Authorizer.java    |  278 ++++++
 .../shiro/authz/HostUnauthorizedException.java     |   84 ++
 .../apache/shiro/authz/ModularRealmAuthorizer.java |  444 +++++++++
 .../java/org/apache/shiro/authz/Permission.java    |   85 ++
 .../shiro/authz/SimpleAuthorizationInfo.java       |  177 ++++
 .../java/org/apache/shiro/authz/SimpleRole.java    |  116 +++
 .../shiro/authz/UnauthenticatedException.java      |   68 ++
 .../apache/shiro/authz/UnauthorizedException.java  |   62 ++
 .../org/apache/shiro/authz/annotation/Logical.java |   29 +
 .../authz/annotation/RequiresAuthentication.java   |   46 +
 .../shiro/authz/annotation/RequiresGuest.java      |   43 +
 .../authz/annotation/RequiresPermissions.java      |   63 ++
 .../shiro/authz/annotation/RequiresRoles.java      |   68 ++
 .../shiro/authz/annotation/RequiresUser.java       |   51 +
 .../shiro/authz/annotation/package-info.java       |   28 +
 .../AnnotationsAuthorizingMethodInterceptor.java   |  105 ++
 .../authz/aop/AuthenticatedAnnotationHandler.java  |   56 ++
 .../AuthenticatedAnnotationMethodInterceptor.java  |   49 +
 .../authz/aop/AuthorizingAnnotationHandler.java    |   55 ++
 .../AuthorizingAnnotationMethodInterceptor.java    |   94 ++
 .../authz/aop/AuthorizingMethodInterceptor.java    |   50 +
 .../shiro/authz/aop/GuestAnnotationHandler.java    |   66 ++
 .../aop/GuestAnnotationMethodInterceptor.java      |   51 +
 .../authz/aop/PermissionAnnotationHandler.java     |   90 ++
 .../aop/PermissionAnnotationMethodInterceptor.java |  107 ++
 .../shiro/authz/aop/RoleAnnotationHandler.java     |   76 ++
 .../authz/aop/RoleAnnotationMethodInterceptor.java |   48 +
 .../shiro/authz/aop/UserAnnotationHandler.java     |   66 ++
 .../authz/aop/UserAnnotationMethodInterceptor.java |   54 ++
 .../org/apache/shiro/authz/aop/package-info.java   |   23 +
 .../java/org/apache/shiro/authz/package-info.java  |   38 +
 .../shiro/authz/permission/AllPermission.java      |   47 +
 .../shiro/authz/permission/DomainPermission.java   |  141 +++
 .../InvalidPermissionStringException.java          |   57 ++
 .../shiro/authz/permission/PermissionResolver.java |   62 ++
 .../authz/permission/PermissionResolverAware.java  |   40 +
 .../authz/permission/RolePermissionResolver.java   |   43 +
 .../permission/RolePermissionResolverAware.java    |   40 +
 .../shiro/authz/permission/WildcardPermission.java |  253 +++++
 .../permission/WildcardPermissionResolver.java     |   43 +
 .../shiro/authz/permission/package-info.java       |   27 +
 .../apache/shiro/cache/AbstractCacheManager.java   |  120 +++
 .../main/java/org/apache/shiro/cache/Cache.java    |   92 ++
 .../org/apache/shiro/cache/CacheException.java     |   66 ++
 .../java/org/apache/shiro/cache/CacheManager.java  |   42 +
 .../org/apache/shiro/cache/CacheManagerAware.java  |   38 +
 .../main/java/org/apache/shiro/cache/MapCache.java |  100 ++
 .../shiro/cache/MemoryConstrainedCacheManager.java |   48 +
 .../java/org/apache/shiro/cache/package-info.java  |   22 +
 .../main/java/org/apache/shiro/codec/Base64.java   |  506 ++++++++++
 .../org/apache/shiro/codec/CodecException.java     |   66 ++
 .../java/org/apache/shiro/codec/CodecSupport.java  |  321 ++++++
 core/src/main/java/org/apache/shiro/codec/H64.java |  129 +++
 core/src/main/java/org/apache/shiro/codec/Hex.java |  162 ++++
 .../java/org/apache/shiro/codec/package-info.java  |   23 +
 .../shiro/concurrent/SubjectAwareExecutor.java     |  131 +++
 .../concurrent/SubjectAwareExecutorService.java    |  158 +++
 .../SubjectAwareScheduledExecutorService.java      |   86 ++
 .../org/apache/shiro/concurrent/package-info.java  |   28 +
 .../shiro/config/ConfigurationException.java       |   66 ++
 .../src/main/java/org/apache/shiro/config/Ini.java |  649 +++++++++++++
 .../org/apache/shiro/config/IniFactorySupport.java |  137 +++
 .../shiro/config/IniSecurityManagerFactory.java    |  252 +++++
 .../org/apache/shiro/config/ReflectionBuilder.java |  565 +++++++++++
 .../apache/shiro/config/ResourceConfigurable.java  |   42 +
 .../config/UnresolveableReferenceException.java    |   63 ++
 .../java/org/apache/shiro/config/package-info.java |   22 +
 .../crypto/AbstractSymmetricCipherService.java     |   65 ++
 .../org/apache/shiro/crypto/AesCipherService.java  |   90 ++
 .../apache/shiro/crypto/BlowfishCipherService.java |   92 ++
 .../org/apache/shiro/crypto/CipherService.java     |  175 ++++
 .../org/apache/shiro/crypto/CryptoException.java   |   41 +
 .../shiro/crypto/DefaultBlockCipherService.java    |  531 ++++++++++
 .../org/apache/shiro/crypto/JcaCipherService.java  |  602 ++++++++++++
 .../org/apache/shiro/crypto/OperationMode.java     |  143 +++
 .../org/apache/shiro/crypto/PaddingScheme.java     |  165 ++++
 .../apache/shiro/crypto/RandomNumberGenerator.java |   72 ++
 .../shiro/crypto/SecureRandomNumberGenerator.java  |  120 +++
 .../shiro/crypto/UnknownAlgorithmException.java    |   40 +
 .../org/apache/shiro/crypto/hash/AbstractHash.java |  361 +++++++
 .../shiro/crypto/hash/ConfigurableHashService.java |   61 ++
 .../shiro/crypto/hash/DefaultHashService.java      |  344 +++++++
 .../java/org/apache/shiro/crypto/hash/Hash.java    |   67 ++
 .../org/apache/shiro/crypto/hash/HashRequest.java  |  225 +++++
 .../org/apache/shiro/crypto/hash/HashService.java  |   77 ++
 .../java/org/apache/shiro/crypto/hash/Md2Hash.java |   65 ++
 .../java/org/apache/shiro/crypto/hash/Md5Hash.java |   66 ++
 .../org/apache/shiro/crypto/hash/Sha1Hash.java     |   67 ++
 .../org/apache/shiro/crypto/hash/Sha256Hash.java   |   68 ++
 .../org/apache/shiro/crypto/hash/Sha384Hash.java   |   69 ++
 .../org/apache/shiro/crypto/hash/Sha512Hash.java   |   69 ++
 .../org/apache/shiro/crypto/hash/SimpleHash.java   |  431 +++++++++
 .../shiro/crypto/hash/SimpleHashRequest.java       |   74 ++
 .../shiro/crypto/hash/format/Base64Format.java     |   41 +
 .../hash/format/DefaultHashFormatFactory.java      |  354 +++++++
 .../shiro/crypto/hash/format/HashFormat.java       |   45 +
 .../crypto/hash/format/HashFormatFactory.java      |   27 +
 .../apache/shiro/crypto/hash/format/HexFormat.java |   41 +
 .../crypto/hash/format/ModularCryptFormat.java     |   42 +
 .../crypto/hash/format/ParsableHashFormat.java     |   43 +
 .../crypto/hash/format/ProvidedHashFormat.java     |   64 ++
 .../crypto/hash/format/Shiro1CryptFormat.java      |  166 ++++
 .../org/apache/shiro/crypto/hash/package-info.java |   25 +
 .../java/org/apache/shiro/crypto/package-info.java |   26 +
 .../org/apache/shiro/dao/DataAccessException.java  |   51 +
 .../shiro/dao/InvalidResourceUsageException.java   |   47 +
 .../java/org/apache/shiro/dao/package-info.java    |   26 +
 .../org/apache/shiro/env/DefaultEnvironment.java   |  170 ++++
 .../java/org/apache/shiro/env/Environment.java     |   44 +
 .../org/apache/shiro/env/EnvironmentException.java |   37 +
 .../apache/shiro/env/NamedObjectEnvironment.java   |   40 +
 .../apache/shiro/env/RequiredTypeException.java    |   36 +
 .../java/org/apache/shiro/env/package-info.java    |   25 +
 .../java/org/apache/shiro/functor/Translator.java  |   45 +
 .../org/apache/shiro/functor/package-info.java     |   30 +
 .../shiro/io/ClassResolvingObjectInputStream.java  |   58 ++
 .../org/apache/shiro/io/DefaultSerializer.java     |   85 ++
 .../java/org/apache/shiro/io/ResourceUtils.java    |  183 ++++
 .../apache/shiro/io/SerializationException.java    |   66 ++
 .../main/java/org/apache/shiro/io/Serializer.java  |   53 +
 .../java/org/apache/shiro/io/XmlSerializer.java    |   79 ++
 .../java/org/apache/shiro/io/package-info.java     |   22 +
 .../java/org/apache/shiro/jndi/JndiCallback.java   |   54 ++
 .../java/org/apache/shiro/jndi/JndiLocator.java    |  179 ++++
 .../org/apache/shiro/jndi/JndiObjectFactory.java   |   63 ++
 .../java/org/apache/shiro/jndi/JndiTemplate.java   |  224 +++++
 .../java/org/apache/shiro/jndi/package-info.java   |   22 +
 ...nsupportedAuthenticationMechanismException.java |   39 +
 .../java/org/apache/shiro/ldap/package-info.java   |   26 +
 .../shiro/mgt/AbstractRememberMeManager.java       |  539 +++++++++++
 .../shiro/mgt/AuthenticatingSecurityManager.java   |  114 +++
 .../shiro/mgt/AuthorizingSecurityManager.java      |  175 ++++
 .../apache/shiro/mgt/CachingSecurityManager.java   |   91 ++
 .../apache/shiro/mgt/DefaultSecurityManager.java   |  615 ++++++++++++
 .../shiro/mgt/DefaultSessionStorageEvaluator.java  |   96 ++
 .../org/apache/shiro/mgt/DefaultSubjectDAO.java    |  283 ++++++
 .../apache/shiro/mgt/DefaultSubjectFactory.java    |   60 ++
 .../org/apache/shiro/mgt/RealmSecurityManager.java |  136 +++
 .../org/apache/shiro/mgt/RememberMeManager.java    |   92 ++
 .../java/org/apache/shiro/mgt/SecurityManager.java |  114 +++
 .../apache/shiro/mgt/SessionStorageEvaluator.java  |   64 ++
 .../apache/shiro/mgt/SessionsSecurityManager.java  |  133 +++
 .../main/java/org/apache/shiro/mgt/SubjectDAO.java |   58 ++
 .../java/org/apache/shiro/mgt/SubjectFactory.java  |   48 +
 .../java/org/apache/shiro/mgt/package-info.java    |   23 +
 .../main/java/org/apache/shiro/package-info.java   |   24 +
 .../apache/shiro/realm/AuthenticatingRealm.java    |  708 ++++++++++++++
 .../org/apache/shiro/realm/AuthorizingRealm.java   |  663 +++++++++++++
 .../java/org/apache/shiro/realm/CachingRealm.java  |  212 ++++
 .../main/java/org/apache/shiro/realm/Realm.java    |  105 ++
 .../java/org/apache/shiro/realm/RealmFactory.java  |   53 +
 .../org/apache/shiro/realm/SimpleAccountRealm.java |  181 ++++
 .../activedirectory/ActiveDirectoryRealm.java      |  236 +++++
 .../shiro/realm/activedirectory/package-info.java  |   22 +
 .../org/apache/shiro/realm/jdbc/JdbcRealm.java     |  427 ++++++++
 .../org/apache/shiro/realm/jdbc/package-info.java  |   23 +
 .../apache/shiro/realm/jndi/JndiRealmFactory.java  |  119 +++
 .../org/apache/shiro/realm/jndi/package-info.java  |   21 +
 .../apache/shiro/realm/ldap/AbstractLdapRealm.java |  242 +++++
 .../realm/ldap/DefaultLdapContextFactory.java      |  259 +++++
 .../shiro/realm/ldap/JndiLdapContextFactory.java   |  507 ++++++++++
 .../org/apache/shiro/realm/ldap/JndiLdapRealm.java |  430 +++++++++
 .../shiro/realm/ldap/LdapContextFactory.java       |   77 ++
 .../org/apache/shiro/realm/ldap/LdapUtils.java     |   97 ++
 .../org/apache/shiro/realm/ldap/package-info.java  |   23 +
 .../java/org/apache/shiro/realm/package-info.java  |   25 +
 .../java/org/apache/shiro/realm/text/IniRealm.java |  193 ++++
 .../apache/shiro/realm/text/PropertiesRealm.java   |  352 +++++++
 .../shiro/realm/text/TextConfigurationRealm.java   |  230 +++++
 .../org/apache/shiro/realm/text/package-info.java  |   23 +
 .../shiro/session/ExpiredSessionException.java     |   64 ++
 .../shiro/session/InvalidSessionException.java     |   71 ++
 .../org/apache/shiro/session/ProxiedSession.java   |  138 +++
 .../java/org/apache/shiro/session/Session.java     |  210 ++++
 .../org/apache/shiro/session/SessionException.java |   67 ++
 .../org/apache/shiro/session/SessionListener.java  |   59 ++
 .../shiro/session/SessionListenerAdapter.java      |   55 ++
 .../shiro/session/StoppedSessionException.java     |   65 ++
 .../shiro/session/UnknownSessionException.java     |   64 ++
 .../session/mgt/AbstractNativeSessionManager.java  |  272 ++++++
 .../shiro/session/mgt/AbstractSessionManager.java  |   87 ++
 .../mgt/AbstractValidatingSessionManager.java      |  309 ++++++
 .../shiro/session/mgt/DefaultSessionContext.java   |   65 ++
 .../shiro/session/mgt/DefaultSessionKey.java       |   45 +
 .../shiro/session/mgt/DefaultSessionManager.java   |  248 +++++
 .../shiro/session/mgt/DelegatingSession.java       |  161 ++++
 .../ExecutorServiceSessionValidationScheduler.java |  113 +++
 .../shiro/session/mgt/ImmutableProxiedSession.java |  107 ++
 .../shiro/session/mgt/NativeSessionManager.java    |  181 ++++
 .../apache/shiro/session/mgt/SessionContext.java   |   91 ++
 .../apache/shiro/session/mgt/SessionFactory.java   |   42 +
 .../org/apache/shiro/session/mgt/SessionKey.java   |   46 +
 .../apache/shiro/session/mgt/SessionManager.java   |   61 ++
 .../session/mgt/SessionValidationScheduler.java    |   52 +
 .../apache/shiro/session/mgt/SimpleSession.java    |  541 +++++++++++
 .../shiro/session/mgt/SimpleSessionFactory.java    |   46 +
 .../shiro/session/mgt/ValidatingSession.java       |   39 +
 .../session/mgt/ValidatingSessionManager.java      |   72 ++
 .../shiro/session/mgt/eis/AbstractSessionDAO.java  |  185 ++++
 .../shiro/session/mgt/eis/CachingSessionDAO.java   |  350 +++++++
 .../session/mgt/eis/EnterpriseCacheSessionDAO.java |   82 ++
 .../mgt/eis/JavaUuidSessionIdGenerator.java        |   43 +
 .../shiro/session/mgt/eis/MemorySessionDAO.java    |  107 ++
 .../session/mgt/eis/RandomSessionIdGenerator.java  |   69 ++
 .../apache/shiro/session/mgt/eis/SessionDAO.java   |  130 +++
 .../shiro/session/mgt/eis/SessionIdGenerator.java  |   51 +
 .../apache/shiro/session/mgt/eis/package-info.java |   23 +
 .../org/apache/shiro/session/mgt/package-info.java |   22 +
 .../org/apache/shiro/session/package-info.java     |   36 +
 .../apache/shiro/subject/ExecutionException.java   |   41 +
 .../shiro/subject/MutablePrincipalCollection.java  |   58 ++
 .../apache/shiro/subject/PrincipalCollection.java  |  147 +++
 .../org/apache/shiro/subject/PrincipalMap.java     |   63 ++
 .../shiro/subject/SimplePrincipalCollection.java   |  301 ++++++
 .../apache/shiro/subject/SimplePrincipalMap.java   |  283 ++++++
 .../java/org/apache/shiro/subject/Subject.java     |  850 ++++++++++++++++
 .../org/apache/shiro/subject/SubjectContext.java   |  237 +++++
 .../org/apache/shiro/subject/package-info.java     |   27 +
 .../subject/support/DefaultSubjectContext.java     |  276 ++++++
 .../shiro/subject/support/DelegatingSubject.java   |  514 ++++++++++
 .../subject/support/DisabledSessionException.java  |   42 +
 .../shiro/subject/support/SubjectCallable.java     |   92 ++
 .../shiro/subject/support/SubjectRunnable.java     |  122 +++
 .../shiro/subject/support/SubjectThreadState.java  |  124 +++
 .../apache/shiro/subject/support/package-info.java |   22 +
 .../org/apache/shiro/util/AbstractFactory.java     |   61 ++
 .../java/org/apache/shiro/util/AntPathMatcher.java |  425 ++++++++
 .../java/org/apache/shiro/util/ByteSource.java     |  191 ++++
 .../java/org/apache/shiro/util/ClassUtils.java     |  260 +++++
 .../org/apache/shiro/util/CollectionUtils.java     |  130 +++
 .../java/org/apache/shiro/util/Destroyable.java    |   35 +
 .../main/java/org/apache/shiro/util/Factory.java   |   37 +
 .../java/org/apache/shiro/util/Initializable.java  |   38 +
 .../apache/shiro/util/InstantiationException.java  |   66 ++
 .../org/apache/shiro/util/JavaEnvironment.java     |  163 ++++
 .../main/java/org/apache/shiro/util/JdbcUtils.java |  115 +++
 .../java/org/apache/shiro/util/LifecycleUtils.java |  102 ++
 .../java/org/apache/shiro/util/MapContext.java     |  133 +++
 .../main/java/org/apache/shiro/util/Nameable.java  |   35 +
 .../java/org/apache/shiro/util/PatternMatcher.java |   42 +
 .../org/apache/shiro/util/PermissionUtils.java     |   57 ++
 .../org/apache/shiro/util/RegExPatternMatcher.java |   52 +
 .../org/apache/shiro/util/SimpleByteSource.java    |  188 ++++
 .../java/org/apache/shiro/util/SoftHashMap.java    |  319 ++++++
 .../java/org/apache/shiro/util/StringUtils.java    |  502 ++++++++++
 .../java/org/apache/shiro/util/ThreadContext.java  |  331 +++++++
 .../java/org/apache/shiro/util/ThreadState.java    |   84 ++
 .../util/UnavailableConstructorException.java      |   67 ++
 .../apache/shiro/util/UnknownClassException.java   |   68 ++
 .../java/org/apache/shiro/util/package-info.java   |   23 +
 .../credential/DefaultPasswordServiceTest.groovy   |  143 +++
 .../authc/credential/PasswordMatcherTest.groovy    |  172 ++++
 .../authc/pam/ModularRealmAuthenticatorTest.groovy |  212 ++++
 .../groovy/org/apache/shiro/codec/H64Test.groovy   |   40 +
 .../config/IniSecurityManagerFactoryTest.groovy    |  222 +++++
 .../shiro/config/MockPermissionResolver.groovy     |   36 +
 .../shiro/config/ReflectionBuilderTest.groovy      |  526 ++++++++++
 .../crypto/hash/DefaultHashServiceTest.groovy      |  159 +++
 .../crypto/hash/HashRequestBuilderTest.groovy      |   58 ++
 .../crypto/hash/format/Base64FormatTest.groovy     |   43 +
 .../format/DefaultHashFormatFactoryTest.groovy     |  129 +++
 .../shiro/crypto/hash/format/HexFormatTest.groovy  |   45 +
 .../hash/format/ProvidedHashFormatTest.groovy      |   40 +
 .../hash/format/Shiro1CryptFormatTest.groovy       |  158 +++
 .../crypto/hash/format/ToStringHashFormat.groovy   |   33 +
 .../apache/shiro/mgt/DefaultSubjectDAOTest.groovy  |  409 ++++++++
 .../AuthenticatingRealmIntegrationTest.groovy      |   63 ++
 .../shiro/realm/AuthenticatingRealmTest.groovy     |  310 ++++++
 .../org/apache/shiro/realm/CachingRealmTest.groovy |  160 +++
 .../shiro/realm/TestAuthenticatingRealm.groovy     |   39 +
 .../test/java/org/apache/shiro/AtUnitTestBase.java |   35 +
 .../test/java/org/apache/shiro/ExceptionTest.java  |   52 +
 .../apache/shiro/aop/AnnotationResolverTest.java   |   64 ++
 .../shiro/authc/AbstractAuthenticatorTest.java     |  155 +++
 .../shiro/authc/ConcurrentAccessExceptionTest.java |   36 +
 .../authc/ExcessiveAttemptsExceptionTest.java      |   36 +
 .../authc/ExpiredCredentialsExceptionTest.java     |   36 +
 .../authc/IncorrectCredentialsExceptionTest.java   |   36 +
 .../shiro/authc/LockedAccountExceptionTest.java    |   36 +
 .../shiro/authc/SimpleAuthenticationInfoTest.java  |   91 ++
 .../shiro/authc/UnknownAccountExceptionTest.java   |   36 +
 .../AbstractHashedCredentialsMatcherTest.java      |   48 +
 .../credential/AllowAllCredentialsMatcherTest.java |   35 +
 .../credential/HashedCredentialsMatcherTest.java   |  120 +++
 .../credential/Md2CredentialsMatcherTest.java      |   39 +
 .../credential/Md5CredentialsMatcherTest.java      |   37 +
 .../credential/Sha1CredentialsMatcherTest.java     |   37 +
 .../credential/Sha256CredentialsMatcherTest.java   |   37 +
 .../credential/Sha384CredentialsMatcherTest.java   |   37 +
 .../credential/Sha512CredentialsMatcherTest.java   |   37 +
 .../shiro/authc/pam/AllSuccessfulStrategyTest.java |   77 ++
 .../shiro/authz/AuthorizationExceptionTest.java    |   32 +
 .../shiro/authz/HostUnauthorizedExceptionTest.java |   32 +
 .../shiro/authz/ModularRealmAuthorizerTest.java    |  115 +++
 .../shiro/authz/UnauthenticatedExceptionTest.java  |   32 +
 .../shiro/authz/UnauthorizedExceptionTest.java     |   32 +
 .../authz/aop/PermissionAnnotationHandlerTest.java |   80 ++
 .../shiro/authz/aop/RoleAnnotationHandlerTest.java |  111 +++
 .../shiro/authz/permission/AllPermissionTest.java  |   38 +
 .../authz/permission/DomainPermissionTest.java     |  266 +++++
 .../authz/permission/WildcardPermissionTest.java   |  199 ++++
 .../SubjectAwareExecutorServiceTest.java           |   75 ++
 .../shiro/concurrent/SubjectAwareExecutorTest.java |   55 ++
 .../org/apache/shiro/config/CompositeBean.java     |  140 +++
 .../apache/shiro/config/HashMapCacheManager.java   |   48 +
 .../test/java/org/apache/shiro/config/IniTest.java |  163 ++++
 .../org/apache/shiro/config/InitializableBean.java |   38 +
 .../java/org/apache/shiro/config/SimpleBean.java   |   82 ++
 .../org/apache/shiro/config/SimpleBeanFactory.java |   49 +
 .../apache/shiro/crypto/AesCipherServiceTest.java  |   79 ++
 .../shiro/crypto/BlowfishCipherServiceTest.java    |   80 ++
 .../apache/shiro/crypto/JcaCipherServiceTest.java  |   36 +
 .../crypto/SecureRandomNumberGeneratorTest.java    |   96 ++
 .../shiro/io/SerializationExceptionTest.java       |   31 +
 .../apache/shiro/jndi/JndiObjectFactoryTest.java   |   98 ++
 .../shiro/mgt/AbstractRememberMeManagerTest.java   |   77 ++
 .../shiro/mgt/AbstractSecurityManagerTest.java     |   45 +
 .../shiro/mgt/DefaultSecurityManagerTest.java      |  156 +++
 .../mgt/VMSingletonDefaultSecurityManagerTest.java |   77 ++
 .../apache/shiro/realm/AuthorizingRealmTest.java   |  255 +++++
 .../org/apache/shiro/realm/UserIdPrincipal.java    |   39 +
 .../org/apache/shiro/realm/UsernamePrincipal.java  |   39 +
 .../activedirectory/ActiveDirectoryRealmTest.java  |  148 +++
 .../org/apache/shiro/realm/jdbc/JDBCRealmTest.java |  373 +++++++
 .../realm/ldap/JndiLdapContextFactoryTest.java     |  187 ++++
 .../apache/shiro/realm/ldap/JndiLdapRealmTest.java |  174 ++++
 .../org/apache/shiro/realm/text/IniRealmTest.java  |   69 ++
 .../realm/text/TextConfigurationRealmTest.java     |  282 ++++++
 .../mgt/AbstractValidatingSessionManagerTest.java  |  138 +++
 .../session/mgt/DefaultSessionManagerTest.java     |  216 +++++
 .../shiro/session/mgt/DelegatingSessionTest.java   |   76 ++
 .../shiro/session/mgt/SimpleSessionTest.java       |   66 ++
 .../shiro/subject/DelegatingSubjectTest.java       |  220 +++++
 .../org/apache/shiro/test/AbstractShiroTest.java   |   96 ++
 .../shiro/test/ExampleShiroIntegrationTest.java    |   63 ++
 .../apache/shiro/test/ExampleShiroUnitTest.java    |   57 ++
 .../shiro/test/SecurityManagerTestSupport.java     |   76 ++
 .../apache/shiro/util/RegExPatternMatcherTest.java |   43 +
 .../org/apache/shiro/util/StringUtilsTest.java     |  123 +++
 core/src/test/resources/log4j.properties           |   39 +
 .../shiro/config/IniSecurityManagerFactoryTest.ini |   24 +
 ...ecurityManagerFactoryTest.propsRealm.properties |   30 +
 .../shiro/realm/text/IniRealmTest.noUsers.ini      |   21 +
 .../shiro/realm/text/IniRealmTest.simple.ini       |   25 +
 core/src/test/resources/test.shiro.ini             |   21 +
 pom.xml                                            | 1018 ++++++++++++++++++++
 samples/aspectj/pom.xml                            |   86 ++
 .../apache/shiro/samples/aspectj/bank/Account.java |  186 ++++
 .../aspectj/bank/AccountNotFoundException.java     |   27 +
 .../samples/aspectj/bank/AccountTransaction.java   |  142 +++
 .../samples/aspectj/bank/BankServerRunner.java     |   58 ++
 .../shiro/samples/aspectj/bank/BankService.java    |   83 ++
 .../samples/aspectj/bank/BankServiceException.java |   31 +
 .../aspectj/bank/InactiveAccountException.java     |   27 +
 .../aspectj/bank/NotEnoughFundsException.java      |   27 +
 .../samples/aspectj/bank/SecureBankService.java    |  304 ++++++
 .../aspectj/bank/SecureBankServiceTest.java        |  238 +++++
 .../aspectj/src/test/resources/META-INF/aop.xml    |   32 +
 .../aspectj/src/test/resources/log4j.properties    |   28 +
 .../src/test/resources/shiroBankServiceTest.ini    |   42 +
 samples/pom.xml                                    |   71 ++
 samples/quickstart/pom.xml                         |   69 ++
 samples/quickstart/src/main/java/Quickstart.java   |  125 +++
 .../quickstart/src/main/resources/log4j.properties |   36 +
 samples/quickstart/src/main/resources/shiro.ini    |   58 ++
 samples/spring-client/pom.xml                      |  128 +++
 .../apache/shiro/samples/spring/SampleManager.java |   64 ++
 .../shiro/samples/spring/ui/WebStartDriver.java    |   38 +
 .../shiro/samples/spring/ui/WebStartView.java      |  158 +++
 .../src/main/jnlp/resources/jsecurity-sample.jks   |  Bin 0 -> 1250 bytes
 samples/spring-client/src/main/jnlp/template.vm    |   53 +
 samples/spring-client/src/main/resources/logo.png  |  Bin 0 -> 10883 bytes
 .../src/main/resources/webstart.spring.xml         |   49 +
 samples/spring-hibernate/pom.xml                   |  151 +++
 .../samples/sprhib/dao/BootstrapDataPopulator.java |   64 ++
 .../shiro/samples/sprhib/dao/HibernateDao.java     |   43 +
 .../shiro/samples/sprhib/dao/HibernateUserDAO.java |   61 ++
 .../apache/shiro/samples/sprhib/dao/UserDAO.java   |   41 +
 .../apache/shiro/samples/sprhib/model/Role.java    |   97 ++
 .../apache/shiro/samples/sprhib/model/User.java    |  114 +++
 .../shiro/samples/sprhib/security/SampleRealm.java |   84 ++
 .../samples/sprhib/service/DefaultUserService.java |   79 ++
 .../shiro/samples/sprhib/service/UserService.java  |   41 +
 .../samples/sprhib/web/CurrentUserInterceptor.java |   54 ++
 .../shiro/samples/sprhib/web/EditUserCommand.java  |   76 ++
 .../samples/sprhib/web/EditUserValidator.java      |   48 +
 .../shiro/samples/sprhib/web/HomeController.java   |   45 +
 .../shiro/samples/sprhib/web/LoginCommand.java     |   55 ++
 .../shiro/samples/sprhib/web/LoginValidator.java   |   37 +
 .../samples/sprhib/web/ManageUsersController.java  |   91 ++
 .../samples/sprhib/web/SecurityController.java     |   73 ++
 .../shiro/samples/sprhib/web/SignupCommand.java    |   55 ++
 .../shiro/samples/sprhib/web/SignupController.java |   69 ++
 .../shiro/samples/sprhib/web/SignupValidator.java  |   48 +
 .../src/main/resources/ehcache.xml                 |   56 ++
 .../src/main/resources/hibernate.cfg.xml           |   35 +
 .../src/main/resources/log4j.properties            |   39 +
 .../src/main/webapp/WEB-INF/applicationContext.xml |  133 +++
 .../src/main/webapp/WEB-INF/jsp/editUser.jsp       |   52 +
 .../src/main/webapp/WEB-INF/jsp/home.jsp           |   55 ++
 .../src/main/webapp/WEB-INF/jsp/login.jsp          |   56 ++
 .../src/main/webapp/WEB-INF/jsp/manageUsers.jsp    |   57 ++
 .../src/main/webapp/WEB-INF/jsp/signup.jsp         |   49 +
 .../src/main/webapp/WEB-INF/sprhib-servlet.xml     |   55 ++
 .../src/main/webapp/WEB-INF/web.xml                |  104 ++
 samples/spring-hibernate/src/main/webapp/index.jsp |   22 +
 .../src/main/webapp/styles/sample.css              |   42 +
 .../src/main/webapp/unauthorized.jsp               |   29 +
 samples/spring/pom.xml                             |  182 ++++
 .../samples/spring/BootstrapDataPopulator.java     |  120 +++
 .../shiro/samples/spring/DefaultSampleManager.java |  105 ++
 .../samples/spring/realm/SaltAwareJdbcRealm.java   |  121 +++
 .../shiro/samples/spring/web/IndexController.java  |  105 ++
 .../shiro/samples/spring/web/JnlpController.java   |   97 ++
 .../shiro/samples/spring/web/LoginCommand.java     |   69 ++
 .../shiro/samples/spring/web/LoginController.java  |   61 ++
 .../shiro/samples/spring/web/LogoutController.java |   40 +
 .../samples/spring/web/SessionValueCommand.java    |   57 ++
 samples/spring/src/main/resources/ehcache.xml      |   98 ++
 .../spring/src/main/resources/jsecurity-sample.jks |  Bin 0 -> 1250 bytes
 samples/spring/src/main/resources/log4j.properties |   36 +
 .../src/main/webapp/WEB-INF/applicationContext.xml |  137 +++
 .../src/main/webapp/WEB-INF/remoting-servlet.xml   |   33 +
 .../src/main/webapp/WEB-INF/resources/include.jsp  |   24 +
 .../src/main/webapp/WEB-INF/resources/login.jsp    |   51 +
 .../main/webapp/WEB-INF/resources/sampleIndex.jsp  |   85 ++
 .../src/main/webapp/WEB-INF/sample-servlet.xml     |   70 ++
 samples/spring/src/main/webapp/WEB-INF/web.xml     |  102 ++
 samples/spring/src/main/webapp/index.jsp           |   29 +
 samples/spring/src/main/webapp/logo.png            |  Bin 0 -> 10883 bytes
 samples/spring/src/main/webapp/shiro.css           |   48 +
 samples/standalone/src/main/java/MyRealm.java      |   78 ++
 samples/standalone/src/main/java/Standalone.java   |   48 +
 .../standalone/src/main/resources/log4j.properties |   37 +
 samples/standalone/src/main/resources/shiro.ini    |   34 +
 samples/web/pom.xml                                |  121 +++
 samples/web/src/main/resources/log4j.properties    |   49 +
 samples/web/src/main/webapp/WEB-INF/shiro.ini      |   48 +
 samples/web/src/main/webapp/WEB-INF/web.xml        |   42 +
 samples/web/src/main/webapp/account/index.jsp      |   36 +
 samples/web/src/main/webapp/home.jsp               |   69 ++
 samples/web/src/main/webapp/include.jsp            |   22 +
 samples/web/src/main/webapp/index.jsp              |   21 +
 samples/web/src/main/webapp/login.jsp              |  110 +++
 samples/web/src/main/webapp/style.css              |   47 +
 .../apache/shiro/test/AbstractContainerTest.java   |   88 ++
 .../shiro/test/ContainerIntegrationTest.java       |   81 ++
 shiro.doap.rdf                                     |   65 ++
 support/aspectj/pom.xml                            |  102 ++
 .../aspectj/ShiroAnnotationAuthorizingAspect.java  |   54 ++
 ...ctjAnnotationsAuthorizingMethodInterceptor.java |   63 ++
 .../BeforeAdviceMethodInvocationAdapter.java       |  107 ++
 .../org/apache/shiro/aspectj/package-info.java     |   26 +
 .../org/apache/shiro/aspectj/DummyService.java     |   39 +
 .../org/apache/shiro/aspectj/DummyServiceTest.java |  193 ++++
 .../shiro/aspectj/RestrictedDummyService.java      |   36 +
 .../apache/shiro/aspectj/SecuredDummyService.java  |   69 ++
 .../aspectj/src/test/resources/META-INF/aop.xml    |   28 +
 .../src/test/resources/shiroDummyServiceTest.ini   |   39 +
 support/cas/pom.xml                                |   89 ++
 .../shiro/cas/CasAuthenticationException.java      |   43 +
 .../main/java/org/apache/shiro/cas/CasFilter.java  |  150 +++
 .../main/java/org/apache/shiro/cas/CasRealm.java   |  310 ++++++
 .../org/apache/shiro/cas/CasSubjectFactory.java    |   56 ++
 .../main/java/org/apache/shiro/cas/CasToken.java   |   64 ++
 .../org/apache/shiro/cas/CasRealmTest.groovy       |  173 ++++
 .../org/apache/shiro/cas/CasTokenTest.groovy       |   46 +
 .../shiro/cas/MockServiceTicketValidator.groovy    |   57 ++
 support/ehcache/pom.xml                            |   79 ++
 .../org/apache/shiro/cache/ehcache/EhCache.java    |  241 +++++
 .../apache/shiro/cache/ehcache/EhCacheManager.java |  249 +++++
 .../apache/shiro/cache/ehcache/package-info.java   |   23 +
 .../org/apache/shiro/cache/ehcache/ehcache.xml     |   99 ++
 .../shiro/cache/ehcache/EhCacheManagerTest.java    |   78 ++
 .../ehcache/src/test/resources/log4j.properties    |   37 +
 support/features/pom.xml                           |   98 ++
 support/features/src/main/resources/features.xml   |   69 ++
 support/guice/pom.xml                              |   99 ++
 .../org/apache/shiro/guice/BeanTypeListener.java   |  140 +++
 .../shiro/guice/DestroyableInjectionListener.java  |   48 +
 .../org/apache/shiro/guice/GuiceEnvironment.java   |   36 +
 .../guice/InitializableInjectionListener.java      |   38 +
 .../apache/shiro/guice/LifecycleTypeListener.java  |   51 +
 .../java/org/apache/shiro/guice/ShiroMatchers.java |   41 +
 .../java/org/apache/shiro/guice/ShiroModule.java   |  169 ++++
 .../org/apache/shiro/guice/ShiroSessionScope.java  |   64 ++
 .../aop/AopAllianceMethodInterceptorAdapter.java   |   43 +
 .../aop/AopAllianceMethodInvocationAdapter.java    |   55 ++
 .../org/apache/shiro/guice/aop/ShiroAopModule.java |   69 ++
 .../shiro/guice/web/AbstractInjectionProvider.java |   83 ++
 .../guice/web/FilterChainResolverProvider.java     |   71 ++
 .../apache/shiro/guice/web/GuiceShiroFilter.java   |   38 +
 .../guice/web/PathMatchingFilterProvider.java      |   41 +
 .../org/apache/shiro/guice/web/ShiroWebModule.java |  256 +++++
 .../apache/shiro/guice/web/SimpleFilterChain.java  |   47 +
 .../shiro/guice/web/SimpleFilterChainResolver.java |   61 ++
 .../shiro/guice/web/WebGuiceEnvironment.java       |   62 ++
 .../apache/shiro/guice/BeanTypeListenerTest.java   |  125 +++
 .../guice/DestroyableInjectionListenerTest.java    |   41 +
 .../apache/shiro/guice/GuiceEnvironmentTest.java   |   46 +
 .../guice/InitializableInjectionListenerTest.java  |   40 +
 .../shiro/guice/LifecycleTypeListenerTest.java     |   73 ++
 .../org/apache/shiro/guice/ShiroMatchersTest.java  |   52 +
 .../org/apache/shiro/guice/ShiroModuleTest.java    |  230 +++++
 .../apache/shiro/guice/ShiroSessionScopeTest.java  |   84 ++
 .../AopAllianceMethodInterceptorAdapterTest.java   |   53 +
 .../AopAllianceMethodInvocationAdapterTest.java    |   92 ++
 .../apache/shiro/guice/aop/ShiroAopModuleTest.java |  213 ++++
 .../guice/web/AbstractInjectionProviderTest.java   |  152 +++
 .../apache/shiro/guice/web/DefaultFiltersTest.java |   57 ++
 .../guice/web/FilterChainResolverProviderTest.java |  101 ++
 .../apache/shiro/guice/web/FilterConfigTest.java   |   94 ++
 .../shiro/guice/web/GuiceShiroFilterTest.java      |   51 +
 .../guice/web/PathMatchingFilterProviderTest.java  |   52 +
 .../apache/shiro/guice/web/ShiroWebModuleTest.java |  162 ++++
 .../guice/web/SimpleFilterChainResolverTest.java   |  126 +++
 .../shiro/guice/web/SimpleFilterChainTest.java     |   63 ++
 .../shiro/guice/web/WebGuiceEnvironmentTest.java   |   67 ++
 support/pom.xml                                    |   45 +
 support/quartz/pom.xml                             |   79 ++
 .../mgt/quartz/QuartzSessionValidationJob.java     |   86 ++
 .../quartz/QuartzSessionValidationScheduler.java   |  238 +++++
 .../shiro/session/mgt/quartz/package-info.java     |   24 +
 support/spring/pom.xml                             |  103 ++
 .../shiro/spring/LifecycleBeanPostProcessor.java   |  137 +++
 .../shiro/spring/aop/SpringAnnotationResolver.java |   57 ++
 .../java/org/apache/shiro/spring/package-info.java |   23 +
 .../remoting/SecureRemoteInvocationExecutor.java   |  124 +++
 .../remoting/SecureRemoteInvocationFactory.java    |  136 +++
 .../apache/shiro/spring/remoting/package-info.java |   23 +
 ...nceAnnotationsAuthorizingMethodInterceptor.java |  117 +++
 .../AuthorizationAttributeSourceAdvisor.java       |  115 +++
 .../spring/security/interceptor/package-info.java  |   22 +
 .../shiro/spring/web/ShiroFilterFactoryBean.java   |  541 +++++++++++
 .../org/apache/shiro/spring/web/package-info.java  |   24 +
 .../SecureRemoteInvocationFactoryTest.java         |  118 +++
 .../AbstractAuthorizationAnnotationTest.java       |  156 +++
 .../DapcAuthorizationAnnotationTest.java           |   57 ++
 .../security/interceptor/DefaultTestService.java   |   57 ++
 .../SchemaAuthorizationAnnotationTest.java         |   37 +
 .../spring/security/interceptor/TestService.java   |   52 +
 .../org/apache/shiro/spring/web/DummyFilter.java   |   46 +
 .../spring/web/ShiroFilterFactoryBeanTest.java     |  109 +++
 support/spring/src/test/resources/log4j.properties |   38 +
 ...AbstractAuthorizationAnnotationTest-context.xml |   48 +
 .../DapcAuthorizationAnnotationTest-context.xml    |   35 +
 .../SchemaAuthorizationAnnotationTest-context.xml  |   51 +
 .../spring/web/ShiroFilterFactoryBeanTest.xml      |   55 ++
 tools/hasher/pom.xml                               |  101 ++
 tools/hasher/src/main/assembly/cli.assembly.xml    |   35 +
 .../java/org/apache/shiro/tools/hasher/Hasher.java |  447 +++++++++
 tools/pom.xml                                      |   40 +
 web/pom.xml                                        |   95 ++
 .../web/config/IniFilterChainResolverFactory.java  |  195 ++++
 .../web/config/WebIniSecurityManagerFactory.java   |   77 ++
 .../org/apache/shiro/web/config/package-info.java  |   22 +
 .../shiro/web/env/DefaultWebEnvironment.java       |   87 ++
 .../apache/shiro/web/env/EnvironmentLoader.java    |  243 +++++
 .../shiro/web/env/EnvironmentLoaderListener.java   |   70 ++
 .../apache/shiro/web/env/IniWebEnvironment.java    |  309 ++++++
 .../shiro/web/env/MutableWebEnvironment.java       |   57 ++
 .../shiro/web/env/ResourceBasedWebEnvironment.java |   49 +
 .../org/apache/shiro/web/env/WebEnvironment.java   |   57 ++
 .../org/apache/shiro/web/env/package-info.java     |   27 +
 .../shiro/web/filter/AccessControlFilter.java      |  230 +++++
 .../shiro/web/filter/PathConfigProcessor.java      |   39 +
 .../shiro/web/filter/PathMatchingFilter.java       |  258 +++++
 .../shiro/web/filter/authc/AnonymousFilter.java    |   65 ++
 .../web/filter/authc/AuthenticatingFilter.java     |  158 +++
 .../web/filter/authc/AuthenticationFilter.java     |   97 ++
 .../authc/BasicHttpAuthenticationFilter.java       |  369 +++++++
 .../web/filter/authc/FormAuthenticationFilter.java |  224 +++++
 .../shiro/web/filter/authc/LogoutFilter.java       |  146 +++
 .../filter/authc/PassThruAuthenticationFilter.java |   58 ++
 .../apache/shiro/web/filter/authc/UserFilter.java  |   69 ++
 .../shiro/web/filter/authc/package-info.java       |   23 +
 .../web/filter/authz/AuthorizationFilter.java      |  126 +++
 .../apache/shiro/web/filter/authz/HostFilter.java  |  106 ++
 .../filter/authz/HttpMethodPermissionFilter.java   |  266 +++++
 .../authz/PermissionsAuthorizationFilter.java      |   57 ++
 .../apache/shiro/web/filter/authz/PortFilter.java  |  124 +++
 .../web/filter/authz/RolesAuthorizationFilter.java |   55 ++
 .../apache/shiro/web/filter/authz/SslFilter.java   |   76 ++
 .../shiro/web/filter/authz/package-info.java       |   23 +
 .../apache/shiro/web/filter/mgt/DefaultFilter.java |   83 ++
 .../web/filter/mgt/DefaultFilterChainManager.java  |  350 +++++++
 .../shiro/web/filter/mgt/FilterChainManager.java   |  198 ++++
 .../shiro/web/filter/mgt/FilterChainResolver.java  |   54 ++
 .../shiro/web/filter/mgt/NamedFilterList.java      |   50 +
 .../mgt/PathMatchingFilterChainResolver.java       |  149 +++
 .../web/filter/mgt/SimpleNamedFilterList.java      |  174 ++++
 .../apache/shiro/web/filter/mgt/package-info.java  |   26 +
 .../org/apache/shiro/web/filter/package-info.java  |   23 +
 .../filter/session/NoSessionCreationFilter.java    |   56 ++
 .../shiro/web/mgt/CookieRememberMeManager.java     |  291 ++++++
 .../shiro/web/mgt/DefaultWebSecurityManager.java   |  253 +++++
 .../web/mgt/DefaultWebSessionStorageEvaluator.java |  100 ++
 .../shiro/web/mgt/DefaultWebSubjectFactory.java    |   78 ++
 .../apache/shiro/web/mgt/WebSecurityManager.java   |   40 +
 .../org/apache/shiro/web/mgt/package-info.java     |   23 +
 .../java/org/apache/shiro/web/package-info.java    |   22 +
 .../apache/shiro/web/servlet/AbstractFilter.java   |  131 +++
 .../shiro/web/servlet/AbstractShiroFilter.java     |  451 +++++++++
 .../org/apache/shiro/web/servlet/AdviceFilter.java |  200 ++++
 .../java/org/apache/shiro/web/servlet/Cookie.java  |   91 ++
 .../apache/shiro/web/servlet/IniShiroFilter.java   |  356 +++++++
 .../apache/shiro/web/servlet/NameableFilter.java   |   95 ++
 .../shiro/web/servlet/OncePerRequestFilter.java    |  212 ++++
 .../shiro/web/servlet/ProxiedFilterChain.java      |   69 ++
 .../shiro/web/servlet/ServletContextSupport.java   |   93 ++
 .../org/apache/shiro/web/servlet/ShiroFilter.java  |   82 ++
 .../shiro/web/servlet/ShiroHttpServletRequest.java |  253 +++++
 .../web/servlet/ShiroHttpServletResponse.java      |  326 +++++++
 .../apache/shiro/web/servlet/ShiroHttpSession.java |  244 +++++
 .../org/apache/shiro/web/servlet/SimpleCookie.java |  394 ++++++++
 .../org/apache/shiro/web/servlet/package-info.java |   24 +
 .../shiro/web/session/HttpServletSession.java      |  170 ++++
 .../web/session/mgt/DefaultWebSessionContext.java  |   67 ++
 .../web/session/mgt/DefaultWebSessionManager.java  |  316 ++++++
 .../mgt/ServletContainerSessionManager.java        |  132 +++
 .../shiro/web/session/mgt/WebSessionContext.java   |   69 ++
 .../shiro/web/session/mgt/WebSessionKey.java       |   60 ++
 .../shiro/web/session/mgt/WebSessionManager.java   |   45 +
 .../org/apache/shiro/web/session/package-info.java |   22 +
 .../org/apache/shiro/web/subject/WebSubject.java   |  159 +++
 .../shiro/web/subject/WebSubjectContext.java       |   72 ++
 .../org/apache/shiro/web/subject/package-info.java |   25 +
 .../subject/support/DefaultWebSubjectContext.java  |  110 +++
 .../web/subject/support/WebDelegatingSubject.java  |  103 ++
 .../shiro/web/subject/support/package-info.java    |   24 +
 .../apache/shiro/web/tags/AuthenticatedTag.java    |   60 ++
 .../java/org/apache/shiro/web/tags/GuestTag.java   |   59 ++
 .../org/apache/shiro/web/tags/HasAnyRolesTag.java  |   61 ++
 .../apache/shiro/web/tags/HasPermissionTag.java    |   35 +
 .../java/org/apache/shiro/web/tags/HasRoleTag.java |   35 +
 .../apache/shiro/web/tags/LacksPermissionTag.java  |   35 +
 .../org/apache/shiro/web/tags/LacksRoleTag.java    |   36 +
 .../apache/shiro/web/tags/NotAuthenticatedTag.java |   55 ++
 .../org/apache/shiro/web/tags/PermissionTag.java   |   71 ++
 .../org/apache/shiro/web/tags/PrincipalTag.java    |  205 ++++
 .../java/org/apache/shiro/web/tags/RoleTag.java    |   55 ++
 .../java/org/apache/shiro/web/tags/SecureTag.java  |   57 ++
 .../java/org/apache/shiro/web/tags/UserTag.java    |   63 ++
 .../org/apache/shiro/web/tags/package-info.java    |   25 +
 .../org/apache/shiro/web/util/RedirectView.java    |  308 ++++++
 .../apache/shiro/web/util/RequestPairSource.java   |   45 +
 .../org/apache/shiro/web/util/SavedRequest.java    |   68 ++
 .../java/org/apache/shiro/web/util/WebUtils.java   |  674 +++++++++++++
 web/src/main/resources/META-INF/shiro.tld          |  166 ++++
 .../IniFilterChainResolverFactoryTest.groovy       |  160 +++
 .../shiro/web/env/IniWebEnvironmentTest.groovy     |   51 +
 .../mgt/DefaultFilterChainManagerTest.groovy       |  323 +++++++
 .../session/NoSessionCreationFilterTest.groovy     |   47 +
 .../DefaultWebSessionStorageEvaluatorTest.groovy   |  144 +++
 .../web/servlet/AbstractShiroFilterTest.groovy     |   87 ++
 .../shiro/web/servlet/IniShiroFilterTest.groovy    |  134 +++
 .../shiro/web/servlet/ShiroFilterTest.groovy       |   61 ++
 .../mgt/DefaultWebSessionManagerTest.groovy        |  316 ++++++
 .../mgt/ServletContainerSessionManagerTest.groovy  |  175 ++++
 .../test/java/org/apache/shiro/web/WebTest.java    |   40 +
 .../config/WebIniSecurityManagerFactoryTest.java   |   66 ++
 .../shiro/web/filter/PathMatchingFilterTest.java   |  125 +++
 .../web/filter/authc/AnonymousFilterTest.java      |   38 +
 .../authc/BasicHttpFilterAuthenticationTest.java   |  144 +++
 .../web/filter/authz/AuthorizationFilterTest.java  |   99 ++
 .../shiro/web/filter/authz/HostFilterTest.java     |   81 ++
 .../authz/HttpMethodPermissionFilterTest.java      |   48 +
 .../shiro/web/filter/authz/PortFilterTest.java     |   88 ++
 .../mgt/PathMatchingFilterChainResolverTest.java   |  166 ++++
 .../web/filter/mgt/SimpleNamedFilterListTest.java  |  154 +++
 .../web/mgt/AbstractWebSecurityManagerTest.java    |   33 +
 .../shiro/web/mgt/CookieRememberMeManagerTest.java |  208 ++++
 .../web/mgt/DefaultWebSecurityManagerTest.java     |  240 +++++
 .../web/servlet/OncePerRequestFilterTest.java      |  104 ++
 .../apache/shiro/web/servlet/SimpleCookieTest.java |  141 +++
 web/src/test/resources/IniShiroFilterTest.ini      |   21 +
 web/src/test/resources/log4j.properties            |   38 +
 744 files changed, 82379 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..753842b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..eea43c6
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,15 @@
+Apache Shiro
+Copyright 2008-2012 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+The implementation for org.apache.shiro.util.SoftHashMap is based 
+on initial ideas from Dr. Heinz Kabutz's publicly posted version 
+available at http://www.javaspecialists.eu/archive/Issue015.html,
+with continued modifications.  
+
+Certain parts (StringUtils etc.) of the source code for this 
+product was copied for simplicity and to reduce dependencies 
+from the source code developed by the Spring Framework Project 
+(http://www.springframework.org).
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..422b664
--- /dev/null
+++ b/README
@@ -0,0 +1,18 @@
+This distribution includes cryptographic software.  The country in
+which you currently reside may have restrictions on the import,
+possession, use, and/or re-export to another country, of
+encryption software.  BEFORE using any encryption software, please
+check your country's laws, regulations and policies concerning the
+import, possession, or use, and re-export of encryption software, to
+see if this is permitted.  See http://www.wassenaar.org for more
+information.
+
+The U.S. Government Department of Commerce, Bureau of Industry and
+Security (BIS), has classified this software as Export Commodity
+Control Number (ECCN) 5D002.C.1, which includes information security
+software using or performing cryptographic functions with asymmetric
+algorithms.  The form and manner of this Apache Software Foundation
+distribution makes it eligible for export under the License Exception
+ENC Technology Software Unrestricted (TSU) exception (see the BIS
+Export Administration Regulations, Section 740.13) for both object
+code and source code.
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
new file mode 100644
index 0000000..fb1f640
--- /dev/null
+++ b/RELEASE-NOTES
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+This is not an official release notes document.  It exists for Shiro developers
+to jot down their notes while working in the source code.  These notes will be
+combined with Jira's auto-generated release notes during a release for the
+total set.
+
+###########################################################
+# 1.2.0
+###########################################################
+
+Backwards Incompatible Changes
+--------------------------------
+- The following org.apache.shiro.mgt.DefaultSecurityManager methods have been removed:
+  bindPrincipalsToSession(principals, context)
+
+  This logic has been moved into a SubjectDAO concept to allow end-users to control
+  exactly how the Session may be used for subject state persistence.  This allows a
+  single point of control rather than needing to configure Shiro in multiple places.
+
+  If you overrode this method in Shiro 1.0 or 1.1, please look at the new
+  org.apache.shiro.mgt.DefaultSubjectDAO implementation, which performs compatible logic.
+  Documentation for this is covered here:
+  http://shiro.apache.org/session-management.html#SessionManagement-SessionsandSubjectState
+
+- The org.apache.shiro.web.session.mgt.ServletContainerSessionManager implementation
+  (enabled by default for all web applications) no longer subclasses
+  org.apache.shiro.session.mgt.AbstractSessionManager.  AbstractSessionManager existed
+  originally to consolidate a 'globalSessionTimeout' configuration property for
+  subclasses.  However, the ServletContainerSessionManager has been changed to always
+  reflect the session configuration from web.xml (per its namesake).  Because web.xml
+  is the definitive source for session timeout configuration, the 'extends' clause
+  was removed to avoid configuration confusion: if someone attempted to configure
+  'globalSessionTimeout' on a ServletContainerSessionManager instance, it would never
+  be honored.  It was better to remove the extends clause to ensure that any
+  such configuration would fail fast when Shiro starts up to reflect the invalid config.
+
+
+Potential Breaking Changes
+--------------------------------
+- The org.apache.shiro.web.filter.mgt.FilterChainManager class's
+  addFilter(String name, Filter filter) semantics have changed.  It now no longer
+  attempts to initialize a filter by default before adding the filter to the chain.
+  If you ever called this method, you can call the
+  addFilter(name, filter, true) method to achieve the <= 1.1 behavior.
+
+- The org.apache.shiro.crypto.SecureRandomNumberGenerator previously defaulted to generating
+  128 random _bytes_ each time the nextBytes() method was called.  This is too large for most purposes, so the
+  default has been changed to 16 _bytes_ (which equals 128 bits - what was originally intended).  If for some reason
+  you need more than 16 bytes (128 bits) of randomly generated bits, you will need to configure the
+  'defaultNextByteSize' property to match your desired size (in bytes, NOT bits).
+
+- Shiro's Block Cipher Services (AesCipherService, BlowfishCipherService) have had the following changes:
+
+  1) The internal Cipher Mode and Streaming Cipher Mode have been changed from CFB to the new default of CBC.
+     CBC is more commonly used for block ciphers today (e.g. SSL).
+     If you were using an AES or Blowfish CipherService you will want to revert to the previous defaults in your config
+     to ensure you can still decrypt previously encrypted data.  For example, in code:
+
+     blockCipherService.setMode(OperationMode.CFB);
+     blockCipherService.setStreamingMode(OperationMode.CFB);
+
+     or, in shiro.ini:
+
+     blockCipherService.modeName = CFB
+     blockCipherService.streamingModeName = CFB
+
+  2) The internal Streaming Padding Scheme has been changed from NONE to PKCS5 as PKCS5 is more commonly used.
+     If you were using an AES or Blowfish CipherService for streaming operations, you will want to revert to the
+     previous padding scheme default to ensure you can still decrypt previously encrypted data.  For example, in code:
+
+     blockCipherService.setStreamingPaddingScheme(PaddingScheme.NONE);
+
+     or, in shiro.ini:
+
+     blockCipherService.streamingPaddingSchemeName = NoPadding
+
+     Note the difference in code vs shiro.ini in this last example: 'NoPadding' is the correct text value, 'NONE' is
+     the correct Enum value.
+
+###########################################################
+# 1.1.0
+###########################################################
+
+Backwards Incompatible Changes
+--------------------------------
+- The org.apache.shiro.web.util.RedirectView class's
+  appendQueryProperties(StringBuffer targetUrl, Map model, String encodingScheme)
+  method has been changed to accept a StringBuilder argument instead of a
+  StringBuffer per SHIRO-191.  RedirectView is considered an internal
+  implementation support class and Shiro end-users should not be affected by this.
diff --git a/all/pom.xml b/all/pom.xml
new file mode 100644
index 0000000..f8768f9
--- /dev/null
+++ b/all/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-all</artifactId>
+    <name>Apache Shiro :: Jar Bundle</name>
+    <description>Creates a bundled Shiro .jar from the module jars</description>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-ehcache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-guice</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>1.3.1</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    <include>${project.groupId}:shiro-core</include>
+                                    <include>${project.groupId}:shiro-web</include>
+                                    <include>${project.groupId}:shiro-ehcache</include>
+                                    <include>${project.groupId}:shiro-quartz</include>
+                                    <include>${project.groupId}:shiro-spring</include>
+                                </includes>
+                            </artifactSet>
+                            <!-- <transformers>
+                                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
+                                    <projectName>Apache ActiveMQ</projectName>
+                                </transformer>
+                            </transformers> -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- <plugin>
+                <groupId>org.apache.geronimo.genesis.plugins</groupId>
+                <artifactId>tools-maven-plugin</artifactId>
+
+                <executions>
+                    <execution>
+                        <id>verify-legal-files</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>verify-legal-files</goal>
+                        </goals>
+                        <configuration>
+                            <strict>false</strict>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin> -->
+
+        </plugins>
+    </build>
+    <reporting>
+        <plugins>
+            <plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+</project>
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..01656cd
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-core</artifactId>
+    <name>Apache Shiro :: Core</name>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <!-- collect the test classes so they can be referenced by other modules -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.core</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.apache.commons.beanutils*;resolution:=optional,
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+        <!-- For Java 1.4 and below only: - ->
+        <dependency>
+            <groupId>backport-util-concurrent</groupId>
+            <artifactId>backport-util-concurrent</artifactId>
+        </dependency>
+        -->
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.mail</groupId>
+                    <artifactId>mail</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- JDBC Realm tests: -->
+        <dependency>
+            <groupId>hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/core/src/main/java/org/apache/shiro/SecurityUtils.java b/core/src/main/java/org/apache/shiro/SecurityUtils.java
new file mode 100644
index 0000000..d78ab07
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/SecurityUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+
+
+/**
+ * Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment.
+ *
+ * @since 0.2
+ */
+public abstract class SecurityUtils {
+
+    /**
+     * ONLY used as a 'backup' in VM Singleton environments (that is, standalone environments), since the
+     * ThreadContext should always be the primary source for Subject instances when possible.
+     */
+    private static SecurityManager securityManager;
+
+    /**
+     * Returns the currently accessible {@code Subject} available to the calling code depending on
+     * runtime environment.
+     * <p/>
+     * This method is provided as a way of obtaining a {@code Subject} without having to resort to
+     * implementation-specific methods.  It also allows the Shiro team to change the underlying implementation of
+     * this method in the future depending on requirements/updates without affecting your code that uses it.
+     *
+     * @return the currently accessible {@code Subject} accessible to the calling code.
+     * @throws IllegalStateException if no {@link Subject Subject} instance or
+     *                               {@link SecurityManager SecurityManager} instance is available with which to obtain
+     *                               a {@code Subject}, which which is considered an invalid application configuration
+     *                               - a Subject should <em>always</em> be available to the caller.
+     */
+    public static Subject getSubject() {
+        Subject subject = ThreadContext.getSubject();
+        if (subject == null) {
+            subject = (new Subject.Builder()).buildSubject();
+            ThreadContext.bind(subject);
+        }
+        return subject;
+    }
+
+    /**
+     * Sets a VM (static) singleton SecurityManager, specifically for transparent use in the
+     * {@link #getSubject() getSubject()} implementation.
+     * <p/>
+     * <b>This method call exists mainly for framework development support.  Application developers should rarely,
+     * if ever, need to call this method.</b>
+     * <p/>
+     * The Shiro development team prefers that SecurityManager instances are non-static application singletons
+     * and <em>not</em> VM static singletons.  Application singletons that do not use static memory require some sort
+     * of application configuration framework to maintain the application-wide SecurityManager instance for you
+     * (for example, Spring or EJB3 environments) such that the object reference does not need to be static.
+     * <p/>
+     * In these environments, Shiro acquires Subject data based on the currently executing Thread via its own
+     * framework integration code, and this is the preferred way to use Shiro.
+     * <p/>
+     * However in some environments, such as a standalone desktop application or Applets that do not use Spring or
+     * EJB or similar config frameworks, a VM-singleton might make more sense (although the former is still preferred).
+     * In these environments, setting the SecurityManager via this method will automatically enable the
+     * {@link #getSubject() getSubject()} call to function with little configuration.
+     * <p/>
+     * For example, in these environments, this will work:
+     * <pre>
+     * DefaultSecurityManager securityManager = new {@link org.apache.shiro.mgt.DefaultSecurityManager DefaultSecurityManager}();
+     * securityManager.setRealms( ... ); //one or more Realms
+     * <b>SecurityUtils.setSecurityManager( securityManager );</b></pre>
+     * <p/>
+     * And then anywhere in the application code, the following call will return the application's Subject:
+     * <pre>
+     * Subject currentUser = SecurityUtils.getSubject();</pre>
+     *
+     * @param securityManager the securityManager instance to set as a VM static singleton.
+     */
+    public static void setSecurityManager(SecurityManager securityManager) {
+        SecurityUtils.securityManager = securityManager;
+    }
+
+    /**
+     * Returns the SecurityManager accessible to the calling code.
+     * <p/>
+     * This implementation favors acquiring a thread-bound {@code SecurityManager} if it can find one.  If one is
+     * not available to the executing thread, it will attempt to use the static singleton if available (see the
+     * {@link #setSecurityManager setSecurityManager} method for more on the static singleton).
+     * <p/>
+     * If neither the thread-local or static singleton instances are available, this method throws an
+     * {@code UnavailableSecurityManagerException} to indicate an error - a SecurityManager should always be accessible
+     * to calling code in an application. If it is not, it is likely due to a Shiro configuration problem.
+     *
+     * @return the SecurityManager accessible to the calling code.
+     * @throws UnavailableSecurityManagerException
+     *          if there is no {@code SecurityManager} instance available to the
+     *          calling code, which typically indicates an invalid application configuration.
+     */
+    public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
+        SecurityManager securityManager = ThreadContext.getSecurityManager();
+        if (securityManager == null) {
+            securityManager = SecurityUtils.securityManager;
+        }
+        if (securityManager == null) {
+            String msg = "No SecurityManager accessible to the calling code, either bound to the " +
+                    ThreadContext.class.getName() + " or as a vm static singleton.  This is an invalid application " +
+                    "configuration.";
+            throw new UnavailableSecurityManagerException(msg);
+        }
+        return securityManager;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/ShiroException.java b/core/src/main/java/org/apache/shiro/ShiroException.java
new file mode 100644
index 0000000..22339cb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/ShiroException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro;
+
+/**
+ * Root exception for all Shiro runtime exceptions.  This class is used as the root instead
+ * of {@link java.lang.SecurityException} to remove the potential for conflicts;  many other
+ * frameworks and products (such as J2EE containers) perform special operations when
+ * encountering {@link java.lang.SecurityException}.
+ *
+ * @since 0.1
+ */
+public class ShiroException extends RuntimeException {
+
+    /**
+     * Creates a new ShiroException.
+     */
+    public ShiroException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ShiroException.
+     *
+     * @param message the reason for the exception
+     */
+    public ShiroException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ShiroException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ShiroException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ShiroException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ShiroException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/UnavailableSecurityManagerException.java b/core/src/main/java/org/apache/shiro/UnavailableSecurityManagerException.java
new file mode 100644
index 0000000..b6b15f2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/UnavailableSecurityManagerException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro;
+
+/**
+ * Exception thrown when attempting to acquire the application's {@code SecurityManager} instance, but Shiro's
+ * lookup heuristics cannot find one.  This typically indicates an invalid application configuration.
+ *
+ * @since 1.0
+ */
+public class UnavailableSecurityManagerException extends ShiroException {
+
+    public UnavailableSecurityManagerException(String message) {
+        super(message);
+    }
+
+    public UnavailableSecurityManagerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/AnnotationHandler.java b/core/src/main/java/org/apache/shiro/aop/AnnotationHandler.java
new file mode 100644
index 0000000..706c56e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/AnnotationHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+
+/**
+ * Base support class for implementations that reads and processes JSR-175 annotations.
+ *
+ * @since 0.9.0
+ */
+public abstract class AnnotationHandler {
+
+    /**
+     * The type of annotation this handler will process.
+     */
+    protected Class<? extends Annotation> annotationClass;
+
+    /**
+     * Constructs an <code>AnnotationHandler</code> who processes annotations of the
+     * specified type.  Immediately calls {@link #setAnnotationClass(Class)}.
+     *
+     * @param annotationClass the type of annotation this handler will process.
+     */
+    public AnnotationHandler(Class<? extends Annotation> annotationClass) {
+        setAnnotationClass(annotationClass);
+    }
+
+    /**
+     * Returns the {@link org.apache.shiro.subject.Subject Subject} associated with the currently-executing code.
+     * <p/>
+     * This default implementation merely calls <code>{@link org.apache.shiro.SecurityUtils#getSubject SecurityUtils.getSubject()}</code>.
+     *
+     * @return the {@link org.apache.shiro.subject.Subject Subject} associated with the currently-executing code.
+     */
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    /**
+     * Sets the type of annotation this handler will inspect and process.
+     *
+     * @param annotationClass the type of annotation this handler will process.
+     * @throws IllegalArgumentException if the argument is <code>null</code>.
+     */
+    protected void setAnnotationClass(Class<? extends Annotation> annotationClass)
+            throws IllegalArgumentException {
+        if (annotationClass == null) {
+            String msg = "annotationClass argument cannot be null";
+            throw new IllegalArgumentException(msg);
+        }
+        this.annotationClass = annotationClass;
+    }
+
+    /**
+     * Returns the type of annotation this handler inspects and processes.
+     *
+     * @return the type of annotation this handler inspects and processes.
+     */
+    public Class<? extends Annotation> getAnnotationClass() {
+        return this.annotationClass;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/AnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/aop/AnnotationMethodInterceptor.java
new file mode 100644
index 0000000..9179651
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/AnnotationMethodInterceptor.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * MethodInterceptor that inspects a specific annotation on the method invocation before continuing
+ * its execution.
+ * </p>
+ * The annotation is acquired from the {@link MethodInvocation MethodInvocation} via a
+ * {@link AnnotationResolver AnnotationResolver} instance that may be configured.  Unless
+ * overridden, the default {@code AnnotationResolver} is a
+ *
+ * @since 0.9
+ */
+public abstract class AnnotationMethodInterceptor extends MethodInterceptorSupport {
+
+    private AnnotationHandler handler;
+
+    /**
+     * The resolver to use to find annotations on intercepted methods.
+     *
+     * @since 1.1
+     */
+    private AnnotationResolver resolver;
+
+    /**
+     * Constructs an <code>AnnotationMethodInterceptor</code> with the
+     * {@link AnnotationHandler AnnotationHandler} that will be used to process annotations of a
+     * corresponding type.
+     *
+     * @param handler the handler to delegate to for processing the annotation.
+     */
+    public AnnotationMethodInterceptor(AnnotationHandler handler) {
+        this(handler, new DefaultAnnotationResolver());
+    }
+
+    /**
+     * Constructs an <code>AnnotationMethodInterceptor</code> with the
+     * {@link AnnotationHandler AnnotationHandler} that will be used to process annotations of a
+     * corresponding type, using the specified {@code AnnotationResolver} to acquire annotations
+     * at runtime.
+     *
+     * @param handler  the handler to use to process any discovered annotation
+     * @param resolver the resolver to use to locate/acquire the annotation
+     * @since 1.1
+     */
+    public AnnotationMethodInterceptor(AnnotationHandler handler, AnnotationResolver resolver) {
+        if (handler == null) {
+            throw new IllegalArgumentException("AnnotationHandler argument cannot be null.");
+        }
+        setHandler(handler);
+        setResolver(resolver != null ? resolver : new DefaultAnnotationResolver());
+    }
+
+    /**
+     * Returns the {@code AnnotationHandler} used to perform authorization behavior based on
+     * an annotation discovered at runtime.
+     *
+     * @return the {@code AnnotationHandler} used to perform authorization behavior based on
+     *         an annotation discovered at runtime.
+     */
+    public AnnotationHandler getHandler() {
+        return handler;
+    }
+
+    /**
+     * Sets the {@code AnnotationHandler} used to perform authorization behavior based on
+     * an annotation discovered at runtime.
+     *
+     * @param handler the {@code AnnotationHandler} used to perform authorization behavior based on
+     *                an annotation discovered at runtime.
+     */
+    public void setHandler(AnnotationHandler handler) {
+        this.handler = handler;
+    }
+
+    /**
+     * Returns the {@code AnnotationResolver} to use to acquire annotations from intercepted
+     * methods at runtime.  The annotation is then used by the {@link #getHandler handler} to
+     * perform authorization logic.
+     *
+     * @return the {@code AnnotationResolver} to use to acquire annotations from intercepted
+     *         methods at runtime.
+     * @since 1.1
+     */
+    public AnnotationResolver getResolver() {
+        return resolver;
+    }
+
+    /**
+     * Returns the {@code AnnotationResolver} to use to acquire annotations from intercepted
+     * methods at runtime.  The annotation is then used by the {@link #getHandler handler} to
+     * perform authorization logic.
+     *
+     * @param resolver the {@code AnnotationResolver} to use to acquire annotations from intercepted
+     *                 methods at runtime.
+     * @since 1.1
+     */
+    public void setResolver(AnnotationResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    /**
+     * Returns <code>true</code> if this interceptor supports, that is, should inspect, the specified
+     * <code>MethodInvocation</code>, <code>false</code> otherwise.
+     * <p/>
+     * The default implementation simply does the following:
+     * <p/>
+     * <code>return {@link #getAnnotation(MethodInvocation) getAnnotation(mi)} != null</code>
+     *
+     * @param mi the <code>MethodInvocation</code> for the method being invoked.
+     * @return <code>true</code> if this interceptor supports, that is, should inspect, the specified
+     *         <code>MethodInvocation</code>, <code>false</code> otherwise.
+     */
+    public boolean supports(MethodInvocation mi) {
+        return getAnnotation(mi) != null;
+    }
+
+    /**
+     * Returns the Annotation that this interceptor will process for the specified method invocation.
+     * <p/>
+     * The default implementation acquires the annotation using an annotation
+     * {@link #getResolver resolver} using the internal annotation {@link #getHandler handler}'s
+     * {@link org.apache.shiro.aop.AnnotationHandler#getAnnotationClass() annotationClass}.
+     *
+     * @param mi the MethodInvocation wrapping the Method from which the Annotation will be acquired.
+     * @return the Annotation that this interceptor will process for the specified method invocation.
+     */
+    protected Annotation getAnnotation(MethodInvocation mi) {
+        return getResolver().getAnnotation(mi, getHandler().getAnnotationClass());
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/AnnotationResolver.java b/core/src/main/java/org/apache/shiro/aop/AnnotationResolver.java
new file mode 100644
index 0000000..7934884
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/AnnotationResolver.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Defines an AOP-framework-independent way of determining if an Annotation exists on a Method.
+ *
+ * @since 1.1
+ */
+public interface AnnotationResolver {
+
+    /**
+     * Returns an {@link Annotation} instance of the specified type based on the given
+     * {@link MethodInvocation MethodInvocation} argument, or {@code null} if no annotation
+     * of that type could be found. First checks the invoked method itself and if not found, 
+     * then the class for the existence of the same annotation. 
+     *
+     * @param mi the intercepted method to be invoked.
+     * @param clazz the annotation class of the annotation to find.
+     * @return the method's annotation of the specified type or {@code null} if no annotation of
+     *         that type could be found.
+     */
+    Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz);
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
new file mode 100644
index 0000000..d33eebe
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * Default {@code AnnotationResolver} implementation that merely inspects the
+ * {@link MethodInvocation MethodInvocation}'s {@link MethodInvocation#getMethod() target method},
+ * and returns {@code targetMethod}.{@link Method#getAnnotation(Class) getAnnotation(class)}.
+ * <p/>
+ * Unfortunately Java's default reflection API for Annotations is not very robust, and this logic
+ * may not be enough - if the incoming method invocation represents a method from an interface,
+ * this default logic would not discover the annotation if it existed on the method implementation
+ * directly (as opposed to being defined directly in the interface definition).
+ * <p/>
+ * More complex class hierarchy traversal logic is required to exhaust a method's target object's
+ * classes, parent classes, interfaces and parent interfaces.  That logic will likely be added
+ * to this implementation in due time, but for now, this implementation relies on the JDK's default
+ * {@link Method#getAnnotation(Class) Method.getAnnotation(class)} logic.
+ *
+ * @since 1.1
+ */
+public class DefaultAnnotationResolver implements AnnotationResolver {
+
+    /**
+     * Returns {@code methodInvocation.}{@link org.apache.shiro.aop.MethodInvocation#getMethod() getMethod()}.{@link Method#getAnnotation(Class) getAnnotation(clazz)}.
+     *
+     * @param mi    the intercepted method to be invoked.
+     * @param clazz the annotation class to use to find an annotation instance on the method.
+     * @return the discovered annotation or {@code null} if an annotation instance could not be
+     *         found.
+     */
+    public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
+        if (mi == null) {
+            throw new IllegalArgumentException("method argument cannot be null");
+        }
+        Method m = mi.getMethod();
+        if (m == null) {
+            String msg = MethodInvocation.class.getName() + " parameter incorrectly constructed.  getMethod() returned null";
+            throw new IllegalArgumentException(msg);
+
+        }
+        Annotation annotation = m.getAnnotation(clazz);
+        return annotation == null ? mi.getThis().getClass().getAnnotation(clazz) : annotation;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/MethodInterceptor.java b/core/src/main/java/org/apache/shiro/aop/MethodInterceptor.java
new file mode 100644
index 0000000..16ae022
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/MethodInterceptor.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+/**
+ * A <tt>MethodInterceptor</tt> intercepts a <tt>MethodInvocation</tt> to perform before or after logic (aka 'advice').
+ *
+ * <p>Shiro's implementations of this interface mostly have to deal with ensuring a current Subject has the
+ * ability to execute the method before allowing it to continue.
+ *
+ * @since 0.2
+ */
+public interface MethodInterceptor {
+
+    /**
+     * Invokes the specified <code>MethodInvocation</code>, allowing implementations to perform pre/post/finally
+     * surrounding the actual invocation.
+     *
+     * @param methodInvocation the <code>MethodInvocation</code> to execute.
+     * @return the result of the invocation
+     * @throws Throwable if the method invocation throws a Throwable or if an error occurs in pre/post/finally advice.
+     */
+    Object invoke(MethodInvocation methodInvocation) throws Throwable;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/MethodInterceptorSupport.java b/core/src/main/java/org/apache/shiro/aop/MethodInterceptorSupport.java
new file mode 100644
index 0000000..e21772e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/MethodInterceptorSupport.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+
+/**
+ * This class is an abstraction of AOP method interceptor behavior specific to Shiro that
+ * leaves AOP implementation specifics to be handled by subclass implementations.  This implementation primarily
+ * enables a <tt>Log</tt> and makes available the currently executing {@link Subject Subject}.
+ *
+ * @since 0.2
+ */
+public abstract class MethodInterceptorSupport implements MethodInterceptor {
+
+    /**
+     * Default no-argument constructor for subclasses.
+     */
+    public MethodInterceptorSupport() {
+    }
+
+    /**
+     * Returns the {@link Subject Subject} associated with the currently-executing code.
+     * <p/>
+     * This default implementation merely calls <code>{@link org.apache.shiro.SecurityUtils#getSubject SecurityUtils.getSubject()}</code>.
+     *
+     * @return the {@link org.apache.shiro.subject.Subject Subject} associated with the currently-executing code.
+     */
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/aop/MethodInvocation.java b/core/src/main/java/org/apache/shiro/aop/MethodInvocation.java
new file mode 100644
index 0000000..d2dd5e3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/MethodInvocation.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import java.lang.reflect.Method;
+
+/**
+ * 3rd-party API independent representation of a method invocation.  This is needed so Shiro can support other
+ * MethodInvocation instances from other AOP frameworks/APIs.
+ *
+ * @since 0.1
+ */
+public interface MethodInvocation {
+
+    /**
+     * Continues the method invocation chain, or if the last in the chain, the method itself.
+     *
+     * @return the result of the Method invocation.
+     * @throws Throwable if the method or chain throws a Throwable
+     */
+    Object proceed() throws Throwable;
+
+    /**
+     * Returns the actual {@link Method Method} to be invoked.
+     *
+     * @return the actual {@link Method Method} to be invoked.
+     */
+    Method getMethod();
+
+    /**
+     * Returns the (possibly null) arguments to be supplied to the method invocation.
+     *
+     * @return the (possibly null) arguments to be supplied to the method invocation.
+     */
+    Object[] getArguments();
+
+    /**
+     * Returns the object that holds the current joinpoint's static part.
+     * For instance, the target object for an invocation.
+     *
+     * @return the object that holds the current joinpoint's static part.
+     * @since 1.0
+     */
+    Object getThis();
+
+
+}
+
diff --git a/core/src/main/java/org/apache/shiro/aop/package-info.java b/core/src/main/java/org/apache/shiro/aop/package-info.java
new file mode 100644
index 0000000..34c94b9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/aop/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components used to support the framework's AOP/interception support classes.
+ * <p/>
+ * As this package is a root-level package under <tt>org.apache.shiro</tt>, it contains AOP support classes
+ * useful for any AOP environment and/or function. Feature-dependent AOP classes (e.g. authorization,
+ * authentication, etc) will use these classes as their base in their respective packages.
+ */
+package org.apache.shiro.aop;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java b/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java
new file mode 100644
index 0000000..b8bba7c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+/**
+ * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication
+ * attempts.
+ * <p/>
+ * This class delegates the actual authentication attempt to subclasses but supports notification for
+ * successful and failed logins as well as logouts. Notification is sent to one or more registered
+ * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic
+ * when these conditions occur.
+ * <p/>
+ * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation)
+ * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}.
+ *
+ * @since 0.1
+ */
+public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
+
+    /*-------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * Private class log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticator.class);
+
+    /*-------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * Any registered listeners that wish to know about things during the authentication process.
+     */
+    private Collection<AuthenticationListener> listeners;
+
+    /*-------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * Default no-argument constructor. Ensures the internal
+     * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}.
+     */
+    public AbstractAuthenticator() {
+        listeners = new ArrayList<AuthenticationListener>();
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
+     * attempts.
+     *
+     * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an
+     *                  authentication attempt.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setAuthenticationListeners(Collection<AuthenticationListener> listeners) {
+        if (listeners == null) {
+            this.listeners = new ArrayList<AuthenticationListener>();
+        } else {
+            this.listeners = listeners;
+        }
+    }
+
+    /**
+     * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
+     * attempts.
+     *
+     * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
+     *         attempts.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public Collection<AuthenticationListener> getAuthenticationListeners() {
+        return this.listeners;
+    }
+
+    /*-------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
+     * authentication was successful for the specified {@code token} which resulted in the specified
+     * {@code info}.  This implementation merely iterates over the internal {@code listeners} collection and
+     * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess}
+     * for each.
+     *
+     * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication.
+     * @param info  the returned {@code AuthenticationInfo} resulting from the successful authentication.
+     */
+    protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
+        for (AuthenticationListener listener : this.listeners) {
+            listener.onSuccess(token, info);
+        }
+    }
+
+    /**
+     * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
+     * authentication failed for the
+     * specified {@code token} which resulted in the specified {@code ae} exception.  This implementation merely
+     * iterates over the internal {@code listeners} collection and calls
+     * {@link AuthenticationListener#onFailure(AuthenticationToken, AuthenticationException) onFailure}
+     * for each.
+     *
+     * @param token the submitted {@code AuthenticationToken} that resulted in a failed authentication.
+     * @param ae    the resulting {@code AuthenticationException} that caused the authentication to fail.
+     */
+    protected void notifyFailure(AuthenticationToken token, AuthenticationException ae) {
+        for (AuthenticationListener listener : this.listeners) {
+            listener.onFailure(token, ae);
+        }
+    }
+
+    /**
+     * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that a
+     * {@code Subject} has logged-out.  This implementation merely
+     * iterates over the internal {@code listeners} collection and calls
+     * {@link AuthenticationListener#onLogout(org.apache.shiro.subject.PrincipalCollection) onLogout}
+     * for each.
+     *
+     * @param principals the identifying principals of the {@code Subject}/account logging out.
+     */
+    protected void notifyLogout(PrincipalCollection principals) {
+        for (AuthenticationListener listener : this.listeners) {
+            listener.onLogout(principals);
+        }
+    }
+
+    /**
+     * This implementation merely calls
+     * {@link #notifyLogout(org.apache.shiro.subject.PrincipalCollection) notifyLogout} to allow any registered listeners
+     * to react to the logout.
+     *
+     * @param principals the identifying principals of the {@code Subject}/account logging out.
+     */
+    public void onLogout(PrincipalCollection principals) {
+        notifyLogout(principals);
+    }
+
+    /**
+     * Implementation of the {@link Authenticator} interface that functions in the following manner:
+     * <ol>
+     * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
+     * authentication behavior.</li>
+     * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
+     * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
+     * {@link AuthenticationListener AuthenticationListener}s of the exception and then propogate the exception
+     * for the caller to handle.</li>
+     * <li>If no exception is thrown (indicating a successful login),
+     * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
+     * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
+     * <li>Return the {@code AuthenticationInfo}</li>
+     * </ol>
+     *
+     * @param token the submitted token representing the subject's (user's) login principals and credentials.
+     * @return the AuthenticationInfo referencing the authenticated user's account data.
+     * @throws AuthenticationException if there is any problem during the authentication process - see the
+     *                                 interface's JavaDoc for a more detailed explanation.
+     */
+    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
+
+        if (token == null) {
+            throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
+        }
+
+        log.trace("Authentication attempt received for token [{}]", token);
+
+        AuthenticationInfo info;
+        try {
+            info = doAuthenticate(token);
+            if (info == null) {
+                String msg = "No account information found for authentication token [" + token + "] by this " +
+                        "Authenticator instance.  Please check that it is configured correctly.";
+                throw new AuthenticationException(msg);
+            }
+        } catch (Throwable t) {
+            AuthenticationException ae = null;
+            if (t instanceof AuthenticationException) {
+                ae = (AuthenticationException) t;
+            }
+            if (ae == null) {
+                //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
+                //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
+                String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
+                        "error? (Typical or expected login exceptions should extend from AuthenticationException).";
+                ae = new AuthenticationException(msg, t);
+            }
+            try {
+                notifyFailure(token, ae);
+            } catch (Throwable t2) {
+                if (log.isWarnEnabled()) {
+                    String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
+                            "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
+                            "and propagating original AuthenticationException instead...";
+                    log.warn(msg, t2);
+                }
+            }
+
+
+            throw ae;
+        }
+
+        log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
+
+        notifySuccess(token, info);
+
+        return info;
+    }
+
+    /**
+     * Template design pattern hook for subclasses to implement specific authentication behavior.
+     * <p/>
+     * Common behavior for most authentication attempts is encapsulated in the
+     * {@link #authenticate} method and that method invokes this one for custom behavior.
+     * <p/>
+     * <b>N.B.</b> Subclasses <em>should</em> throw some kind of
+     * {@code AuthenticationException} if there is a problem during
+     * authentication instead of returning {@code null}.  A {@code null} return value indicates
+     * a configuration or programming error, since {@code AuthenticationException}s should
+     * indicate any expected problem (such as an unknown account or username, or invalid password, etc).
+     *
+     * @param token the authentication token encapsulating the user's login information.
+     * @return an {@code AuthenticationInfo} object encapsulating the user's account information
+     *         important to Shiro.
+     * @throws AuthenticationException if there is a problem logging in the user.
+     */
+    protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
+            throws AuthenticationException;
+
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/Account.java b/core/src/main/java/org/apache/shiro/authc/Account.java
new file mode 100644
index 0000000..0f84d01
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/Account.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.authz.AuthorizationInfo;
+
+/**
+ * An <tt>Account</tt> is a convenience interface that extends both {@link AuthenticationInfo} and
+ * {@link AuthorizationInfo} and represents authentication and authorization for a <em>single account</em> in a
+ * <em>single Realm</em>.
+ * <p/>
+ * This interface can be useful when a Realm implementation finds it more convenient to use a single object to
+ * encapsulate both the authentication and authorization information used by both authc and authz operations.
+ * <p/>
+ * <b>Please Note</b>:  Since Shiro sometimes logs account operations, please ensure your Account's <code>toString()</code>
+ * implementation does <em>not</em> print out account credentials (password, etc), as these might be viewable to
+ * someone reading your logs.  This is good practice anyway, and account principals should rarely (if ever) be printed
+ * out for any reason.  If you're using Shiro's default implementations of this interface, they only ever print the
+ * account {@link #getPrincipals() principals}, so you do not need to do anything additional.
+ *
+ * @see SimpleAccount
+ * @since 0.9
+ */
+public interface Account extends AuthenticationInfo, AuthorizationInfo {
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/AccountException.java b/core/src/main/java/org/apache/shiro/authc/AccountException.java
new file mode 100644
index 0000000..f953884
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AccountException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Exception thrown due to a problem with the account
+ * under which an authentication attempt is being executed.
+ *
+ * @since 0.1
+ */
+public class AccountException extends AuthenticationException {
+
+    /**
+     * Creates a new AccountException.
+     */
+    public AccountException() {
+        super();
+    }
+
+    /**
+     * Constructs a new AccountException.
+     *
+     * @param message the reason for the exception
+     */
+    public AccountException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new AccountException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public AccountException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new AccountException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public AccountException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/AuthenticationException.java b/core/src/main/java/org/apache/shiro/authc/AuthenticationException.java
new file mode 100644
index 0000000..056b30b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AuthenticationException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * General exception thrown due to an error during the Authentication process.
+ *
+ * @since 0.1
+ */
+public class AuthenticationException extends ShiroException
+{
+
+    /**
+     * Creates a new AuthenticationException.
+     */
+    public AuthenticationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new AuthenticationException.
+     *
+     * @param message the reason for the exception
+     */
+    public AuthenticationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new AuthenticationException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public AuthenticationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new AuthenticationException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public AuthenticationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/AuthenticationInfo.java b/core/src/main/java/org/apache/shiro/authc/AuthenticationInfo.java
new file mode 100644
index 0000000..07fe3e6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AuthenticationInfo.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.shiro.authc;
+
+import org.apache.shiro.subject.PrincipalCollection;
+
+import java.io.Serializable;
+
+/**
+ * <code>AuthenticationInfo</code> represents a Subject's (aka user's) stored account information relevant to the
+ * authentication/log-in process only.
+ * <p/>
+ * It is important to understand the difference between this interface and the
+ * {@link AuthenticationToken AuthenticationToken} interface.  <code>AuthenticationInfo</code> implementations
+ * represent already-verified and stored account data, whereas an <code>AuthenticationToken</code> represents data
+ * submitted for any given login attempt (which may or may not successfully match the verified and stored account
+ * <code>AuthenticationInfo</code>).
+ * <p/>
+ * Because the act of authentication (log-in) is orthogonal to authorization (access control), this interface is
+ * intended to represent only the account data needed by Shiro during an authentication attempt.  Shiro also
+ * has a parallel {@link org.apache.shiro.authz.AuthorizationInfo AuthorizationInfo} interface for use during the
+ * authorization process that references access control data such as roles and permissions.
+ * <p/>
+ * But because many if not most {@link org.apache.shiro.realm.Realm Realm}s store both sets of data for a Subject, it might be
+ * convenient for a <code>Realm</code> implementation to utilize an implementation of the {@link Account Account}
+ * interface instead, which is a convenience interface that combines both <code>AuthenticationInfo</code> and
+ * <code>AuthorizationInfo</code>.  Whether you choose to implement these two interfaces separately or implement the one
+ * <code>Account</code> interface for a given <code>Realm</code> is entirely based on your application's needs or your
+ * preferences.
+ * <p/>
+ * <p><b>Pleae note:</b>  Since Shiro sometimes logs authentication operations, please ensure your AuthenticationInfo's
+ * <code>toString()</code> implementation does <em>not</em> print out account credentials (password, etc), as these might be viewable to
+ * someone reading your logs.  This is good practice anyway, and account credentials should rarely (if ever) be printed
+ * out for any reason.  If you're using Shiro's default implementations of this interface, they only ever print the
+ * account {@link #getPrincipals() principals}, so you do not need to do anything additional.</p>
+ *
+ * @see org.apache.shiro.authz.AuthorizationInfo AuthorizationInfo
+ * @see Account
+ * @since 0.9
+ */
+public interface AuthenticationInfo extends Serializable {
+
+    /**
+     * Returns all principals associated with the corresponding Subject.  Each principal is an identifying piece of
+     * information useful to the application such as a username, or user id, a given name, etc - anything useful
+     * to the application to identify the current <code>Subject</code>.
+     * <p/>
+     * The returned PrincipalCollection should <em>not</em> contain any credentials used to verify principals, such
+     * as passwords, private keys, etc.  Those should be instead returned by {@link #getCredentials() getCredentials()}.
+     *
+     * @return all principals associated with the corresponding Subject.
+     */
+    PrincipalCollection getPrincipals();
+
+    /**
+     * Returns the credentials associated with the corresponding Subject.  A credential verifies one or more of the
+     * {@link #getPrincipals() principals} associated with the Subject, such as a password or private key.  Credentials
+     * are used by Shiro particularly during the authentication process to ensure that submitted credentials
+     * during a login attempt match exactly the credentials here in the <code>AuthenticationInfo</code> instance.
+     *
+     * @return the credentials associated with the corresponding Subject.
+     */
+    Object getCredentials();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/AuthenticationListener.java b/core/src/main/java/org/apache/shiro/authc/AuthenticationListener.java
new file mode 100644
index 0000000..ecdba8e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AuthenticationListener.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ * An {@code AuthenticationListener} listens for notifications while {@code Subject}s authenticate with the system.
+ *
+ * @since 0.9
+ */
+public interface AuthenticationListener {
+
+    /**
+     * Callback triggered when an authentication attempt for a {@code Subject} has succeeded.
+     *
+     * @param token the authentication token submitted during the {@code Subject} (user)'s authentication attempt.
+     * @param info  the authentication-related account data acquired after authentication for the corresponding {@code Subject}.
+     */
+    void onSuccess(AuthenticationToken token, AuthenticationInfo info);
+
+    /**
+     * Callback triggered when an authentication attempt for a {@code Subject} has failed.
+     *
+     * @param token the authentication token submitted during the {@code Subject} (user)'s authentication attempt.
+     * @param ae    the {@code AuthenticationException} that occurred as a result of the attempt.
+     */
+    void onFailure(AuthenticationToken token, AuthenticationException ae);
+
+    /**
+     * Callback triggered when a {@code Subject} logs-out of the system.
+     * <p/>
+     * This method will only be triggered when a Subject explicitly logs-out of the session.  It will not
+     * be triggered if their Session times out.
+     *
+     * @param principals the identifying principals of the Subject logging out.
+     */
+    void onLogout(PrincipalCollection principals);
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/AuthenticationToken.java b/core/src/main/java/org/apache/shiro/authc/AuthenticationToken.java
new file mode 100644
index 0000000..7da053f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/AuthenticationToken.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import java.io.Serializable;
+
+/**
+ * <p>An <tt>AuthenticationToken</tt> is a consolidation of an account's principals and supporting
+ * credentials submitted by a user during an authentication attempt.
+ * <p/>
+ * <p>The token is submitted to an {@link Authenticator Authenticator} via the
+ * {@link Authenticator#authenticate(AuthenticationToken) authenticate(token)} method.  The
+ * Authenticator then executes the authentication/log-in process.
+ * <p/>
+ * <p>Common implementations of an <tt>AuthenticationToken</tt> would have username/password
+ * pairs, X.509 Certificate, PGP key, or anything else you can think of.  The token can be
+ * anything needed by an {@link Authenticator} to authenticate properly.
+ * <p/>
+ * <p>Because applications represent user data and credentials in different ways, implementations
+ * of this interface are application-specific.  You are free to acquire a user's principals and
+ * credentials however you wish (e.g. web form, Swing form, fingerprint identification, etc) and
+ * then submit them to the Shiro framework in the form of an implementation of this
+ * interface.
+ * <p/>
+ * <p>If your application's authentication process is  username/password based
+ * (like most), instead of implementing this interface yourself, take a look at the
+ * {@link UsernamePasswordToken UsernamePasswordToken} class, as it is probably sufficient for your needs.
+ * <p/>
+ * <p>RememberMe services are enabled for a token if they implement a sub-interface of this one, called
+ * {@link RememberMeAuthenticationToken RememberMeAuthenticationToken}.  Implement that interfac if you need
+ * RememberMe services (the <tt>UsernamePasswordToken</tt> already implements this interface).
+ * <p/>
+ * <p>If you are familiar with JAAS, an <tt>AuthenticationToken</tt> replaces the concept of a
+ * {@link javax.security.auth.callback.Callback}, and  defines meaningful behavior
+ * (<tt>Callback</tt> is just a marker interface, and of little use).  We
+ * also think the name <em>AuthenticationToken</em> more accurately reflects its true purpose
+ * in a login framework, whereas <em>Callback</em> is less obvious.
+ *
+ * @see RememberMeAuthenticationToken
+ * @see HostAuthenticationToken
+ * @see UsernamePasswordToken
+ * @since 0.1
+ */
+public interface AuthenticationToken extends Serializable {
+
+    /**
+     * Returns the account identity submitted during the authentication process.
+     * <p/>
+     * <p>Most application authentications are username/password based and have this
+     * object represent a username.  If this is the case for your application,
+     * take a look at the {@link UsernamePasswordToken UsernamePasswordToken}, as it is probably
+     * sufficient for your use.
+     * <p/>
+     * <p>Ultimately, the object returned is application specific and can represent
+     * any account identity (user id, X.509 certificate, etc).
+     *
+     * @return the account identity submitted during the authentication process.
+     * @see UsernamePasswordToken
+     */
+    Object getPrincipal();
+
+    /**
+     * Returns the credentials submitted by the user during the authentication process that verifies
+     * the submitted {@link #getPrincipal() account identity}.
+     * <p/>
+     * <p>Most application authentications are username/password based and have this object
+     * represent a submitted password.  If this is the case for your application,
+     * take a look at the {@link UsernamePasswordToken UsernamePasswordToken}, as it is probably
+     * sufficient for your use.
+     * <p/>
+     * <p>Ultimately, the credentials Object returned is application specific and can represent
+     * any credential mechanism.
+     *
+     * @return the credential submitted by the user during the authentication process.
+     */
+    Object getCredentials();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/Authenticator.java b/core/src/main/java/org/apache/shiro/authc/Authenticator.java
new file mode 100644
index 0000000..ca4df42
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/Authenticator.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * An Authenticator is responsible for authenticating accounts in an application.  It
+ * is one of the primary entry points into the Shiro API.
+ * <p/>
+ * Although not a requirement, there is usually a single 'master' Authenticator configured for
+ * an application.  Enabling Pluggable Authentication Module (PAM) behavior
+ * (Two Phase Commit, etc.) is usually achieved by the single {@code Authenticator} coordinating
+ * and interacting with an application-configured set of {@link org.apache.shiro.realm.Realm Realm}s.
+ * <p/>
+ * Note that most Shiro users will not interact with an {@code Authenticator} instance directly.
+ * Shiro's default architecture is based on an overall {@code SecurityManager} which typically
+ * wraps an {@code Authenticator} instance.
+ *
+ * @see org.apache.shiro.mgt.SecurityManager
+ * @see AbstractAuthenticator AbstractAuthenticator
+ * @see org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator
+ * @since 0.1
+ */
+public interface Authenticator {
+
+    /**
+     * Authenticates a user based on the submitted {@code AuthenticationToken}.
+     * <p/>
+     * If the authentication is successful, an {@link AuthenticationInfo} instance is returned that represents the
+     * user's account data relevant to Shiro.  This returned object is generally used in turn to construct a
+     * {@code Subject} representing a more complete security-specific 'view' of an account that also allows access to
+     * a {@code Session}.
+     *
+     * @param authenticationToken any representation of a user's principals and credentials submitted during an
+     *                            authentication attempt.
+     * @return the AuthenticationInfo representing the authenticating user's account data.
+     * @throws AuthenticationException if there is any problem during the authentication process.
+     *                                 See the specific exceptions listed below to as examples of what could happen
+     *                                 in order to accurately handle these problems and to notify the user in an
+     *                                 appropriate manner why the authentication attempt failed.  Realize an
+     *                                 implementation of this interface may or may not throw those listed or may
+     *                                 throw other AuthenticationExceptions, but the list shows the most common ones.
+     * @see ExpiredCredentialsException
+     * @see IncorrectCredentialsException
+     * @see ExcessiveAttemptsException
+     * @see LockedAccountException
+     * @see ConcurrentAccessException
+     * @see UnknownAccountException
+     */
+    public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
+            throws AuthenticationException;
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/ConcurrentAccessException.java b/core/src/main/java/org/apache/shiro/authc/ConcurrentAccessException.java
new file mode 100644
index 0000000..10f4323
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/ConcurrentAccessException.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown when an authentication attempt has been received for an account that has already been
+ * authenticated (i.e. logged-in), and the system is configured to prevent such concurrent access.
+ *
+ * <p>This is useful when an application must ensure that only one person is logged-in to a single
+ * account at any given time.
+ *
+ * <p>Sometimes account names and passwords are lazily given away
+ * to many people for easy access to a system.  Such behavior is undesirable in systems where
+ * users are accountable for their actions, such as in government applications, or when licensing
+ * agreements must be maintained, such as those which only allow 1 user per paid license.
+ *
+ * <p>By disallowing concurrent access, such systems can ensure that each authenticated session
+ * corresponds to one and only one user at any given time.
+ *
+ * @since 0.1
+ */
+public class ConcurrentAccessException extends AccountException {
+
+    /**
+     * Creates a new ConcurrentAccessException.
+     */
+    public ConcurrentAccessException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ConcurrentAccessException.
+     *
+     * @param message the reason for the exception
+     */
+    public ConcurrentAccessException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ConcurrentAccessException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ConcurrentAccessException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ConcurrentAccessException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ConcurrentAccessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/CredentialsException.java b/core/src/main/java/org/apache/shiro/authc/CredentialsException.java
new file mode 100644
index 0000000..877286c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/CredentialsException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Exception thrown due to a problem with the credential(s) submitted for an
+ * account during the authentication process.
+ *
+ * @since 0.1
+ */
+public class CredentialsException extends AuthenticationException {
+
+    /**
+     * Creates a new CredentialsException.
+     */
+    public CredentialsException() {
+        super();
+    }
+
+    /**
+     * Constructs a new CredentialsException.
+     *
+     * @param message the reason for the exception
+     */
+    public CredentialsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new CredentialsException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public CredentialsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new CredentialsException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public CredentialsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/DisabledAccountException.java b/core/src/main/java/org/apache/shiro/authc/DisabledAccountException.java
new file mode 100644
index 0000000..03756f8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/DisabledAccountException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown when attempting to authenticate and the corresponding account has been disabled for
+ * some reason.
+ *
+ * @see LockedAccountException
+ * @since 0.1
+ */
+public class DisabledAccountException extends AccountException {
+
+    /**
+     * Creates a new DisabledAccountException.
+     */
+    public DisabledAccountException() {
+        super();
+    }
+
+    /**
+     * Constructs a new DisabledAccountException.
+     *
+     * @param message the reason for the exception
+     */
+    public DisabledAccountException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new DisabledAccountException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public DisabledAccountException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new DisabledAccountException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public DisabledAccountException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/ExcessiveAttemptsException.java b/core/src/main/java/org/apache/shiro/authc/ExcessiveAttemptsException.java
new file mode 100644
index 0000000..29d560e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/ExcessiveAttemptsException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown when a system is configured to only allow a certain number of authentication attempts
+ * over a period of time and the current session has failed to authenticate successfully within
+ * that number.  The resulting action of such an exception is application-specific, but
+ * most systems either temporarily or permanently lock that account to prevent further
+ * attempts.
+ *
+ * @since 0.1
+ */
+public class ExcessiveAttemptsException extends AccountException {
+
+    /**
+     * Creates a new ExcessiveAttemptsException.
+     */
+    public ExcessiveAttemptsException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ExcessiveAttemptsException.
+     *
+     * @param message the reason for the exception
+     */
+    public ExcessiveAttemptsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ExcessiveAttemptsException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExcessiveAttemptsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ExcessiveAttemptsException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExcessiveAttemptsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/ExpiredCredentialsException.java b/core/src/main/java/org/apache/shiro/authc/ExpiredCredentialsException.java
new file mode 100644
index 0000000..f06632f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/ExpiredCredentialsException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown during the authentication process when the system determines the submitted credential(s)
+ * has expired and will not allow login.
+ *
+ * <p>This is most often used to alert a user that their credentials (e.g. password or
+ * cryptography key) has expired and they should change the value.  In such systems, the component
+ * invoking the authentication might catch this exception and redirect the user to an appropriate
+ * view to allow them to update their password or other credentials mechanism.
+ *
+ * @since 0.1
+ */
+public class ExpiredCredentialsException extends CredentialsException {
+
+    /**
+     * Creates a new ExpiredCredentialsException.
+     */
+    public ExpiredCredentialsException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ExpiredCredentialsException.
+     *
+     * @param message the reason for the exception
+     */
+    public ExpiredCredentialsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ExpiredCredentialsException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExpiredCredentialsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ExpiredCredentialsException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExpiredCredentialsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/HostAuthenticationToken.java b/core/src/main/java/org/apache/shiro/authc/HostAuthenticationToken.java
new file mode 100644
index 0000000..475b9f7
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/HostAuthenticationToken.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * A {@code HostAuthenticationToken} retains the host information from where
+ * an authentication attempt originates.
+ *
+ * @since 1.0
+ */
+public interface HostAuthenticationToken extends AuthenticationToken {
+
+    /**
+     * Returns the host name of the client from where the
+     * authentication attempt originates or if the Shiro environment cannot or
+     * chooses not to resolve the hostname to improve performance, this method
+     * returns the String representation of the client's IP address.
+     * <p/>
+     * When used in web environments, this value is usually the same as the
+     * {@code ServletRequest.getRemoteHost()} value.
+     *
+     * @return the fully qualified name of the client from where the
+     *         authentication attempt originates or the String representation
+     *         of the client's IP address is hostname resolution is not
+     *         available or disabled.
+     */
+    String getHost();
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/IncorrectCredentialsException.java b/core/src/main/java/org/apache/shiro/authc/IncorrectCredentialsException.java
new file mode 100644
index 0000000..5644746
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/IncorrectCredentialsException.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown when attempting to authenticate with credential(s) that do not match the actual
+ * credentials associated with the account principal.
+ *
+ * <p>For example, this exception might be thrown if a user's password is "secret" and
+ * "secrets" was entered by mistake.
+ *
+ * <p>Whether or not an application wishes to let
+ * the user know if they entered incorrect credentials is at the discretion of those
+ * responsible for defining the view and what happens when this exception occurs.
+ *
+ * @since 0.1
+ */
+public class IncorrectCredentialsException extends CredentialsException {
+
+    /**
+     * Creates a new IncorrectCredentialsException.
+     */
+    public IncorrectCredentialsException() {
+        super();
+    }
+
+    /**
+     * Constructs a new IncorrectCredentialsException.
+     *
+     * @param message the reason for the exception
+     */
+    public IncorrectCredentialsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new IncorrectCredentialsException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public IncorrectCredentialsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new IncorrectCredentialsException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public IncorrectCredentialsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/LockedAccountException.java b/core/src/main/java/org/apache/shiro/authc/LockedAccountException.java
new file mode 100644
index 0000000..3914926
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/LockedAccountException.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * A special kind of <tt>DisabledAccountException</tt>, this exception is thrown when attempting
+ * to authenticate and the corresponding account has been disabled explicitly due to being locked.
+ *
+ * <p>For example, an account can be locked if an administrator explicitly locks an account or
+ * perhaps an account can be locked automatically by the system if too many unsuccessful
+ * authentication attempts take place during a specific period of time (perhaps indicating a
+ * hacking attempt).
+ *
+ * @since 0.1
+ */
+public class LockedAccountException extends DisabledAccountException {
+
+    /**
+     * Creates a new LockedAccountException.
+     */
+    public LockedAccountException() {
+        super();
+    }
+
+    /**
+     * Constructs a new LockedAccountException.
+     *
+     * @param message the reason for the exception
+     */
+    public LockedAccountException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new LockedAccountException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public LockedAccountException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new LockedAccountException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public LockedAccountException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/LogoutAware.java b/core/src/main/java/org/apache/shiro/authc/LogoutAware.java
new file mode 100644
index 0000000..30f73c6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/LogoutAware.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ * An SPI interface allowing cleanup logic to be executed during logout of a previously authenticated Subject/user.
+ *
+ * <p>As it is an SPI interface, it is really intended for SPI implementors such as those implementing Realms.
+ *
+ * <p>All of Shiro's concrete Realm implementations implement this interface as a convenience for those wishing
+ * to subclass them.
+ *
+ * @since 0.9
+ */
+public interface LogoutAware {
+
+    /**
+     * Callback triggered when a <code>Subject</code> logs out of the system.
+     *
+     * @param principals the identifying principals of the Subject logging out.
+     */
+    public void onLogout(PrincipalCollection principals);
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/MergableAuthenticationInfo.java b/core/src/main/java/org/apache/shiro/authc/MergableAuthenticationInfo.java
new file mode 100644
index 0000000..0103fc8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/MergableAuthenticationInfo.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * <p>An extension of the {@link AuthenticationInfo} interface to be implemented by
+ * classes that support merging with other {@link AuthenticationInfo} instances.</p>
+ *
+ * <p>This allows an instance of this class to be an <em>aggregation</em>, or <em>composition</em> of account data
+ * from across multiple <code>Realm</code>s <tt>Realm</tt>s, not just one realm.</p>
+ *
+ * <p>This is useful in a multi-realm authentication configuration - the individual <tt>AuthenticationInfo</tt>
+ * objects obtained from each realm can be {@link #merge merged} into a single instance.  This instance can then be
+ * returned at the end of the authentication process, giving the impression of a single underlying
+ * realm/data source.
+ *
+ * @since 0.9
+ */
+public interface MergableAuthenticationInfo extends AuthenticationInfo {
+
+    /**
+     * Merges the given {@link AuthenticationInfo} into this instance.  The specific way
+     * that the merge occurs is up to the implementation, but typically it involves combining
+     * the principals and credentials together in this instance.  The <code>info</code> argument should
+     * not be modified in any way.
+     *
+     * @param info the info that should be merged into this instance.
+     */
+    void merge(AuthenticationInfo info);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/RememberMeAuthenticationToken.java b/core/src/main/java/org/apache/shiro/authc/RememberMeAuthenticationToken.java
new file mode 100644
index 0000000..88da10d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/RememberMeAuthenticationToken.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * An {@code AuthenticationToken} that indicates if the user wishes their identity to be remembered across sessions.
+ * <p/>
+ * Note however that when a new session is created for the corresponding user, that user's identity would be
+ * remembered, but they are <em>NOT</em> considered authenticated.  Please see the
+ * {@link org.apache.shiro.subject.Subject#isRemembered()} JavaDoc for an in-depth explanation of the semantic
+ * differences of what it means to be remembered vs. authenticated.
+ *
+ * @see org.apache.shiro.subject.Subject#isRemembered()
+ * @since 0.9
+ */
+public interface RememberMeAuthenticationToken extends AuthenticationToken {
+
+    /**
+     * Returns {@code true} if the submitting user wishes their identity (principal(s)) to be remembered
+     * across sessions, {@code false} otherwise.
+     *
+     * @return {@code true} if the submitting user wishes their identity (principal(s)) to be remembered
+     *         across sessions, {@code false} otherwise.
+     */
+    boolean isRememberMe();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java b/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java
new file mode 100644
index 0000000..d4dd09e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Interface representing account information that may use a salt when hashing credentials.  This interface
+ * exists primarily to support environments that hash user credentials (e.g. passwords).
+ * <p/>
+ * Salts should typically be generated from a secure pseudo-random number generator so they are effectively
+ * impossible to guess.  The salt value should be safely stored along side the account information to ensure
+ * it is maintained along with the account's credentials.
+ * <p/>
+ * This interface exists as a way for Shiro to acquire that salt so it can correctly perform
+ * {@link org.apache.shiro.authc.credential.CredentialsMatcher credentials matching} during login attempts.
+ * See the {@link org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher} JavaDoc for
+ * more information on hashing credentials with salts.
+ *
+ * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher
+ *
+ * @since 1.1
+ */
+public interface SaltedAuthenticationInfo extends AuthenticationInfo {
+
+    /**
+     * Returns the salt used to salt the account's credentials or {@code null} if no salt was used.
+     *
+     * @return the salt used to salt the account's credentials or {@code null} if no salt was used.
+     */
+    ByteSource getCredentialsSalt();
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java b/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java
new file mode 100644
index 0000000..933cbbf
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java
@@ -0,0 +1,494 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Set;
+
+
+/**
+ * Simple implementation of the {@link org.apache.shiro.authc.Account} interface that
+ * contains principal and credential and authorization information (roles and permissions) as instance variables and
+ * exposes them via getters and setters using standard JavaBean notation.
+ *
+ * @since 0.1
+ */
+public class SimpleAccount implements Account, MergableAuthenticationInfo, SaltedAuthenticationInfo, Serializable {
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * The authentication information (principals and credentials) for this account.
+     */
+    private SimpleAuthenticationInfo authcInfo;
+
+    /**
+     * The authorization information for this account.
+     */
+    private SimpleAuthorizationInfo authzInfo;
+
+    /**
+     * Indicates this account is locked.  This isn't honored by all <tt>Realms</tt> but is honored by
+     * {@link org.apache.shiro.realm.SimpleAccountRealm}.
+     */
+    private boolean locked;
+
+    /**
+     * Indicates credentials on this account are expired.  This isn't honored by all <tt>Realms</tt> but is honored by
+     * {@link org.apache.shiro.realm.SimpleAccountRealm}.
+     */
+    private boolean credentialsExpired;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * Default no-argument constructor.
+     */
+    public SimpleAccount() {
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principals and credentials.
+     *
+     * @param principal   the 'primary' identifying attribute of the account, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param realmName   the name of the realm that accesses this account data
+     */
+    public SimpleAccount(Object principal, Object credentials, String realmName) {
+        this(principal instanceof PrincipalCollection ? (PrincipalCollection) principal : new SimplePrincipalCollection(principal, realmName), credentials);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principals, hashedCredentials and
+     * credentials salt used when hashing the credentials.
+     *
+     * @param principal         the 'primary' identifying attribute of the account, for example, a user id or username.
+     * @param hashedCredentials the credentials that verify identity for the account
+     * @param credentialsSalt   the salt used when hashing the credentials
+     * @param realmName         the name of the realm that accesses this account data
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAccount(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
+        this(principal instanceof PrincipalCollection ? (PrincipalCollection) principal : new SimplePrincipalCollection(principal, realmName),
+                hashedCredentials, credentialsSalt);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principals and credentials.
+     *
+     * @param principals  the identifying attributes of the account, at least one of which should be considered the
+     *                    account's 'primary' identifying attribute, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param realmName   the name of the realm that accesses this account data
+     */
+    public SimpleAccount(Collection principals, Object credentials, String realmName) {
+        this(new SimplePrincipalCollection(principals, realmName), credentials);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified principals and credentials.
+     *
+     * @param principals  the identifying attributes of the account, at least one of which should be considered the
+     *                    account's 'primary' identifying attribute, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     */
+    public SimpleAccount(PrincipalCollection principals, Object credentials) {
+        this.authcInfo = new SimpleAuthenticationInfo(principals, credentials);
+        this.authzInfo = new SimpleAuthorizationInfo();
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified principals and credentials.
+     *
+     * @param principals        the identifying attributes of the account, at least one of which should be considered the
+     *                          account's 'primary' identifying attribute, for example, a user id or username.
+     * @param hashedCredentials the hashed credentials that verify identity for the account
+     * @param credentialsSalt   the salt used when hashing the credentials
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAccount(PrincipalCollection principals, Object hashedCredentials, ByteSource credentialsSalt) {
+        this.authcInfo = new SimpleAuthenticationInfo(principals, hashedCredentials, credentialsSalt);
+        this.authzInfo = new SimpleAuthorizationInfo();
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified principals and credentials, with the assigned roles.
+     *
+     * @param principals  the identifying attributes of the account, at least one of which should be considered the
+     *                    account's 'primary' identifying attribute, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param roles       the names of the roles assigned to this account.
+     */
+    public SimpleAccount(PrincipalCollection principals, Object credentials, Set<String> roles) {
+        this.authcInfo = new SimpleAuthenticationInfo(principals, credentials);
+        this.authzInfo = new SimpleAuthorizationInfo(roles);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principal and credentials, with the
+     * the assigned roles and permissions.
+     *
+     * @param principal   the 'primary' identifying attributes of the account, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param realmName   the name of the realm that accesses this account data
+     * @param roleNames   the names of the roles assigned to this account.
+     * @param permissions the permissions assigned to this account directly (not those assigned to any of the realms).
+     */
+    public SimpleAccount(Object principal, Object credentials, String realmName, Set<String> roleNames, Set<Permission> permissions) {
+        this.authcInfo = new SimpleAuthenticationInfo(new SimplePrincipalCollection(principal, realmName), credentials);
+        this.authzInfo = new SimpleAuthorizationInfo(roleNames);
+        this.authzInfo.setObjectPermissions(permissions);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principals and credentials, with the
+     * the assigned roles and permissions.
+     *
+     * @param principals  the identifying attributes of the account, at least one of which should be considered the
+     *                    account's 'primary' identifying attribute, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param realmName   the name of the realm that accesses this account data
+     * @param roleNames   the names of the roles assigned to this account.
+     * @param permissions the permissions assigned to this account directly (not those assigned to any of the realms).
+     */
+    public SimpleAccount(Collection principals, Object credentials, String realmName, Set<String> roleNames, Set<Permission> permissions) {
+        this.authcInfo = new SimpleAuthenticationInfo(new SimplePrincipalCollection(principals, realmName), credentials);
+        this.authzInfo = new SimpleAuthorizationInfo(roleNames);
+        this.authzInfo.setObjectPermissions(permissions);
+    }
+
+    /**
+     * Constructs a SimpleAccount instance from the given principals and credentials, with the
+     * the assigned roles and permissions.
+     *
+     * @param principals  the identifying attributes of the account, at least one of which should be considered the
+     *                    account's 'primary' identifying attribute, for example, a user id or username.
+     * @param credentials the credentials that verify identity for the account
+     * @param roleNames   the names of the roles assigned to this account.
+     * @param permissions the permissions assigned to this account directly (not those assigned to any of the realms).
+     */
+    public SimpleAccount(PrincipalCollection principals, Object credentials, Set<String> roleNames, Set<Permission> permissions) {
+        this.authcInfo = new SimpleAuthenticationInfo(principals, credentials);
+        this.authzInfo = new SimpleAuthorizationInfo(roleNames);
+        this.authzInfo.setObjectPermissions(permissions);
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Returns the principals, aka the identifying attributes (username, user id, first name, last name, etc) of this
+     * Account.
+     *
+     * @return all the principals, aka the identifying attributes, of this Account.
+     */
+    public PrincipalCollection getPrincipals() {
+        return authcInfo.getPrincipals();
+    }
+
+    /**
+     * Sets the principals, aka the identifying attributes (username, user id, first name, last name, etc) of this
+     * Account.
+     *
+     * @param principals all the principals, aka the identifying attributes, of this Account.
+     * @see Account#getPrincipals()
+     */
+    public void setPrincipals(PrincipalCollection principals) {
+        this.authcInfo.setPrincipals(principals);
+    }
+
+
+    /**
+     * Simply returns <code>this.authcInfo.getCredentials</code>.  The <code>authcInfo</code> attribute is constructed
+     * via the constructors to wrap the input arguments.
+     *
+     * @return this Account's credentials.
+     */
+    public Object getCredentials() {
+        return authcInfo.getCredentials();
+    }
+
+    /**
+     * Sets this Account's credentials that verify one or more of the Account's
+     * {@link #getPrincipals() principals}, such as a password or private key.
+     *
+     * @param credentials the credentials associated with this Account that verify one or more of the Account principals.
+     * @see org.apache.shiro.authc.Account#getCredentials()
+     */
+    public void setCredentials(Object credentials) {
+        this.authcInfo.setCredentials(credentials);
+    }
+
+    /**
+     * Returns the salt used to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     * was used or credentials were not hashed at all.
+     *
+     * @return the salt used to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     *         was used or credentials were not hashed at all.
+     * @since 1.1
+     */
+    public ByteSource getCredentialsSalt() {
+        return this.authcInfo.getCredentialsSalt();
+    }
+
+    /**
+     * Sets the salt to use to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     * is used or credentials are not hashed at all.
+     *
+     * @param salt the salt to use to hash this Account's credentials (eg for password hashing), or {@code null} if no
+     *             salt is used or credentials are not hashed at all.
+     * @since 1.1
+     */
+    public void setCredentialsSalt(ByteSource salt) {
+        this.authcInfo.setCredentialsSalt(salt);
+    }
+
+    /**
+     * Returns <code>this.authzInfo.getRoles();</code>
+     *
+     * @return the Account's assigned roles.
+     */
+    public Collection<String> getRoles() {
+        return authzInfo.getRoles();
+    }
+
+    /**
+     * Sets the Account's assigned roles.  Simply calls <code>this.authzInfo.setRoles(roles)</code>.
+     *
+     * @param roles the Account's assigned roles.
+     * @see Account#getRoles()
+     */
+    public void setRoles(Set<String> roles) {
+        this.authzInfo.setRoles(roles);
+    }
+
+    /**
+     * Adds a role to this Account's set of assigned roles.  Simply delegates to
+     * <code>this.authzInfo.addRole(role)</code>.
+     *
+     * @param role a role to assign to this Account.
+     */
+    public void addRole(String role) {
+        this.authzInfo.addRole(role);
+    }
+
+    /**
+     * Adds one or more roles to this Account's set of assigned roles. Simply delegates to
+     * <code>this.authzInfo.addRoles(roles)</code>.
+     *
+     * @param roles one or more roles to assign to this Account.
+     */
+    public void addRole(Collection<String> roles) {
+        this.authzInfo.addRoles(roles);
+    }
+
+    /**
+     * Returns all String-based permissions assigned to this Account.  Simply delegates to
+     * <code>this.authzInfo.getStringPermissions()</code>.
+     *
+     * @return all String-based permissions assigned to this Account.
+     */
+    public Collection<String> getStringPermissions() {
+        return authzInfo.getStringPermissions();
+    }
+
+    /**
+     * Sets the String-based permissions assigned to this Account.  Simply delegates to
+     * <code>this.authzInfo.setStringPermissions(permissions)</code>.
+     *
+     * @param permissions all String-based permissions assigned to this Account.
+     * @see org.apache.shiro.authc.Account#getStringPermissions()
+     */
+    public void setStringPermissions(Set<String> permissions) {
+        this.authzInfo.setStringPermissions(permissions);
+    }
+
+    /**
+     * Assigns a String-based permission directly to this Account (not to any of its realms).
+     *
+     * @param permission the String-based permission to assign.
+     */
+    public void addStringPermission(String permission) {
+        this.authzInfo.addStringPermission(permission);
+    }
+
+    /**
+     * Assigns one or more string-based permissions directly to this Account (not to any of its realms).
+     *
+     * @param permissions one or more String-based permissions to assign.
+     */
+    public void addStringPermissions(Collection<String> permissions) {
+        this.authzInfo.addStringPermissions(permissions);
+    }
+
+    /**
+     * Returns all object-based permissions assigned directly to this Account (not any of its realms).
+     *
+     * @return all object-based permissions assigned directly to this Account (not any of its realms).
+     */
+    public Collection<Permission> getObjectPermissions() {
+        return authzInfo.getObjectPermissions();
+    }
+
+    /**
+     * Sets all object-based permissions assigned directly to this Account (not any of its realms).
+     *
+     * @param permissions the object-based permissions to assign directly to this Account.
+     */
+    public void setObjectPermissions(Set<Permission> permissions) {
+        this.authzInfo.setObjectPermissions(permissions);
+    }
+
+    /**
+     * Assigns an object-based permission directly to this Account (not any of its realms).
+     *
+     * @param permission the object-based permission to assign directly to this Account (not any of its realms).
+     */
+    public void addObjectPermission(Permission permission) {
+        this.authzInfo.addObjectPermission(permission);
+    }
+
+    /**
+     * Assigns one or more object-based permissions directly to this Account (not any of its realms).
+     *
+     * @param permissions one or more object-based permissions to assign directly to this Account (not any of its realms).
+     */
+    public void addObjectPermissions(Collection<Permission> permissions) {
+        this.authzInfo.addObjectPermissions(permissions);
+    }
+
+    /**
+     * Returns <code>true</code> if this Account is locked and thus cannot be used to login, <code>false</code> otherwise.
+     *
+     * @return <code>true</code> if this Account is locked and thus cannot be used to login, <code>false</code> otherwise.
+     */
+    public boolean isLocked() {
+        return locked;
+    }
+
+    /**
+     * Sets whether or not the account is locked and can be used to login.
+     *
+     * @param locked <code>true</code> if this Account is locked and thus cannot be used to login, <code>false</code> otherwise.
+     */
+    public void setLocked(boolean locked) {
+        this.locked = locked;
+    }
+
+    /**
+     * Returns whether or not the Account's credentials are expired.  This usually indicates that the Subject or an application
+     * administrator would need to change the credentials before the account could be used.
+     *
+     * @return whether or not the Account's credentials are expired.
+     */
+    public boolean isCredentialsExpired() {
+        return credentialsExpired;
+    }
+
+    /**
+     * Sets whether or not the Account's credentials are expired.  A <code>true</code> value indicates that the Subject
+     * or application administrator would need to change their credentials before the account could be used.
+     *
+     * @param credentialsExpired <code>true</code> if this Account's credentials are expired and need to be changed,
+     *                           <code>false</code> otherwise.
+     */
+    public void setCredentialsExpired(boolean credentialsExpired) {
+        this.credentialsExpired = credentialsExpired;
+    }
+
+
+    /**
+     * Merges the specified <code>AuthenticationInfo</code> into this <code>Account</code>.
+     * <p/>
+     * If the specified argument is also an instance of {@link SimpleAccount SimpleAccount}, the
+     * {@link #isLocked()} and {@link #isCredentialsExpired()} attributes are merged (set on this instance) as well
+     * (only if their values are <code>true</code>).
+     *
+     * @param info the <code>AuthenticationInfo</code> to merge into this account.
+     */
+    public void merge(AuthenticationInfo info) {
+        authcInfo.merge(info);
+
+        // Merge SimpleAccount specific info
+        if (info instanceof SimpleAccount) {
+            SimpleAccount otherAccount = (SimpleAccount) info;
+            if (otherAccount.isLocked()) {
+                setLocked(true);
+            }
+
+            if (otherAccount.isCredentialsExpired()) {
+                setCredentialsExpired(true);
+            }
+        }
+    }
+
+    /**
+     * If the {@link #getPrincipals() principals} are not null, returns <code>principals.hashCode()</code>, otherwise
+     * returns 0 (zero).
+     *
+     * @return <code>principals.hashCode()</code> if they are not null, 0 (zero) otherwise.
+     */
+    public int hashCode() {
+        return (getPrincipals() != null ? getPrincipals().hashCode() : 0);
+    }
+
+    /**
+     * Returns <code>true</code> if the specified object is also a {@link SimpleAccount SimpleAccount} and its
+     * {@link #getPrincipals() principals} are equal to this object's <code>principals</code>, <code>false</code> otherwise.
+     *
+     * @param o the object to test for equality.
+     * @return <code>true</code> if the specified object is also a {@link SimpleAccount SimpleAccount} and its
+     *         {@link #getPrincipals() principals} are equal to this object's <code>principals</code>, <code>false</code> otherwise.
+     */
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof SimpleAccount) {
+            SimpleAccount sa = (SimpleAccount) o;
+            //principal should be unique across the application, so only check this for equality:
+            return (getPrincipals() != null ? getPrincipals().equals(sa.getPrincipals()) : sa.getPrincipals() == null);
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@link #getPrincipals() principals}.toString() if they are not null, otherwise prints out the string
+     * "empty"
+     *
+     * @return the String representation of this Account object.
+     */
+    public String toString() {
+        return getPrincipals() != null ? getPrincipals().toString() : "empty";
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java b/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java
new file mode 100644
index 0000000..f0b6ac3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.subject.MutablePrincipalCollection;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * Simple implementation of the {@link org.apache.shiro.authc.MergableAuthenticationInfo} interface that holds the principals and
+ * credentials.
+ *
+ * @see org.apache.shiro.realm.AuthenticatingRealm
+ * @since 0.9
+ */
+public class SimpleAuthenticationInfo implements MergableAuthenticationInfo, SaltedAuthenticationInfo {
+
+    /**
+     * The principals identifying the account associated with this AuthenticationInfo instance.
+     */
+    protected PrincipalCollection principals;
+    /**
+     * The credentials verifying the account principals.
+     */
+    protected Object credentials;
+
+    /**
+     * Any salt used in hashing the credentials.
+     *
+     * @since 1.1
+     */
+    protected ByteSource credentialsSalt;
+
+    /**
+     * Default no-argument constructor.
+     */
+    public SimpleAuthenticationInfo() {
+    }
+
+    /**
+     * Constructor that takes in a single 'primary' principal of the account and its corresponding credentials,
+     * associated with the specified realm.
+     * <p/>
+     * This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
+     * on the {@code principal} and {@code realmName} argument.
+     *
+     * @param principal   the 'primary' principal associated with the specified realm.
+     * @param credentials the credentials that verify the given principal.
+     * @param realmName   the realm from where the principal and credentials were acquired.
+     */
+    public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
+        this.principals = new SimplePrincipalCollection(principal, realmName);
+        this.credentials = credentials;
+    }
+
+    /**
+     * Constructor that takes in a single 'primary' principal of the account, its corresponding hashed credentials,
+     * the salt used to hash the credentials, and the name of the realm to associate with the principals.
+     * <p/>
+     * This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
+     * on the <code>principal</code> and <code>realmName</code> argument.
+     *
+     * @param principal         the 'primary' principal associated with the specified realm.
+     * @param hashedCredentials the hashed credentials that verify the given principal.
+     * @param credentialsSalt   the salt used when hashing the given hashedCredentials
+     * @param realmName         the realm from where the principal and credentials were acquired.
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
+        this.principals = new SimplePrincipalCollection(principal, realmName);
+        this.credentials = hashedCredentials;
+        this.credentialsSalt = credentialsSalt;
+    }
+
+    /**
+     * Constructor that takes in an account's identifying principal(s) and its corresponding credentials that verify
+     * the principals.
+     *
+     * @param principals  a Realm's account's identifying principal(s)
+     * @param credentials the accounts corresponding principals that verify the principals.
+     */
+    public SimpleAuthenticationInfo(PrincipalCollection principals, Object credentials) {
+        this.principals = new SimplePrincipalCollection(principals);
+        this.credentials = credentials;
+    }
+
+    /**
+     * Constructor that takes in an account's identifying principal(s), hashed credentials used to verify the
+     * principals, and the salt used when hashing the credentials.
+     *
+     * @param principals        a Realm's account's identifying principal(s)
+     * @param hashedCredentials the hashed credentials that verify the principals.
+     * @param credentialsSalt   the salt used when hashing the hashedCredentials.
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAuthenticationInfo(PrincipalCollection principals, Object hashedCredentials, ByteSource credentialsSalt) {
+        this.principals = new SimplePrincipalCollection(principals);
+        this.credentials = hashedCredentials;
+        this.credentialsSalt = credentialsSalt;
+    }
+
+
+    public PrincipalCollection getPrincipals() {
+        return principals;
+    }
+
+    /**
+     * Sets the identifying principal(s) represented by this instance.
+     *
+     * @param principals the indentifying attributes of the corresponding Realm account.
+     */
+    public void setPrincipals(PrincipalCollection principals) {
+        this.principals = principals;
+    }
+
+    public Object getCredentials() {
+        return credentials;
+    }
+
+    /**
+     * Sets the credentials that verify the principals/identity of the associated Realm account.
+     *
+     * @param credentials attribute(s) that verify the account's identity/principals, such as a password or private key.
+     */
+    public void setCredentials(Object credentials) {
+        this.credentials = credentials;
+    }
+
+    /**
+     * Returns the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     * hashed at all.
+     * <p/>
+     * Note that this attribute is <em>NOT</em> handled in the
+     * {@link #merge(AuthenticationInfo) merge} method - a hash salt is only useful within a single realm (as each
+     * realm will perform it's own Credentials Matching logic), and once finished in that realm, Shiro has no further
+     * use for salts.  Therefore it doesn't make sense to 'merge' salts in a multi-realm scenario.
+     *
+     * @return the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     *         hashed at all.
+     * @since 1.1
+     */
+    public ByteSource getCredentialsSalt() {
+        return credentialsSalt;
+    }
+
+    /**
+     * Sets the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     * hashed at all.
+     * <p/>
+     * Note that this attribute is <em>NOT</em> handled in the
+     * {@link #merge(AuthenticationInfo) merge} method - a hash salt is only useful within a single realm (as each
+     * realm will perform it's own Credentials Matching logic), and once finished in that realm, Shiro has no further
+     * use for salts.  Therefore it doesn't make sense to 'merge' salts in a multi-realm scenario.
+     *
+     * @param salt the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     *             hashed at all.
+     * @since 1.1
+     */
+    public void setCredentialsSalt(ByteSource salt) {
+        this.credentialsSalt = salt;
+    }
+
+    /**
+     * Takes the specified <code>info</code> argument and adds its principals and credentials into this instance.
+     *
+     * @param info the <code>AuthenticationInfo</code> to add into this instance.
+     */
+    @SuppressWarnings("unchecked")
+    public void merge(AuthenticationInfo info) {
+        if (info == null || info.getPrincipals() == null || info.getPrincipals().isEmpty()) {
+            return;
+        }
+
+        if (this.principals == null) {
+            this.principals = info.getPrincipals();
+        } else {
+            if (!(this.principals instanceof MutablePrincipalCollection)) {
+                this.principals = new SimplePrincipalCollection(this.principals);
+            }
+            ((MutablePrincipalCollection) this.principals).addAll(info.getPrincipals());
+        }
+
+        //only mess with a salt value if we don't have one yet.  It doesn't make sense
+        //to merge salt values from different realms because a salt is used only within
+        //the realm's credential matching process.  But if the current instance's salt
+        //is null, then it can't hurt to pull in a non-null value if one exists.
+        //
+        //since 1.1:
+        if (this.credentialsSalt == null && info instanceof SaltedAuthenticationInfo) {
+            this.credentialsSalt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
+        }
+
+        Object thisCredentials = getCredentials();
+        Object otherCredentials = info.getCredentials();
+
+        if (otherCredentials == null) {
+            return;
+        }
+
+        if (thisCredentials == null) {
+            this.credentials = otherCredentials;
+            return;
+        }
+
+        if (!(thisCredentials instanceof Collection)) {
+            Set newSet = new HashSet();
+            newSet.add(thisCredentials);
+            setCredentials(newSet);
+        }
+
+        // At this point, the credentials should be a collection
+        Collection credentialCollection = (Collection) getCredentials();
+        if (otherCredentials instanceof Collection) {
+            credentialCollection.addAll((Collection) otherCredentials);
+        } else {
+            credentialCollection.add(otherCredentials);
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if the Object argument is an <code>instanceof SimpleAuthenticationInfo</code> and
+     * its {@link #getPrincipals() principals} are equal to this instance's principals, <code>false</code> otherwise.
+     *
+     * @param o the object to compare for equality.
+     * @return <code>true</code> if the Object argument is an <code>instanceof SimpleAuthenticationInfo</code> and
+     *         its {@link #getPrincipals() principals} are equal to this instance's principals, <code>false</code> otherwise.
+     */
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SimpleAuthenticationInfo)) return false;
+
+        SimpleAuthenticationInfo that = (SimpleAuthenticationInfo) o;
+
+        //noinspection RedundantIfStatement
+        if (principals != null ? !principals.equals(that.principals) : that.principals != null) return false;
+
+        return true;
+    }
+
+    /**
+     * Returns the hashcode of the internal {@link #getPrincipals() principals} instance.
+     *
+     * @return the hashcode of the internal {@link #getPrincipals() principals} instance.
+     */
+    public int hashCode() {
+        return (principals != null ? principals.hashCode() : 0);
+    }
+
+    /**
+     * Simple implementation that merely returns <code>{@link #getPrincipals() principals}.toString()</code>
+     *
+     * @return <code>{@link #getPrincipals() principals}.toString()</code>
+     */
+    public String toString() {
+        return principals.toString();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/UnknownAccountException.java b/core/src/main/java/org/apache/shiro/authc/UnknownAccountException.java
new file mode 100644
index 0000000..d243678
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/UnknownAccountException.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * Thrown when attempting to authenticate with a principal that doesn't exist in the system (e.g.
+ * by specifying a username that doesn't relate to a user account).
+ *
+ * <p>Whether or not an application wishes to alert a user logging in to the system of this fact is
+ * at the discretion of those responsible for designing the view and what happens when this
+ * exception occurs.
+ *
+ * @since 0.1
+ */
+public class UnknownAccountException extends AccountException {
+
+    /**
+     * Creates a new UnknownAccountException.
+     */
+    public UnknownAccountException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnknownAccountException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnknownAccountException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnknownAccountException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownAccountException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnknownAccountException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownAccountException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/UsernamePasswordToken.java b/core/src/main/java/org/apache/shiro/authc/UsernamePasswordToken.java
new file mode 100644
index 0000000..25cf4c4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/UsernamePasswordToken.java
@@ -0,0 +1,369 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+/**
+ * <p>A simple username/password authentication token to support the most widely-used authentication mechanism.  This
+ * class also implements the {@link RememberMeAuthenticationToken RememberMeAuthenticationToken} interface to support
+ * "Remember Me" services across user sessions as well as the
+ * {@link org.apache.shiro.authc.HostAuthenticationToken HostAuthenticationToken} interface to retain the host name
+ * or IP address location from where the authentication attempt is occuring.</p>
+ * <p/>
+ * <p>"Remember Me" authentications are disabled by default, but if the application developer wishes to allow
+ * it for a login attempt, all that is necessary is to call {@link #setRememberMe setRememberMe(true)}.  If the underlying
+ * <tt>SecurityManager</tt> implementation also supports <tt>RememberMe</tt> services, the user's identity will be
+ * remembered across sessions.
+ * <p/>
+ * <p>Note that this class stores a password as a char[] instead of a String
+ * (which may seem more logical).  This is because Strings are immutable and their
+ * internal value cannot be overwritten - meaning even a nulled String instance might be accessible in memory at a later
+ * time (e.g. memory dump).  This is not good for sensitive information such as passwords. For more information, see the
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx">
+ * Java Cryptography Extension Reference Guide</a>.</p>
+ * <p/>
+ * <p>To avoid this possibility of later memory access, the application developer should always call
+ * {@link #clear() clear()} after using the token to perform a login attempt.</p>
+ *
+ * @since 0.1
+ */
+public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * The username
+     */
+    private String username;
+
+    /**
+     * The password, in char[] format
+     */
+    private char[] password;
+
+    /**
+     * Whether or not 'rememberMe' should be enabled for the corresponding login attempt;
+     * default is <code>false</code>
+     */
+    private boolean rememberMe = false;
+
+    /**
+     * The location from where the login attempt occurs, or <code>null</code> if not known or explicitly
+     * omitted.
+     */
+    private String host;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * JavaBeans compatible no-arg constructor.
+     */
+    public UsernamePasswordToken() {
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted
+     * during an authentication attempt, with a <tt>null</tt> {@link #getHost() host} and a
+     * <tt>rememberMe</tt> default of <tt>false</tt>.
+     *
+     * @param username the username submitted for authentication
+     * @param password the password character array submitted for authentication
+     */
+    public UsernamePasswordToken(final String username, final char[] password) {
+        this(username, password, false, null);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted
+     * during an authentication attempt, with a <tt>null</tt> {@link #getHost() host} and
+     * a <tt>rememberMe</tt> default of <tt>false</tt>
+     * <p/>
+     * <p>This is a convience constructor and maintains the password internally via a character
+     * array, i.e. <tt>password.toCharArray();</tt>.  Note that storing a password as a String
+     * in your code could have possible security implications as noted in the class JavaDoc.</p>
+     *
+     * @param username the username submitted for authentication
+     * @param password the password string submitted for authentication
+     */
+    public UsernamePasswordToken(final String username, final String password) {
+        this(username, password != null ? password.toCharArray() : null, false, null);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, the
+     * inetAddress from where the attempt is occurring, and a default <tt>rememberMe</tt> value of <tt>false</tt>
+     *
+     * @param username the username submitted for authentication
+     * @param password the password string submitted for authentication
+     * @param host     the host name or IP string from where the attempt is occuring
+     * @since 0.2
+     */
+    public UsernamePasswordToken(final String username, final char[] password, final String host) {
+        this(username, password, false, host);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, the
+     * inetAddress from where the attempt is occurring, and a default <tt>rememberMe</tt> value of <tt>false</tt>
+     * <p/>
+     * <p>This is a convience constructor and maintains the password internally via a character
+     * array, i.e. <tt>password.toCharArray();</tt>.  Note that storing a password as a String
+     * in your code could have possible security implications as noted in the class JavaDoc.</p>
+     *
+     * @param username the username submitted for authentication
+     * @param password the password string submitted for authentication
+     * @param host     the host name or IP string from where the attempt is occuring
+     * @since 1.0
+     */
+    public UsernamePasswordToken(final String username, final String password, final String host) {
+        this(username, password != null ? password.toCharArray() : null, false, host);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, as well as if the user
+     * wishes their identity to be remembered across sessions.
+     *
+     * @param username   the username submitted for authentication
+     * @param password   the password string submitted for authentication
+     * @param rememberMe if the user wishes their identity to be remembered across sessions
+     * @since 0.9
+     */
+    public UsernamePasswordToken(final String username, final char[] password, final boolean rememberMe) {
+        this(username, password, rememberMe, null);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, as well as if the user
+     * wishes their identity to be remembered across sessions.
+     * <p/>
+     * <p>This is a convience constructor and maintains the password internally via a character
+     * array, i.e. <tt>password.toCharArray();</tt>.  Note that storing a password as a String
+     * in your code could have possible security implications as noted in the class JavaDoc.</p>
+     *
+     * @param username   the username submitted for authentication
+     * @param password   the password string submitted for authentication
+     * @param rememberMe if the user wishes their identity to be remembered across sessions
+     * @since 0.9
+     */
+    public UsernamePasswordToken(final String username, final String password, final boolean rememberMe) {
+        this(username, password != null ? password.toCharArray() : null, rememberMe, null);
+    }
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, if the user
+     * wishes their identity to be remembered across sessions, and the inetAddress from where the attempt is ocurring.
+     *
+     * @param username   the username submitted for authentication
+     * @param password   the password character array submitted for authentication
+     * @param rememberMe if the user wishes their identity to be remembered across sessions
+     * @param host       the host name or IP string from where the attempt is occuring
+     * @since 1.0
+     */
+    public UsernamePasswordToken(final String username, final char[] password,
+                                 final boolean rememberMe, final String host) {
+
+        this.username = username;
+        this.password = password;
+        this.rememberMe = rememberMe;
+        this.host = host;
+    }
+
+
+    /**
+     * Constructs a new UsernamePasswordToken encapsulating the username and password submitted, if the user
+     * wishes their identity to be remembered across sessions, and the inetAddress from where the attempt is ocurring.
+     * <p/>
+     * <p>This is a convience constructor and maintains the password internally via a character
+     * array, i.e. <tt>password.toCharArray();</tt>.  Note that storing a password as a String
+     * in your code could have possible security implications as noted in the class JavaDoc.</p>
+     *
+     * @param username   the username submitted for authentication
+     * @param password   the password string submitted for authentication
+     * @param rememberMe if the user wishes their identity to be remembered across sessions
+     * @param host       the host name or IP string from where the attempt is occuring
+     * @since 1.0
+     */
+    public UsernamePasswordToken(final String username, final String password,
+                                 final boolean rememberMe, final String host) {
+        this(username, password != null ? password.toCharArray() : null, rememberMe, host);
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Returns the username submitted during an authentication attempt.
+     *
+     * @return the username submitted during an authentication attempt.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Sets the username for submission during an authentication attempt.
+     *
+     * @param username the username to be used for submission during an authentication attempt.
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+
+    /**
+     * Returns the password submitted during an authentication attempt as a character array.
+     *
+     * @return the password submitted during an authentication attempt as a character array.
+     */
+    public char[] getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the password for submission during an authentication attempt.
+     *
+     * @param password the password to be used for submission during an authentication attemp.
+     */
+    public void setPassword(char[] password) {
+        this.password = password;
+    }
+
+    /**
+     * Simply returns {@link #getUsername() getUsername()}.
+     *
+     * @return the {@link #getUsername() username}.
+     * @see org.apache.shiro.authc.AuthenticationToken#getPrincipal()
+     */
+    public Object getPrincipal() {
+        return getUsername();
+    }
+
+    /**
+     * Returns the {@link #getPassword() password} char array.
+     *
+     * @return the {@link #getPassword() password} char array.
+     * @see org.apache.shiro.authc.AuthenticationToken#getCredentials()
+     */
+    public Object getCredentials() {
+        return getPassword();
+    }
+
+    /**
+     * Returns the host name or IP string from where the authentication attempt occurs.  May be <tt>null</tt> if the
+     * host name/IP is unknown or explicitly omitted.  It is up to the Authenticator implementation processing this
+     * token if an authentication attempt without a host is valid or not.
+     * <p/>
+     * <p>(Shiro's default Authenticator allows <tt>null</tt> hosts to support localhost and proxy server environments).</p>
+     *
+     * @return the host from where the authentication attempt occurs, or <tt>null</tt> if it is unknown or
+     *         explicitly omitted.
+     * @since 1.0
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * Sets the host name or IP string from where the authentication attempt occurs.  It is up to the Authenticator
+     * implementation processing this token if an authentication attempt without a host is valid or not.
+     * <p/>
+     * <p>(Shiro's default Authenticator
+     * allows <tt>null</tt> hosts to allow localhost and proxy server environments).</p>
+     *
+     * @param host the host name or IP string from where the attempt is occuring
+     * @since 1.0
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    /**
+     * Returns <tt>true</tt> if the submitting user wishes their identity (principal(s)) to be remembered
+     * across sessions, <tt>false</tt> otherwise.  Unless overridden, this value is <tt>false</tt> by default.
+     *
+     * @return <tt>true</tt> if the submitting user wishes their identity (principal(s)) to be remembered
+     *         across sessions, <tt>false</tt> otherwise (<tt>false</tt> by default).
+     * @since 0.9
+     */
+    public boolean isRememberMe() {
+        return rememberMe;
+    }
+
+    /**
+     * Sets if the submitting user wishes their identity (pricipal(s)) to be remembered across sessions.  Unless
+     * overridden, the default value is <tt>false</tt>, indicating <em>not</em> to be remembered across sessions.
+     *
+     * @param rememberMe value inidicating if the user wishes their identity (principal(s)) to be remembered across
+     *                   sessions.
+     * @since 0.9
+     */
+    public void setRememberMe(boolean rememberMe) {
+        this.rememberMe = rememberMe;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Clears out (nulls) the username, password, rememberMe, and inetAddress.  The password bytes are explicitly set to
+     * <tt>0x00</tt> before nulling to eliminate the possibility of memory access at a later time.
+     */
+    public void clear() {
+        this.username = null;
+        this.host = null;
+        this.rememberMe = false;
+
+        if (this.password != null) {
+            for (int i = 0; i < password.length; i++) {
+                this.password[i] = 0x00;
+            }
+            this.password = null;
+        }
+
+    }
+
+    /**
+     * Returns the String representation.  It does not include the password in the resulting
+     * string for security reasons to prevent accidentially printing out a password
+     * that might be widely viewable).
+     *
+     * @return the String representation of the <tt>UsernamePasswordToken</tt>, omitting
+     *         the password.
+     */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getName());
+        sb.append(" - ");
+        sb.append(username);
+        sb.append(", rememberMe=").append(rememberMe);
+        if (host != null) {
+            sb.append(" (").append(host).append(")");
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcher.java
new file mode 100644
index 0000000..f16638f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcher.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ * A credentials matcher that always returns {@code true} when matching credentials no matter what arguments
+ * are passed in.  This can be used for testing or when credentials are implicitly trusted for a particular
+ * {@link org.apache.shiro.realm.Realm Realm}.
+ *
+ * @since 0.2
+ */
+public class AllowAllCredentialsMatcher implements CredentialsMatcher {
+
+    /**
+     * Returns <code>true</code> <em>always</em> no matter what the method arguments are.
+     *
+     * @param token   the token submitted for authentication.
+     * @param info    the account being verified for access
+     * @return <code>true</code> <em>always</em>.
+     */
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        return true;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/CredentialsMatcher.java
new file mode 100644
index 0000000..58b7514
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/CredentialsMatcher.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+
+
+/**
+ * Interface implemented by classes that can determine if an AuthenticationToken's provided
+ * credentials matches a corresponding account's credentials stored in the system.
+ *
+ * <p>Simple direct comparisons are handled well by the
+ * {@link SimpleCredentialsMatcher SimpleCredentialsMatcher}.  If you
+ * hash user's credentials before storing them in a realm (a common practice), look at the
+ * {@link HashedCredentialsMatcher HashedCredentialsMatcher} implementations,
+ * as they support this scenario.
+ *
+ * @see SimpleCredentialsMatcher
+ * @see AllowAllCredentialsMatcher
+ * @see Md5CredentialsMatcher
+ * @see Sha1CredentialsMatcher
+ * @since 0.1
+ */
+public interface CredentialsMatcher {
+
+    /**
+     * Returns {@code true} if the provided token credentials match the stored account credentials,
+     * {@code false} otherwise.
+     *
+     * @param token   the {@code AuthenticationToken} submitted during the authentication attempt
+     * @param info the {@code AuthenticationInfo} stored in the system.
+     * @return {@code true} if the provided token credentials match the stored account credentials,
+     *         {@code false} otherwise.
+     */
+    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java b/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
new file mode 100644
index 0000000..d45858e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.DefaultHashService;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.HashRequest;
+import org.apache.shiro.crypto.hash.HashService;
+import org.apache.shiro.crypto.hash.format.*;
+import org.apache.shiro.util.ByteSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default implementation of the {@link PasswordService} interface that relies on an internal
+ * {@link HashService}, {@link HashFormat}, and {@link HashFormatFactory} to function:
+ * <h2>Hashing Passwords</h2>
+ *
+ * <h2>Comparing Passwords</h2>
+ * All hashing operations are performed by the internal {@link #getHashService() hashService}.  After the hash
+ * is computed, it is formatted into a String value via the internal {@link #getHashFormat() hashFormat}.
+ *
+ * @since 1.2
+ */
+public class DefaultPasswordService implements HashingPasswordService {
+
+    public static final String DEFAULT_HASH_ALGORITHM = "SHA-256";
+    public static final int DEFAULT_HASH_ITERATIONS = 500000; //500,000
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultPasswordService.class);
+
+    private HashService hashService;
+    private HashFormat hashFormat;
+    private HashFormatFactory hashFormatFactory;
+
+    private volatile boolean hashFormatWarned; //used to avoid excessive log noise
+
+    public DefaultPasswordService() {
+        this.hashFormatWarned = false;
+
+        DefaultHashService hashService = new DefaultHashService();
+        hashService.setHashAlgorithmName(DEFAULT_HASH_ALGORITHM);
+        hashService.setHashIterations(DEFAULT_HASH_ITERATIONS);
+        hashService.setGeneratePublicSalt(true); //always want generated salts for user passwords to be most secure
+        this.hashService = hashService;
+
+        this.hashFormat = new Shiro1CryptFormat();
+        this.hashFormatFactory = new DefaultHashFormatFactory();
+    }
+
+    public String encryptPassword(Object plaintext) {
+        Hash hash = hashPassword(plaintext);
+        checkHashFormatDurability();
+        return this.hashFormat.format(hash);
+    }
+
+    public Hash hashPassword(Object plaintext) {
+        ByteSource plaintextBytes = createByteSource(plaintext);
+        if (plaintextBytes == null || plaintextBytes.isEmpty()) {
+            return null;
+        }
+        HashRequest request = createHashRequest(plaintextBytes);
+        return hashService.computeHash(request);
+    }
+
+    public boolean passwordsMatch(Object plaintext, Hash saved) {
+        ByteSource plaintextBytes = createByteSource(plaintext);
+
+        if (saved == null || saved.isEmpty()) {
+            return plaintextBytes == null || plaintextBytes.isEmpty();
+        } else {
+            if (plaintextBytes == null || plaintextBytes.isEmpty()) {
+                return false;
+            }
+        }
+
+        HashRequest request = buildHashRequest(plaintextBytes, saved);
+
+        Hash computed = this.hashService.computeHash(request);
+
+        return saved.equals(computed);
+    }
+
+    protected void checkHashFormatDurability() {
+
+        if (!this.hashFormatWarned) {
+
+            HashFormat format = this.hashFormat;
+
+            if (!(format instanceof ParsableHashFormat) && log.isWarnEnabled()) {
+                String msg = "The configured hashFormat instance [" + format.getClass().getName() + "] is not a " +
+                        ParsableHashFormat.class.getName() + " implementation.  This is " +
+                        "required if you wish to support backwards compatibility for saved password checking (almost " +
+                        "always desirable).  Without a " + ParsableHashFormat.class.getSimpleName() + " instance, " +
+                        "any hashService configuration changes will break previously hashed/saved passwords.";
+                log.warn(msg);
+                this.hashFormatWarned = true;
+            }
+        }
+    }
+
+    protected HashRequest createHashRequest(ByteSource plaintext) {
+        return new HashRequest.Builder().setSource(plaintext).build();
+    }
+
+    protected ByteSource createByteSource(Object o) {
+        return ByteSource.Util.bytes(o);
+    }
+
+    public boolean passwordsMatch(Object submittedPlaintext, String saved) {
+        ByteSource plaintextBytes = createByteSource(submittedPlaintext);
+
+        if (saved == null || saved.length() == 0) {
+            return plaintextBytes == null || plaintextBytes.isEmpty();
+        } else {
+            if (plaintextBytes == null || plaintextBytes.isEmpty()) {
+                return false;
+            }
+        }
+
+        //First check to see if we can reconstitute the original hash - this allows us to
+        //perform password hash comparisons even for previously saved passwords that don't
+        //match the current HashService configuration values.  This is a very nice feature
+        //for password comparisons because it ensures backwards compatibility even after
+        //configuration changes.
+        HashFormat discoveredFormat = this.hashFormatFactory.getInstance(saved);
+
+        if (discoveredFormat != null && discoveredFormat instanceof ParsableHashFormat) {
+
+            ParsableHashFormat parsableHashFormat = (ParsableHashFormat)discoveredFormat;
+            Hash savedHash = parsableHashFormat.parse(saved);
+
+            return passwordsMatch(submittedPlaintext, savedHash);
+        }
+
+        //If we're at this point in the method's execution, We couldn't reconstitute the original hash.
+        //So, we need to hash the submittedPlaintext using current HashService configuration and then
+        //compare the formatted output with the saved string.  This will correctly compare passwords,
+        //but does not allow changing the HashService configuration without breaking previously saved
+        //passwords:
+
+        //The saved text value can't be reconstituted into a Hash instance.  We need to format the
+        //submittedPlaintext and then compare this formatted value with the saved value:
+        HashRequest request = createHashRequest(plaintextBytes);
+        Hash computed = this.hashService.computeHash(request);
+        String formatted = this.hashFormat.format(computed);
+
+        return saved.equals(formatted);
+    }
+
+    protected HashRequest buildHashRequest(ByteSource plaintext, Hash saved) {
+        //keep everything from the saved hash except for the source:
+        return new HashRequest.Builder().setSource(plaintext)
+                //now use the existing saved data:
+                .setAlgorithmName(saved.getAlgorithmName())
+                .setSalt(saved.getSalt())
+                .setIterations(saved.getIterations())
+                .build();
+    }
+
+    public HashService getHashService() {
+        return hashService;
+    }
+
+    public void setHashService(HashService hashService) {
+        this.hashService = hashService;
+    }
+
+    public HashFormat getHashFormat() {
+        return hashFormat;
+    }
+
+    public void setHashFormat(HashFormat hashFormat) {
+        this.hashFormat = hashFormat;
+    }
+
+    public HashFormatFactory getHashFormatFactory() {
+        return hashFormatFactory;
+    }
+
+    public void setHashFormatFactory(HashFormatFactory hashFormatFactory) {
+        this.hashFormatFactory = hashFormatFactory;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java
new file mode 100644
index 0000000..a08f6d8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SaltedAuthenticationInfo;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * A {@code HashedCredentialMatcher} provides support for hashing of supplied {@code AuthenticationToken} credentials
+ * before being compared to those in the {@code AuthenticationInfo} from the data store.
+ * <p/>
+ * Credential hashing is one of the most common security techniques when safeguarding a user's private credentials
+ * (passwords, keys, etc).  Most developers never want to store their users' credentials in plain form, viewable by
+ * anyone, so they often hash the users' credentials before they are saved in the data store.
+ * <p/>
+ * This class (and its subclasses) function as follows:
+ * <ol>
+ * <li>Hash the {@code AuthenticationToken} credentials supplied by the user during their login.</li>
+ * <li>Compare this hashed value directly with the {@code AuthenticationInfo} credentials stored in the system
+ * (the stored account credentials are expected to already be in hashed form).</li>
+ * <li>If these two values are {@link #equals(Object, Object) equal}, the submitted credentials match, otherwise
+ * they do not.</li>
+ * </ol>
+ * <h2>Salting and Multiple Hash Iterations</h2>
+ * Because simple hashing is usually not good enough for secure applications, this class also supports 'salting'
+ * and multiple hash iterations.  Please read this excellent
+ * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a> to learn about
+ * salting and multiple iterations and why you might want to use them. (Note of sections 5
+ * "Why add salt?" and 6 "Hardening against the attacker's attack").   We should also note here that all of
+ * Shiro's Hash implementations (for example, {@link org.apache.shiro.crypto.hash.Md5Hash Md5Hash},
+ * {@link org.apache.shiro.crypto.hash.Sha1Hash Sha1Hash}, etc) support salting and multiple hash iterations via
+ * overloaded constructors.
+ * <h4>Real World Case Study</h4>
+ * In April 2010, some public Atlassian Jira and Confluence
+ * installations (Apache Software Foundation, Codehaus, etc) were the target of account attacks and user accounts
+ * were compromised.  The reason?  Jira and Confluence at the time did not salt user passwords and attackers were
+ * able to use dictionary attacks to compromise user accounts (Atlassian has since
+ * <a href="http://blogs.atlassian.com/news/2010/04/oh_man_what_a_day_an_update_on_our_security_breach.html">
+ * fixed the problem</a> of course).
+ * <p/>
+ * The lesson?
+ * <p/>
+ * <b>ALWAYS, ALWAYS, ALWAYS SALT USER PASSWORDS!</b>
+ * <p/>
+ * <h3>Salting</h3>
+ * Prior to Shiro 1.1, salts could be obtained based on the end-user submitted
+ * {@link AuthenticationToken AuthenticationToken} via the now-deprecated
+ * {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getSalt(AuthenticationToken)} method.  This however
+ * could constitute a security hole since ideally salts should never be obtained based on what a user can submit.
+ * User-submitted salt mechanisms are <em>much</em> more susceptible to dictionary attacks and <b>SHOULD NOT</b> be
+ * used in secure systems.  Instead salts should ideally be a secure randomly-generated number that is generated when
+ * the user account is created.  The secure number should never be disseminated to the user and always kept private
+ * by the application.
+ * <h4>Shiro 1.1</h4>
+ * As of Shiro 1.1, it is expected that any salt used to hash the submitted credentials will be obtained from the
+ * stored account information (represented as an {@link AuthenticationInfo AuthenticationInfo} instance).  This is much
+ * more secure because the salt value remains private to the application (Shiro will never store this value).
+ * <p/>
+ * To enable this, {@code Realm}s should return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances
+ * during authentication.  {@code HashedCredentialsMatcher} implementations will then use the provided
+ * {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt credentialsSalt} for hashing.  To avoid
+ * security risks,
+ * <b>it is highly recommended that any existing {@code Realm} implementations that support hashed credentials are
+ * updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as soon as possible</b>.
+ * <h4>Shiro 1.0 Backwards Compatibility</h4>
+ * Because of the identified security risk, {@code Realm} implementations that support credentials hashing should
+ * be updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as
+ * soon as possible.
+ * <p/>
+ * If this is not possible for some reason, this class will retain 1.0 backwards-compatible behavior of obtaining
+ * the salt via the now-deprecated {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} method.  This
+ * method will only be invoked if a {@code Realm} <em>does not</em> return
+ * {@link SaltedAuthenticationInfo SaltedAutenticationInfo} instances and {@link #isHashSalted() hashSalted} is
+ * {@code true}.
+ * But please note that the {@link #isHashSalted() hashSalted} property and the
+ * {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} methods will be removed before the Shiro 2.0
+ * release.
+ * <h3>Multiple Hash Iterations</h3>
+ * If you hash your users' credentials multiple times before persisting to the data store, you will also need to
+ * set this class's {@link #setHashIterations(int) hashIterations} property.  See the
+ * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a>'s
+ * <a href="http://www.owasp.org/index.php/Hashing_Java#Hardening_against_the_attacker.27s_attack">
+ * "Hardening against the attacker's attack"</a> section to learn more about why you might want to use
+ * multiple hash iterations.
+ * <h2>MD5 & SHA-1 Notice</h2>
+ * <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
+ * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to
+ * compromise and/or collisions (read the linked pages for more).  While most applications are ok with either of these
+ * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
+ * supporting {@code CredentialsMatcher} implementations.
+ *
+ * @see org.apache.shiro.crypto.hash.Md5Hash
+ * @see org.apache.shiro.crypto.hash.Sha1Hash
+ * @see org.apache.shiro.crypto.hash.Sha256Hash
+ * @since 0.9
+ */
+public class HashedCredentialsMatcher extends SimpleCredentialsMatcher {
+
+    /**
+     * @since 1.1
+     */
+    private String hashAlgorithm;
+    private int hashIterations;
+    private boolean hashSalted;
+    private boolean storedCredentialsHexEncoded;
+
+    /**
+     * JavaBeans-compatibile no-arg constructor intended for use in IoC/Dependency Injection environments.  If you
+     * use this constructor, you <em>MUST</em> also additionally set the
+     * {@link #setHashAlgorithmName(String) hashAlgorithmName} property.
+     */
+    public HashedCredentialsMatcher() {
+        this.hashAlgorithm = null;
+        this.hashSalted = false;
+        this.hashIterations = 1;
+        this.storedCredentialsHexEncoded = true; //false means Base64-encoded
+    }
+
+    /**
+     * Creates an instance using the specified {@link #getHashAlgorithmName() hashAlgorithmName} to hash submitted
+     * credentials.
+     * @param hashAlgorithmName the {@code Hash} {@link org.apache.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName}
+     *                          to use when performing hashes for credentials matching.
+     * @since 1.1
+     */
+    public HashedCredentialsMatcher(String hashAlgorithmName) {
+        this();
+        if (!StringUtils.hasText(hashAlgorithmName) ) {
+            throw new IllegalArgumentException("hashAlgorithmName cannot be null or empty.");
+        }
+        this.hashAlgorithm = hashAlgorithmName;
+    }
+
+    /**
+     * Returns the {@code Hash} {@link org.apache.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use
+     * when performing hashes for credentials matching.
+     *
+     * @return the {@code Hash} {@link org.apache.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use
+     *         when performing hashes for credentials matching.
+     * @since 1.1
+     */
+    public String getHashAlgorithmName() {
+        return hashAlgorithm;
+    }
+
+    /**
+     * Sets the {@code Hash} {@link org.apache.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName} to use
+     * when performing hashes for credentials matching.
+     *
+     * @param hashAlgorithmName the {@code Hash} {@link org.apache.shiro.crypto.hash.Hash#getAlgorithmName() algorithmName}
+     *                          to use when performing hashes for credentials matching.
+     * @since 1.1
+     */
+    public void setHashAlgorithmName(String hashAlgorithmName) {
+        this.hashAlgorithm = hashAlgorithmName;
+    }
+
+    /**
+     * Returns {@code true} if the system's stored credential hash is Hex encoded, {@code false} if it
+     * is Base64 encoded.
+     * <p/>
+     * Default value is {@code true} for convenience - all of Shiro's {@link Hash Hash#toString()}
+     * implementations return Hex encoded values by default, making this class's use with those implementations
+     * easier.
+     *
+     * @return {@code true} if the system's stored credential hash is Hex encoded, {@code false} if it
+     *         is Base64 encoded.  Default is {@code true}
+     */
+    public boolean isStoredCredentialsHexEncoded() {
+        return storedCredentialsHexEncoded;
+    }
+
+    /**
+     * Sets the indicator if this system's stored credential hash is Hex encoded or not.
+     * <p/>
+     * A value of {@code true} will cause this class to decode the system credential from Hex, a
+     * value of {@code false} will cause this class to decode the system credential from Base64.
+     * <p/>
+     * Unless overridden via this method, the default value is {@code true} for convenience - all of Shiro's
+     * {@link Hash Hash#toString()} implementations return Hex encoded values by default, making this class's use with
+     * those implementations easier.
+     *
+     * @param storedCredentialsHexEncoded the indicator if this system's stored credential hash is Hex
+     *                                    encoded or not ('not' automatically implying it is Base64 encoded).
+     */
+    public void setStoredCredentialsHexEncoded(boolean storedCredentialsHexEncoded) {
+        this.storedCredentialsHexEncoded = storedCredentialsHexEncoded;
+    }
+
+    /**
+     * Returns {@code true} if a submitted {@code AuthenticationToken}'s credentials should be salted when hashing,
+     * {@code false} if it should not be salted.
+     * <p/>
+     * If enabled, the salt used will be obtained via the {@link #getSalt(AuthenticationToken) getSalt} method.
+     * <p/>
+     * The default value is {@code false}.
+     *
+     * @return {@code true} if a submitted {@code AuthenticationToken}'s credentials should be salted when hashing,
+     *         {@code false} if it should not be salted.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.
+     *             <p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
+     */
+    @Deprecated
+    public boolean isHashSalted() {
+        return hashSalted;
+    }
+
+    /**
+     * Sets whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing.
+     * <p/>
+     * If enabled, the salt used will be obtained via the {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getCredentialsSalt} method.
+     * </p>
+     * The default value is {@code false}.
+     *
+     * @param hashSalted whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.
+     *             <p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
+     */
+    @Deprecated
+    public void setHashSalted(boolean hashSalted) {
+        this.hashSalted = hashSalted;
+    }
+
+    /**
+     * Returns the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before
+     * comparing to the credentials stored in the system.
+     * <p/>
+     * Unless overridden, the default value is {@code 1}, meaning a normal hash execution will occur.
+     *
+     * @return the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before
+     *         comparing to the credentials stored in the system.
+     */
+    public int getHashIterations() {
+        return hashIterations;
+    }
+
+    /**
+     * Sets the number of times a submitted {@code AuthenticationToken}'s credentials will be hashed before comparing
+     * to the credentials stored in the system.
+     * <p/>
+     * Unless overridden, the default value is {@code 1}, meaning a normal single hash execution will occur.
+     * <p/>
+     * If this argument is less than 1 (i.e. 0 or negative), the default value of 1 is applied.  There must always be
+     * at least 1 hash iteration (otherwise there would be no hash).
+     *
+     * @param hashIterations the number of times to hash a submitted {@code AuthenticationToken}'s credentials.
+     */
+    public void setHashIterations(int hashIterations) {
+        if (hashIterations < 1) {
+            this.hashIterations = 1;
+        } else {
+            this.hashIterations = hashIterations;
+        }
+    }
+
+    /**
+     * Returns a salt value used to hash the token's credentials.
+     * <p/>
+     * This default implementation merely returns {@code token.getPrincipal()}, effectively using the user's
+     * identity (username, user id, etc) as the salt, a most common technique.  If you wish to provide the
+     * authentication token's salt another way, you may override this method.
+     *
+     * @param token the AuthenticationToken submitted during the authentication attempt.
+     * @return a salt value to use to hash the authentication token's credentials.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.<p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
+     */
+    @Deprecated
+    protected Object getSalt(AuthenticationToken token) {
+        return token.getPrincipal();
+    }
+
+    /**
+     * Returns a {@link Hash Hash} instance representing the already-hashed AuthenticationInfo credentials stored in the system.
+     * <p/>
+     * This method reconstructs a {@link Hash Hash} instance based on a {@code info.getCredentials} call,
+     * but it does <em>not</em> hash that value - it is expected that method call will return an already-hashed value.
+     * <p/>
+     * This implementation's reconstruction effort functions as follows:
+     * <ol>
+     * <li>Convert {@code account.getCredentials()} to a byte array via the {@link #toBytes toBytes} method.
+     * <li>If {@code account.getCredentials()} was originally a String or char[] before {@code toBytes} was
+     * called, check for encoding:
+     * <li>If {@link #storedCredentialsHexEncoded storedCredentialsHexEncoded}, Hex decode that byte array, otherwise
+     * Base64 decode the byte array</li>
+     * <li>Set the byte[] array directly on the {@code Hash} implementation and return it.</li>
+     * </ol>
+     *
+     * @param info the AuthenticationInfo from which to retrieve the credentials which assumed to be in already-hashed form.
+     * @return a {@link Hash Hash} instance representing the given AuthenticationInfo's stored credentials.
+     */
+    protected Object getCredentials(AuthenticationInfo info) {
+        Object credentials = info.getCredentials();
+
+        byte[] storedBytes = toBytes(credentials);
+
+        if (credentials instanceof String || credentials instanceof char[]) {
+            //account.credentials were a char[] or String, so
+            //we need to do text decoding first:
+            if (isStoredCredentialsHexEncoded()) {
+                storedBytes = Hex.decode(storedBytes);
+            } else {
+                storedBytes = Base64.decode(storedBytes);
+            }
+        }
+        AbstractHash hash = newHashInstance();
+        hash.setBytes(storedBytes);
+        return hash;
+    }
+
+    /**
+     * This implementation first hashes the {@code token}'s credentials, potentially using a
+     * {@code salt} if the {@code info} argument is a
+     * {@link org.apache.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo}.  It then compares the hash
+     * against the {@code AuthenticationInfo}'s
+     * {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) already-hashed credentials}.  This method
+     * returns {@code true} if those two values are {@link #equals(Object, Object) equal}, {@code false} otherwise.
+     *
+     * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
+     * @param info  the {@code AuthenticationInfo} stored in the system matching the token principal
+     * @return {@code true} if the provided token credentials hash match to the stored account credentials hash,
+     *         {@code false} otherwise
+     * @since 1.1
+     */
+    @Override
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        Object tokenHashedCredentials = hashProvidedCredentials(token, info);
+        Object accountCredentials = getCredentials(info);
+        return equals(tokenHashedCredentials, accountCredentials);
+    }
+
+    /**
+     * Hash the provided {@code token}'s credentials using the salt stored with the account if the
+     * {@code info} instance is an {@code instanceof} {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} (see
+     * the class-level JavaDoc for why this is the preferred approach).
+     * <p/>
+     * If the {@code info} instance is <em>not</em>
+     * an {@code instanceof} {@code SaltedAuthenticationInfo}, the logic will fall back to Shiro 1.0
+     * backwards-compatible logic:  it will first check to see {@link #isHashSalted() isHashSalted} and if so, will try
+     * to acquire the salt from {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)}.  See the class-level
+     * JavaDoc for why this is not recommended.  This 'fallback' logic exists only for backwards-compatibility.
+     * {@code Realm}s should be updated as soon as possible to return {@code SaltedAuthenticationInfo} instances
+     * if account credentials salting is enabled (highly recommended for password-based systems).
+     *
+     * @param token the submitted authentication token from which its credentials will be hashed
+     * @param info  the stored account data, potentially used to acquire a salt
+     * @return the token credentials hash
+     * @since 1.1
+     */
+    protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
+        Object salt = null;
+        if (info instanceof SaltedAuthenticationInfo) {
+            salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
+        } else {
+            //retain 1.0 backwards compatibility:
+            if (isHashSalted()) {
+                salt = getSalt(token);
+            }
+        }
+        return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
+    }
+
+    /**
+     * Returns the {@link #getHashAlgorithmName() hashAlgorithmName} property, but will throw an
+     * {@link IllegalStateException} if it has not been set.
+     *
+     * @return the required {@link #getHashAlgorithmName() hashAlgorithmName} property
+     * @throws IllegalStateException if the property has not been set prior to calling this method.
+     * @since 1.1
+     */
+    private String assertHashAlgorithmName() throws IllegalStateException {
+        String hashAlgorithmName = getHashAlgorithmName();
+        if (hashAlgorithmName == null) {
+            String msg = "Required 'hashAlgorithmName' property has not been set.  This is required to execute " +
+                    "the hashing algorithm.";
+            throw new IllegalStateException(msg);
+        }
+        return hashAlgorithmName;
+    }
+
+    /**
+     * Hashes the provided credentials a total of {@code hashIterations} times, using the given salt.  The hash
+     * implementation/algorithm used is based on the {@link #getHashAlgorithmName() hashAlgorithmName} property.
+     *
+     * @param credentials    the submitted authentication token's credentials to hash
+     * @param salt           the value to salt the hash, or {@code null} if a salt will not be used.
+     * @param hashIterations the number of times to hash the credentials.  At least one hash will always occur though,
+     *                       even if this argument is 0 or negative.
+     * @return the hashed value of the provided credentials, according to the specified salt and hash iterations.
+     */
+    protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
+        String hashAlgorithmName = assertHashAlgorithmName();
+        return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
+    }
+
+    /**
+     * Returns a new, <em>uninitialized</em> instance, without its byte array set.  Used as a utility method in the
+     * {@link SimpleCredentialsMatcher#getCredentials(org.apache.shiro.authc.AuthenticationInfo) getCredentials(AuthenticationInfo)} implementation.
+     *
+     * @return a new, <em>uninitialized</em> instance, without its byte array set.
+     */
+    protected AbstractHash newHashInstance() {
+        String hashAlgorithmName = assertHashAlgorithmName();
+        return new SimpleHash(hashAlgorithmName);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/HashingPasswordService.java b/core/src/main/java/org/apache/shiro/authc/credential/HashingPasswordService.java
new file mode 100644
index 0000000..121626a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/HashingPasswordService.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code HashingPasswordService} is a {@link PasswordService} that performs password encryption and comparisons
+ * based on cryptographic {@link Hash}es.
+ *
+ * @since 1.2
+ */
+public interface HashingPasswordService extends PasswordService {
+
+    /**
+     * Hashes the specified plaintext password using internal hashing configuration settings pertinent to password
+     * hashing.
+     * <p/>
+     * Note
+     * that this method is only likely to be used in more complex environments that wish to format and/or save the
+     * returned {@code Hash} object in a custom manner.  Most applications will find the
+     * {@link #encryptPassword(Object) encryptPassword} method suitable enough for safety
+     * and ease-of-use.
+     * <h3>Usage</h3>
+     * The input argument type can be any 'byte backed' {@code Object} - almost always either a
+     * String or character array representing passwords (character arrays are often a safer way to represent passwords
+     * as they can be cleared/nulled-out after use.  Any argument type supported by
+     * {@link ByteSource.Util#isCompatible(Object)} is valid.
+     * <p/>
+     * Regardless of your choice of using Strings or character arrays to represent submitted passwords, you can wrap
+     * either as a {@code ByteSource} by using {@link ByteSource.Util}, for example, when the passwords are captured as
+     * Strings:
+     * <pre>
+     * ByteSource passwordBytes = ByteSource.Util.bytes(submittedPasswordString);
+     * Hash hashedPassword = hashingPasswordService.hashPassword(passwordBytes);
+     * </pre>
+     * or, identically, when captured as a character array:
+     * <pre>
+     * ByteSource passwordBytes = ByteSource.Util.bytes(submittedPasswordCharacterArray);
+     * Hash hashedPassword = hashingPasswordService.hashPassword(passwordBytes);
+     * </pre>
+     *
+     * @param plaintext the raw password as 'byte-backed' object (String, character array, {@link ByteSource},
+     *                  etc) usually acquired from your application's 'new user' or 'password reset' workflow.
+     * @return the hashed password.
+     * @throws IllegalArgumentException if the argument cannot be easily converted to bytes as defined by
+     *                                  {@link ByteSource.Util#isCompatible(Object)}.
+     * @see ByteSource.Util#isCompatible(Object)
+     * @see #encryptPassword(Object)
+     */
+    Hash hashPassword(Object plaintext) throws IllegalArgumentException;
+
+    /**
+     * Returns {@code true} if the {@code submittedPlaintext} password matches the existing {@code savedPasswordHash},
+     * {@code false} otherwise.  Note that this method is only likely to be used in more complex environments that
+     * save hashes in a custom manner.  Most applications will find the
+     * {@link #passwordsMatch(Object, String) passwordsMatch(plaintext,string)} method
+     * sufficient if {@link #encryptPassword(Object) encrypting passwords as Strings}.
+     * <h3>Usage</h3>
+     * The {@code submittedPlaintext} argument type can be any 'byte backed' {@code Object} - almost always either a
+     * String or character array representing passwords (character arrays are often a safer way to represent passwords
+     * as they can be cleared/nulled-out after use.  Any argument type supported by
+     * {@link ByteSource.Util#isCompatible(Object)} is valid.
+     *
+     * @param plaintext a raw/plaintext password submitted by an end user/Subject.
+     * @param savedPasswordHash  the previously hashed password known to be associated with an account.
+     *                           This value is expected to have been previously generated from the
+     *                           {@link #hashPassword(Object) hashPassword} method (typically
+     *                           when the account is created or the account's password is reset).
+     * @return {@code true} if the {@code plaintext} password matches the existing {@code savedPasswordHash},
+     *         {@code false} otherwise.
+     */
+    boolean passwordsMatch(Object plaintext, Hash savedPasswordHash);
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Md2CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Md2CredentialsMatcher.java
new file mode 100644
index 0000000..c968df5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Md2CredentialsMatcher.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Md2Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * MD2 hashed.
+ * <p/>
+ * <b>Note:</b> the MD2, <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
+ * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to
+ * compromise and/or collisions (read the linked pages for more).  While most applications are ok with either of these
+ * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
+ * supporting <code>CredentialsMatcher</code> implementations.</p>
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+ at Deprecated
+public class Md2CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Md2CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Md2Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java
new file mode 100644
index 0000000..81b8f13
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Md5Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * MD5 hashed.
+ * <p/>
+ * <b>Note:</b> <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
+ * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to
+ * compromise and/or collisions (read the linked pages for more).  While most applications are ok with either of these
+ * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
+ * supporting <code>CredentialsMatcher</code> implementations.</p>
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+public class Md5CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Md5CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
new file mode 100644
index 0000000..e687dcc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * A {@link CredentialsMatcher} that employs best-practices comparisons for hashed text passwords.
+ * <p/>
+ * This implementation delegates to an internal {@link PasswordService} to perform the actual password
+ * comparison.  This class is essentially a bridge between the generic CredentialsMatcher interface and the
+ * more specific {@code PasswordService} component.
+ *
+ * @since 1.2
+ */
+public class PasswordMatcher implements CredentialsMatcher {
+
+    private PasswordService passwordService;
+
+    public PasswordMatcher() {
+        this.passwordService = new DefaultPasswordService();
+    }
+
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+
+        PasswordService service = ensurePasswordService();
+
+        Object submittedPassword = getSubmittedPassword(token);
+        Object storedCredentials = getStoredPassword(info);
+        assertStoredCredentialsType(storedCredentials);
+
+        if (storedCredentials instanceof Hash) {
+            Hash hashedPassword = (Hash)storedCredentials;
+            HashingPasswordService hashingService = assertHashingPasswordService(service);
+            return hashingService.passwordsMatch(submittedPassword, hashedPassword);
+        }
+        //otherwise they are a String (asserted in the 'assertStoredCredentialsType' method call above):
+        String formatted = (String)storedCredentials;
+        return passwordService.passwordsMatch(submittedPassword, formatted);
+    }
+
+    private HashingPasswordService assertHashingPasswordService(PasswordService service) {
+        if (service instanceof HashingPasswordService) {
+            return (HashingPasswordService) service;
+        }
+        String msg = "AuthenticationInfo's stored credentials are a Hash instance, but the " +
+                "configured passwordService is not a " +
+                HashingPasswordService.class.getName() + " instance.  This is required to perform Hash " +
+                "object password comparisons.";
+        throw new IllegalStateException(msg);
+    }
+
+    private PasswordService ensurePasswordService() {
+        PasswordService service = getPasswordService();
+        if (service == null) {
+            String msg = "Required PasswordService has not been configured.";
+            throw new IllegalStateException(msg);
+        }
+        return service;
+    }
+
+    protected Object getSubmittedPassword(AuthenticationToken token) {
+        return token != null ? token.getCredentials() : null;
+    }
+
+    private void assertStoredCredentialsType(Object credentials) {
+        if (credentials instanceof String || credentials instanceof Hash) {
+            return;
+        }
+
+        String msg = "Stored account credentials are expected to be either a " +
+                Hash.class.getName() + " instance or a formatted hash String.";
+        throw new IllegalArgumentException(msg);
+    }
+
+    protected Object getStoredPassword(AuthenticationInfo storedAccountInfo) {
+        Object stored = storedAccountInfo != null ? storedAccountInfo.getCredentials() : null;
+        //fix for https://issues.apache.org/jira/browse/SHIRO-363
+        if (stored instanceof char[]) {
+            stored = new String((char[])stored);
+        }
+        return stored;
+    }
+
+    public PasswordService getPasswordService() {
+        return passwordService;
+    }
+
+    public void setPasswordService(PasswordService passwordService) {
+        this.passwordService = passwordService;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java b/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
new file mode 100644
index 0000000..bd35600
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code PasswordService} supports common use cases when using passwords as a credentials mechanism.
+ * <p/>
+ * Most importantly, implementations of this interface are expected to employ best-practices to ensure that
+ * passwords remain as safe as possible in application environments.
+ * <h2>Usage</h2>
+ * A {@code PasswordService} is used at two different times during an application's lifecycle:
+ * <ul>
+ * <li>When creating a user account or resetting their password</li>
+ * <li>When a user logs in, when passwords must be compared</li>
+ * </ul>
+ * <h3>Account Creation or Password Reset</h3>
+ * Whenever you create a new user account or reset that account's password, we must translate the end-user submitted
+ * raw/plaintext password value to a string format that is much safer to store.  You do that by calling the
+ * {@link #encryptPassword(Object)} method to create the safer value.  For
+ * example:
+ * <pre>
+ * String submittedPlaintextPassword = ...
+ * String encryptedValue = passwordService.encryptPassword(submittedPlaintextPassword);
+ * ...
+ * userAccount.setPassword(encryptedValue);
+ * userAccount.save(); //create or update to your data store
+ * </pre>
+ * Be sure to save this encrypted password in your data store and never the original/raw submitted password.
+ * <h3>Login Password Comparison</h3>
+ * Shiro performs the comparison during login automatically.  Along with your {@code PasswordService}, you just
+ * have to configure a {@link PasswordMatcher} on a realm that has password-based accounts.   During a login attempt,
+ * shiro will use the {@code PasswordMatcher} and the {@code PasswordService} to automatically compare submitted
+ * passwords.
+ * <p/>
+ * For example, if using Shiro's INI, here is how you might configure the PasswordMatcher and PasswordService:
+ * <pre>
+ * [main]
+ * ...
+ * passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
+ * # configure the passwordService to use the settings you desire
+ * ...
+ * passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
+ * passwordMatcher.passwordService = $passwordService
+ * ...
+ * # Finally, set the matcher on a realm that requires password matching for account authentication:
+ * myRealm = ...
+ * myRealm.credentialsMatcher = $passwordMatcher
+ * </pre>
+ *
+ * @see DefaultPasswordService
+ * @see PasswordMatcher
+ * @since 1.2
+ */
+public interface PasswordService {
+
+    /**
+     * Converts the specified plaintext password (usually acquired from your application's 'new user' or 'password reset'
+     * workflow) into a formatted string safe for storage.  The returned string can be safely saved with the
+     * corresponding user account record (e.g. as a 'password' attribute).
+     * <p/>
+     * It is expected that the String returned from this method will be presented to the
+     * {@link #passwordsMatch(Object, String) passwordsMatch(plaintext,encrypted)} method when performing a
+     * password comparison check.
+     * <h3>Usage</h3>
+     * The input argument type can be any 'byte backed' {@code Object} - almost always either a
+     * String or character array representing passwords (character arrays are often a safer way to represent passwords
+     * as they can be cleared/nulled-out after use.  Any argument type supported by
+     * {@link ByteSource.Util#isCompatible(Object)} is valid.
+     * <p/>
+     * For example:
+     * <pre>
+     * String rawPassword = ...
+     * String encryptedValue = passwordService.encryptPassword(rawPassword);
+     * </pre>
+     * or, identically:
+     * <pre>
+     * char[] rawPasswordChars = ...
+     * String encryptedValue = passwordService.encryptPassword(rawPasswordChars);
+     * </pre>
+     * <p/>
+     * The resulting {@code encryptedValue} should be stored with the account to be retrieved later during a
+     * login attempt.  For example:
+     * <pre>
+     * String encryptedValue = passwordService.encryptPassword(rawPassword);
+     * ...
+     * userAccount.setPassword(encryptedValue);
+     * userAccount.save(); //create or update to your data store
+     * </pre>
+     *
+     * @param plaintextPassword the raw password as 'byte-backed' object (String, character array, {@link ByteSource},
+     *                          etc) usually acquired from your application's 'new user' or 'password reset' workflow.
+     * @return the encrypted password, formatted for storage.
+     * @throws IllegalArgumentException if the argument cannot be easily converted to bytes as defined by
+     *                                  {@link ByteSource.Util#isCompatible(Object)}.
+     * @see ByteSource.Util#isCompatible(Object)
+     */
+    String encryptPassword(Object plaintextPassword) throws IllegalArgumentException;
+
+    /**
+     * Returns {@code true} if the {@code submittedPlaintext} password matches the existing {@code saved} password,
+     * {@code false} otherwise.
+     * <h3>Usage</h3>
+     * The {@code submittedPlaintext} argument type can be any 'byte backed' {@code Object} - almost always either a
+     * String or character array representing passwords (character arrays are often a safer way to represent passwords
+     * as they can be cleared/nulled-out after use.  Any argument type supported by
+     * {@link ByteSource.Util#isCompatible(Object)} is valid.
+     * <p/>
+     * For example:
+     * <pre>
+     * String submittedPassword = ...
+     * passwordService.passwordsMatch(submittedPassword, encryptedPassword);
+     * </pre>
+     * or similarly:
+     * <pre>
+     * char[] submittedPasswordCharacters = ...
+     * passwordService.passwordsMatch(submittedPasswordCharacters, encryptedPassword);
+     * </pre>
+     *
+     * @param submittedPlaintext a raw/plaintext password submitted by an end user/Subject.
+     * @param encrypted          the previously encrypted password known to be associated with an account.
+     *                           This value is expected to have been previously generated from the
+     *                           {@link #encryptPassword(Object) encryptPassword} method (typically
+     *                           when the account is created or the account's password is reset).
+     * @return {@code true} if the {@code submittedPlaintext} password matches the existing {@code saved} password,
+     *         {@code false} otherwise.
+     * @see ByteSource.Util#isCompatible(Object)
+     */
+    boolean passwordsMatch(Object submittedPlaintext, String encrypted);
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java
new file mode 100644
index 0000000..6cdd328
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Sha1Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * SHA hashed.
+ * <p/>
+ * <b>Note:</b> <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
+ * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to
+ * compromise and/or collisions (read the linked pages for more).  While most applications are ok with either of these
+ * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
+ * supporting <code>CredentialsMatcher</code> implementations.</p>
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+public class Sha1CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Sha1CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Sha1Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcher.java
new file mode 100644
index 0000000..be84edc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcher.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Sha256Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * SHA-256 hashed.
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+public class Sha256CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Sha256CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcher.java
new file mode 100644
index 0000000..ec47578
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcher.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Sha384Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * SHA-384 hashed.
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+public class Sha384CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Sha384CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Sha384Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcher.java
new file mode 100644
index 0000000..5989b23
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcher.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.Sha512Hash;
+
+
+/**
+ * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
+ * SHA-512 hashed.
+ *
+ * @since 0.9
+ * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
+ *             {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
+ */
+public class Sha512CredentialsMatcher extends HashedCredentialsMatcher {
+
+    public Sha512CredentialsMatcher() {
+        super();
+        setHashAlgorithmName(Sha512Hash.ALGORITHM_NAME);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/SimpleCredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/SimpleCredentialsMatcher.java
new file mode 100644
index 0000000..70972fd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/SimpleCredentialsMatcher.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.codec.CodecSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+
+
+/**
+ * Simple CredentialsMatcher implementation.  Supports direct (plain) comparison for credentials of type
+ * byte[], char[], and Strings, and if the arguments do not match these types, then reverts back to simple
+ * <code>Object.equals</code> comparison.
+ * <p/>
+ * <p>Hashing comparisons (the most common technique used in secure applications) are not supported by this class, but
+ * instead by the {@link org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher}.
+ *
+ * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher
+ * @since 0.9
+ */
+public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {
+
+    private static final Logger log = LoggerFactory.getLogger(SimpleCredentialsMatcher.class);
+
+    /**
+     * Returns the {@code token}'s credentials.
+     * <p/>
+     * <p>This default implementation merely returns
+     * {@link AuthenticationToken#getCredentials() authenticationToken.getCredentials()} and exists as a template hook
+     * if subclasses wish to obtain the credentials in a different way or convert them to a different format before
+     * returning.
+     *
+     * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
+     * @return the {@code token}'s associated credentials.
+     */
+    protected Object getCredentials(AuthenticationToken token) {
+        return token.getCredentials();
+    }
+
+    /**
+     * Returns the {@code account}'s credentials.
+     * <p/>
+     * <p>This default implementation merely returns
+     * {@link AuthenticationInfo#getCredentials() account.getCredentials()} and exists as a template hook if subclasses
+     * wish to obtain the credentials in a different way or convert them to a different format before
+     * returning.
+     *
+     * @param info the {@code AuthenticationInfo} stored in the data store to be compared against the submitted authentication
+     *             token's credentials.
+     * @return the {@code account}'s associated credentials.
+     */
+    protected Object getCredentials(AuthenticationInfo info) {
+        return info.getCredentials();
+    }
+
+    /**
+     * Returns {@code true} if the {@code tokenCredentials} argument is logically equal to the
+     * {@code accountCredentials} argument.
+     * <p/>
+     * <p>If both arguments are either a byte array (byte[]), char array (char[]) or String, they will be both be
+     * converted to raw byte arrays via the {@link #toBytes toBytes} method first, and then resulting byte arrays
+     * are compared via {@link Arrays#equals(byte[], byte[]) Arrays.equals(byte[],byte[])}.</p>
+     * <p/>
+     * <p>If either argument cannot be converted to a byte array as described, a simple Object <code>equals</code>
+     * comparison is made.</p>
+     * <p/>
+     * <p>Subclasses should override this method for more explicit equality checks.
+     *
+     * @param tokenCredentials   the {@code AuthenticationToken}'s associated credentials.
+     * @param accountCredentials the {@code AuthenticationInfo}'s stored credentials.
+     * @return {@code true} if the {@code tokenCredentials} are equal to the {@code accountCredentials}.
+     */
+    protected boolean equals(Object tokenCredentials, Object accountCredentials) {
+        if (log.isDebugEnabled()) {
+            log.debug("Performing credentials equality check for tokenCredentials of type [" +
+                    tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
+                    accountCredentials.getClass().getName() + "]");
+        }
+        if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
+            if (log.isDebugEnabled()) {
+                log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing " +
+                        "array equals comparison");
+            }
+            byte[] tokenBytes = toBytes(tokenCredentials);
+            byte[] accountBytes = toBytes(accountCredentials);
+            return Arrays.equals(tokenBytes, accountBytes);
+        } else {
+            return accountCredentials.equals(tokenCredentials);
+        }
+    }
+
+    /**
+     * This implementation acquires the {@code token}'s credentials
+     * (via {@link #getCredentials(AuthenticationToken) getCredentials(token)})
+     * and then the {@code account}'s credentials
+     * (via {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) getCredentials(account)}) and then passes both of
+     * them to the {@link #equals(Object,Object) equals(tokenCredentials, accountCredentials)} method for equality
+     * comparison.
+     *
+     * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
+     * @param info  the {@code AuthenticationInfo} stored in the system matching the token principal.
+     * @return {@code true} if the provided token credentials are equal to the stored account credentials,
+     *         {@code false} otherwise
+     */
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        Object tokenCredentials = getCredentials(token);
+        Object accountCredentials = getCredentials(info);
+        return equals(tokenCredentials, accountCredentials);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/package-info.java b/core/src/main/java/org/apache/shiro/authc/credential/package-info.java
new file mode 100644
index 0000000..6531eb0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/credential/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support for validating <em>credentials</em> (such as passwords or X509 certificates) during
+ * authentication via the {@link org.apache.shiro.authc.credential.CredentialsMatcher CredentialsMatcher}
+ * interface and its supporting implementations.
+ */
+package org.apache.shiro.authc.credential;
diff --git a/core/src/main/java/org/apache/shiro/authc/package-info.java b/core/src/main/java/org/apache/shiro/authc/package-info.java
new file mode 100644
index 0000000..35b8b05
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Core interfaces and exceptions concerning Authentication (the act of logging-in).
+ * <p/>
+ * Shiro abbreviates the word 'AuthentiCation' as <tt>authc</tt> to distinguish it seperately from
+ * 'AuthoriZation', abbreviated as <tt>authz</tt>.
+ * <p/>
+ * The primary item of interest in this package is the <tt>Authenticator</tt> interface, which acts as the
+ * entry point (facade) to all other other authentication components. Other components, interfaces and
+ * exceptions are here to support <tt>Authenticator</tt> implementations.
+ */
+package org.apache.shiro.authc;
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java
new file mode 100644
index 0000000..55b7a20
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.realm.Realm;
+
+import java.util.Collection;
+
+
+/**
+ * Abstract base implementation for Shiro's concrete <code>AuthenticationStrategy</code>
+ * implementations.
+ *
+ * @since 0.9
+ */
+public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy {
+
+    /**
+     * Simply returns <code>new {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo}();</code>, which supports
+     * aggregating account data across realms.
+     */
+    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
+        return new SimpleAuthenticationInfo();
+    }
+
+    /**
+     * Simply returns the <code>aggregate</code> method argument, without modification.
+     */
+    public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
+        return aggregate;
+    }
+
+    /**
+     * Base implementation that will aggregate the specified <code>singleRealmInfo</code> into the
+     * <code>aggregateInfo</code> and then returns the aggregate.  Can be overridden by subclasses for custom behavior.
+     */
+    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
+        AuthenticationInfo info;
+        if (singleRealmInfo == null) {
+            info = aggregateInfo;
+        } else {
+            if (aggregateInfo == null) {
+                info = singleRealmInfo;
+            } else {
+                info = merge(singleRealmInfo, aggregateInfo);
+            }
+        }
+
+        return info;
+    }
+
+    /**
+     * Merges the specified <code>info</code> argument into the <code>aggregate</code> argument and then returns an
+     * aggregate for continued use throughout the login process.
+     * <p/>
+     * This implementation merely checks to see if the specified <code>aggregate</code> argument is an instance of
+     * {@link org.apache.shiro.authc.MergableAuthenticationInfo MergableAuthenticationInfo}, and if so, calls
+     * <code>aggregate.merge(info)</code>  If it is <em>not</em> an instance of
+     * <code>MergableAuthenticationInfo</code>, an {@link IllegalArgumentException IllegalArgumentException} is thrown.
+     * Can be overridden by subclasses for custom merging behavior if implementing the
+     * {@link org.apache.shiro.authc.MergableAuthenticationInfo MergableAuthenticationInfo} is not desired for some reason.
+     */
+    protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
+        if( aggregate instanceof MergableAuthenticationInfo ) {
+            ((MergableAuthenticationInfo)aggregate).merge(info);
+            return aggregate;
+        } else {
+            throw new IllegalArgumentException( "Attempt to merge authentication info from multiple realms, but aggregate " +
+                      "AuthenticationInfo is not of type MergableAuthenticationInfo." );
+        }
+    }
+
+    /**
+     * Simply returns the <code>aggregate</code> argument without modification.  Can be overridden for custom behavior.
+     */
+    public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
+        return aggregate;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AllSuccessfulStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AllSuccessfulStrategy.java
new file mode 100644
index 0000000..a6dbbfe
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/AllSuccessfulStrategy.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UnknownAccountException;
+import org.apache.shiro.realm.Realm;
+
+
+/**
+ * <tt>AuthenticationStrategy</tt> implementation that requires <em>all</em> configured realms to
+ * <b>successfully</b> process the submitted <tt>AuthenticationToken</tt> during the log-in attempt.
+ * <p/>
+ * <p>If one or more realms do not support the submitted token, or one or more are unable to acquire
+ * <tt>AuthenticationInfo</tt> for the token, this implementation will immediately fail the log-in attempt for the
+ * associated subject (user).
+ *
+ * @since 0.2
+ */
+public class AllSuccessfulStrategy extends AbstractAuthenticationStrategy {
+
+    /** Private class log instance. */
+    private static final Logger log = LoggerFactory.getLogger(AllSuccessfulStrategy.class);
+
+    /**
+     * Because all realms in this strategy must complete successfully, this implementation ensures that the given
+     * <code>Realm</code> {@link org.apache.shiro.realm.Realm#supports(org.apache.shiro.authc.AuthenticationToken) supports} the given
+     * <code>token</code> argument.  If it does not, this method throws an
+     * {@link UnsupportedTokenException UnsupportedTokenException} to end the authentication
+     * process immediately. If the realm does support the token, the <code>info</code> argument is returned immediately.
+     */
+    public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
+        if (!realm.supports(token)) {
+            String msg = "Realm [" + realm + "] of type [" + realm.getClass().getName() + "] does not support " +
+                    " the submitted AuthenticationToken [" + token + "].  The [" + getClass().getName() +
+                    "] implementation requires all configured realm(s) to support and be able to process the submitted " +
+                    "AuthenticationToken.";
+            throw new UnsupportedTokenException(msg);
+        }
+
+        return info;
+    }
+
+    /**
+     * Merges the specified <code>info</code> into the <code>aggregate</code> argument and returns it (just as the
+     * parent implementation does), but additionally ensures the following:
+     * <ol>
+     * <li>if the <code>Throwable</code> argument is not <code>null</code>, re-throws it to immediately cancel the
+     * authentication process, since this strategy requires all realms to authenticate successfully.</li>
+     * <li>neither the <code>info</code> or <code>aggregate</code> argument is <code>null</code> to ensure that each
+     * realm did in fact authenticate successfully</li>
+     * </ol>
+     */
+    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t)
+            throws AuthenticationException {
+        if (t != null) {
+            if (t instanceof AuthenticationException) {
+                //propagate:
+                throw ((AuthenticationException) t);
+            } else {
+                String msg = "Unable to acquire account data from realm [" + realm + "].  The [" +
+                        getClass().getName() + " implementation requires all configured realm(s) to operate successfully " +
+                        "for a successful authentication.";
+                throw new AuthenticationException(msg, t);
+            }
+        }
+        if (info == null) {
+            String msg = "Realm [" + realm + "] could not find any associated account data for the submitted " +
+                    "AuthenticationToken [" + token + "].  The [" + getClass().getName() + "] implementation requires " +
+                    "all configured realm(s) to acquire valid account data for a submitted token during the " +
+                    "log-in process.";
+            throw new UnknownAccountException(msg);
+        }
+
+        log.debug("Account successfully authenticated using realm [{}]", realm);
+
+        // If non-null account is returned, then the realm was able to authenticate the
+        // user - so merge the account with any accumulated before:
+        merge(info, aggregate);
+
+        return aggregate;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AtLeastOneSuccessfulStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AtLeastOneSuccessfulStrategy.java
new file mode 100644
index 0000000..8744586
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/AtLeastOneSuccessfulStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.util.CollectionUtils;
+
+/**
+ * <tt>AuthenticationStrategy</tt> implementation that requires <em>at least one</em> configured realm to
+ * successfully process the submitted <tt>AuthenticationToken</tt> during the log-in attempt.
+ * <p/>
+ * <p>This means any number of configured realms do not have to support the submitted log-in token, or they may
+ * be unable to acquire <tt>AuthenticationInfo</tt> for the token, but as long as at least one can do both, this
+ * Strategy implementation will allow the log-in process to be successful.
+ * <p/>
+ * <p>Note that this implementation will aggregate the account data from <em>all</em> successfully consulted
+ * realms during the authentication attempt. If you want only the account data from the first successfully
+ * consulted realm and want to ignore all subsequent realms, use the
+ * {@link FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy} instead.
+ *
+ * @see FirstSuccessfulStrategy FirstSuccessfulAuthenticationStrategy
+ * @since 0.2
+ */
+public class AtLeastOneSuccessfulStrategy extends AbstractAuthenticationStrategy {
+
+    /**
+     * Ensures that the <code>aggregate</code> method argument is not <code>null</code> and
+     * <code>aggregate.{@link org.apache.shiro.authc.AuthenticationInfo#getPrincipals() getPrincipals()}</code>
+     * is not <code>null</code>, and if either is <code>null</code>, throws an AuthenticationException to indicate
+     * that none of the realms authenticated successfully.
+     */
+    public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
+        //we know if one or more were able to succesfully authenticate if the aggregated account object does not
+        //contain null or empty data:
+        if (aggregate == null || CollectionUtils.isEmpty(aggregate.getPrincipals())) {
+            throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
+                    "could not be authenticated by any configured realms.  Please ensure that at least one realm can " +
+                    "authenticate these tokens.");
+        }
+
+        return aggregate;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java
new file mode 100644
index 0000000..0b2410e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.realm.Realm;
+
+import java.util.Collection;
+
+
+/**
+ * A {@code AuthenticationStrategy} implementation assists the {@link ModularRealmAuthenticator} during the
+ * log-in process in a pluggable realm (PAM) environment.
+ *
+ * <p>The {@code ModularRealmAuthenticator} will consult implementations of this interface on what to do during each
+ * interaction with the configured Realms.  This allows a pluggable strategy of whether or not an authentication
+ * attempt must be successful for all realms, only 1 or more realms, no realms, etc.
+ *
+ * @see AllSuccessfulStrategy
+ * @see AtLeastOneSuccessfulStrategy
+ * @see FirstSuccessfulStrategy
+ * @since 0.2
+ */
+public interface AuthenticationStrategy {
+
+    /**
+     * Method invoked by the ModularAuthenticator signifying that the authentication process is about to begin for the
+     * specified {@code token} - called before any {@code Realm} is actually invoked.
+     *
+     * <p>The {@code AuthenticationInfo} object returned from this method is essentially an empty place holder for
+     * aggregating account data across multiple realms.  It should be populated by the strategy implementation over the
+     * course of authentication attempts across the multiple realms.  It will be passed into the
+     * {@link #beforeAttempt} calls, allowing inspection of the aggregated account data up to that point in the
+     * multi-realm authentication, allowing any logic to be executed accordingly.
+     *
+     * @param realms the Realms that will be consulted during the authentication process for the specified token.
+     * @param token  the Principal/Credential representation to be used during authentication for a corresponding subject.
+     * @return an empty AuthenticationInfo object that will populated with data from multiple realms.
+     * @throws AuthenticationException if the strategy implementation does not wish the Authentication attempt to execute.
+     */
+    AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException;
+
+    /**
+     * Method invoked by the ModularAuthenticator just prior to the realm being consulted for account data,
+     * allowing pre-authentication-attempt logic for that realm only.
+     *
+     * <p>This method returns an {@code AuthenticationInfo} object that will be used for further interaction with realms.  Most
+     * implementations will merely return the {@code aggregate} method argument if they don't have a need to
+     * manipulate it.
+     *
+     * @param realm     the realm that will be consulted for {@code AuthenticationInfo} for the specified {@code token}.
+     * @param token     the {@code AuthenticationToken} submitted for the subject attempting system log-in.
+     * @param aggregate the aggregated AuthenticationInfo object being used across the multi-realm authentication attempt
+     * @return the AuthenticationInfo object that will be presented to further realms in the authentication process - returning
+     *         the {@code aggregate} method argument is the normal case if no special action needs to be taken.
+     * @throws org.apache.shiro.authc.AuthenticationException
+     *          an exception thrown by the Strategy implementation if it wishes the login
+     *          process for the associated subject (user) to stop immediately.
+     */
+    AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
+
+    /**
+     * Method invoked by the ModularAuthenticator just after the given realm has been consulted for authentication,
+     * allowing post-authentication-attempt logic for that realm only.
+     *
+     * <p>This method returns an {@code AuthenticationInfo} object that will be used for further interaction with realms.  Most
+     * implementations will merge the {@code singleRealmInfo} into the {@code aggregateInfo} and
+     * just return the {@code aggregateInfo} for continued use throughout the authentication process.</p>
+     *
+     * @param realm           the realm that was just consulted for {@code AuthenticationInfo} for the given {@code token}.
+     * @param token           the {@code AuthenticationToken} submitted for the subject attempting system log-in.
+     * @param singleRealmInfo the info returned from a single realm.
+     * @param aggregateInfo   the aggregate info representing all realms in a multi-realm environment.
+     * @param t               the Throwable thrown by the Realm during the attempt, or {@code null} if the method returned normally.
+     * @return the AuthenticationInfo object that will be presented to further realms in the authentication process - returning
+     *         the {@code aggregateAccount} method argument is the normal case if no special action needs to be taken.
+     * @throws AuthenticationException an exception thrown by the Strategy implementation if it wishes the login process
+     *                                 for the associated subject (user) to stop immediately.
+     */
+    AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)
+            throws AuthenticationException;
+
+    /**
+     * Method invoked by the ModularAuthenticator signifying that all of its configured Realms have been consulted
+     * for account data, allowing post-proccessing after all realms have completed.
+     *
+     * <p>Returns the final AuthenticationInfo object that will be returned from the Authenticator to the authenticate() caller.
+     * This is most likely the aggregate AuthenticationInfo object that has been populated by many realms, but the actual return value is
+     * always up to the implementation.
+     *
+     * @param token     the {@code AuthenticationToken} submitted for the subject attempting system log-in.
+     * @param aggregate the aggregate {@code AuthenticationInfo} instance populated by all realms during the log-in attempt.
+     * @return the final {@code AuthenticationInfo} object to return to the Authenticator.authenticate() caller.
+     * @throws AuthenticationException if the Strategy implementation wishes to fail the authentication attempt.
+     */
+    AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/FirstSuccessfulStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/FirstSuccessfulStrategy.java
new file mode 100644
index 0000000..fc66714
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/FirstSuccessfulStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.util.CollectionUtils;
+
+import java.util.Collection;
+
+/**
+ * {@link AuthenticationStrategy} implementation that only accepts the account data from
+ * the first successfully consulted Realm and ignores all subsequent realms.  This is slightly
+ * different behavior than {@link AtLeastOneSuccessfulStrategy}, so please review both to see
+ * which one meets your needs better.
+ *
+ * @see AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy
+ * @since 0.9
+ */
+public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy {
+
+    /**
+     * Returns {@code null} immediately, relying on this class's {@link #merge merge} implementation to return
+     * only the first {@code info} object it encounters, ignoring all subsequent ones.
+     */
+    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
+        return null;
+    }
+
+    /**
+     * Returns the specified {@code aggregate} instance if is non null and valid (that is, has principals and they are
+     * not empty) immediately, or, if it is null or not valid, the {@code info} argument is returned instead.
+     * <p/>
+     * This logic ensures that the first valid info encountered is the one retained and all subsequent ones are ignored,
+     * since this strategy mandates that only the info from the first successfully authenticated realm be used.
+     */
+    protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
+        if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {
+            return aggregate;
+        }
+        return info != null ? info : aggregate;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java b/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java
new file mode 100644
index 0000000..3632431
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+/**
+ * A {@code ModularRealmAuthenticator} delgates account lookups to a pluggable (modular) collection of
+ * {@link Realm}s.  This enables PAM (Pluggable Authentication Module) behavior in Shiro.
+ * In addition to authorization duties, a Shiro Realm can also be thought of a PAM 'module'.
+ * <p/>
+ * Using this Authenticator allows you to "plug-in" your own
+ * {@code Realm}s as you see fit.  Common realms are those based on accessing
+ * LDAP, relational databases, file systems, etc.
+ * <p/>
+ * If only one realm is configured (this is often the case for most applications), authentication success is naturally
+ * only dependent upon invoking this one Realm's
+ * {@link Realm#getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} method.
+ * <p/>
+ * But if two or more realms are configured, PAM behavior is implemented by iterating over the collection of realms
+ * and interacting with each over the course of the authentication attempt.  As this is more complicated, this
+ * authenticator allows customized behavior for interpreting what happens when interacting with multiple realms - for
+ * example, you might require all realms to be successful during the attempt, or perhaps only at least one must be
+ * successful, or some other interpretation.  This customized behavior can be performed via the use of a
+ * {@link #setAuthenticationStrategy(AuthenticationStrategy) AuthenticationStrategy}, which
+ * you can inject as a property of this class.
+ * <p/>
+ * The strategy object provides callback methods that allow you to
+ * determine what constitutes a success or failure in a multi-realm (PAM) scenario.  And because this only makes sense
+ * in a mult-realm scenario, the strategy object is only utilized when more than one Realm is configured.
+ * <p/>
+ * As most multi-realm applications require at least one Realm authenticates successfully, the default
+ * implementation is the {@link AtLeastOneSuccessfulStrategy}.
+ *
+ * @see #setRealms
+ * @see AtLeastOneSuccessfulStrategy
+ * @see AllSuccessfulStrategy
+ * @see FirstSuccessfulStrategy
+ * @since 0.1
+ */
+public class ModularRealmAuthenticator extends AbstractAuthenticator {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * List of realms that will be iterated through when a user authenticates.
+     */
+    private Collection<Realm> realms;
+
+    /**
+     * The authentication strategy to use during authentication attempts, defaults to a
+     * {@link org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy} instance.
+     */
+    private AuthenticationStrategy authenticationStrategy;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * Default no-argument constructor which
+     * {@link #setAuthenticationStrategy(AuthenticationStrategy) enables}  an
+     * {@link org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy} by default.
+     */
+    public ModularRealmAuthenticator() {
+        this.authenticationStrategy = new AtLeastOneSuccessfulStrategy();
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Sets all realms used by this Authenticator, providing PAM (Pluggable Authentication Module) configuration.
+     *
+     * @param realms the realms to consult during authentication attempts.
+     */
+    public void setRealms(Collection<Realm> realms) {
+        this.realms = realms;
+    }
+
+    /**
+     * Returns the realm(s) used by this {@code Authenticator} during an authentication attempt.
+     *
+     * @return the realm(s) used by this {@code Authenticator} during an authentication attempt.
+     */
+    protected Collection<Realm> getRealms() {
+        return this.realms;
+    }
+
+    /**
+     * Returns the {@code AuthenticationStrategy} utilized by this modular authenticator during a multi-realm
+     * log-in attempt.  This object is only used when two or more Realms are configured.
+     * <p/>
+     * Unless overridden by
+     * the {@link #setAuthenticationStrategy(AuthenticationStrategy)} method, the default implementation
+     * is the {@link org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy}.
+     *
+     * @return the {@code AuthenticationStrategy} utilized by this modular authenticator during a log-in attempt.
+     * @since 0.2
+     */
+    public AuthenticationStrategy getAuthenticationStrategy() {
+        return authenticationStrategy;
+    }
+
+    /**
+     * Allows overriding the default {@code AuthenticationStrategy} utilized during multi-realm log-in attempts.
+     * This object is only used when two or more Realms are configured.
+     *
+     * @param authenticationStrategy the strategy implementation to use during log-in attempts.
+     * @since 0.2
+     */
+    public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
+        this.authenticationStrategy = authenticationStrategy;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+
+    /**
+     * Used by the internal {@link #doAuthenticate} implementation to ensure that the {@code realms} property
+     * has been set.  The default implementation ensures the property is not null and not empty.
+     *
+     * @throws IllegalStateException if the {@code realms} property is configured incorrectly.
+     */
+
+    protected void assertRealmsConfigured() throws IllegalStateException {
+        Collection<Realm> realms = getRealms();
+        if (CollectionUtils.isEmpty(realms)) {
+            String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
+                    "present to execute an authentication attempt.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    /**
+     * Performs the authentication attempt by interacting with the single configured realm, which is significantly
+     * simpler than performing multi-realm logic.
+     *
+     * @param realm the realm to consult for AuthenticationInfo.
+     * @param token the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
+     * @return the AuthenticationInfo associated with the user account corresponding to the specified {@code token}
+     */
+    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
+        if (!realm.supports(token)) {
+            String msg = "Realm [" + realm + "] does not support authentication token [" +
+                    token + "].  Please ensure that the appropriate Realm implementation is " +
+                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
+            throw new UnsupportedTokenException(msg);
+        }
+        AuthenticationInfo info = realm.getAuthenticationInfo(token);
+        if (info == null) {
+            String msg = "Realm [" + realm + "] was unable to find account data for the " +
+                    "submitted AuthenticationToken [" + token + "].";
+            throw new UnknownAccountException(msg);
+        }
+        return info;
+    }
+
+    /**
+     * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
+     * as each realm is consulted for {@code AuthenticationInfo} for the specified {@code token}.
+     *
+     * @param realms the multiple realms configured on this Authenticator instance.
+     * @param token  the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
+     * @return an aggregated AuthenticationInfo instance representing account data across all the successfully
+     *         consulted realms.
+     */
+    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
+
+        AuthenticationStrategy strategy = getAuthenticationStrategy();
+
+        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
+
+        if (log.isTraceEnabled()) {
+            log.trace("Iterating through {} realms for PAM authentication", realms.size());
+        }
+
+        for (Realm realm : realms) {
+
+            aggregate = strategy.beforeAttempt(realm, token, aggregate);
+
+            if (realm.supports(token)) {
+
+                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
+
+                AuthenticationInfo info = null;
+                Throwable t = null;
+                try {
+                    info = realm.getAuthenticationInfo(token);
+                } catch (Throwable throwable) {
+                    t = throwable;
+                    if (log.isDebugEnabled()) {
+                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
+                        log.debug(msg, t);
+                    }
+                }
+
+                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
+
+            } else {
+                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
+            }
+        }
+
+        aggregate = strategy.afterAllAttempts(token, aggregate);
+
+        return aggregate;
+    }
+
+
+    /**
+     * Attempts to authenticate the given token by iterating over the internal collection of
+     * {@link Realm}s.  For each realm, first the {@link Realm#supports(org.apache.shiro.authc.AuthenticationToken)}
+     * method will be called to determine if the realm supports the {@code authenticationToken} method argument.
+     * <p/>
+     * If a realm does support
+     * the token, its {@link Realm#getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)}
+     * method will be called.  If the realm returns a non-null account, the token will be
+     * considered authenticated for that realm and the account data recorded.  If the realm returns {@code null},
+     * the next realm will be consulted.  If no realms support the token or all supporting realms return null,
+     * an {@link AuthenticationException} will be thrown to indicate that the user could not be authenticated.
+     * <p/>
+     * After all realms have been consulted, the information from each realm is aggregated into a single
+     * {@link AuthenticationInfo} object and returned.
+     *
+     * @param authenticationToken the token containing the authentication principal and credentials for the
+     *                            user being authenticated.
+     * @return account information attributed to the authenticated user.
+     * @throws IllegalStateException   if no realms have been configured at the time this method is invoked
+     * @throws AuthenticationException if the user could not be authenticated or the user is denied authentication
+     *                                 for the given principal and credentials.
+     */
+    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
+        assertRealmsConfigured();
+        Collection<Realm> realms = getRealms();
+        if (realms.size() == 1) {
+            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
+        } else {
+            return doMultiRealmAuthentication(realms, authenticationToken);
+        }
+    }
+
+    /**
+     * First calls <code>super.onLogout(principals)</code> to ensure a logout notification is issued, and for each
+     * wrapped {@code Realm} that implements the {@link LogoutAware LogoutAware} interface, calls
+     * <code>((LogoutAware)realm).onLogout(principals)</code> to allow each realm the opportunity to perform
+     * logout/cleanup operations during an user-logout.
+     * <p/>
+     * Shiro's Realm implementations all implement the {@code LogoutAware} interface by default and can be
+     * overridden for realm-specific logout logic.
+     *
+     * @param principals the application-specific Subject/user identifier.
+     */
+    public void onLogout(PrincipalCollection principals) {
+        super.onLogout(principals);
+        Collection<Realm> realms = getRealms();
+        if (!CollectionUtils.isEmpty(realms)) {
+            for (Realm realm : realms) {
+                if (realm instanceof LogoutAware) {
+                    ((LogoutAware) realm).onLogout(principals);
+                }
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/UnsupportedTokenException.java b/core/src/main/java/org/apache/shiro/authc/pam/UnsupportedTokenException.java
new file mode 100644
index 0000000..ddbe830
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/UnsupportedTokenException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import org.apache.shiro.authc.AuthenticationException;
+
+
+/**
+ * Exception thrown during the authentication process when an
+ * {@link org.apache.shiro.authc.AuthenticationToken AuthenticationToken} implementation is encountered that is not
+ * supported by one or more configured {@link org.apache.shiro.realm.Realm Realm}s.
+ *
+ * @see org.apache.shiro.authc.pam.AuthenticationStrategy
+ * @since 0.2
+ */
+public class UnsupportedTokenException extends AuthenticationException {
+
+    /**
+     * Creates a new UnsupportedTokenException.
+     */
+    public UnsupportedTokenException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnsupportedTokenException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnsupportedTokenException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnsupportedTokenException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnsupportedTokenException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnsupportedTokenException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnsupportedTokenException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authc/pam/package-info.java b/core/src/main/java/org/apache/shiro/authc/pam/package-info.java
new file mode 100644
index 0000000..1f20962
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authc/pam/package-info.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support for <em>PAM</em>, or <b>P</b>luggable <b>A</b>uthentication <b>M</b>odules, which is
+ * the capability to authenticate a user against multiple configurable (pluggable) <em>modules</em> (Shiro
+ * calls these {@link org.apache.shiro.realm.Realm Realm}s).
+ * <p/>
+ * The primary class of interest here is the {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}
+ * which is an <code>Authenticator</code> implementation that coordinates authentication attempts across
+ * one or more Realm instances.
+ * <p/>
+ * How the <code>ModularRealmAuthenticator</code> actually coordinates this behavior is configurable based on your
+ * application's needs using an injectible
+ * {@link AuthenticationStrategy}.
+ */
+package org.apache.shiro.authc.pam;
diff --git a/core/src/main/java/org/apache/shiro/authz/AuthorizationException.java b/core/src/main/java/org/apache/shiro/authz/AuthorizationException.java
new file mode 100644
index 0000000..41ccce9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/AuthorizationException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Exception thrown if there is a problem during authorization (access control check).
+ *
+ * @since 0.1
+ */
+public class AuthorizationException extends ShiroException
+{
+
+    /**
+     * Creates a new AuthorizationException.
+     */
+    public AuthorizationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new AuthorizationException.
+     *
+     * @param message the reason for the exception
+     */
+    public AuthorizationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new AuthorizationException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public AuthorizationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new AuthorizationException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public AuthorizationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/AuthorizationInfo.java b/core/src/main/java/org/apache/shiro/authz/AuthorizationInfo.java
new file mode 100644
index 0000000..17b699c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/AuthorizationInfo.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * <code>AuthorizationInfo</code> represents a single Subject's stored authorization data (roles, permissions, etc)
+ * used during authorization (access control) checks only.
+ * <p/>
+ * Roles are represented as a <code>Collection</code> of Strings
+ * ({@link java.util.Collection Collection}<{@link String String}>), typically each element being the Role name.
+ * <p/>
+ * {@link Permission Permission}s are provided in two ways:
+ * <ul>
+ * <li>A <code>Collection</code> of Strings, where each String can usually be converted into <code>Permission</code>
+ * objects by a <code>Realm</code>'s
+ * {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver}</li>
+ * <li>A <code>Collection</code> of {@link Permission Permission} objects</li>
+ * </ul>
+ * Both permission collections together represent the total aggregate collection of permissions.  You may use one
+ * or both depending on your preference and needs.
+ * <p/>
+ * Because the act of authorization (access control) is orthoganal to authentication (log-in), this interface is
+ * intended to represent only the account data needed by Shiro during an access control check
+ * (role, permission, etc).  Shiro also has a parallel
+ * {@link org.apache.shiro.authc.AuthenticationInfo AuthenticationInfo} interface for use during the authentication
+ * process that represents identity data such as principals and credentials.
+ * <p/>
+ * Because many if not most {@link org.apache.shiro.realm.Realm Realm}s store both sets of data for a Subject, it might be
+ * convenient for a <code>Realm</code> implementation to utilize an implementation of the
+ * {@link org.apache.shiro.authc.Account Account} interface instead, which is a convenience interface that combines both
+ * <code>AuthenticationInfo</code> and <code>AuthorizationInfo</code>.  Whether you choose to implement these two
+ * interfaces separately or implement the one <code>Account</code> interface for a given <code>Realm</code> is
+ * entirely based on your application's needs or your preferences.
+ *
+ * @see org.apache.shiro.authc.AuthenticationInfo AuthenticationInfo
+ * @see org.apache.shiro.authc.Account
+ * @since 0.9
+ */
+public interface AuthorizationInfo extends Serializable {
+
+    /**
+     * Returns the names of all roles assigned to a corresponding Subject.
+     *
+     * @return the names of all roles assigned to a corresponding Subject.
+     */
+    Collection<String> getRoles();
+
+    /**
+     * Returns all string-based permissions assigned to the corresponding Subject.  The permissions here plus those
+     * returned from {@link #getObjectPermissions() getObjectPermissions()} represent the total set of permissions
+     * assigned.  The aggregate set is used to perform a permission authorization check.
+     * <p/>
+     * This method is a convenience mechanism that allows Realms to represent permissions as Strings if they choose.
+     * When performing a security check, a <code>Realm</code> usually converts these strings to object
+     * {@link Permission Permission}s via an internal
+     * {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver}
+     * in order to perform the actual permission check.  This is not a requirement of course, since <code>Realm</code>s
+     * can perform security checks in whatever manner deemed necessary, but this explains the conversion mechanism that
+     * most Shiro Realms execute for string-based permission checks.
+     *
+     * @return all string-based permissions assigned to the corresponding Subject.
+     */
+    Collection<String> getStringPermissions();
+
+    /**
+     * Returns all type-safe {@link Permission Permission}s assigned to the corresponding Subject.  The permissions
+     * returned from this method plus any returned from {@link #getStringPermissions() getStringPermissions()}
+     * represent the total set of permissions.  The aggregate set is used to perform a permission authorization check.
+     *
+     * @return all type-safe {@link Permission Permission}s assigned to the corresponding Subject.
+     */
+    Collection<Permission> getObjectPermissions();
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/Authorizer.java b/core/src/main/java/org/apache/shiro/authz/Authorizer.java
new file mode 100644
index 0000000..f3f8b0e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/Authorizer.java
@@ -0,0 +1,278 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.subject.PrincipalCollection;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An <tt>Authorizer</tt> performs authorization (access control) operations for any given Subject
+ * (aka 'application user').
+ *
+ * <p>Each method requires a subject principal to perform the action for the corresponding Subject/user.
+ *
+ * <p>This principal argument is usually an object representing a user database primary key or a String username or
+ * something similar that uniquely identifies an application user.  The runtime value of the this principal
+ * is application-specific and provided by the application's configured Realms.
+ *
+ * <p>Note that there are many *Permission methods in this interface overloaded to accept String arguments instead of
+ * {@link Permission Permission} instances. They are a convenience allowing the caller to use a String representation of
+ * a {@link Permission Permission} if desired.  Most implementations of this interface will simply convert these
+ * String values to {@link Permission Permission} instances and then just call the corresponding type-safe method.
+ * (Shiro's default implementations do String-to-Permission conversion for these methods using
+ * {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver}s.)
+ *
+ * <p>These overloaded *Permission methods <em>do</em> forego type-saftey for the benefit of convenience and simplicity,
+ * so you should choose which ones to use based on your preferences and needs.
+ *
+ * @since 0.1
+ */
+public interface Authorizer {
+
+    /**
+     * Returns <tt>true</tt> if the corresponding subject/user is permitted to perform an action or access a resource
+     * summarized by the specified permission string.
+     *
+     * <p>This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param principals the application-specific subject/user identifier.
+     * @param permission the String representation of a Permission that is being checked.
+     * @return true if the corresponding Subject/user is permitted, false otherwise.
+     * @see #isPermitted(PrincipalCollection principals,Permission permission)
+     * @since 0.9
+     */
+    boolean isPermitted(PrincipalCollection principals, String permission);
+
+    /**
+     * Returns <tt>true</tt> if the corresponding subject/user is permitted to perform an action or access a resource
+     * summarized by the specified permission.
+     *
+     * <p>More specifically, this method determines if any <tt>Permission</tt>s associated
+     * with the subject {@link Permission#implies(Permission) imply} the specified permission.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permission       the permission that is being checked.
+     * @return true if the corresponding Subject/user is permitted, false otherwise.
+     */
+    boolean isPermitted(PrincipalCollection subjectPrincipal, Permission permission);
+
+    /**
+     * Checks if the corresponding Subject implies the given permission strings and returns a boolean array
+     * indicating which permissions are implied.
+     *
+     * <p>This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the String representations of the Permissions that are being checked.
+     * @return an array of booleans whose indices correspond to the index of the
+     *         permissions in the given list.  A true value at an index indicates the user is permitted for
+     *         for the associated <tt>Permission</tt> string in the list.  A false value at an index
+     *         indicates otherwise.
+     * @since 0.9
+     */
+    boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions);
+
+    /**
+     * Checks if the corresponding Subject/user implies the given Permissions and returns a boolean array indicating
+     * which permissions are implied.
+     *
+     * <p>More specifically, this method should determine if each <tt>Permission</tt> in
+     * the array is {@link Permission#implies(Permission) implied} by permissions
+     * already associated with the subject.
+     *
+     * <p>This is primarily a performance-enhancing method to help reduce the number of
+     * {@link #isPermitted} invocations over the wire in client/server systems.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the permissions that are being checked.
+     * @return an array of booleans whose indices correspond to the index of the
+     *         permissions in the given list.  A true value at an index indicates the user is permitted for
+     *         for the associated <tt>Permission</tt> object in the list.  A false value at an index
+     *         indicates otherwise.
+     */
+    boolean[] isPermitted(PrincipalCollection subjectPrincipal, List<Permission> permissions);
+
+    /**
+     * Returns <tt>true</tt> if the corresponding Subject/user implies all of the specified permission strings,
+     * <tt>false</tt> otherwise.
+     *
+     * <p>This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the String representations of the Permissions that are being checked.
+     * @return true if the user has all of the specified permissions, false otherwise.
+     * @see #isPermittedAll(PrincipalCollection,Collection)
+     * @since 0.9
+     */
+    boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions);
+
+    /**
+     * Returns <tt>true</tt> if the corresponding Subject/user implies all of the specified permissions, <tt>false</tt>
+     * otherwise.
+     *
+     * <p>More specifically, this method determines if all of the given <tt>Permission</tt>s are
+     * {@link Permission#implies(Permission) implied by} permissions already associated with the subject.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the permissions to check.
+     * @return true if the user has all of the specified permissions, false otherwise.
+     */
+    boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions);
+
+    /**
+     * Ensures the corresponding Subject/user implies the specified permission String.
+     *
+     * <p>If the subject's existing associated permissions do not {@link Permission#implies(Permission)} imply}
+     * the given permission, an {@link AuthorizationException} will be thrown.
+     *
+     * <p>This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permission       the String representation of the Permission to check.
+     * @throws AuthorizationException
+     *          if the user does not have the permission.
+     * @since 0.9
+     */
+    void checkPermission(PrincipalCollection subjectPrincipal, String permission) throws AuthorizationException;
+
+    /**
+     * Ensures a subject/user {@link Permission#implies(Permission)} implies} the specified <tt>Permission</tt>.
+     * If the subject's exisiting associated permissions do not {@link Permission#implies(Permission)} imply}
+     * the given permission, an {@link AuthorizationException} will be thrown.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permission       the Permission to check.
+     * @throws AuthorizationException
+     *          if the user does not have the permission.
+     */
+    void checkPermission(PrincipalCollection subjectPrincipal, Permission permission) throws AuthorizationException;
+
+    /**
+     * Ensures the corresponding Subject/user
+     * {@link Permission#implies(Permission) implies} all of the
+     * specified permission strings.
+     *
+     * If the subject's exisiting associated permissions do not
+     * {@link Permission#implies(Permission) imply} all of the given permissions,
+     * an {@link AuthorizationException} will be thrown.
+     *
+     * <p>This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the string representations of Permissions to check.
+     * @throws AuthorizationException if the user does not have all of the given permissions.
+     * @since 0.9
+     */
+    void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException;
+
+    /**
+     * Ensures the corresponding Subject/user
+     * {@link Permission#implies(Permission) implies} all of the
+     * specified permission strings.
+     *
+     * If the subject's exisiting associated permissions do not
+     * {@link Permission#implies(Permission) imply} all of the given permissions,
+     * an {@link AuthorizationException} will be thrown.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param permissions      the Permissions to check.
+     * @throws AuthorizationException if the user does not have all of the given permissions.
+     */
+    void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException;
+
+    /**
+     * Returns <tt>true</tt> if the corresponding Subject/user has the specified role, <tt>false</tt> otherwise.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifier   the application-specific role identifier (usually a role id or role name).
+     * @return <tt>true</tt> if the corresponding subject has the specified role, <tt>false</tt> otherwise.
+     */
+    boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);
+
+    /**
+     * Checks if the corresponding Subject/user has the specified roles, returning a boolean array indicating
+     * which roles are associated with the given subject.
+     *
+     * <p>This is primarily a performance-enhancing method to help reduce the number of
+     * {@link #hasRole} invocations over the wire in client/server systems.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifiers  the application-specific role identifiers to check (usually role ids or role names).
+     * @return an array of booleans whose indices correspond to the index of the
+     *         roles in the given identifiers.  A true value indicates the user has the
+     *         role at that index.  False indicates the user does not have the role at that index.
+     */
+    boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers);
+
+    /**
+     * Returns <tt>true</tt> if the corresponding Subject/user has all of the specified roles, <tt>false</tt> otherwise.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifiers  the application-specific role identifiers to check (usually role ids or role names).
+     * @return true if the user has all the roles, false otherwise.
+     */
+    boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers);
+
+    /**
+     * Asserts the corresponding Subject/user has the specified role by returning quietly if they do or throwing an
+     * {@link AuthorizationException} if they do not.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifier   the application-specific role identifier (usually a role id or role name ).
+     * @throws AuthorizationException
+     *          if the user does not have the role.
+     */
+    void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException;
+
+    /**
+     * Asserts the corresponding Subject/user has all of the specified roles by returning quietly if they do or
+     * throwing an {@link AuthorizationException} if they do not.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifiers  the application-specific role identifiers to check (usually role ids or role names).
+     * @throws AuthorizationException
+     *          if the user does not have all of the specified roles.
+     */
+    void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException;
+
+    /**
+     * Same as {@link #checkRoles(org.apache.shiro.subject.PrincipalCollection, java.util.Collection)
+     * checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers)} but doesn't require a collection
+     * as an argument.
+     * Asserts the corresponding Subject/user has all of the specified roles by returning quietly if they do or
+     * throwing an {@link AuthorizationException} if they do not.
+     *
+     * @param subjectPrincipal the application-specific subject/user identifier.
+     * @param roleIdentifiers  the application-specific role identifiers to check (usually role ids or role names).
+     * @throws AuthorizationException
+     *          if the user does not have all of the specified roles.
+     *          
+     *  @since 1.1.0
+     */
+    void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException;
+    
+}
+
diff --git a/core/src/main/java/org/apache/shiro/authz/HostUnauthorizedException.java b/core/src/main/java/org/apache/shiro/authz/HostUnauthorizedException.java
new file mode 100644
index 0000000..fb5f5d3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/HostUnauthorizedException.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+/**
+ * Thrown when a particular client (that is, host address) has not been enabled to access the system
+ * or if the client has been enabled access but is not permitted to perform a particular operation
+ * or access a particular resource.
+ *
+ * @since 0.1
+ */
+public class HostUnauthorizedException extends UnauthorizedException {
+
+    private String host;
+
+    /**
+     * Creates a new HostUnauthorizedException.
+     */
+    public HostUnauthorizedException() {
+        super();
+    }
+
+    /**
+     * Constructs a new HostUnauthorizedException.
+     *
+     * @param message the reason for the exception
+     */
+    public HostUnauthorizedException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new HostUnauthorizedException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public HostUnauthorizedException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new HostUnauthorizedException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public HostUnauthorizedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Returns the host associated with this exception.
+     *
+     * @return the host associated with this exception.
+     */
+    public String getHost() {
+        return this.host;
+    }
+
+    /**
+     * Sets the host associated with this exception.
+     *
+     * @param host the host associated with this exception.
+     */
+    public void setHostAddress(String host) {
+        this.host = host;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java b/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java
new file mode 100644
index 0000000..4a12024
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java
@@ -0,0 +1,444 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.authz.permission.PermissionResolver;
+import org.apache.shiro.authz.permission.PermissionResolverAware;
+import org.apache.shiro.authz.permission.RolePermissionResolver;
+import org.apache.shiro.authz.permission.RolePermissionResolverAware;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * A <tt>ModularRealmAuthorizer</tt> is an <tt>Authorizer</tt> implementation that consults one or more configured
+ * {@link Realm Realm}s during an authorization operation.
+ *
+ * @since 0.2
+ */
+public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
+
+    /**
+     * The realms to consult during any authorization check.
+     */
+    protected Collection<Realm> realms;
+
+    /**
+     * A PermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
+     * to configure different resolvers for different realms.
+     */
+    protected PermissionResolver permissionResolver;
+
+    /**
+     * A RolePermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
+     * to configure different resolvers for different realms.
+     */
+    protected RolePermissionResolver rolePermissionResolver;
+
+    /**
+     * Default no-argument constructor, does nothing.
+     */
+    public ModularRealmAuthorizer() {
+    }
+
+    /**
+     * Constructor that accepts the <code>Realm</code>s to consult during an authorization check.  Immediately calls
+     * {@link #setRealms setRealms(realms)}.
+     *
+     * @param realms the realms to consult during an authorization check.
+     */
+    public ModularRealmAuthorizer(Collection<Realm> realms) {
+        setRealms(realms);
+    }
+
+    /**
+     * Returns the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
+     *
+     * @return the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
+     */
+    public Collection<Realm> getRealms() {
+        return this.realms;
+    }
+
+    /**
+     * Sets the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
+     *
+     * @param realms the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
+     */
+    public void setRealms(Collection<Realm> realms) {
+        this.realms = realms;
+        applyPermissionResolverToRealms();
+        applyRolePermissionResolverToRealms();
+    }
+
+    /**
+     * Returns the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
+     * if all realm instances will each configure their own permission resolver.
+     *
+     * @return the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
+     *         if realm instances will each configure their own permission resolver.
+     * @since 1.0
+     */
+    public PermissionResolver getPermissionResolver() {
+        return this.permissionResolver;
+    }
+
+    /**
+     * Sets the specified {@link PermissionResolver PermissionResolver} on <em>all</em> of the wrapped realms that
+     * implement the {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
+     * <p/>
+     * Only call this method if you want the permission resolver to be passed to all realms that implement the
+     * <code>PermissionResolver</code> interface.  If you do not want this to occur, the realms must
+     * configure themselves individually (or be configured individually).
+     *
+     * @param permissionResolver the permissionResolver to set on all of the wrapped realms that implement the
+     *                           {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
+     */
+    public void setPermissionResolver(PermissionResolver permissionResolver) {
+        this.permissionResolver = permissionResolver;
+        applyPermissionResolverToRealms();
+    }
+
+    /**
+     * Sets the internal {@link #getPermissionResolver} on any internal configured
+     * {@link #getRealms Realms} that implement the {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
+     * <p/>
+     * This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the
+     * {@link #setPermissionResolver(org.apache.shiro.authz.permission.PermissionResolver) setPermissionResolver} method.
+     * <p/>
+     * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
+     * newly available realms to be given the <code>PermissionResolver</code> already in use.
+     *
+     * @since 1.0
+     */
+    protected void applyPermissionResolverToRealms() {
+        PermissionResolver resolver = getPermissionResolver();
+        Collection<Realm> realms = getRealms();
+        if (resolver != null && realms != null && !realms.isEmpty()) {
+            for (Realm realm : realms) {
+                if (realm instanceof PermissionResolverAware) {
+                    ((PermissionResolverAware) realm).setPermissionResolver(resolver);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
+     * if all realm instances will each configure their own permission resolver.
+     *
+     * @return the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
+     *         if realm instances will each configure their own role permission resolver.
+     * @since 1.0
+     */
+    public RolePermissionResolver getRolePermissionResolver() {
+        return this.rolePermissionResolver;
+    }
+
+    /**
+     * Sets the specified {@link RolePermissionResolver RolePermissionResolver} on <em>all</em> of the wrapped realms that
+     * implement the {@link org.apache.shiro.authz.permission.RolePermissionResolverAware PermissionResolverAware} interface.
+     * <p/>
+     * Only call this method if you want the permission resolver to be passed to all realms that implement the
+     * <code>RolePermissionResolver</code> interface.  If you do not want this to occur, the realms must
+     * configure themselves individually (or be configured individually).
+     *
+     * @param rolePermissionResolver the rolePermissionResolver to set on all of the wrapped realms that implement the
+     *                               {@link org.apache.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface.
+     */
+    public void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) {
+        this.rolePermissionResolver = rolePermissionResolver;
+        applyRolePermissionResolverToRealms();
+    }
+
+
+    /**
+     * Sets the internal {@link #getRolePermissionResolver} on any internal configured
+     * {@link #getRealms Realms} that implement the {@link org.apache.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface.
+     * <p/>
+     * This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the
+     * {@link #setRolePermissionResolver(org.apache.shiro.authz.permission.RolePermissionResolver) setRolePermissionResolver} method.
+     * <p/>
+     * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
+     * newly available realms to be given the <code>RolePermissionResolver</code> already in use.
+     *
+     * @since 1.0
+     */
+    protected void applyRolePermissionResolverToRealms() {
+        RolePermissionResolver resolver = getRolePermissionResolver();
+        Collection<Realm> realms = getRealms();
+        if (resolver != null && realms != null && !realms.isEmpty()) {
+            for (Realm realm : realms) {
+                if (realm instanceof RolePermissionResolverAware) {
+                    ((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Used by the {@link Authorizer Authorizer} implementation methods to ensure that the {@link #setRealms realms}
+     * has been set.  The default implementation ensures the property is not null and not empty.
+     *
+     * @throws IllegalStateException if the <tt>realms</tt> property is configured incorrectly.
+     */
+    protected void assertRealmsConfigured() throws IllegalStateException {
+        Collection<Realm> realms = getRealms();
+        if (realms == null || realms.isEmpty()) {
+            String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
+                    "present to execute an authorization operation.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String)} returns <code>true</code>,
+     * <code>false</code> otherwise.
+     */
+    public boolean isPermitted(PrincipalCollection principals, String permission) {
+        assertRealmsConfigured();
+        for (Realm realm : getRealms()) {
+            if (!(realm instanceof Authorizer)) continue;
+            if (((Authorizer) realm).isPermitted(principals, permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>,
+     * <code>false</code> otherwise.
+     */
+    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
+        assertRealmsConfigured();
+        for (Realm realm : getRealms()) {
+            if (!(realm instanceof Authorizer)) continue;
+            if (((Authorizer) realm).isPermitted(principals, permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermittedAll(org.apache.shiro.subject.PrincipalCollection, String...)} call returns
+     * <code>true</code>, <code>false</code> otherwise.
+     */
+    public boolean[] isPermitted(PrincipalCollection principals, String... permissions) {
+        assertRealmsConfigured();
+        if (permissions != null && permissions.length > 0) {
+            boolean[] isPermitted = new boolean[permissions.length];
+            for (int i = 0; i < permissions.length; i++) {
+                isPermitted[i] = isPermitted(principals, permissions[i]);
+            }
+            return isPermitted;
+        }
+        return new boolean[0];
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, List)} call returns <code>true</code>,
+     * <code>false</code> otherwise.
+     */
+    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
+        assertRealmsConfigured();
+        if (permissions != null && !permissions.isEmpty()) {
+            boolean[] isPermitted = new boolean[permissions.size()];
+            int i = 0;
+            for (Permission p : permissions) {
+                isPermitted[i++] = isPermitted(principals, p);
+            }
+            return isPermitted;
+        }
+
+        return new boolean[0];
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String)} call returns <code>true</code>
+     * for <em>all</em> of the specified string permissions, <code>false</code> otherwise.
+     */
+    public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
+        assertRealmsConfigured();
+        if (permissions != null && permissions.length > 0) {
+            for (String perm : permissions) {
+                if (!isPermitted(principals, perm)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>
+     * for <em>all</em> of the specified Permissions, <code>false</code> otherwise.
+     */
+    public boolean isPermittedAll(PrincipalCollection principals, Collection<Permission> permissions) {
+        assertRealmsConfigured();
+        if (permissions != null && !permissions.isEmpty()) {
+            for (Permission permission : permissions) {
+                if (!isPermitted(principals, permission)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String) isPermitted(permission)}, throws
+     * an <code>UnauthorizedException</code> otherwise returns quietly.
+     */
+    public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (!isPermitted(principals, permission)) {
+            throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
+        }
+    }
+
+    /**
+     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws
+     * an <code>UnauthorizedException</code> otherwise returns quietly.
+     */
+    public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (!isPermitted(principals, permission)) {
+            throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
+        }
+    }
+
+    /**
+     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String...) isPermitted(permission)},
+     * throws an <code>UnauthorizedException</code> otherwise returns quietly.
+     */
+    public void checkPermissions(PrincipalCollection principals, String... permissions) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (permissions != null && permissions.length > 0) {
+            for (String perm : permissions) {
+                checkPermission(principals, perm);
+            }
+        }
+    }
+
+    /**
+     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)} for
+     * <em>all</em> the given Permissions, throws
+     * an <code>UnauthorizedException</code> otherwise returns quietly.
+     */
+    public void checkPermissions(PrincipalCollection principals, Collection<Permission> permissions) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (permissions != null) {
+            for (Permission permission : permissions) {
+                checkPermission(principals, permission);
+            }
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if any of the configured realms'
+     * {@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String)} call returns <code>true</code>,
+     * <code>false</code> otherwise.
+     */
+    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
+        assertRealmsConfigured();
+        for (Realm realm : getRealms()) {
+            if (!(realm instanceof Authorizer)) continue;
+            if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Calls {@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String)} for each role name in the specified
+     * collection and places the return value from each call at the respective location in the returned array.
+     */
+    public boolean[] hasRoles(PrincipalCollection principals, List<String> roleIdentifiers) {
+        assertRealmsConfigured();
+        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
+            boolean[] hasRoles = new boolean[roleIdentifiers.size()];
+            int i = 0;
+            for (String roleId : roleIdentifiers) {
+                hasRoles[i++] = hasRole(principals, roleId);
+            }
+            return hasRoles;
+        }
+
+        return new boolean[0];
+    }
+
+    /**
+     * Returns <code>true</code> iff any of the configured realms'
+     * {@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String)} call returns <code>true</code> for
+     * <em>all</em> roles specified, <code>false</code> otherwise.
+     */
+    public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
+        assertRealmsConfigured();
+        for (String roleIdentifier : roleIdentifiers) {
+            if (!hasRole(principals, roleIdentifier)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * If !{@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String) hasRole(role)}, throws
+     * an <code>UnauthorizedException</code> otherwise returns quietly.
+     */
+    public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (!hasRole(principals, role)) {
+            throw new UnauthorizedException("Subject does not have role [" + role + "]");
+        }
+    }
+
+    /**
+     * Calls {@link #checkRoles(PrincipalCollection principals, String... roles) checkRoles(PrincipalCollection principals, String... roles) }.
+     */
+    public void checkRoles(PrincipalCollection principals, Collection<String> roles) throws AuthorizationException {
+        //SHIRO-234 - roles.toArray() -> roles.toArray(new String[roles.size()])
+        if (roles != null && !roles.isEmpty()) checkRoles(principals, roles.toArray(new String[roles.size()]));
+    }
+
+    /**
+     * Calls {@link #checkRole(org.apache.shiro.subject.PrincipalCollection, String) checkRole} for each role specified.
+     */
+    public void checkRoles(PrincipalCollection principals, String... roles) throws AuthorizationException {
+        assertRealmsConfigured();
+        if (roles != null) {
+            for (String role : roles) {
+                checkRole(principals, role);
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/Permission.java b/core/src/main/java/org/apache/shiro/authz/Permission.java
new file mode 100644
index 0000000..ea6d185
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/Permission.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+/**
+ * A Permission represents the ability to perform an action or access a resource.  A Permission is the most
+ * granular, or atomic, unit in a system's security policy and is the cornerstone upon which fine-grained security
+ * models are built.
+ * <p/>
+ * It is important to understand a Permission instance only represents functionality or access - it does not grant it.
+ * Granting access to an application functionality or a particular resource is done by the application's security
+ * configuration, typically by assigning Permissions to users, roles and/or groups.
+ * <p/>
+ * Most typical systems are what the Shiro team calls <em>role-based</em> in nature, where a role represents
+ * common behavior for certain user types.  For example, a system might have an <em>Aministrator</em> role, a
+ * <em>User</em> or <em>Guest</em> roles, etc.
+ * <p/>
+ * But if you have a dynamic security model, where roles can be created and deleted at runtime, you can't hard-code
+ * role names in your code.  In this environment, roles themselves aren't aren't very useful.  What matters is what
+ * <em>permissions</em> are assigned to these roles.
+ * <p/>
+ * Under this paradigm, permissions are immutable and reflect an application's raw functionality
+ * (opening files, accessing a web URL, creating users, etc).  This is what allows a system's security policy
+ * to be dynamic: because Permissions represent raw functionality and only change when the application's
+ * source code changes, they are immutable at runtime - they represent 'what' the system can do.  Roles, users, and
+ * groups are the 'who' of the application.  Determining 'who' can do 'what' then becomes a simple exercise of
+ * associating Permissions to roles, users, and groups in some way.
+ * <p/>
+ * Most applications do this by associating a named role with permissions (i.e. a role 'has a' collection of
+ * Permissions) and then associate users with roles (i.e. a user 'has a' collection of roles) so that by transitive
+ * association, the user 'has' the permissions in their roles.  There are numerous variations on this theme
+ * (permissions assigned directly to users, or assigned to groups, and users added to groups and these groups in turn
+ * have roles, etc, etc).  When employing a permission-based security model instead of a role-based one, users, roles,
+ * and groups can all be created, configured and/or deleted at runtime.  This enables  an extremely powerful security
+ * model.
+ * <p/>
+ * A benefit to Shiro is that, although it assumes most systems are based on these types of static role or
+ * dynamic role w/ permission schemes, it does not require a system to model their security data this way - all
+ * Permission checks are relegated to {@link org.apache.shiro.realm.Realm} implementations, and only those
+ * implementations really determine how a user 'has' a permission or not.  The Realm could use the semantics described
+ * here, or it could utilize some other mechanism entirely - it is always up to the application developer.
+ * <p/>
+ * Shiro provides a very powerful default implementation of this interface in the form of the
+ * {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission}.  We highly recommend that you
+ * investigate this class before trying to implement your own <code>Permission</code>s.
+ *
+ * @see org.apache.shiro.authz.permission.WildcardPermission WildcardPermission
+ * @since 0.2
+ */
+public interface Permission {
+
+    /**
+     * Returns {@code true} if this current instance <em>implies</em> all the functionality and/or resource access
+     * described by the specified {@code Permission} argument, {@code false} otherwise.
+     * <p/>
+     * <p>That is, this current instance must be exactly equal to or a <em>superset</em> of the functionalty
+     * and/or resource access described by the given {@code Permission} argument.  Yet another way of saying this
+     * would be:
+     * <p/>
+     * <p>If "permission1 implies permission2", i.e. <code>permission1.implies(permission2)</code> ,
+     * then any Subject granted {@code permission1} would have ability greater than or equal to that defined by
+     * {@code permission2}.
+     *
+     * @param p the permission to check for behavior/functionality comparison.
+     * @return {@code true} if this current instance <em>implies</em> all the functionality and/or resource access
+     *         described by the specified {@code Permission} argument, {@code false} otherwise.
+     */
+    boolean implies(Permission p);
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/SimpleAuthorizationInfo.java b/core/src/main/java/org/apache/shiro/authz/SimpleAuthorizationInfo.java
new file mode 100644
index 0000000..cb09c46
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/SimpleAuthorizationInfo.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple POJO implementation of the {@link AuthorizationInfo} interface that stores roles and permissions as internal
+ * attributes.
+ *
+ * @see org.apache.shiro.realm.AuthorizingRealm
+ * @since 0.9
+ */
+public class SimpleAuthorizationInfo implements AuthorizationInfo {
+
+    /**
+     * The internal roles collection.
+     */
+    protected Set<String> roles;
+
+    /**
+     * Collection of all string-based permissions associated with the account.
+     */
+    protected Set<String> stringPermissions;
+
+    /**
+     * Collection of all object-based permissions associaed with the account.
+     */
+    protected Set<Permission> objectPermissions;
+
+    /**
+     * Default no-argument constructor.
+     */
+    public SimpleAuthorizationInfo() {
+    }
+
+    /**
+     * Creates a new instance with the specified roles and no permissions.
+     * @param roles the roles assigned to the realm account.
+     */
+    public SimpleAuthorizationInfo(Set<String> roles) {
+        this.roles = roles;
+    }
+
+    public Set<String> getRoles() {
+        return roles;
+    }
+
+    /**
+     * Sets the roles assigned to the account.
+     * @param roles the roles assigned to the account.
+     */
+    public void setRoles(Set<String> roles) {
+        this.roles = roles;
+    }
+
+    /**
+     * Adds (assigns) a role to those associated with the account.  If the account doesn't yet have any roles, a
+     * new roles collection (a Set) will be created automatically.
+     * @param role the role to add to those associated with the account.
+     */
+    public void addRole(String role) {
+        if (this.roles == null) {
+            this.roles = new HashSet<String>();
+        }
+        this.roles.add(role);
+    }
+
+    /**
+     * Adds (assigns) multiple roles to those associated with the account.  If the account doesn't yet have any roles, a
+     * new roles collection (a Set) will be created automatically.
+     * @param roles the roles to add to those associated with the account.
+     */
+    public void addRoles(Collection<String> roles) {
+        if (this.roles == null) {
+            this.roles = new HashSet<String>();
+        }
+        this.roles.addAll(roles);
+    }
+
+    public Set<String> getStringPermissions() {
+        return stringPermissions;
+    }
+
+    /**
+     * Sets the string-based permissions assigned directly to the account.  The permissions set here, in addition to any
+     * {@link #getObjectPermissions() object permissions} constitute the total permissions assigned directly to the
+     * account.
+     *
+     * @param stringPermissions the string-based permissions assigned directly to the account.
+     */
+    public void setStringPermissions(Set<String> stringPermissions) {
+        this.stringPermissions = stringPermissions;
+    }
+
+    /**
+     * Adds (assigns) a permission to those directly associated with the account.  If the account doesn't yet have any
+     * direct permissions, a new permission collection (a Set<String>) will be created automatically.
+     * @param permission the permission to add to those directly assigned to the account.
+     */
+    public void addStringPermission(String permission) {
+        if (this.stringPermissions == null) {
+            this.stringPermissions = new HashSet<String>();
+        }
+        this.stringPermissions.add(permission);
+    }
+
+    /**
+     * Adds (assigns) multiple permissions to those associated directly with the account.  If the account doesn't yet
+     * have any string-based permissions, a  new permissions collection (a Set<String>) will be created automatically.
+     * @param permissions the permissions to add to those associated directly with the account.
+     */
+    public void addStringPermissions(Collection<String> permissions) {
+        if (this.stringPermissions == null) {
+            this.stringPermissions = new HashSet<String>();
+        }
+        this.stringPermissions.addAll(permissions);
+    }
+
+    public Set<Permission> getObjectPermissions() {
+        return objectPermissions;
+    }
+
+    /**
+     * Sets the object-based permissions assigned directly to the account.  The permissions set here, in addition to any
+     * {@link #getStringPermissions() string permissions} constitute the total permissions assigned directly to the
+     * account.
+     *
+     * @param objectPermissions the object-based permissions assigned directly to the account.
+     */
+    public void setObjectPermissions(Set<Permission> objectPermissions) {
+        this.objectPermissions = objectPermissions;
+    }
+
+    /**
+     * Adds (assigns) a permission to those directly associated with the account.  If the account doesn't yet have any
+     * direct permissions, a new permission collection (a Set<{@link Permission Permission}>) will be created automatically.
+     * @param permission the permission to add to those directly assigned to the account.
+     */
+    public void addObjectPermission(Permission permission) {
+        if (this.objectPermissions == null) {
+            this.objectPermissions = new HashSet<Permission>();
+        }
+        this.objectPermissions.add(permission);
+    }
+
+    /**
+     * Adds (assigns) multiple permissions to those associated directly with the account.  If the account doesn't yet
+     * have any object-based permissions, a  new permissions collection (a Set<{@link Permission Permission}>)
+     * will be created automatically.
+     * @param permissions the permissions to add to those associated directly with the account.
+     */
+    public void addObjectPermissions(Collection<Permission> permissions) {
+        if (this.objectPermissions == null) {
+            this.objectPermissions = new HashSet<Permission>();
+        }
+        this.objectPermissions.addAll(permissions);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/SimpleRole.java b/core/src/main/java/org/apache/shiro/authz/SimpleRole.java
new file mode 100644
index 0000000..73f65ae
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/SimpleRole.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A simple representation of a security role that has a name and a collection of permissions.  This object can be
+ * used internally by Realms to maintain authorization state.
+ *
+ * @since 0.2
+ */
+public class SimpleRole implements Serializable {
+
+    protected String name = null;
+    protected Set<Permission> permissions;
+
+    public SimpleRole() {
+    }
+
+    public SimpleRole(String name) {
+        setName(name);
+    }
+
+    public SimpleRole(String name, Set<Permission> permissions) {
+        setName(name);
+        setPermissions(permissions);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Set<Permission> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<Permission> permissions) {
+        this.permissions = permissions;
+    }
+
+    public void add(Permission permission) {
+        Set<Permission> permissions = getPermissions();
+        if (permissions == null) {
+            permissions = new LinkedHashSet<Permission>();
+            setPermissions(permissions);
+        }
+        permissions.add(permission);
+    }
+
+    public void addAll(Collection<Permission> perms) {
+        if (perms != null && !perms.isEmpty()) {
+            Set<Permission> permissions = getPermissions();
+            if (permissions == null) {
+                permissions = new LinkedHashSet<Permission>(perms.size());
+                setPermissions(permissions);
+            }
+            permissions.addAll(perms);
+        }
+    }
+
+    public boolean isPermitted(Permission p) {
+        Collection<Permission> perms = getPermissions();
+        if (perms != null && !perms.isEmpty()) {
+            for (Permission perm : perms) {
+                if (perm.implies(p)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return (getName() != null ? getName().hashCode() : 0);
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof SimpleRole) {
+            SimpleRole sr = (SimpleRole) o;
+            //only check name, since role names should be unique across an entire application:
+            return (getName() != null ? getName().equals(sr.getName()) : sr.getName() == null);
+        }
+        return false;
+    }
+
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/UnauthenticatedException.java b/core/src/main/java/org/apache/shiro/authz/UnauthenticatedException.java
new file mode 100644
index 0000000..128bcad
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/UnauthenticatedException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+/**
+ * Exception thrown when attempting to execute an authorization action when a successful
+ * authentication hasn't yet occurred.
+ *
+ * <p>Authorizations can only be performed after a successful
+ * authentication because authorization data (roles, permissions, etc) must always be associated
+ * with a known identity.  Such a known identity can only be obtained upon a successful log-in.
+ *
+ * @since 0.1
+ */
+public class UnauthenticatedException extends AuthorizationException {
+
+    /**
+     * Creates a new UnauthenticatedException.
+     */
+    public UnauthenticatedException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnauthenticatedException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnauthenticatedException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnauthenticatedException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnauthenticatedException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnauthenticatedException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnauthenticatedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/UnauthorizedException.java b/core/src/main/java/org/apache/shiro/authz/UnauthorizedException.java
new file mode 100644
index 0000000..42f026b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/UnauthorizedException.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+/**
+ * Thrown to indicate a requested operation or access to a requested resource is not allowed.
+ *
+ * @since 0.1
+ */
+public class UnauthorizedException extends AuthorizationException {
+
+    /**
+     * Creates a new UnauthorizedException.
+     */
+    public UnauthorizedException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnauthorizedException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnauthorizedException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnauthorizedException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnauthorizedException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnauthorizedException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnauthorizedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/Logical.java b/core/src/main/java/org/apache/shiro/authz/annotation/Logical.java
new file mode 100644
index 0000000..cf29773
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/Logical.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+/**
+ * An enum for specifying a logical operation that can be used for 
+ * interpreting authorization annotations 
+ *
+ * @since 1.1.0
+ */
+public enum Logical {
+    AND, OR
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/RequiresAuthentication.java b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresAuthentication.java
new file mode 100644
index 0000000..b0a3bfb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresAuthentication.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Requires the current Subject to have been authenticated <em>during their current session</em> for the annotated
+ * class/instance/method to be accessed or invoked.  This is <em>more</em> restrictive than the
+ * {@link RequiresUser RequiresUser} annotation.
+ * <p/>
+ * This annotation basically ensures that
+ * <code>{@link org.apache.shiro.subject.Subject subject}.{@link org.apache.shiro.subject.Subject#isAuthenticated() isAuthenticated()} === true</code>
+ * <p/>
+ * See the {@link RequiresUser RequiresUser} and
+ * {@link org.apache.shiro.authc.RememberMeAuthenticationToken RememberMeAuthenticationToken} JavaDoc for an
+ * explaination of why these two states are considered different.
+ *
+ * @see RequiresUser
+ * @see RequiresGuest
+ *
+ * @since 0.9.0
+ */
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresAuthentication {
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/RequiresGuest.java b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresGuest.java
new file mode 100644
index 0000000..7c52bd4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresGuest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Requires the current Subject to be a "guest", that is, they are not authenticated <em>or</em> remembered
+ * from a previous session for the annotated class/instance/method to be accessed or invoked.
+ * <p/>
+ * This annotation is the logical inverse of the {@link RequiresUser RequiresUser} annotation. That is,
+ * <code>RequiresUser == !RequiresGuest</code>, or more accurately,
+ * <p/>
+ * <code>RequiresGuest === subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()} == null</code>.
+ *
+ * @see RequiresAuthentication
+ * @see RequiresUser
+ *
+ * @since 0.9.0
+ */
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresGuest {
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/RequiresPermissions.java b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresPermissions.java
new file mode 100644
index 0000000..f8d92c9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresPermissions.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Requires the current executor's Subject to imply a particular permission in
+ * order to execute the annotated method.  If the executor's associated
+ * {@link org.apache.shiro.subject.Subject Subject} determines that the
+ * executor does not imply the specified permission, the method will not be executed.
+ * </p>
+ *
+ * <p>For example, this declaration:
+ * <p/>
+ * <code>@RequiresPermissions( {"file:read", "write:aFile.txt"} )<br/>
+ * void someMethod();</code>
+ * <p/>
+ * indicates the current user must be able to both <tt>read</tt> and <tt>write</tt>
+ * to the file <tt>aFile.txt</tt> in order for the <tt>someMethod()</tt> to execute, otherwise
+ * an {@link org.apache.shiro.authz.AuthorizationException AuthorizationException} will be thrown.
+ *
+ * @see org.apache.shiro.subject.Subject#checkPermission
+ * @since 0.1
+ */
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresPermissions {
+
+    /**
+     * The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
+     * to determine if the user is allowed to invoke the code protected by this annotation.
+     */
+    String[] value();
+    
+    /**
+     * The logical operation for the permission checks in case multiple roles are specified. AND is the default
+     * @since 1.1.0
+     */
+    Logical logical() default Logical.AND; 
+
+}
+
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/RequiresRoles.java b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresRoles.java
new file mode 100644
index 0000000..b18a73b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresRoles.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Requires the currently executing {@link org.apache.shiro.subject.Subject Subject} to have all of the 
+ * specified roles. If they do not have the role(s), the method will not be executed and
+ * an {@link org.apache.shiro.authz.AuthorizationException AuthorizationException} is thrown.
+ * <p/>
+ * For example,
+ * <p/>
+ * <code>@RequiresRoles("aRoleName");<br/>
+ * void someMethod();</code>
+ * <p/>
+ * means <tt>someMethod()</tt> could only be executed by subjects who have been assigned the
+ * 'aRoleName' role.
+ *
+ * <p><b>*Usage Note*:</b> Be careful using this annotation if your application has a <em>dynamic</em>
+ * security model where roles can be added and deleted at runtime.  If your application allowed the
+ * annotated role to be deleted during runtime, the method would not be able to
+ * be executed by anyone (at least until a new role with the same name was created again).
+ *
+ * <p>If you require such dynamic functionality, only the
+ * {@link RequiresPermissions RequiresPermissions} annotation makes sense - Permission
+ * types will not change during runtime for an application since permissions directly correspond to how
+ * the application's functionality is programmed (that is, they reflect the application's functionality only, not
+ * <em>who</em> is executing the the functionality).
+ *
+ * @see org.apache.shiro.subject.Subject#hasRole(String)
+ * @since 0.1
+ */
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresRoles {
+
+    /**
+     * A single String role name or multiple comma-delimitted role names required in order for the method
+     * invocation to be allowed.
+     */
+    String[] value();
+    
+    /**
+     * The logical operation for the permission check in case multiple roles are specified. AND is the default
+     * @since 1.1.0
+     */
+    Logical logical() default Logical.AND; 
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/RequiresUser.java b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresUser.java
new file mode 100644
index 0000000..e773bc0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/RequiresUser.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Requires the current Subject to be an application <em>user</em> for the annotated class/instance/method to be
+ * accessed or invoked.  This is <em>less</em> restrictive than the {@link RequiresAuthentication RequiresAuthentication}
+ * annotation.
+ * <p/>
+ * Shiro defines a "user" as a Subject that is either
+ * "remembered" <b><em>or</em></b> authenticated:
+ * <ul>
+ * <li>An <b>authenticated</b> user is a Subject that has successfully logged in (proven their identity)
+ * <em>during their current session</em>.</li>
+ * <li>A <b>remembered</b> user is any Subject that has proven their identity at least once, although not necessarily
+ * during their current session, and asked the system to remember them.</li>
+ * </ul>
+ * <p/>
+ * See the {@link org.apache.shiro.authc.RememberMeAuthenticationToken RememberMeAuthenticationToken} JavaDoc for an
+ * explaination of why these two states are considered different.
+ *
+ * @see RequiresAuthentication
+ * @see RequiresGuest
+ *
+ * @since 0.9.0
+ */
+ at Target({ElementType.TYPE, ElementType.METHOD})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresUser {
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/annotation/package-info.java b/core/src/main/java/org/apache/shiro/authz/annotation/package-info.java
new file mode 100644
index 0000000..550f666
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/annotation/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Annotations used to restrict which classes, instances, or methods may be accessed or invoked depending on the
+ * caller's access abilities or authentication state.
+ * 
+ * Since 1.1, all core annotations were extends to accept Target ElementType.TYPE in additon to ElementType.METHOD 
+ */
+package org.apache.shiro.authz.annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
new file mode 100644
index 0000000..f9bd41a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.AuthorizationException;
+
+/**
+ * An <tt>AnnotationsAuthorizingMethodInterceptor</tt> is a MethodInterceptor that asserts a given method is authorized
+ * to execute based on one or more configured <tt>AuthorizingAnnotationMethodInterceptor</tt>s.
+ *
+ * <p>This allows multiple annotations on a method to be processed before the method
+ * executes, and if any of the <tt>AuthorizingAnnotationMethodInterceptor</tt>s indicate that the method should not be
+ * executed, an <tt>AuthorizationException</tt> will be thrown, otherwise the method will be invoked as expected.
+ *
+ * <p>It is essentially a convenience mechanism to allow multiple annotations to be processed in a single method
+ * interceptor.
+ *
+ * @since 0.2
+ */
+public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor {
+
+    /**
+     * The method interceptors to execute for the annotated method.
+     */
+    protected Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors;
+
+    /**
+     * Default no-argument constructor that defaults the 
+     * {@link #methodInterceptors methodInterceptors} attribute to contain two interceptors by default - the
+     * {@link RoleAnnotationMethodInterceptor RoleAnnotationMethodInterceptor} and the
+     * {@link PermissionAnnotationMethodInterceptor PermissionAnnotationMethodInterceptor} to
+     * support role and permission annotations.
+     */
+    public AnnotationsAuthorizingMethodInterceptor() {
+        methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
+        methodInterceptors.add(new RoleAnnotationMethodInterceptor());
+        methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
+        methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
+        methodInterceptors.add(new UserAnnotationMethodInterceptor());
+        methodInterceptors.add(new GuestAnnotationMethodInterceptor());
+    }
+
+    /**
+     * Returns the method interceptors to execute for the annotated method.
+     * <p/>
+     * Unless overridden by the {@link #setMethodInterceptors(java.util.Collection)} method, the default collection
+     * contains a
+     * {@link RoleAnnotationMethodInterceptor RoleAnnotationMethodInterceptor} and a
+     * {@link PermissionAnnotationMethodInterceptor PermissionAnnotationMethodInterceptor} to
+     * support role and permission annotations automatically.
+     * @return the method interceptors to execute for the annotated method.
+     */
+    public Collection<AuthorizingAnnotationMethodInterceptor> getMethodInterceptors() {
+        return methodInterceptors;
+    }
+
+    /**
+     * Sets the method interceptors to execute for the annotated method.
+     * @param methodInterceptors the method interceptors to execute for the annotated method.
+     * @see #getMethodInterceptors()
+     */
+    public void setMethodInterceptors(Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors) {
+        this.methodInterceptors = methodInterceptors;
+    }
+
+    /**
+     * Iterates over the internal {@link #getMethodInterceptors() methodInterceptors} collection, and for each one,
+     * ensures that if the interceptor
+     * {@link AuthorizingAnnotationMethodInterceptor#supports(org.apache.shiro.aop.MethodInvocation) supports}
+     * the invocation, that the interceptor
+     * {@link AuthorizingAnnotationMethodInterceptor#assertAuthorized(org.apache.shiro.aop.MethodInvocation) asserts}
+     * that the invocation is authorized to proceed.
+     */
+    protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
+        //default implementation just ensures no deny votes are cast:
+        Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
+        if (aamis != null && !aamis.isEmpty()) {
+            for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
+                if (aami.supports(methodInvocation)) {
+                    aami.assertAuthorized(methodInvocation);
+                }
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationHandler.java
new file mode 100644
index 0000000..d45d740
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.annotation.RequiresAuthentication;
+
+
+/**
+ * Handles {@link RequiresAuthentication RequiresAuthentication} annotations and ensures the calling subject is
+ * authenticated before allowing access.
+ *
+ * @since 0.9.0
+ */
+public class AuthenticatedAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this handler to process
+     * {@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication} annotations.
+     */
+    public AuthenticatedAnnotationHandler() {
+        super(RequiresAuthentication.class);
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> is authenticated, and if not, throws an
+     * {@link org.apache.shiro.authz.UnauthenticatedException UnauthenticatedException} indicating the method is not allowed to be executed.
+     *
+     * @param a the annotation to inspect
+     * @throws org.apache.shiro.authz.UnauthenticatedException if the calling <code>Subject</code> has not yet
+     * authenticated.
+     */
+    public void assertAuthorized(Annotation a) throws UnauthenticatedException {
+        if (a instanceof RequiresAuthentication && !getSubject().isAuthenticated() ) {
+            throw new UnauthenticatedException( "The current Subject is not authenticated.  Access denied." );
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..07a4353
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AuthenticatedAnnotationMethodInterceptor.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthenticated} annotation
+ * is declared, and if so, ensures the calling
+ * <code>Subject</code>.{@link org.apache.shiro.subject.Subject#isAuthenticated() isAuthenticated()} before invoking
+ * the method.
+ *
+ * @since 0.9.0
+ */
+public class AuthenticatedAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication} annotations in a method
+     * declaration.
+     */
+    public AuthenticatedAnnotationMethodInterceptor() {
+        super(new AuthenticatedAnnotationHandler());
+    }
+
+    /**
+     * @param resolver
+     * @since 1.1
+     */
+    public AuthenticatedAnnotationMethodInterceptor(AnnotationResolver resolver) {
+        super(new AuthenticatedAnnotationHandler(), resolver);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationHandler.java
new file mode 100644
index 0000000..8984796
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.shiro.aop.AnnotationHandler;
+import org.apache.shiro.authz.AuthorizationException;
+
+/**
+ * An AnnotationHandler that executes authorization (access control) behavior based on directive(s) found in a
+ * JSR-175 Annotation.
+ *
+ * @since 0.9.0
+ */
+public abstract class AuthorizingAnnotationHandler extends AnnotationHandler {
+
+    /**
+     * Constructs an <code>AuthorizingAnnotationHandler</code> who processes annotations of the
+     * specified type.  Immediately calls <code>super(annotationClass)</code>.
+     *
+     * @param annotationClass the type of annotation this handler will process.
+     */
+    public AuthorizingAnnotationHandler(Class<? extends Annotation> annotationClass) {
+        super(annotationClass);
+    }
+
+    /**
+     * Ensures the calling Subject is authorized to execute based on the directive(s) found in the given
+     * annotation.
+     * <p/>
+     * As this is an AnnotationMethodInterceptor, the implementations of this method typically inspect the annotation
+     * and perform a corresponding authorization check based.
+     *
+     * @param a the <code>Annotation</code> to check for performing an authorization check.
+     * @throws org.apache.shiro.authz.AuthorizationException if the class/instance/method is not allowed to proceed/execute.
+     */
+    public abstract void assertAuthorized(Annotation a) throws AuthorizationException;
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..0728eca
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationMethodInterceptor;
+import org.apache.shiro.aop.AnnotationResolver;
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.AuthorizationException;
+
+
+/**
+ * An <tt>AnnotationMethodInterceptor</tt> that asserts the calling code is authorized to execute the method
+ * before allowing the invocation to continue by inspecting code annotations to perform an access control check.
+ *
+ * @since 0.1
+ */
+public abstract class AuthorizingAnnotationMethodInterceptor extends AnnotationMethodInterceptor
+{
+    
+    /**
+     * Constructor that ensures the internal <code>handler</code> is set which will be used to perform the
+     * authorization assertion checks when a supported annotation is encountered.
+     * @param handler the internal <code>handler</code> used to perform authorization assertion checks when a 
+     * supported annotation is encountered.
+     */
+    public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler ) {
+        super(handler);
+    }
+
+    /**
+     *
+     * @param handler
+     * @param resolver
+     * @since 1.1
+     */
+    public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler,
+                                                   AnnotationResolver resolver) {
+        super(handler, resolver);
+    }
+
+    /**
+     * Ensures the <code>methodInvocation</code> is allowed to execute first before proceeding by calling the
+     * {@link #assertAuthorized(org.apache.shiro.aop.MethodInvocation) assertAuthorized} method first.
+     *
+     * @param methodInvocation the method invocation to check for authorization prior to allowing it to proceed/execute.
+     * @return the return value from the method invocation (the value of {@link org.apache.shiro.aop.MethodInvocation#proceed() MethodInvocation.proceed()}).
+     * @throws org.apache.shiro.authz.AuthorizationException if the <code>MethodInvocation</code> is not allowed to proceed.
+     * @throws Throwable if any other error occurs.
+     */
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+        assertAuthorized(methodInvocation);
+        return methodInvocation.proceed();
+    }
+
+    /**
+     * Ensures the calling Subject is authorized to execute the specified <code>MethodInvocation</code>.
+     * <p/>
+     * As this is an AnnotationMethodInterceptor, this implementation merely delegates to the internal
+     * {@link AuthorizingAnnotationHandler AuthorizingAnnotationHandler} by first acquiring the annotation by
+     * calling {@link #getAnnotation(MethodInvocation) getAnnotation(methodInvocation)} and then calls
+     * {@link AuthorizingAnnotationHandler#assertAuthorized(java.lang.annotation.Annotation) handler.assertAuthorized(annotation)}.
+     *
+     * @param mi the <code>MethodInvocation</code> to check to see if it is allowed to proceed/execute.
+     * @throws AuthorizationException if the method invocation is not allowed to continue/execute.
+     */
+    public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
+        try {
+            ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
+        }
+        catch(AuthorizationException ae) {
+            // Annotation handler doesn't know why it was called, so add the information here if possible. 
+            // Don't wrap the exception here since we don't want to mask the specific exception, such as 
+            // UnauthenticatedException etc. 
+            if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
+            throw ae;
+        }         
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingMethodInterceptor.java
new file mode 100644
index 0000000..08b310a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AuthorizingMethodInterceptor.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.MethodInterceptorSupport;
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.AuthorizationException;
+
+/**
+ * Basic abstract class to support intercepting methods that perform authorization (access control) checks.
+ *
+ * @since 0.9
+ */
+public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport {
+
+    /**
+     * Invokes the specified method (<code>methodInvocation.{@link org.apache.shiro.aop.MethodInvocation#proceed proceed}()</code>
+     * if authorization is allowed by first
+     * calling {@link #assertAuthorized(org.apache.shiro.aop.MethodInvocation) assertAuthorized}.
+     */
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+        assertAuthorized(methodInvocation);
+        return methodInvocation.proceed();
+    }
+
+    /**
+     * Asserts that the specified MethodInvocation is allowed to continue by performing any necessary authorization
+     * (access control) checks first.
+     * @param methodInvocation the <code>MethodInvocation</code> to invoke.
+     * @throws AuthorizationException if the <code>methodInvocation</code> should not be allowed to continue/execute.
+     */
+    protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationHandler.java
new file mode 100644
index 0000000..9c18162
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.annotation.RequiresGuest;
+
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotation
+ * is declared, and if so, ensures the calling <code>Subject</code> does <em>not</em>
+ * have an {@link org.apache.shiro.subject.Subject#getPrincipal() identity} before invoking the method.
+ * <p>
+ * This annotation essentially ensures that <code>subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()} == null</code>.
+ *
+ * @since 0.9.0
+ */
+public class GuestAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     *
+     * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotations in a method
+     * declaration.
+     */
+    public GuestAnnotationHandler() {
+        super(RequiresGuest.class);
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> is NOT a <em>user</em>, that is, they do not
+     * have an {@link org.apache.shiro.subject.Subject#getPrincipal() identity} before continuing.  If they are
+     * a user ({@link org.apache.shiro.subject.Subject#getPrincipal() Subject.getPrincipal()} != null), an
+     * <code>AuthorizingException</code> will be thrown indicating that execution is not allowed to continue.
+     *
+     * @param a the annotation to check for one or more roles
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if the calling <code>Subject</code> is not a "guest".
+     */
+    public void assertAuthorized(Annotation a) throws AuthorizationException {
+        if (a instanceof RequiresGuest && getSubject().getPrincipal() != null) {
+            throw new UnauthenticatedException("Attempting to perform a guest-only operation.  The current Subject is " +
+                    "not a guest (they have been authenticated or remembered from a previous login).  Access " +
+                    "denied.");
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..8828acb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/GuestAnnotationMethodInterceptor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotation
+ * is declared, and if so, ensures the calling <code>Subject</code> does <em>not</em>
+ * have an {@link org.apache.shiro.subject.Subject#getPrincipal() identity} before invoking the method.
+ * <p>
+ * This annotation essentially ensures that <code>subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()} == null</code>.
+ *
+ * @since 0.9.0
+ */
+public class GuestAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotations in a method
+     * declaration.
+     */
+    public GuestAnnotationMethodInterceptor() {
+        super(new GuestAnnotationHandler());
+    }
+
+    /**
+     * @param resolver
+     * @since 1.1
+     */
+    public GuestAnnotationMethodInterceptor(AnnotationResolver resolver) {
+        super(new GuestAnnotationHandler(), resolver);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationHandler.java
new file mode 100644
index 0000000..839b473
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationHandler.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.subject.Subject;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotation is
+ * declared, and if so, performs a permission check to see if the calling <code>Subject</code> is allowed continued
+ * access.
+ *
+ * @since 0.9.0
+ */
+public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this handler looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations.
+     */
+    public PermissionAnnotationHandler() {
+        super(RequiresPermissions.class);
+    }
+
+    /**
+     * Returns the annotation {@link RequiresPermissions#value value}, from which the Permission will be constructed.
+     *
+     * @param a the RequiresPermissions annotation being inspected.
+     * @return the annotation's <code>value</code>, from which the Permission will be constructed.
+     */
+    protected String[] getAnnotationValue(Annotation a) {
+        RequiresPermissions rpAnnotation = (RequiresPermissions) a;
+        return rpAnnotation.value();
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> has the Annotation's specified permissions, and if not, throws an
+     * <code>AuthorizingException</code> indicating access is denied.
+     *
+     * @param a the RequiresPermission annotation being inspected to check for one or more permissions
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if the calling <code>Subject</code> does not have the permission(s) necessary to
+     *          continue access or execution.
+     */
+    public void assertAuthorized(Annotation a) throws AuthorizationException {
+        if (!(a instanceof RequiresPermissions)) return;
+
+        RequiresPermissions rpAnnotation = (RequiresPermissions) a;
+        String[] perms = getAnnotationValue(a);
+        Subject subject = getSubject();
+
+        if (perms.length == 1) {
+            subject.checkPermission(perms[0]);
+            return;
+        }
+        if (Logical.AND.equals(rpAnnotation.logical())) {
+            getSubject().checkPermissions(perms);
+            return;
+        }
+        if (Logical.OR.equals(rpAnnotation.logical())) {
+            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
+            boolean hasAtLeastOnePermission = false;
+            for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
+            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
+            if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
+            
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..1bf70a4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/PermissionAnnotationMethodInterceptor.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotation is declared, and if so, performs
+ * a permission check to see if the calling <code>Subject</code> is allowed to call the method.
+ * @since 0.9
+ */
+public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    /*
+     * The character to look for that closes a permission definition.
+     **/
+    //private static final char ARRAY_CLOSE_CHAR = ']';
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations in a method declaration.
+     */
+    public PermissionAnnotationMethodInterceptor() {
+        super( new PermissionAnnotationHandler() );
+    }
+
+    /**
+     * @param resolver
+     * @since 1.1
+     */
+    public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
+        super( new PermissionAnnotationHandler(), resolver);
+    }
+
+    /*
+     * Infers the permission from the specified name path in the annotation.
+     * @param methodArgs the <code>MethodInvocation</code> method arguments.
+     * @param namePath the Annotation 'name' value, which is a string-based permission definition.
+     * @return the String permission representation.
+     * @throws Exception if there is an error infering the target.
+     *
+    protected String inferTargetFromPath(Object[] methodArgs, String namePath) throws Exception {
+        int propertyStartIndex = -1;
+
+        char[] chars = namePath.toCharArray();
+        StringBuilder buf = new StringBuilder();
+        //init iteration at index 1 (instead of 0).  This is because the first
+        //character must be the ARRAY_OPEN_CHAR (eliminates unnecessary iteration)
+        for (int i = 1; i < chars.length; i++) {
+            if (chars[i] == ARRAY_CLOSE_CHAR) {
+                // skip the delimiting period after the ARRAY_CLOSE_CHAR.  The resulting
+                // index is the init of the property path that we'll use with
+                // BeanUtils.getProperty:
+                propertyStartIndex = i + 2;
+                break;
+            } else {
+                buf.append(chars[i]);
+            }
+        }
+
+        Integer methodArgIndex = Integer.parseInt(buf.toString());
+        String beanUtilsPath = new String(chars, propertyStartIndex,
+                chars.length - propertyStartIndex);
+        Object targetValue = PropertyUtils.getProperty(methodArgs[methodArgIndex], beanUtilsPath);
+        return targetValue.toString();
+    }
+
+    /*
+     * Returns the <code>MethodInvocation</code>'s arguments, or <code>null</code> if there were none.
+     * @param invocation the methodInvocation to inspect.
+     * @return the method invocation's method arguments, or <code>null</code> if there were none.
+     *
+    protected Object[] getMethodArguments(MethodInvocation invocation) {
+        if (invocation != null) {
+            return invocation.getArguments();
+        } else {
+            return null;
+        }
+    }
+
+    /*
+     * Returns the annotation {@link RequiresPermissions#value value}, from which the Permission will be constructed.
+     *
+     * @param invocation the method being invoked.
+     * @return the method annotation's <code>value</code>, from which the Permission will be constructed.
+     *
+    protected String getAnnotationValue(MethodInvocation invocation) {
+        RequiresPermissions prAnnotation = (RequiresPermissions) getAnnotation(invocation);
+        return prAnnotation.value();
+    } */
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationHandler.java
new file mode 100644
index 0000000..db54424
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles} annotation is declared, and if so, performs
+ * a role check to see if the calling <code>Subject</code> is allowed to proceed.
+ *
+ * @since 0.9.0
+ */
+public class RoleAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this handler looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles} annotations.
+     */
+    public RoleAnnotationHandler() {
+        super(RequiresRoles.class);
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> has the Annotation's specified roles, and if not, throws an
+     * <code>AuthorizingException</code> indicating that access is denied.
+     *
+     * @param a the RequiresRoles annotation to use to check for one or more roles
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if the calling <code>Subject</code> does not have the role(s) necessary to
+     *          proceed.
+     */
+    public void assertAuthorized(Annotation a) throws AuthorizationException {
+        if (!(a instanceof RequiresRoles)) return;
+
+        RequiresRoles rrAnnotation = (RequiresRoles) a;
+        String[] roles = rrAnnotation.value();
+
+        if (roles.length == 1) {
+            getSubject().checkRole(roles[0]);
+            return;
+        }
+        if (Logical.AND.equals(rrAnnotation.logical())) {
+            getSubject().checkRoles(Arrays.asList(roles));
+            return;
+        }
+        if (Logical.OR.equals(rrAnnotation.logical())) {
+            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
+            boolean hasAtLeastOneRole = false;
+            for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true;
+            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
+            if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..a034042
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/RoleAnnotationMethodInterceptor.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+
+
+/**
+ * Checks to see if a @{@link RequiresRoles RequiresRoles} annotation is declared, and if so, performs
+ * a role check to see if the calling <code>Subject</code> is allowed to invoke the method.
+ *
+ * @since 0.9
+ */
+public class RoleAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     * {@link RequiresRoles RequiresRoles} annotations in a method declaration.
+     */
+    public RoleAnnotationMethodInterceptor() {
+        super( new RoleAnnotationHandler() );
+    }
+
+    /**
+     * @param resolver
+     * @since 1.1
+     */
+    public RoleAnnotationMethodInterceptor(AnnotationResolver resolver) {
+        super(new RoleAnnotationHandler(), resolver);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationHandler.java
new file mode 100644
index 0000000..d48ca81
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.annotation.RequiresUser;
+
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} annotation
+ * is declared, and if so, ensures the calling <code>Subject</code> is <em>either</em>
+ * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated} <b><em>or</em></b> remembered via remember
+ * me services before allowing access.
+ * <p>
+ * This annotation essentially ensures that <code>subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()} != null</code>.
+ *
+ * @since 0.9.0
+ */
+public class UserAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this handler looks for
+     *
+     * {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} annotations.
+     */
+    public UserAnnotationHandler() {
+        super(RequiresUser.class);
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> is a <em>user</em>, that is, they are <em>either</code>
+     * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated} <b><em>or</em></b> remembered via remember
+     * me services before allowing access, and if not, throws an
+     * <code>AuthorizingException</code> indicating access is not allowed.
+     *
+     * @param a the RequiresUser annotation to check
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *         if the calling <code>Subject</code> is not authenticated or remembered via rememberMe services.
+     */
+    public void assertAuthorized(Annotation a) throws AuthorizationException {
+        if (a instanceof RequiresUser && getSubject().getPrincipal() == null) {
+            throw new UnauthenticatedException("Attempting to perform a user-only operation.  The current Subject is " +
+                    "not a user (they haven't been authenticated or remembered from a previous login).  " +
+                    "Access denied.");
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationMethodInterceptor.java
new file mode 100644
index 0000000..2b680ef
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/UserAnnotationMethodInterceptor.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+
+/**
+ * Checks to see if a @{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} annotation
+ * is declared, and if so, ensures the calling <code>Subject</code> is <em>either</em>
+ * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated} <b><em>or</em></b> remembered via remember
+ * me services before invoking the method.
+ * <p>
+ * This annotation essentially ensures that <code>subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()} != null</code>.
+ *
+ * @since 0.9.0
+ */
+public class UserAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     *
+     * {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser} annotations in a method
+     * declaration.
+     */
+    public UserAnnotationMethodInterceptor() {
+        super( new UserAnnotationHandler() );
+    }
+
+    /**
+     *
+     * @param resolver
+     * @since 1.1
+     */
+    public UserAnnotationMethodInterceptor(AnnotationResolver resolver) {
+        super(new UserAnnotationHandler(), resolver);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/package-info.java b/core/src/main/java/org/apache/shiro/authz/aop/package-info.java
new file mode 100644
index 0000000..6e5c0ec
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Contains AOP implementation support classes specifically used for authorization operations, particularly supporting
+ * AOP Method Interceptors and JSR-175 metadata Annotations.
+ */
+package org.apache.shiro.authz.aop;
diff --git a/core/src/main/java/org/apache/shiro/authz/package-info.java b/core/src/main/java/org/apache/shiro/authz/package-info.java
new file mode 100644
index 0000000..3403023
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Core interfaces and exceptions supporting Authorization (access control).
+ * <p/>
+ * Shiro abbreviates the word 'AuthoriZation' as <tt>authz</tt> to distinguish it seperately from
+ * 'AuthentiCation', abbreviated as <tt>authc</tt>.
+ * <p/>
+ * This package's primary interface of interest, which is the core of Shiro authorization functionality,
+ * is the <tt>Authorizer</tt>. This interface handles all aspects of principal-related security and is the
+ * facade to all other Shiro authorization components.
+ * <p/>
+ * Shiro has the ability to authorize subjects (a.k.a. users) without being intrusive to the application's
+ * domain model. Most applications will utilize the concepts of <tt>group</tt>s, <tt>role</tt>s, and
+ * <tt>permission</tt>s, but Shiro tries to be as non-invasive as possible doesn't require any such
+ * interfaces (although a Permission interface is made available for fine-grained access control policies if
+ * you want to use Shiro's permission support out-of-the-box).
+ * <p/>
+ * Although it is possible for applications to implement this and other interfaces directly, it is not
+ * recommended. Shiro already has base implementations which should be suitable for 99% of deployments.
+ */
+package org.apache.shiro.authz;
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/AllPermission.java b/core/src/main/java/org/apache/shiro/authz/permission/AllPermission.java
new file mode 100644
index 0000000..5916067
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/AllPermission.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import java.io.Serializable;
+
+import org.apache.shiro.authz.Permission;
+
+
+/**
+ * An all <tt>AllPermission</tt> instance is one that always implies any other permission; that is, its
+ * {@link #implies implies} method always returns <tt>true</tt>.
+ *
+ * <p>You should be very careful about the users, roles, and/or groups to which this permission is assigned since
+ * those respective entities will have the ability to do anything.  As such, an instance of this class
+ * is typically only assigned only to "root" or "administrator" users or roles.
+ *
+ * @since 0.1
+ */
+public class AllPermission implements Permission, Serializable {
+
+    /**
+     * Always returns <tt>true</tt>, indicating any Subject granted this permission can do anything.
+     *
+     * @param p the Permission to check for implies logic.
+     * @return <tt>true</tt> always, indicating any Subject grated this permission can do anything.
+     */
+    public boolean implies(Permission p) {
+        return true;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java b/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
new file mode 100644
index 0000000..665454b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.util.StringUtils;
+
+import java.util.Set;
+
+/**
+ * Provides a base Permission class from which type-safe/domain-specific subclasses may extend.  Can be used
+ * as a base class for JPA/Hibernate persisted permissions that wish to store the parts of the permission string
+ * in separate columns (e.g. 'domain', 'actions' and 'targets' columns), which can be used in querying
+ * strategies.
+ *
+ * @since 1.0
+ */
+public class DomainPermission extends WildcardPermission {
+
+    private String domain;
+    private Set<String> actions;
+    private Set<String> targets;
+
+    private static final long serialVersionUID = 1l;
+
+    /**
+     * Creates a domain permission with *all* actions for *all* targets;
+     */
+    public DomainPermission() {
+        this.domain = getDomain(getClass());
+        setParts(getDomain(getClass()));
+    }
+
+    public DomainPermission(String actions) {
+        domain = getDomain(getClass());
+        this.actions = StringUtils.splitToSet(actions, SUBPART_DIVIDER_TOKEN);
+        encodeParts(domain, actions, null);
+    }
+
+    public DomainPermission(String actions, String targets) {
+        this.domain = getDomain(getClass());
+        this.actions = StringUtils.splitToSet(actions, SUBPART_DIVIDER_TOKEN);
+        this.targets = StringUtils.splitToSet(targets, SUBPART_DIVIDER_TOKEN);
+        encodeParts(this.domain, actions, targets);
+    }
+
+    protected DomainPermission(Set<String> actions, Set<String> targets) {
+        this.domain = getDomain(getClass());
+        setParts(domain, actions, targets);
+    }
+
+    private void encodeParts(String domain, String actions, String targets) {
+        if (!StringUtils.hasText(domain)) {
+            throw new IllegalArgumentException("domain argument cannot be null or empty.");
+        }
+        StringBuilder sb = new StringBuilder(domain);
+
+        if (!StringUtils.hasText(actions)) {
+            if (StringUtils.hasText(targets)) {
+                sb.append(PART_DIVIDER_TOKEN).append(WILDCARD_TOKEN);
+            }
+        } else {
+            sb.append(PART_DIVIDER_TOKEN).append(actions);
+        }
+        if (StringUtils.hasText(targets)) {
+            sb.append(PART_DIVIDER_TOKEN).append(targets);
+        }
+        setParts(sb.toString());
+    }
+
+    protected void setParts(String domain, Set<String> actions, Set<String> targets) {
+        String actionsString = StringUtils.toDelimitedString(actions, SUBPART_DIVIDER_TOKEN);
+        String targetsString = StringUtils.toDelimitedString(targets, SUBPART_DIVIDER_TOKEN);
+        encodeParts(domain, actionsString, targetsString);
+        this.domain = domain;
+        this.actions = actions;
+        this.targets = targets;
+    }
+
+    protected String getDomain(Class<? extends DomainPermission> clazz) {
+        String domain = clazz.getSimpleName().toLowerCase();
+        //strip any trailing 'permission' text from the name (as all subclasses should have been named):
+        int index = domain.lastIndexOf("permission");
+        if (index != -1) {
+            domain = domain.substring(0, index);
+        }
+        return domain;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    protected void setDomain(String domain) {
+        if (this.domain != null && this.domain.equals(domain)) {
+            return;
+        }
+        this.domain = domain;
+        setParts(domain, actions, targets);
+    }
+
+    public Set<String> getActions() {
+        return actions;
+    }
+
+    protected void setActions(Set<String> actions) {
+        if (this.actions != null && this.actions.equals(actions)) {
+            return;
+        }
+        this.actions = actions;
+        setParts(domain, actions, targets);
+    }
+
+    public Set<String> getTargets() {
+        return targets;
+    }
+
+    protected void setTargets(Set<String> targets) {
+        this.targets = targets;
+        if (this.targets != null && this.targets.equals(targets)) {
+            return;
+        }
+        this.targets = targets;
+        setParts(domain, actions, targets);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/InvalidPermissionStringException.java b/core/src/main/java/org/apache/shiro/authz/permission/InvalidPermissionStringException.java
new file mode 100644
index 0000000..fb830f9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/InvalidPermissionStringException.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Thrown by {@link PermissionResolver#resolvePermission(String)} when the String being parsed is not
+ * valid for that resolver.
+ *
+ * @since 0.9
+ */
+public class InvalidPermissionStringException extends ShiroException
+{
+
+    private String permissionString;
+
+    /**
+     * Constructs a new exception with the given message and permission string.
+     *
+     * @param message          the exception message.
+     * @param permissionString the invalid permission string.
+     */
+    public InvalidPermissionStringException(String message, String permissionString) {
+        super(message);
+        this.permissionString = permissionString;
+    }
+
+    /**
+     * Returns the permission string that was invalid and caused this exception to
+     * be thrown.
+     *
+     * @return the permission string.
+     */
+    public String getPermissionString() {
+        return this.permissionString;
+    }
+
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolver.java b/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolver.java
new file mode 100644
index 0000000..7636d33
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolver.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.authz.Permission;
+
+/**
+ * A {@code PermisisonResolver} resolves a String value and converts it into a
+ * {@link org.apache.shiro.authz.Permission Permission} instance.
+ * <p/>
+ * The default {@link WildcardPermissionResolver} should be
+ * suitable for most purposes, which constructs {@link WildcardPermission} objects.
+ * However, any resolver may be configured if an application wishes to use different
+ * {@link org.apache.shiro.authz.Permission} implementations.
+ * <p/>
+ * A {@code PermissionResolver} is used by many Shiro components such as annotations, property file
+ * configuration, URL configuration, etc.  It is useful whenever a String representation of a permission is specified
+ * and that String needs to be converted to a Permission instance before executing a security check.
+ * <p/>
+ * Shiro chooses to support {@link WildcardPermission Wildcardpermission}s by default in almost all components and
+ * we do that in the form of the {@link WildcardPermissionResolver WildcardPermissionResolver}.   One of the nice
+ * things about {@code WildcardPermission}s being supported by default is that it makes it very easy to
+ * store complex permissions in the database - and also makes it very easy to represent permissions in JSP files,
+ * annotations, etc., where a simple string representation is useful.
+ * <p/>
+ * Although this happens to be the Shiro default, you are of course free to provide custom
+ * String-to-Permission conversion by providing Shiro components any instance of this interface.
+ *
+ * @see org.apache.shiro.authz.ModularRealmAuthorizer#setPermissionResolver(PermissionResolver) ModularRealmAuthorizer.setPermissionResolver
+ * @see org.apache.shiro.realm.AuthorizingRealm#setPermissionResolver(PermissionResolver) AuthorizingRealm.setPermissionResolver
+ * @see PermissionResolverAware PermissionResolverAware
+ * @since 0.9
+ */
+public interface PermissionResolver {
+
+    /**
+     * Resolves a Permission based on the given String representation.
+     *
+     * @param permissionString the String representation of a permission.
+     * @return A Permission object that can be used internally to determine a subject's permissions.
+     * @throws InvalidPermissionStringException
+     *          if the permission string is not valid for this resolver.
+     */
+    Permission resolvePermission(String permissionString);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolverAware.java b/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolverAware.java
new file mode 100644
index 0000000..9ea76c2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/PermissionResolverAware.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+/**
+ * Interface implemented by a component that wishes to use any application-configured <tt>PermissionResolver</tt> that
+ * might already exist instead of potentially creating one itself.
+ *
+ * <p>This is mostly implemented by {@link org.apache.shiro.authz.Authorizer Authorizer} and
+ * {@link org.apache.shiro.realm.Realm Realm} implementations since they
+ * are the ones performing permission checks and need to know how to resolve Strings into
+ * {@link org.apache.shiro.authz.Permission Permission} instances.
+ *
+ * @since 0.9
+ */
+public interface PermissionResolverAware {
+
+    /**
+     * Sets the specified <tt>PermissionResolver</tt> on this instance.
+     *
+     * @param pr the <tt>PermissionResolver</tt> being set.
+     */
+    public void setPermissionResolver(PermissionResolver pr);
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolver.java b/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolver.java
new file mode 100644
index 0000000..68e3cdb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolver.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.authz.Permission;
+
+import java.util.Collection;
+
+/**
+ * A RolePermissionResolver resolves a String value and converts it into a Collection of
+ * {@link org.apache.shiro.authz.Permission} instances.
+ * <p/>
+ * In some cases a {@link org.apache.shiro.realm.Realm} my only be able to return a list of roles.  This
+ * component allows an application to resolve the roles into permissions.
+ *
+ */
+public interface RolePermissionResolver {
+
+    /**
+     * Resolves a Collection of Permissions based on the given String representation.
+     *
+     * @param roleString the String representation of a role name to resolve.
+     * @return a Collection of Permissions based on the given String representation.
+     */
+    Collection<Permission> resolvePermissionsInRole(String roleString);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolverAware.java b/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolverAware.java
new file mode 100644
index 0000000..be9d841
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/RolePermissionResolverAware.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+/**
+ * Interface implemented by a component that wishes to use any application-configured <tt>RolePermissionResolver</tt> that
+ * might already exist instead of potentially creating one itself.
+ *
+ * <p>This is mostly implemented by {@link org.apache.shiro.authz.Authorizer Authorizer} and
+ * {@link org.apache.shiro.realm.Realm Realm} implementations since they
+ * are the ones performing permission checks and need to know how to resolve Strings into
+ * {@link org.apache.shiro.authz.Permission Permission} instances.
+ *
+ * @since 1.0
+ */
+public interface RolePermissionResolverAware {
+
+    /**
+     * Sets the specified <tt>RolePermissionResolver</tt> on this instance.
+     *
+     * @param rpr the <tt>RolePermissionResolver</tt> being set.
+     */
+    public void setRolePermissionResolver(RolePermissionResolver rpr);
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
new file mode 100644
index 0000000..1a755ee
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.util.CollectionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A <code>WildcardPermission</code> is a very flexible permission construct supporting multiple levels of
+ * permission matching. However, most people will probably follow some standard conventions as explained below.
+ * <p/>
+ * <h3>Simple Usage</h3>
+ * <p/>
+ * In the simplest form, <code>WildcardPermission</code> can be used as a simple permission string. You could grant a
+ * user an "editNewsletter" permission and then check to see if the user has the editNewsletter
+ * permission by calling
+ * <p/>
+ * <code>subject.isPermitted("editNewsletter")</code>
+ * <p/>
+ * This is (mostly) equivalent to
+ * <p/>
+ * <code>subject.isPermitted( new WildcardPermission("editNewsletter") )</code>
+ * <p/>
+ * but more on that later.
+ * <p/>
+ * The simple permission string may work for simple applications, but it requires you to have permissions like
+ * <code>"viewNewsletter"</code>, <code>"deleteNewsletter"</code>,
+ * <code>"createNewsletter"</code>, etc. You can also grant a user <code>"*"</code> permissions
+ * using the wildcard character (giving this class its name), which means they have <em>all</em> permissions. But
+ * using this approach there's no way to just say a user has "all newsletter permissions".
+ * <p/>
+ * For this reason, <code>WildcardPermission</code> supports multiple <em>levels</em> of permissioning.
+ * <p/>
+ * <h3>Multiple Levels</h3>
+ * <p/>
+ * WildcardPermission</code> also supports the concept of multiple <em>levels</em>.  For example, you could
+ * restructure the previous simple example by granting a user the permission <code>"newsletter:edit"</code>.
+ * The colon in this example is a special character used by the <code>WildcardPermission</code> that delimits the
+ * next token in the permission.
+ * <p/>
+ * In this example, the first token is the <em>domain</em> that is being operated on
+ * and the second token is the <em>action</em> being performed. Each level can contain multiple values.  So you
+ * could simply grant a user the permission <code>"newsletter:view,edit,create"</code> which gives them
+ * access to perform <code>view</code>, <code>edit</code>, and <code>create</code> actions in the <code>newsletter</code>
+ * <em>domain</em>. Then you could check to see if the user has the <code>"newsletter:create"</code>
+ * permission by calling
+ * <p/>
+ * <code>subject.isPermitted("newsletter:create")</code>
+ * <p/>
+ * (which would return true).
+ * <p/>
+ * In addition to granting multiple permissions via a single string, you can grant all permission for a particular
+ * level. So if you wanted to grant a user all actions in the <code>newsletter</code> domain, you could simply give
+ * them <code>"newsletter:*"</code>. Now, any permission check for <code>"newsletter:XXX"</code>
+ * will return <code>true</code>. It is also possible to use the wildcard token at the domain level (or both): so you
+ * could grant a user the <code>"view"</code> action across all domains <code>"*:view"</code>.
+ * <p/>
+ * <h3>Instance-level Access Control</h3>
+ * <p/>
+ * Another common usage of the <code>WildcardPermission</code> is to model instance-level Access Control Lists.
+ * In this scenario you use three tokens - the first is the <em>domain</em>, the second is the <em>action</em>, and
+ * the third is the <em>instance</em> you are acting on.
+ * <p/>
+ * So for example you could grant a user <code>"newsletter:edit:12,13,18"</code>.  In this example, assume
+ * that the third token is the system's ID of the newsletter. That would allow the user to edit newsletters
+ * <code>12</code>, <code>13</code>, and <code>18</code>. This is an extremely powerful way to express permissions,
+ * since you can now say things like <code>"newsletter:*:13"</code> (grant a user all actions for newsletter
+ * <code>13</code>), <code>"newsletter:view,create,edit:*"</code> (allow the user to
+ * <code>view</code>, <code>create</code>, or <code>edit</code> <em>any</em> newsletter), or
+ * <code>"newsletter:*:*</code> (allow the user to perform <em>any</em> action on <em>any</em> newsletter).
+ * <p/>
+ * To perform checks against these instance-level permissions, the application should include the instance ID in the
+ * permission check like so:
+ * <p/>
+ * <code>subject.isPermitted( "newsletter:edit:13" )</code>
+ * <p/>
+ * There is no limit to the number of tokens that can be used, so it is up to your imagination in terms of ways that
+ * this could be used in your application.  However, the Shiro team likes to standardize some common usages shown
+ * above to help people get started and provide consistency in the Shiro community.
+ *
+ * @since 0.9
+ */
+public class WildcardPermission implements Permission, Serializable {
+
+    //TODO - JavaDoc methods
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    protected static final String WILDCARD_TOKEN = "*";
+    protected static final String PART_DIVIDER_TOKEN = ":";
+    protected static final String SUBPART_DIVIDER_TOKEN = ",";
+    protected static final boolean DEFAULT_CASE_SENSITIVE = false;
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private List<Set<String>> parts;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+    /**
+     * Default no-arg constructor for subclasses only - end-user developers instantiating Permission instances must
+     * provide a wildcard string at a minimum, since Permission instances are immutable once instantiated.
+     * <p/>
+     * Note that the WildcardPermission class is very robust and typically subclasses are not necessary unless you
+     * wish to create type-safe Permission objects that would be used in your application, such as perhaps a
+     * {@code UserPermission}, {@code SystemPermission}, {@code PrinterPermission}, etc.  If you want such type-safe
+     * permission usage, consider subclassing the {@link DomainPermission DomainPermission} class for your needs.
+     */
+    protected WildcardPermission() {
+    }
+
+    public WildcardPermission(String wildcardString) {
+        this(wildcardString, DEFAULT_CASE_SENSITIVE);
+    }
+
+    public WildcardPermission(String wildcardString, boolean caseSensitive) {
+        setParts(wildcardString, caseSensitive);
+    }
+
+    protected void setParts(String wildcardString) {
+        setParts(wildcardString, DEFAULT_CASE_SENSITIVE);
+    }
+
+    protected void setParts(String wildcardString, boolean caseSensitive) {
+        if (wildcardString == null || wildcardString.trim().length() == 0) {
+            throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
+        }
+
+        wildcardString = wildcardString.trim();
+
+        List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
+
+        this.parts = new ArrayList<Set<String>>();
+        for (String part : parts) {
+            Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
+            if (!caseSensitive) {
+                subparts = lowercase(subparts);
+            }
+            if (subparts.isEmpty()) {
+                throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
+            }
+            this.parts.add(subparts);
+        }
+
+        if (this.parts.isEmpty()) {
+            throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
+        }
+    }
+
+    private Set<String> lowercase(Set<String> subparts) {
+        Set<String> lowerCasedSubparts = new LinkedHashSet<String>(subparts.size());
+        for (String subpart : subparts) {
+            lowerCasedSubparts.add(subpart.toLowerCase());
+        }
+        return lowerCasedSubparts;
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+    protected List<Set<String>> getParts() {
+        return this.parts;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    public boolean implies(Permission p) {
+        // By default only supports comparisons with other WildcardPermissions
+        if (!(p instanceof WildcardPermission)) {
+            return false;
+        }
+
+        WildcardPermission wp = (WildcardPermission) p;
+
+        List<Set<String>> otherParts = wp.getParts();
+
+        int i = 0;
+        for (Set<String> otherPart : otherParts) {
+            // If this permission has less parts than the other permission, everything after the number of parts contained
+            // in this permission is automatically implied, so return true
+            if (getParts().size() - 1 < i) {
+                return true;
+            } else {
+                Set<String> part = getParts().get(i);
+                if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
+                    return false;
+                }
+                i++;
+            }
+        }
+
+        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
+        for (; i < getParts().size(); i++) {
+            Set<String> part = getParts().get(i);
+            if (!part.contains(WILDCARD_TOKEN)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        for (Set<String> part : parts) {
+            if (buffer.length() > 0) {
+                buffer.append(":");
+            }
+            buffer.append(part);
+        }
+        return buffer.toString();
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof WildcardPermission) {
+            WildcardPermission wp = (WildcardPermission) o;
+            return parts.equals(wp.parts);
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return parts.hashCode();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermissionResolver.java b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermissionResolver.java
new file mode 100644
index 0000000..b6af40e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermissionResolver.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.apache.shiro.authz.Permission;
+
+
+/**
+ * <tt>PermissionResolver</tt> implementation that returns a new {@link WildcardPermission WildcardPermission}
+ * based on the input string.
+ *
+ * @since 0.9
+ */
+public class WildcardPermissionResolver implements PermissionResolver {
+
+    /**
+     * Returns a new {@link WildcardPermission WildcardPermission} instance constructed based on the specified
+     * <tt>permissionString</tt>.
+     *
+     * @param permissionString the permission string to convert to a {@link Permission Permission} instance.
+     * @return a new {@link WildcardPermission WildcardPermission} instance constructed based on the specified
+     *         <tt>permissionString</tt>
+     */
+    public Permission resolvePermission(String permissionString) {
+        return new WildcardPermission(permissionString);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/permission/package-info.java b/core/src/main/java/org/apache/shiro/authz/permission/package-info.java
new file mode 100644
index 0000000..748c049
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/permission/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support and default implementations for Shiro's {@link org.apache.shiro.authz.Permission Permission}
+ * interface.
+ * <p/>
+ * Also note the {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver} interface, as
+ * it plays an important part in many of Shiro's {@link org.apache.shiro.realm.Realm Realm} implementations
+ * and AOP support.
+ */
+package org.apache.shiro.authz.permission;
diff --git a/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java b/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java
new file mode 100644
index 0000000..d5f08e5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/AbstractCacheManager.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.StringUtils;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Very simple abstract {@code CacheManager} implementation that retains all created {@link Cache Cache} instances in
+ * an in-memory {@link ConcurrentMap ConcurrentMap}.  {@code Cache} instance creation is left to subclasses via
+ * the {@link #createCache createCache} method implementation.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractCacheManager implements CacheManager, Destroyable {
+
+    /**
+     * Retains all Cache objects maintained by this cache manager.
+     */
+    private final ConcurrentMap<String, Cache> caches;
+
+    /**
+     * Default no-arg constructor that instantiates an internal name-to-cache {@code ConcurrentMap}.
+     */
+    public AbstractCacheManager() {
+        this.caches = new ConcurrentHashMap<String, Cache>();
+    }
+
+    /**
+     * Returns the cache with the specified {@code name}.  If the cache instance does not yet exist, it will be lazily
+     * created, retained for further access, and then returned.
+     *
+     * @param name the name of the cache to acquire.
+     * @return the cache with the specified {@code name}.
+     * @throws IllegalArgumentException if the {@code name} argument is {@code null} or does not contain text.
+     * @throws CacheException           if there is a problem lazily creating a {@code Cache} instance.
+     */
+    public <K, V> Cache<K, V> getCache(String name) throws IllegalArgumentException, CacheException {
+        if (!StringUtils.hasText(name)) {
+            throw new IllegalArgumentException("Cache name cannot be null or empty.");
+        }
+
+        Cache cache;
+
+        cache = caches.get(name);
+        if (cache == null) {
+            cache = createCache(name);
+            Cache existing = caches.putIfAbsent(name, cache);
+            if (existing != null) {
+                cache = existing;
+            }
+        }
+
+        //noinspection unchecked
+        return cache;
+    }
+
+    /**
+     * Creates a new {@code Cache} instance associated with the specified {@code name}.
+     *
+     * @param name the name of the cache to create
+     * @return a new {@code Cache} instance associated with the specified {@code name}.
+     * @throws CacheException if the {@code Cache} instance cannot be created.
+     */
+    protected abstract Cache createCache(String name) throws CacheException;
+
+    /**
+     * Cleanup method that first {@link LifecycleUtils#destroy destroys} all of it's managed caches and then
+     * {@link java.util.Map#clear clears} out the internally referenced cache map.
+     *
+     * @throws Exception if any of the managed caches can't destroy properly.
+     */
+    public void destroy() throws Exception {
+        while (!caches.isEmpty()) {
+            for (Cache cache : caches.values()) {
+                LifecycleUtils.destroy(cache);
+            }
+            caches.clear();
+        }
+    }
+
+    public String toString() {
+        Collection<Cache> values = caches.values();
+        StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+                .append(" with ")
+                .append(caches.size())
+                .append(" cache(s)): [");
+        int i = 0;
+        for (Cache cache : values) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(cache.toString());
+            i++;
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/Cache.java b/core/src/main/java/org/apache/shiro/cache/Cache.java
new file mode 100644
index 0000000..0ff7979
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/Cache.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * A Cache efficiently stores temporary objects primarily to improve an application's performance.
+ *
+ * <p>Shiro doesn't implement a full Cache mechanism itself, since that is outside the core competency of a
+ * Security framework.  Instead, this interface provides an abstraction (wrapper) API on top of an underlying
+ * cache framework's cache instance (e.g. JCache, Ehcache, JCS, OSCache, JBossCache, TerraCotta, Coherence,
+ * GigaSpaces, etc, etc), allowing a Shiro user to configure any cache mechanism they choose.
+ *
+ * @since 0.2
+ */
+public interface Cache<K, V> {
+
+    /**
+     * Returns the Cached value stored under the specified {@code key} or
+     * {@code null} if there is no Cache entry for that {@code key}.
+     *
+     * @param key the key that the value was previous added with
+     * @return the cached object or {@code null} if there is no entry for the specified {@code key}
+     * @throws CacheException if there is a problem accessing the underlying cache system
+     */
+    public V get(K key) throws CacheException;
+
+    /**
+     * Adds a Cache entry.
+     *
+     * @param key   the key used to identify the object being stored.
+     * @param value the value to be stored in the cache.
+     * @return the previous value associated with the given {@code key} or {@code null} if there was previous value
+     * @throws CacheException if there is a problem accessing the underlying cache system
+     */
+    public V put(K key, V value) throws CacheException;
+
+    /**
+     * Remove the cache entry corresponding to the specified key.
+     *
+     * @param key the key of the entry to be removed.
+     * @return the previous value associated with the given {@code key} or {@code null} if there was previous value
+     * @throws CacheException if there is a problem accessing the underlying cache system
+     */
+    public V remove(K key) throws CacheException;
+
+    /**
+     * Clear all entries from the cache.
+     *
+     * @throws CacheException if there is a problem accessing the underlying cache system
+     */
+    public void clear() throws CacheException;
+
+    /**
+     * Returns the number of entries in the cache.
+     *
+     * @return the number of entries in the cache.
+     */
+    public int size();
+
+    /**
+     * Returns a view of all the keys for entries contained in this cache.
+     *
+     * @return a view of all the keys for entries contained in this cache.
+     */
+    public Set<K> keys();
+
+    /**
+     * Returns a view of all of the values contained in this cache.
+     *
+     * @return a view of all of the values contained in this cache.
+     */
+    public Collection<V> values();
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/CacheException.java b/core/src/main/java/org/apache/shiro/cache/CacheException.java
new file mode 100644
index 0000000..a798702
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/CacheException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Root class of all Shiro exceptions related to caching operations.
+ *
+ * @since 0.2
+ */
+public class CacheException extends ShiroException
+{
+
+    /**
+     * Creates a new <code>CacheException</code>.
+     */
+    public CacheException() {
+        super();
+    }
+
+    /**
+     * Creates a new <code>CacheException</code>.
+     *
+     * @param message the reason for the exception.
+     */
+    public CacheException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new <code>CacheException</code>.
+     *
+     * @param cause the underlying cause of the exception.
+     */
+    public CacheException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new <code>CacheException</code>.
+     *
+     * @param message the reason for the exception.
+     * @param cause   the underlying cause of the exception.
+     */
+    public CacheException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/CacheManager.java b/core/src/main/java/org/apache/shiro/cache/CacheManager.java
new file mode 100644
index 0000000..4719d4d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/CacheManager.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+/**
+ * A CacheManager provides and maintains the lifecycles of {@link Cache Cache} instances.
+ *
+ * <p>Shiro doesn't implement a full Cache mechanism itself, since that is outside the core competency of a
+ * Security framework.  Instead, this interface provides an abstraction (wrapper) API on top of an underlying
+ * cache framework's main Manager component (e.g. JCache, Ehcache, JCS, OSCache, JBossCache, TerraCotta, Coherence,
+ * GigaSpaces, etc, etc), allowing a Shiro user to configure any cache mechanism they choose.
+ *
+ * @since 0.9
+ */
+public interface CacheManager {
+
+    /**
+     * Acquires the cache with the specified <code>name</code>.  If a cache does not yet exist with that name, a new one
+     * will be created with that name and returned.
+     *
+     * @param name the name of the cache to acquire.
+     * @return the Cache with the given name
+     * @throws CacheException if there is an error acquiring the Cache instance.
+     */
+    public <K, V> Cache<K, V> getCache(String name) throws CacheException;
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/CacheManagerAware.java b/core/src/main/java/org/apache/shiro/cache/CacheManagerAware.java
new file mode 100644
index 0000000..1b6aa0f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/CacheManagerAware.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+/**
+ * Interface implemented by components that utilize a CacheManager and wish that CacheManager to be supplied if
+ * one is available.
+ *
+ * <p>This is used so internal security components that use a CacheManager can be injected with it instead of having
+ * to create one on their own.
+ *
+ * @since 0.9
+ */
+public interface CacheManagerAware {
+
+    /**
+     * Sets the available CacheManager instance on this component.
+     *
+     * @param cacheManager the CacheManager instance to set on this component.
+     */
+    void setCacheManager(CacheManager cacheManager);
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/MapCache.java b/core/src/main/java/org/apache/shiro/cache/MapCache.java
new file mode 100644
index 0000000..04db6ec
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/MapCache.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+import org.apache.shiro.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A <code>MapCache</code> is a {@link Cache Cache} implementation that uses a backing {@link Map} instance to store
+ * and retrieve cached data.
+ *
+ * @since 1.0
+ */
+public class MapCache<K, V> implements Cache<K, V> {
+
+    /**
+     * Backing instance.
+     */
+    private final Map<K, V> map;
+
+    /**
+     * The name of this cache.
+     */
+    private final String name;
+
+    public MapCache(String name, Map<K, V> backingMap) {
+        if (name == null) {
+            throw new IllegalArgumentException("Cache name cannot be null.");
+        }
+        if (backingMap == null) {
+            throw new IllegalArgumentException("Backing map cannot be null.");
+        }
+        this.name = name;
+        this.map = backingMap;
+    }
+
+    public V get(K key) throws CacheException {
+        return map.get(key);
+    }
+
+    public V put(K key, V value) throws CacheException {
+        return map.put(key, value);
+    }
+
+    public V remove(K key) throws CacheException {
+        return map.remove(key);
+    }
+
+    public void clear() throws CacheException {
+        map.clear();
+    }
+
+    public int size() {
+        return map.size();
+    }
+
+    public Set<K> keys() {
+        Set<K> keys = map.keySet();
+        if (!keys.isEmpty()) {
+            return Collections.unmodifiableSet(keys);
+        }
+        return Collections.emptySet();
+    }
+
+    public Collection<V> values() {
+        Collection<V> values = map.values();
+        if (!CollectionUtils.isEmpty(values)) {
+            return Collections.unmodifiableCollection(values);
+        }
+        return Collections.emptySet();
+    }
+
+    public String toString() {
+        return new StringBuilder("MapCache '")
+                .append(name).append("' (")
+                .append(map.size())
+                .append(" entries)")
+                .toString();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java b/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java
new file mode 100644
index 0000000..56cf12c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/MemoryConstrainedCacheManager.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache;
+
+import org.apache.shiro.util.SoftHashMap;
+
+/**
+ * Simple memory-only based {@link CacheManager CacheManager} implementation usable in production
+ * environments.  It will not cause memory leaks as it produces {@link Cache Cache}s backed by
+ * {@link SoftHashMap SoftHashMap}s which auto-size themselves based on the runtime environment's memory
+ * limitations and garbage collection behavior.
+ * <p/>
+ * While the {@code Cache} instances created are thread-safe, they do not offer any enterprise-level features such as
+ * cache coherency, optimistic locking, failover or other similar features.  For more enterprise features, consider
+ * using a different {@code CacheManager} implementation backed by an enterprise-grade caching product (EhCache,
+ * TerraCotta, Coherence, GigaSpaces, etc, etc).
+ *
+ * @since 1.0
+ */
+public class MemoryConstrainedCacheManager extends AbstractCacheManager {
+
+    /**
+     * Returns a new {@link MapCache MapCache} instance backed by a {@link SoftHashMap}.
+     *
+     * @param name the name of the cache
+     * @return a new {@link MapCache MapCache} instance backed by a {@link SoftHashMap}.
+     */
+    @Override
+    protected Cache createCache(String name) {
+        return new MapCache<Object, Object>(name, new SoftHashMap<Object, Object>());
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/cache/package-info.java b/core/src/main/java/org/apache/shiro/cache/package-info.java
new file mode 100644
index 0000000..5fc3cb3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/cache/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Caching support used to enhance performance for any security operation.
+ */
+package org.apache.shiro.cache;
diff --git a/core/src/main/java/org/apache/shiro/codec/Base64.java b/core/src/main/java/org/apache/shiro/codec/Base64.java
new file mode 100644
index 0000000..2d6cb08
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/Base64.java
@@ -0,0 +1,506 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.codec;
+
+/**
+ * Provides <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a> encoding and decoding as defined by
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
+ * <p/>
+ * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
+ * <p/>
+ * This class was borrowed from Apache Commons Codec SVN repository (rev. 618419) with modifications
+ * to enable Base64 conversion without a full dependecny on Commons Codec.  We didn't want to reinvent the wheel of
+ * great work they've done, but also didn't want to force every Shiro user to depend on the commons-codec.jar
+ * <p/>
+ * As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
+ * remained in tact.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Base64">Wikipedia: Base 64</a>
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @since 0.9
+ */
+public class Base64 {
+
+    /**
+     * Chunk size per RFC 2045 section 6.8.
+     * <p/>
+     * The character limit does not count the trailing CRLF, but counts all other characters, including any
+     * equal signs.
+     *
+     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
+     */
+    static final int CHUNK_SIZE = 76;
+
+    /**
+     * Chunk separator per RFC 2045 section 2.1.
+     *
+     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
+     */
+    static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
+
+    /**
+     * The base length.
+     */
+    private static final int BASELENGTH = 255;
+
+    /**
+     * Lookup length.
+     */
+    private static final int LOOKUPLENGTH = 64;
+
+    /**
+     * Used to calculate the number of bits in a byte.
+     */
+    private static final int EIGHTBIT = 8;
+
+    /**
+     * Used when encoding something which has fewer than 24 bits.
+     */
+    private static final int SIXTEENBIT = 16;
+
+    /**
+     * Used to determine how many bits data contains.
+     */
+    private static final int TWENTYFOURBITGROUP = 24;
+
+    /**
+     * Used to get the number of Quadruples.
+     */
+    private static final int FOURBYTE = 4;
+
+    /**
+     * Used to test the sign of a byte.
+     */
+    private static final int SIGN = -128;
+
+    /**
+     * Byte used to pad output.
+     */
+    private static final byte PAD = (byte) '=';
+
+    /**
+     * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as
+     * indices.
+     * <p/>
+     * <p>For example, <code>base64Alphabet['+']</code> returns <code>62</code>.</p>
+     * <p/>
+     * <p>The value of undefined encodings is <code>-1</code>.</p>
+     */
+    private static final byte[] base64Alphabet = new byte[BASELENGTH];
+
+    /**
+     * <p>Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through
+     * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and
+     * <code>/</code>.</p>
+     * <p/>
+     * <p>This array is accessed by using character values as indices.</p>
+     * <p/>
+     * <p>For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.</p>
+     */
+    private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
+
+    // Populating the lookup and character arrays
+
+    static {
+        for (int i = 0; i < BASELENGTH; i++) {
+            base64Alphabet[i] = (byte) -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++) {
+            lookUpBase64Alphabet[i] = (byte) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++) {
+            lookUpBase64Alphabet[i] = (byte) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++) {
+            lookUpBase64Alphabet[i] = (byte) ('0' + j);
+        }
+
+        lookUpBase64Alphabet[62] = (byte) '+';
+        lookUpBase64Alphabet[63] = (byte) '/';
+    }
+
+    /**
+     * Returns whether or not the <code>octect</code> is in the base 64 alphabet.
+     *
+     * @param octect The value to test
+     * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
+     */
+    private static boolean isBase64(byte octect) {
+        if (octect == PAD) {
+            return true;
+        } else //noinspection RedundantIfStatement
+            if (octect < 0 || base64Alphabet[octect] == -1) {
+                return false;
+            } else {
+                return true;
+            }
+    }
+
+    /**
+     * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
+     *
+     * @param arrayOctect byte array to test
+     * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
+     *         empty; false, otherwise
+     */
+    public static boolean isBase64(byte[] arrayOctect) {
+
+        arrayOctect = discardWhitespace(arrayOctect);
+
+        int length = arrayOctect.length;
+        if (length == 0) {
+            // shouldn't a 0 length array be valid base64 data?
+            // return false;
+            return true;
+        }
+        for (int i = 0; i < length; i++) {
+            if (!isBase64(arrayOctect[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Discards any whitespace from a base-64 encoded block.
+     *
+     * @param data The base-64 encoded data to discard the whitespace from.
+     * @return The data, less whitespace (see RFC 2045).
+     */
+    static byte[] discardWhitespace(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (byte aByte : data) {
+            switch (aByte) {
+                case (byte) ' ':
+                case (byte) '\n':
+                case (byte) '\r':
+                case (byte) '\t':
+                    break;
+                default:
+                    groomedData[bytesCopied++] = aByte;
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+
+    /**
+     * Base64 encodes the specified byte array and then encodes it as a String using Shiro's preferred character
+     * encoding (UTF-8).
+     *
+     * @param bytes the byte array to Base64 encode.
+     * @return a UTF-8 encoded String of the resulting Base64 encoded byte array.
+     */
+    public static String encodeToString(byte[] bytes) {
+        byte[] encoded = encode(bytes);
+        return CodecSupport.toString(encoded);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
+     *
+     * @param binaryData binary data to encodeToChars
+     * @return Base64 characters chunked in 76 character blocks
+     */
+    public static byte[] encodeChunked(byte[] binaryData) {
+        return encode(binaryData, true);
+    }
+
+    /**
+     * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
+     *
+     * @param pArray a byte array containing binary data
+     * @return A byte array containing only Base64 character data
+     */
+    public static byte[] encode(byte[] pArray) {
+        return encode(pArray, false);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+     *
+     * @param binaryData Array containing binary data to encodeToChars.
+     * @param isChunked  if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
+     * @return Base64-encoded data.
+     * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+     */
+    public static byte[] encode(byte[] binaryData, boolean isChunked) {
+        long binaryDataLength = binaryData.length;
+        long lengthDataBits = binaryDataLength * EIGHTBIT;
+        long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        long tripletCount = lengthDataBits / TWENTYFOURBITGROUP;
+        long encodedDataLengthLong;
+        int chunckCount = 0;
+
+        if (fewerThan24bits != 0) {
+            // data not divisible by 24 bit
+            encodedDataLengthLong = (tripletCount + 1) * 4;
+        } else {
+            // 16 or 8 bit
+            encodedDataLengthLong = tripletCount * 4;
+        }
+
+        // If the output is to be "chunked" into 76 character sections,
+        // for compliance with RFC 2045 MIME, then it is important to
+        // allow for extra length to account for the separator(s)
+        if (isChunked) {
+
+            chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math
+                    .ceil((float) encodedDataLengthLong / CHUNK_SIZE));
+            encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length;
+        }
+
+        if (encodedDataLengthLong > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException(
+                    "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
+        }
+        int encodedDataLength = (int) encodedDataLengthLong;
+        byte encodedData[] = new byte[encodedDataLength];
+
+        byte k, l, b1, b2, b3;
+
+        int encodedIndex = 0;
+        int dataIndex;
+        int i;
+        int nextSeparatorIndex = CHUNK_SIZE;
+        int chunksSoFar = 0;
+
+        // log.debug("number of triplets = " + numberTriplets);
+        for (i = 0; i < tripletCount; i++) {
+            dataIndex = i * 3;
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            b3 = binaryData[dataIndex + 2];
+
+            // log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+            // log.debug( "val2 = " + val2 );
+            // log.debug( "k4 = " + (k<<4) );
+            // log.debug( "vak = " + (val2 | (k<<4)) );
+            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
+
+            encodedIndex += 4;
+
+            // If we are chunking, let's put a chunk separator down.
+            if (isChunked) {
+                // this assumes that CHUNK_SIZE % 4 == 0
+                if (encodedIndex == nextSeparatorIndex) {
+                    System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
+                    chunksSoFar++;
+                    nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
+                    encodedIndex += CHUNK_SEPARATOR.length;
+                }
+            }
+        }
+
+        // form integral number of 6-bit groups
+        dataIndex = i * 3;
+
+        if (fewerThan24bits == EIGHTBIT) {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            // log.debug("b1=" + b1);
+            // log.debug("b1<<2 = " + (b1>>2) );
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex + 2] = PAD;
+            encodedData[encodedIndex + 3] = PAD;
+        } else if (fewerThan24bits == SIXTEENBIT) {
+
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex + 3] = PAD;
+        }
+
+        if (isChunked) {
+            // we also add a separator to the end of the final chunk.
+            if (chunksSoFar < chunckCount) {
+                System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
+                        CHUNK_SEPARATOR.length);
+            }
+        }
+
+        return encodedData;
+    }
+
+    /**
+     * Converts the specified UTF-8 Base64 encoded String and decodes it to a resultant UTF-8 encoded string.
+     *
+     * @param base64Encoded a UTF-8 Base64 encoded String
+     * @return the decoded String, UTF-8 encoded.
+     */
+    public static String decodeToString(String base64Encoded) {
+        byte[] encodedBytes = CodecSupport.toBytes(base64Encoded);
+        return decodeToString(encodedBytes);
+    }
+
+    /**
+     * Decodes the specified Base64 encoded byte array and returns the decoded result as a UTF-8 encoded.
+     *
+     * @param base64Encoded a Base64 encoded byte array
+     * @return the decoded String, UTF-8 encoded.
+     */
+    public static String decodeToString(byte[] base64Encoded) {
+        byte[] decoded = decode(base64Encoded);
+        return CodecSupport.toString(decoded);
+    }
+
+    /**
+     * Converts the specified UTF-8 Base64 encoded String and decodes it to a raw Base64 decoded byte array.
+     *
+     * @param base64Encoded a UTF-8 Base64 encoded String
+     * @return the raw Base64 decoded byte array.
+     */
+    public static byte[] decode(String base64Encoded) {
+        byte[] bytes = CodecSupport.toBytes(base64Encoded);
+        return decode(bytes);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param base64Data Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    public static byte[] decode(byte[] base64Data) {
+        // RFC 2045 requires that we discard ALL non-Base64 characters
+        base64Data = discardNonBase64(base64Data);
+
+        // handle the edge case, so we don't have to worry about it later
+        if (base64Data.length == 0) {
+            return new byte[0];
+        }
+
+        int numberQuadruple = base64Data.length / FOURBYTE;
+        byte decodedData[];
+        byte b1, b2, b3, b4, marker0, marker1;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex;
+        {
+            // this sizes the output array properly - rlw
+            int lastData = base64Data.length;
+            // ignore the '=' padding
+            while (base64Data[lastData - 1] == PAD) {
+                if (--lastData == 0) {
+                    return new byte[0];
+                }
+            }
+            decodedData = new byte[lastData - numberQuadruple];
+        }
+
+        for (int i = 0; i < numberQuadruple; i++) {
+            dataIndex = i * 4;
+            marker0 = base64Data[dataIndex + 2];
+            marker1 = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+            if (marker0 != PAD && marker1 != PAD) {
+                // No PAD e.g 3cQl
+                b3 = base64Alphabet[marker0];
+                b4 = base64Alphabet[marker1];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+            } else if (marker0 == PAD) {
+                // Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+            } else {
+                // One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[marker0];
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            }
+            encodedIndex += 3;
+        }
+        return decodedData;
+    }
+
+    /**
+     * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
+     * characters outside of the base64 alphabet are to be ignored in base64 encoded data."
+     *
+     * @param data The base-64 encoded data to groom
+     * @return The data, less non-base64 characters (see RFC 2045).
+     */
+    static byte[] discardNonBase64(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (byte aByte : data) {
+            if (isBase64(aByte)) {
+                groomedData[bytesCopied++] = aByte;
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/codec/CodecException.java b/core/src/main/java/org/apache/shiro/codec/CodecException.java
new file mode 100644
index 0000000..0bc4193
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/CodecException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.codec;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Root exception related to issues during encoding or decoding.
+ *
+ * @since 0.9
+ */
+public class CodecException extends ShiroException
+{
+
+    /**
+     * Creates a new <code>CodecException</code>.
+     */
+    public CodecException() {
+        super();
+    }
+
+    /**
+     * Creates a new <code>CodecException</code>.
+     *
+     * @param message the reason for the exception.
+     */
+    public CodecException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new <code>CodecException</code>.
+     *
+     * @param cause the underlying cause of the exception.
+     */
+    public CodecException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new <code>CodecException</code>.
+     *
+     * @param message the reason for the exception.
+     * @param cause   the underlying cause of the exception.
+     */
+    public CodecException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/codec/CodecSupport.java b/core/src/main/java/org/apache/shiro/codec/CodecSupport.java
new file mode 100644
index 0000000..b41d55b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/CodecSupport.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.codec;
+
+import org.apache.shiro.util.ByteSource;
+
+import java.io.*;
+
+/**
+ * Base abstract class that provides useful encoding and decoding operations, especially for character data.
+ *
+ * @since 0.9
+ */
+public abstract class CodecSupport {
+
+    /**
+     * Shiro's default preferred character encoding, equal to <b><code>UTF-8</code></b>.
+     */
+    public static final String PREFERRED_ENCODING = "UTF-8";
+
+    /**
+     * Converts the specified character array to a byte array using the Shiro's preferred encoding (UTF-8).
+     * <p/>
+     * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
+     * a wrapping String and {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}, i.e.
+     * <p/>
+     * <code>toBytes( new String(chars), {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING} );</code>
+     *
+     * @param chars the character array to be converted to a byte array.
+     * @return the byte array of the UTF-8 encoded character array.
+     */
+    public static byte[] toBytes(char[] chars) {
+        return toBytes(new String(chars), PREFERRED_ENCODING);
+    }
+
+    /**
+     * Converts the specified character array into a byte array using the specified character encoding.
+     * <p/>
+     * This is a convenience method equivalent to calling the {@link #toBytes(String,String)} method with a
+     * a wrapping String and the specified encoding, i.e.
+     * <p/>
+     * <code>toBytes( new String(chars), encoding );</code>
+     *
+     * @param chars    the character array to be converted to a byte array
+     * @param encoding the character encoding to use to when converting to bytes.
+     * @return the bytes of the specified character array under the specified encoding.
+     * @throws CodecException if the JVM does not support the specified encoding.
+     */
+    public static byte[] toBytes(char[] chars, String encoding) throws CodecException {
+        return toBytes(new String(chars), encoding);
+    }
+
+    /**
+     * Converts the specified source argument to a byte array with Shiro's
+     * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
+     *
+     * @param source the string to convert to a byte array.
+     * @return the bytes representing the specified string under the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
+     * @see #toBytes(String, String)
+     */
+    public static byte[] toBytes(String source) {
+        return toBytes(source, PREFERRED_ENCODING);
+    }
+
+    /**
+     * Converts the specified source to a byte array via the specified encoding, throwing a
+     * {@link CodecException CodecException} if the encoding fails.
+     *
+     * @param source   the source string to convert to a byte array.
+     * @param encoding the encoding to use to use.
+     * @return the byte array of the specified source with the given encoding.
+     * @throws CodecException if the JVM does not support the specified encoding.
+     */
+    public static byte[] toBytes(String source, String encoding) throws CodecException {
+        try {
+            return source.getBytes(encoding);
+        } catch (UnsupportedEncodingException e) {
+            String msg = "Unable to convert source [" + source + "] to byte array using " +
+                    "encoding '" + encoding + "'";
+            throw new CodecException(msg, e);
+        }
+    }
+
+    /**
+     * Converts the specified byte array to a String using the {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
+     *
+     * @param bytes the byte array to turn into a String.
+     * @return the specified byte array as an encoded String ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
+     * @see #toString(byte[], String)
+     */
+    public static String toString(byte[] bytes) {
+        return toString(bytes, PREFERRED_ENCODING);
+    }
+
+    /**
+     * Converts the specified byte array to a String using the specified character encoding.  This implementation
+     * does the same thing as <code>new {@link String#String(byte[], String) String(byte[], encoding)}</code>, but will
+     * wrap any {@link UnsupportedEncodingException} with a nicer runtime {@link CodecException}, allowing you to
+     * decide whether or not you want to catch the exception or let it propagate.
+     *
+     * @param bytes    the byte array to convert to a String
+     * @param encoding the character encoding used to encode the String.
+     * @return the specified byte array as an encoded String
+     * @throws CodecException if the JVM does not support the specified encoding.
+     */
+    public static String toString(byte[] bytes, String encoding) throws CodecException {
+        try {
+            return new String(bytes, encoding);
+        } catch (UnsupportedEncodingException e) {
+            String msg = "Unable to convert byte array to String with encoding '" + encoding + "'.";
+            throw new CodecException(msg, e);
+        }
+    }
+
+    /**
+     * Returns the specified byte array as a character array using the
+     * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
+     *
+     * @param bytes the byte array to convert to a char array
+     * @return the specified byte array encoded as a character array ({@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}).
+     * @see #toChars(byte[], String)
+     */
+    public static char[] toChars(byte[] bytes) {
+        return toChars(bytes, PREFERRED_ENCODING);
+    }
+
+    /**
+     * Converts the specified byte array to a character array using the specified character encoding.
+     * <p/>
+     * Effectively calls <code>{@link #toString(byte[], String) toString(bytes,encoding)}.{@link String#toCharArray() toCharArray()};</code>
+     *
+     * @param bytes    the byte array to convert to a String
+     * @param encoding the character encoding used to encode the bytes.
+     * @return the specified byte array as an encoded char array
+     * @throws CodecException if the JVM does not support the specified encoding.
+     */
+    public static char[] toChars(byte[] bytes, String encoding) throws CodecException {
+        return toString(bytes, encoding).toCharArray();
+    }
+
+    /**
+     * Returns {@code true} if the specified object can be easily converted to bytes by instances of this class,
+     * {@code false} otherwise.
+     * <p/>
+     * The default implementation returns {@code true} IFF the specified object is an instance of one of the following
+     * types:
+     * <ul>
+     * <li>{@code byte[]}</li>
+     * <li>{@code char[]}</li>
+     * <li>{@link ByteSource}</li>
+     * <li>{@link String}</li>
+     * <li>{@link File}</li>
+     * </li>{@link InputStream}</li>
+     * </ul>
+     *
+     * @param o the object to test to see if it can be easily converted to a byte array
+     * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
+     *         {@code false} otherwise.
+     * @since 1.0
+     */
+    protected boolean isByteSource(Object o) {
+        return o instanceof byte[] || o instanceof char[] || o instanceof String ||
+                o instanceof ByteSource || o instanceof File || o instanceof InputStream;
+    }
+
+    /**
+     * Converts the specified Object into a byte array.
+     * <p/>
+     * If the argument is a {@code byte[]}, {@code char[]}, {@link ByteSource}, {@link String}, {@link File}, or
+     * {@link InputStream}, it will be converted automatically and returned.}
+     * <p/>
+     * If the argument is anything other than these types, it is passed to the
+     * {@link #objectToBytes(Object) objectToBytes} method which must be overridden by subclasses.
+     *
+     * @param o the Object to convert into a byte array
+     * @return a byte array representation of the Object argument.
+     */
+    protected byte[] toBytes(Object o) {
+        if (o == null) {
+            String msg = "Argument for byte conversion cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        if (o instanceof byte[]) {
+            return (byte[]) o;
+        } else if (o instanceof ByteSource) {
+            return ((ByteSource) o).getBytes();
+        } else if (o instanceof char[]) {
+            return toBytes((char[]) o);
+        } else if (o instanceof String) {
+            return toBytes((String) o);
+        } else if (o instanceof File) {
+            return toBytes((File) o);
+        } else if (o instanceof InputStream) {
+            return toBytes((InputStream) o);
+        } else {
+            return objectToBytes(o);
+        }
+    }
+
+    /**
+     * Converts the specified Object into a String.
+     * <p/>
+     * If the argument is a {@code byte[]} or {@code char[]} it will be converted to a String using the
+     * {@link #PREFERRED_ENCODING}.  If a String, it will be returned as is.
+     * <p/>
+     * If the argument is anything other than these three types, it is passed to the
+     * {@link #objectToString(Object) objectToString} method.
+     *
+     * @param o the Object to convert into a byte array
+     * @return a byte array representation of the Object argument.
+     */
+    protected String toString(Object o) {
+        if (o == null) {
+            String msg = "Argument for String conversion cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        if (o instanceof byte[]) {
+            return toString((byte[]) o);
+        } else if (o instanceof char[]) {
+            return new String((char[]) o);
+        } else if (o instanceof String) {
+            return (String) o;
+        } else {
+            return objectToString(o);
+        }
+    }
+
+    protected byte[] toBytes(File file) {
+        if (file == null) {
+            throw new IllegalArgumentException("File argument cannot be null.");
+        }
+        try {
+            return toBytes(new FileInputStream(file));
+        } catch (FileNotFoundException e) {
+            String msg = "Unable to acquire InputStream for file [" + file + "]";
+            throw new CodecException(msg, e);
+        }
+    }
+
+    /**
+     * Converts the specified {@link InputStream InputStream} into a byte array.
+     *
+     * @param in the InputStream to convert to a byte array
+     * @return the bytes of the input stream
+     * @throws IllegalArgumentException if the {@code InputStream} argument is {@code null}.
+     * @throws CodecException           if there is any problem reading from the {@link InputStream}.
+     * @since 1.0
+     */
+    protected byte[] toBytes(InputStream in) {
+        if (in == null) {
+            throw new IllegalArgumentException("InputStream argument cannot be null.");
+        }
+        final int BUFFER_SIZE = 512;
+        ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int bytesRead;
+        try {
+            while ((bytesRead = in.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesRead);
+            }
+            return out.toByteArray();
+        } catch (IOException ioe) {
+            throw new CodecException(ioe);
+        } finally {
+            try {
+                in.close();
+            } catch (IOException ignored) {
+            }
+            try {
+                out.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Default implementation throws a CodecException immediately since it can't infer how to convert the Object
+     * to a byte array.  This method must be overridden by subclasses if anything other than the three default
+     * types (listed in the {@link #toBytes(Object) toBytes(Object)} JavaDoc) are to be converted to a byte array.
+     *
+     * @param o the Object to convert to a byte array.
+     * @return a byte array representation of the Object argument.
+     */
+    protected byte[] objectToBytes(Object o) {
+        String msg = "The " + getClass().getName() + " implementation only supports conversion to " +
+                "byte[] if the source is of type byte[], char[], String, " + ByteSource.class.getName() +
+                " File or InputStream.  The instance provided as a method " +
+                "argument is of type [" + o.getClass().getName() + "].  If you would like to convert " +
+                "this argument type to a byte[], you can 1) convert the argument to one of the supported types " +
+                "yourself and then use that as the method argument or 2) subclass " + getClass().getName() +
+                "and override the objectToBytes(Object o) method.";
+        throw new CodecException(msg);
+    }
+
+    /**
+     * Default implementation merely returns <code>objectArgument.toString()</code>.  Subclasses can override this
+     * method for different mechanisms of converting an object to a String.
+     *
+     * @param o the Object to convert to a byte array.
+     * @return a String representation of the Object argument.
+     */
+    protected String objectToString(Object o) {
+        return o.toString();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/codec/H64.java b/core/src/main/java/org/apache/shiro/codec/H64.java
new file mode 100644
index 0000000..8c674c2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/H64.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * The apr_md5_encode() routine in the APR project's apr_md5.c file uses much
+ * code obtained from the FreeBSD 3.0 MD5 crypt() function, which is licenced
+ * as follows:
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk at login.dknet.dk> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+package org.apache.shiro.codec;
+
+import java.io.IOException;
+
+/**
+ * Codec for <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Unix Crypt</a>-style encoding.  While similar to
+ * Base64, it is not compatible with Base64.
+ * <p/>
+ * This implementation is based on encoding algorithms found in the Apache Portable Runtime library's
+ * <a href="http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_md5.c?revision=HEAD&view=markup">apr_md5.c</a>
+ * implementation for its {@code crypt}-style support.  The APR team in turn received inspiration for its encoding
+ * implementation based on FreeBSD 3.0's {@code /usr/src/lib/libcrypt/crypt.c} implementation.  The
+ * accompanying license headers have been retained at the top of this source file.
+ * <p/>
+ * This file and all that it contains is ASL 2.0 compatible.
+ *
+ * @since 1.2
+ */
+public class H64 {
+
+    private static final char[] itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+
+    private static short toShort(byte b) {
+        return (short) (b & 0xff);
+    }
+
+    private static int toInt(byte[] bytes, int offset, int numBytes) {
+        if (numBytes < 1 || numBytes > 4) {
+            throw new IllegalArgumentException("numBytes must be between 1 and 4.");
+        }
+        int val = toShort(bytes[offset]); //1st byte
+        for (int i = 1; i < numBytes; i++) { //any remaining bytes:
+            short s = toShort(bytes[offset + i]);
+            switch (i) {
+                case 1: val |= s << 8; break;
+                case 2: val |= s << 16; break;
+                case 3: val |= s << 24; break;
+            }
+        }
+        return val;
+    }
+
+    /**
+     * Appends the specified character into the buffer, rethrowing any encountered
+     * {@link IOException} as an {@link IllegalStateException} (since this method is used for internal
+     * implementation needs and we only ever use StringBuilders, we should never encounter an IOException).
+     *
+     * @param buf the buffer to append to
+     * @param c   the character to append.
+     */
+    private static void append(Appendable buf, char c) {
+        try {
+            buf.append(c);
+        } catch (IOException e) {
+            throw new IllegalStateException("Unable to append character to internal buffer.", e);
+        }
+    }
+
+    /**
+     * Encodes the specified integer to {@code numChars} H64-compatible characters and appends them into {@code buf}.
+     *
+     * @param value    the integer to encode to H64-compatible characters
+     * @param buf      the output buffer
+     * @param numChars the number of characters the value should be converted to.  3, 2 or 1.
+     */
+    private static void encodeAndAppend(int value, Appendable buf, int numChars) {
+        for (int i = 0; i < numChars; i++) {
+            append(buf, itoa64[value & 0x3f]);
+            value >>= 6;
+        }
+    }
+
+    /**
+     * Encodes the specified bytes to an {@code H64}-encoded String.
+     *
+     * @param bytes
+     * @return
+     */
+    public static String encodeToString(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) return null;
+
+        StringBuilder buf = new StringBuilder();
+
+        int length = bytes.length;
+        int remainder = length % 3;
+        int i = 0; //starting byte
+        int last3ByteIndex = length - remainder; //last byte whose index is a multiple of 3
+
+        for(; i < last3ByteIndex; i += 3) {
+            int twentyFourBit = toInt(bytes, i, 3);
+            encodeAndAppend(twentyFourBit, buf, 4);
+        }
+        if (remainder > 0) {
+            //one or two bytes that we still need to encode:
+            int a = toInt(bytes, i, remainder);
+            encodeAndAppend(a, buf, remainder + 1);
+        }
+        return buf.toString();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/codec/Hex.java b/core/src/main/java/org/apache/shiro/codec/Hex.java
new file mode 100644
index 0000000..eb41583
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/Hex.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.codec;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hexadecimal</a> encoder and decoder.
+ * <p/>
+ * This class was borrowed from Apache Commons Codec SVN repository (rev. {@code 560660}) with modifications
+ * to enable Hex conversion without a full dependency on Commons Codec.  We didn't want to reinvent the wheel of
+ * great work they've done, but also didn't want to force every Shiro user to depend on the commons-codec.jar
+ * <p/>
+ * As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
+ * remained in tact.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Hexadecimal">Wikipedia: Hexadecimal</a>
+ * @since 0.9
+ */
+public class Hex {
+
+    /**
+     * Used to build output as Hex
+     */
+    private static final char[] DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    /**
+     * Encodes the specifed byte array to a character array and then returns that character array
+     * as a String.
+     *
+     * @param bytes the byte array to Hex-encode.
+     * @return A String representation of the resultant hex-encoded char array.
+     */
+    public static String encodeToString(byte[] bytes) {
+        char[] encodedChars = encode(bytes);
+        return new String(encodedChars);
+    }
+
+    /**
+     * Converts an array of bytes into an array of characters representing the hexidecimal values of each byte in order.
+     * The returned array will be double the length of the passed array, as it takes two characters to represent any
+     * given byte.
+     *
+     * @param data byte[] to convert to Hex characters
+     * @return A char[] containing hexidecimal characters
+     */
+    public static char[] encode(byte[] data) {
+
+        int l = data.length;
+
+        char[] out = new char[l << 1];
+
+        // two characters form the hex value.
+        for (int i = 0, j = 0; i < l; i++) {
+            out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
+            out[j++] = DIGITS[0x0F & data[i]];
+        }
+
+        return out;
+    }
+
+    /**
+     * Converts an array of character bytes representing hexidecimal values into an
+     * array of bytes of those same values. The returned array will be half the
+     * length of the passed array, as it takes two characters to represent any
+     * given byte. An exception is thrown if the passed char array has an odd
+     * number of elements.
+     *
+     * @param array An array of character bytes containing hexidecimal digits
+     * @return A byte array containing binary data decoded from
+     *         the supplied byte array (representing characters).
+     * @throws IllegalArgumentException Thrown if an odd number of characters is supplied
+     *                                  to this function
+     * @see #decode(char[])
+     */
+    public static byte[] decode(byte[] array) throws IllegalArgumentException {
+        String s = CodecSupport.toString(array);
+        return decode(s);
+    }
+
+    /**
+     * Converts the specified Hex-encoded String into a raw byte array.  This is a
+     * convenience method that merely delegates to {@link #decode(char[])} using the
+     * argument's hex.toCharArray() value.
+     *
+     * @param hex a Hex-encoded String.
+     * @return A byte array containing binary data decoded from the supplied String's char array.
+     */
+    public static byte[] decode(String hex) {
+        return decode(hex.toCharArray());
+    }
+
+    /**
+     * Converts an array of characters representing hexidecimal values into an
+     * array of bytes of those same values. The returned array will be half the
+     * length of the passed array, as it takes two characters to represent any
+     * given byte. An exception is thrown if the passed char array has an odd
+     * number of elements.
+     *
+     * @param data An array of characters containing hexidecimal digits
+     * @return A byte array containing binary data decoded from
+     *         the supplied char array.
+     * @throws IllegalArgumentException if an odd number or illegal of characters
+     *                                  is supplied
+     */
+    public static byte[] decode(char[] data) throws IllegalArgumentException {
+
+        int len = data.length;
+
+        if ((len & 0x01) != 0) {
+            throw new IllegalArgumentException("Odd number of characters.");
+        }
+
+        byte[] out = new byte[len >> 1];
+
+        // two characters form the hex value.
+        for (int i = 0, j = 0; j < len; i++) {
+            int f = toDigit(data[j], j) << 4;
+            j++;
+            f = f | toDigit(data[j], j);
+            j++;
+            out[i] = (byte) (f & 0xFF);
+        }
+
+        return out;
+    }
+
+    /**
+     * Converts a hexadecimal character to an integer.
+     *
+     * @param ch    A character to convert to an integer digit
+     * @param index The index of the character in the source
+     * @return An integer
+     * @throws IllegalArgumentException if ch is an illegal hex character
+     */
+    protected static int toDigit(char ch, int index) throws IllegalArgumentException {
+        int digit = Character.digit(ch, 16);
+        if (digit == -1) {
+            throw new IllegalArgumentException("Illegal hexadecimal charcter " + ch + " at index " + index);
+        }
+        return digit;
+    }
+
+
+}
diff --git a/core/src/main/java/org/apache/shiro/codec/package-info.java b/core/src/main/java/org/apache/shiro/codec/package-info.java
new file mode 100644
index 0000000..408dd51
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/codec/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components for encoding and decoding of data across multiple formats, especially useful in Shiro's
+ * cryptography and web functionality.
+ */
+package org.apache.shiro.codec;
diff --git a/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutor.java b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutor.java
new file mode 100644
index 0000000..78c8424
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutor.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.concurrent;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+import java.util.concurrent.Executor;
+
+/**
+ * {@code Executor} implementation that will automatically first associate any argument
+ * {@link Runnable} instances with the currently available {@link Subject} and then
+ * dispatch the Subject-enabled runnable to an underlying delegate {@link Executor}
+ * instance.
+ * <p/>
+ * This is a simplification for applications that want to execute code as the currently
+ * executing {@code Subject} on another thread, but don't want or need to call the
+ * {@link Subject#associateWith(Runnable)} method and dispatch to a Thread manually.  This
+ * simplifies code and reduces Shiro dependencies across application source code.
+ * <p/>
+ * Consider this code that could be repeated in many places across an application:
+ * <pre>
+ * {@link Runnable Runnable} applicationWork = //instantiate or acquire Runnable from somewhere
+ * {@link Subject Subject} subject = {@link SecurityUtils SecurityUtils}.{@link SecurityUtils#getSubject() getSubject()};
+ * {@link Runnable Runnable} work = subject.{@link Subject#associateWith(Runnable) associateWith(applicationWork)};
+ * {@link Executor anExecutor}.{@link Executor#execute(Runnable) execute(work)};
+ * </pre>
+ * Instead, if the {@code Executor} instance used in application code is an instance of this class (which delegates
+ * to the target Executor that you want), all places in code like the above reduce to this:
+ * <pre>
+ * {@link Runnable Runnable} applicationWork = //instantiate or acquire Runnable from somewhere
+ * {@link Executor anExecutor}.{@link Executor#execute(Runnable) execute(work)};
+ * </pre>
+ * Notice there is no use of the Shiro API in the 2nd code block, encouraging the principle of loose coupling across
+ * your codebase.
+ *
+ * @see SubjectAwareExecutorService
+ * @since 1.0
+ */
+public class SubjectAwareExecutor implements Executor {
+
+    /**
+     * The target Executor instance that will actually execute the subject-associated Runnable instances.
+     */
+    private Executor targetExecutor;
+
+    public SubjectAwareExecutor() {
+    }
+
+    public SubjectAwareExecutor(Executor targetExecutor) {
+        if (targetExecutor == null) {
+            throw new NullPointerException("target Executor instance cannot be null.");
+        }
+        this.targetExecutor = targetExecutor;
+    }
+
+    /**
+     * Returns the target Executor instance that will actually execute the subject-associated Runnable instances.
+     *
+     * @return target Executor instance that will actually execute the subject-associated Runnable instances.
+     */
+    public Executor getTargetExecutor() {
+        return targetExecutor;
+    }
+
+    /**
+     * Sets target Executor instance that will actually execute the subject-associated Runnable instances.
+     *
+     * @param targetExecutor the target Executor instance that will actually execute the subject-associated Runnable
+     *                       instances.
+     */
+    public void setTargetExecutor(Executor targetExecutor) {
+        this.targetExecutor = targetExecutor;
+    }
+
+    /**
+     * Returns the currently Subject instance that should be associated with Runnable or Callable instances before
+     * being dispatched to the target {@code Executor} instance.  This implementation merely defaults to returning
+     * {@code SecurityUtils}.{@link SecurityUtils#getSubject() getSubject()}.
+     *
+     * @return the currently Subject instance that should be associated with Runnable or Callable instances before
+     *         being dispatched to the target {@code Executor} instance.
+     */
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    /**
+     * Utility method for subclasses to associate the argument {@code Runnable} with the currently executing subject
+     * and then return the associated Runnable.  The default implementation merely defaults to
+     * <pre>
+     * Subject subject = {@link #getSubject() getSubject()};
+     * return subject.{@link Subject#associateWith(Runnable) associateWith(r)};
+     * </pre>
+     *
+     * @param r the argument runnable to be associated with the current subject
+     * @return the associated runnable instance reflecting the current subject
+     */
+    protected Runnable associateWithSubject(Runnable r) {
+        Subject subject = getSubject();
+        return subject.associateWith(r);
+    }
+
+    /**
+     * Executes the specified runnable by first associating it with the currently executing {@code Subject} and then
+     * dispatches the associated Runnable to the underlying target {@link Executor} instance.
+     *
+     * @param command the runnable to associate with the currently executing subject and then to execute via the target
+     *                {@code Executor} instance.
+     */
+    public void execute(Runnable command) {
+        Runnable associated = associateWithSubject(command);
+        getTargetExecutor().execute(associated);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutorService.java b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutorService.java
new file mode 100644
index 0000000..07be8bd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareExecutorService.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.concurrent;
+
+import org.apache.shiro.subject.Subject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * {@code ExecutorService} implementation that will automatically first associate any argument
+ * {@link Runnable} or {@link Callable} instances with the {@link #getSubject currently available subject} and then
+ * dispatch the Subject-enabled runnable or callable to an underlying delegate
+ * {@link java.util.concurrent.ExecutorService ExecutorService} instance.  The principle is the same as the
+ * parent {@link SubjectAwareExecutor} class, but enables the richer {@link ExecutorService} API.
+ * <p/>
+ * This is a simplification for applications that want to execute code as the currently
+ * executing {@code Subject} on another thread, but don't want or need to call the
+ * {@link Subject#associateWith(Runnable)} or {@link Subject#associateWith(Callable)} methods and dispatch them to a
+ * Thread manually.  This simplifies code and reduces Shiro dependencies across application source code.
+ * <p/>
+ * Consider this code that could be repeated in many places across an application:
+ * <pre>
+ * {@link Callable Callable} applicationWork = //instantiate or acquire Callable from somewhere
+ * {@link Subject Subject} subject = {@link org.apache.shiro.SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()};
+ * {@link Callable Callable} work = subject.{@link Subject#associateWith(Callable) associateWith(applicationWork)};
+ * {@link ExecutorService anExecutorService}.{@link ExecutorService#submit(Callable) submit(work)};
+ * </pre>
+ * Instead, if the {@code ExecutorService} instance used at runtime is an instance of this class
+ * (which delegates to the target ExecutorService that you want), all places in code like the above reduce to this:
+ * <pre>
+ * {@link Callable Callable} applicationWork = //instantiate or acquire Callable from somewhere
+ * {@link ExecutorService anExecutorService}.{@link ExecutorService#submit(Callable) submit(work)};
+ * </pre>
+ * Notice there is no use of the Shiro API in the 2nd code block, encouraging the principle of loose coupling across
+ * your codebase.
+ *
+ * @since 1.0
+ */
+public class SubjectAwareExecutorService extends SubjectAwareExecutor implements ExecutorService {
+
+    private ExecutorService targetExecutorService;
+
+    public SubjectAwareExecutorService() {
+    }
+
+    public SubjectAwareExecutorService(ExecutorService target) {
+        setTargetExecutorService(target);
+    }
+
+    public ExecutorService getTargetExecutorService() {
+        return targetExecutorService;
+    }
+
+    public void setTargetExecutorService(ExecutorService targetExecutorService) {
+        super.setTargetExecutor(targetExecutorService);
+        this.targetExecutorService = targetExecutorService;
+    }
+
+    @Override
+    public void setTargetExecutor(Executor targetExecutor) {
+        if (!(targetExecutor instanceof ExecutorService)) {
+            String msg = "The " + getClass().getName() + " implementation only accepts " +
+                    ExecutorService.class.getName() + " target instances.";
+            throw new IllegalArgumentException(msg);
+        }
+        super.setTargetExecutor(targetExecutor);
+    }
+
+    public void shutdown() {
+        this.targetExecutorService.shutdown();
+    }
+
+    public List<Runnable> shutdownNow() {
+        return this.targetExecutorService.shutdownNow();
+    }
+
+    public boolean isShutdown() {
+        return this.targetExecutorService.isShutdown();
+    }
+
+    public boolean isTerminated() {
+        return this.targetExecutorService.isTerminated();
+    }
+
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+        return this.targetExecutorService.awaitTermination(timeout, unit);
+    }
+
+    protected <T> Callable<T> associateWithSubject(Callable<T> task) {
+        Subject subject = getSubject();
+        return subject.associateWith(task);
+    }
+
+    public <T> Future<T> submit(Callable<T> task) {
+        Callable<T> work = associateWithSubject(task);
+        return this.targetExecutorService.submit(work);
+    }
+
+    public <T> Future<T> submit(Runnable task, T result) {
+        Runnable work = associateWithSubject(task);
+        return this.targetExecutorService.submit(work, result);
+    }
+
+    public Future<?> submit(Runnable task) {
+        Runnable work = associateWithSubject(task);
+        return this.targetExecutorService.submit(work);
+    }
+
+    protected <T> Collection<Callable<T>> associateWithSubject(Collection<? extends Callable<T>> tasks) {
+        Collection<Callable<T>> workItems = new ArrayList<Callable<T>>(tasks.size());
+        for (Callable<T> task : tasks) {
+            Callable<T> work = associateWithSubject(task);
+            workItems.add(work);
+        }
+        return workItems;
+    }
+
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+        Collection<Callable<T>> workItems = associateWithSubject(tasks);
+        return this.targetExecutorService.invokeAll(workItems);
+    }
+
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+            throws InterruptedException {
+        Collection<Callable<T>> workItems = associateWithSubject(tasks);
+        return this.targetExecutorService.invokeAll(workItems, timeout, unit);
+    }
+
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+        Collection<Callable<T>> workItems = associateWithSubject(tasks);
+        return this.targetExecutorService.invokeAny(workItems);
+    }
+
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        Collection<Callable<T>> workItems = associateWithSubject(tasks);
+        return this.targetExecutorService.invokeAny(workItems, timeout, unit);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareScheduledExecutorService.java b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareScheduledExecutorService.java
new file mode 100644
index 0000000..9013ec8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/concurrent/SubjectAwareScheduledExecutorService.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.concurrent;
+
+import java.util.concurrent.*;
+
+/**
+ * Same concept as the {@link SubjectAwareExecutorService} but additionally supports the
+ * {@link ScheduledExecutorService} interface.
+ */
+public class SubjectAwareScheduledExecutorService extends SubjectAwareExecutorService implements ScheduledExecutorService {
+
+    private ScheduledExecutorService targetScheduledExecutorService;
+
+    public SubjectAwareScheduledExecutorService() {
+    }
+
+    public SubjectAwareScheduledExecutorService(ScheduledExecutorService target) {
+        setTargetScheduledExecutorService(target);
+    }
+
+    public ScheduledExecutorService getTargetScheduledExecutorService() {
+        return targetScheduledExecutorService;
+    }
+
+    public void setTargetScheduledExecutorService(ScheduledExecutorService targetScheduledExecutorService) {
+        super.setTargetExecutorService(targetScheduledExecutorService);
+        this.targetScheduledExecutorService = targetScheduledExecutorService;
+    }
+
+    @Override
+    public void setTargetExecutor(Executor targetExecutor) {
+        if (!(targetExecutor instanceof ScheduledExecutorService)) {
+            String msg = "The " + getClass().getName() + " implementation only accepts " +
+                    ScheduledExecutorService.class.getName() + " target instances.";
+            throw new IllegalArgumentException(msg);
+        }
+        super.setTargetExecutorService((ScheduledExecutorService) targetExecutor);
+    }
+
+    @Override
+    public void setTargetExecutorService(ExecutorService targetExecutorService) {
+        if (!(targetExecutorService instanceof ScheduledExecutorService)) {
+            String msg = "The " + getClass().getName() + " implementation only accepts " +
+                    ScheduledExecutorService.class.getName() + " target instances.";
+            throw new IllegalArgumentException(msg);
+        }
+        super.setTargetExecutorService(targetExecutorService);
+    }
+
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+        Runnable work = associateWithSubject(command);
+        return this.targetScheduledExecutorService.schedule(work, delay, unit);
+    }
+
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+        Callable<V> work = associateWithSubject(callable);
+        return this.targetScheduledExecutorService.schedule(work, delay, unit);
+    }
+
+    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+        Runnable work = associateWithSubject(command);
+        return this.targetScheduledExecutorService.scheduleAtFixedRate(work, initialDelay, period, unit);
+    }
+
+    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+        Runnable work = associateWithSubject(command);
+        return this.targetScheduledExecutorService.scheduleWithFixedDelay(work, initialDelay, delay, unit);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/concurrent/package-info.java b/core/src/main/java/org/apache/shiro/concurrent/package-info.java
new file mode 100644
index 0000000..9163cdd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/concurrent/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * {@link java.util.concurrent.Executor Executor}, {@link java.util.concurrent.ExecutorService ExecutorService},
+ * and {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService} implementations for transparent
+ * {@link org.apache.shiro.subject.Subject Subject} association with threads in an asynchronous execution environment.
+ * 
+ * @see SubjectAwareExecutor
+ * @see SubjectAwareExecutorService
+ * @see SubjectAwareScheduledExecutorService
+ */
+package org.apache.shiro.concurrent;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/config/ConfigurationException.java b/core/src/main/java/org/apache/shiro/config/ConfigurationException.java
new file mode 100644
index 0000000..34b5def
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/ConfigurationException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Root exception indicating there was a problem parsing or processing the Shiro configuration.
+ *
+ * @since 0.9
+ */
+public class ConfigurationException extends ShiroException
+{
+
+    /**
+     * Creates a new ConfigurationException.
+     */
+    public ConfigurationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ConfigurationException.
+     *
+     * @param message the reason for the exception
+     */
+    public ConfigurationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ConfigurationException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ConfigurationException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/config/Ini.java b/core/src/main/java/org/apache/shiro/config/Ini.java
new file mode 100644
index 0000000..8dbaef0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/Ini.java
@@ -0,0 +1,649 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A class representing the <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> text configuration format.
+ * <p/>
+ * An Ini instance is a map of {@link Ini.Section Section}s, keyed by section name.  Each
+ * {@code Section} is itself a map of {@code String} name/value pairs.  Name/value pairs are guaranteed to be unique
+ * within each {@code Section} only - not across the entire {@code Ini} instance.
+ *
+ * @since 1.0
+ */
+public class Ini implements Map<String, Ini.Section> {
+
+    private static transient final Logger log = LoggerFactory.getLogger(Ini.class);
+
+    public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section
+    public static final String DEFAULT_CHARSET_NAME = "UTF-8";
+
+    public static final String COMMENT_POUND = "#";
+    public static final String COMMENT_SEMICOLON = ";";
+    public static final String SECTION_PREFIX = "[";
+    public static final String SECTION_SUFFIX = "]";
+
+    protected static final char ESCAPE_TOKEN = '\\';
+
+    private final Map<String, Section> sections;
+
+    /**
+     * Creates a new empty {@code Ini} instance.
+     */
+    public Ini() {
+        this.sections = new LinkedHashMap<String, Section>();
+    }
+
+    /**
+     * Creates a new {@code Ini} instance with the specified defaults.
+     *
+     * @param defaults the default sections and/or key-value pairs to copy into the new instance.
+     */
+    public Ini(Ini defaults) {
+        this();
+        if (defaults == null) {
+            throw new NullPointerException("Defaults cannot be null.");
+        }
+        for (Section section : defaults.getSections()) {
+            Section copy = new Section(section);
+            this.sections.put(section.getName(), copy);
+        }
+    }
+
+    /**
+     * Returns {@code true} if no sections have been configured, or if there are sections, but the sections themselves
+     * are all empty, {@code false} otherwise.
+     *
+     * @return {@code true} if no sections have been configured, or if there are sections, but the sections themselves
+     *         are all empty, {@code false} otherwise.
+     */
+    public boolean isEmpty() {
+        Collection<Section> sections = this.sections.values();
+        if (!sections.isEmpty()) {
+            for (Section section : sections) {
+                if (!section.isEmpty()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns the names of all sections managed by this {@code Ini} instance or an empty collection if there are
+     * no sections.
+     *
+     * @return the names of all sections managed by this {@code Ini} instance or an empty collection if there are
+     *         no sections.
+     */
+    public Set<String> getSectionNames() {
+        return Collections.unmodifiableSet(sections.keySet());
+    }
+
+    /**
+     * Returns the sections managed by this {@code Ini} instance or an empty collection if there are
+     * no sections.
+     *
+     * @return the sections managed by this {@code Ini} instance or an empty collection if there are
+     *         no sections.
+     */
+    public Collection<Section> getSections() {
+        return Collections.unmodifiableCollection(sections.values());
+    }
+
+    /**
+     * Returns the {@link Section} with the given name or {@code null} if no section with that name exists.
+     *
+     * @param sectionName the name of the section to retrieve.
+     * @return the {@link Section} with the given name or {@code null} if no section with that name exists.
+     */
+    public Section getSection(String sectionName) {
+        String name = cleanName(sectionName);
+        return sections.get(name);
+    }
+
+    /**
+     * Ensures a section with the specified name exists, adding a new one if it does not yet exist.
+     *
+     * @param sectionName the name of the section to ensure existence
+     * @return the section created if it did not yet exist, or the existing Section that already existed.
+     */
+    public Section addSection(String sectionName) {
+        String name = cleanName(sectionName);
+        Section section = getSection(name);
+        if (section == null) {
+            section = new Section(name);
+            this.sections.put(name, section);
+        }
+        return section;
+    }
+
+    /**
+     * Removes the section with the specified name and returns it, or {@code null} if the section did not exist.
+     *
+     * @param sectionName the name of the section to remove.
+     * @return the section with the specified name or {@code null} if the section did not exist.
+     */
+    public Section removeSection(String sectionName) {
+        String name = cleanName(sectionName);
+        return this.sections.remove(name);
+    }
+
+    private static String cleanName(String sectionName) {
+        String name = StringUtils.clean(sectionName);
+        if (name == null) {
+            log.trace("Specified name was null or empty.  Defaulting to the default section (name = \"\")");
+            name = DEFAULT_SECTION_NAME;
+        }
+        return name;
+    }
+
+    /**
+     * Sets a name/value pair for the section with the given {@code sectionName}.  If the section does not yet exist,
+     * it will be created.  If the {@code sectionName} is null or empty, the name/value pair will be placed in the
+     * default (unnamed, empty string) section.
+     *
+     * @param sectionName   the name of the section to add the name/value pair
+     * @param propertyName  the name of the property to add
+     * @param propertyValue the property value
+     */
+    public void setSectionProperty(String sectionName, String propertyName, String propertyValue) {
+        String name = cleanName(sectionName);
+        Section section = getSection(name);
+        if (section == null) {
+            section = addSection(name);
+        }
+        section.put(propertyName, propertyValue);
+    }
+
+    /**
+     * Returns the value of the specified section property, or {@code null} if the section or property do not exist.
+     *
+     * @param sectionName  the name of the section to retrieve to acquire the property value
+     * @param propertyName the name of the section property for which to return the value
+     * @return the value of the specified section property, or {@code null} if the section or property do not exist.
+     */
+    public String getSectionProperty(String sectionName, String propertyName) {
+        Section section = getSection(sectionName);
+        return section != null ? section.get(propertyName) : null;
+    }
+
+    /**
+     * Returns the value of the specified section property, or the {@code defaultValue} if the section or
+     * property do not exist.
+     *
+     * @param sectionName  the name of the section to add the name/value pair
+     * @param propertyName the name of the property to add
+     * @param defaultValue the default value to return if the section or property do not exist.
+     * @return the value of the specified section property, or the {@code defaultValue} if the section or
+     *         property do not exist.
+     */
+    public String getSectionProperty(String sectionName, String propertyName, String defaultValue) {
+        String value = getSectionProperty(sectionName, propertyName);
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * Creates a new {@code Ini} instance loaded with the INI-formatted data in the resource at the given path.  The
+     * resource path may be any value interpretable by the
+     * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
+     *
+     * @param resourcePath the resource location of the INI data to load when creating the {@code Ini} instance.
+     * @return a new {@code Ini} instance loaded with the INI-formatted data in the resource at the given path.
+     * @throws ConfigurationException if the path cannot be loaded into an {@code Ini} instance.
+     */
+    public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
+        if (!StringUtils.hasLength(resourcePath)) {
+            throw new IllegalArgumentException("Resource Path argument cannot be null or empty.");
+        }
+        Ini ini = new Ini();
+        ini.loadFromPath(resourcePath);
+        return ini;
+    }
+
+    /**
+     * Loads data from the specified resource path into this current {@code Ini} instance.  The
+     * resource path may be any value interpretable by the
+     * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
+     *
+     * @param resourcePath the resource location of the INI data to load into this instance.
+     * @throws ConfigurationException if the path cannot be loaded
+     */
+    public void loadFromPath(String resourcePath) throws ConfigurationException {
+        InputStream is;
+        try {
+            is = ResourceUtils.getInputStreamForPath(resourcePath);
+        } catch (IOException e) {
+            throw new ConfigurationException(e);
+        }
+        load(is);
+    }
+
+    /**
+     * Loads the specified raw INI-formatted text into this instance.
+     *
+     * @param iniConfig the raw INI-formatted text to load into this instance.
+     * @throws ConfigurationException if the text cannot be loaded
+     */
+    public void load(String iniConfig) throws ConfigurationException {
+        load(new Scanner(iniConfig));
+    }
+
+    /**
+     * Loads the INI-formatted text backed by the given InputStream into this instance.  This implementation will
+     * close the input stream after it has finished loading.  It is expected that the stream's contents are
+     * UTF-8 encoded.
+     *
+     * @param is the {@code InputStream} from which to read the INI-formatted text
+     * @throws ConfigurationException if unable
+     */
+    public void load(InputStream is) throws ConfigurationException {
+        if (is == null) {
+            throw new NullPointerException("InputStream argument cannot be null.");
+        }
+        InputStreamReader isr;
+        try {
+            isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);
+        } catch (UnsupportedEncodingException e) {
+            throw new ConfigurationException(e);
+        }
+        load(isr);
+    }
+
+    /**
+     * Loads the INI-formatted text backed by the given Reader into this instance.  This implementation will close the
+     * reader after it has finished loading.
+     *
+     * @param reader the {@code Reader} from which to read the INI-formatted text
+     */
+    public void load(Reader reader) {
+        Scanner scanner = new Scanner(reader);
+        try {
+            load(scanner);
+        } finally {
+            try {
+                scanner.close();
+            } catch (Exception e) {
+                log.debug("Unable to cleanly close the InputStream scanner.  Non-critical - ignoring.", e);
+            }
+        }
+    }
+
+    private void addSection(String name, StringBuilder content) {
+        if (content.length() > 0) {
+            String contentString = content.toString();
+            String cleaned = StringUtils.clean(contentString);
+            if (cleaned != null) {
+                Section section = new Section(name, contentString);
+                if (!section.isEmpty()) {
+                    sections.put(name, section);
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads the INI-formatted text backed by the given Scanner.  This implementation will close the
+     * scanner after it has finished loading.
+     *
+     * @param scanner the {@code Scanner} from which to read the INI-formatted text
+     */
+    public void load(Scanner scanner) {
+
+        String sectionName = DEFAULT_SECTION_NAME;
+        StringBuilder sectionContent = new StringBuilder();
+
+        while (scanner.hasNextLine()) {
+
+            String rawLine = scanner.nextLine();
+            String line = StringUtils.clean(rawLine);
+
+            if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
+                //skip empty lines and comments:
+                continue;
+            }
+
+            String newSectionName = getSectionName(line);
+            if (newSectionName != null) {
+                //found a new section - convert the currently buffered one into a Section object
+                addSection(sectionName, sectionContent);
+
+                //reset the buffer for the new section:
+                sectionContent = new StringBuilder();
+
+                sectionName = newSectionName;
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
+                }
+            } else {
+                //normal line - add it to the existing content buffer:
+                sectionContent.append(rawLine).append("\n");
+            }
+        }
+
+        //finish any remaining buffered content:
+        addSection(sectionName, sectionContent);
+    }
+
+    protected static boolean isSectionHeader(String line) {
+        String s = StringUtils.clean(line);
+        return s != null && s.startsWith(SECTION_PREFIX) && s.endsWith(SECTION_SUFFIX);
+    }
+
+    protected static String getSectionName(String line) {
+        String s = StringUtils.clean(line);
+        if (isSectionHeader(s)) {
+            return cleanName(s.substring(1, s.length() - 1));
+        }
+        return null;
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof Ini) {
+            Ini ini = (Ini) obj;
+            return this.sections.equals(ini.sections);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.sections.hashCode();
+    }
+
+    public String toString() {
+        if (CollectionUtils.isEmpty(this.sections)) {
+            return "<empty INI>";
+        } else {
+            StringBuilder sb = new StringBuilder("sections=");
+            int i = 0;
+            for (Ini.Section section : this.sections.values()) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                sb.append(section.toString());
+                i++;
+            }
+            return sb.toString();
+        }
+    }
+
+    public int size() {
+        return this.sections.size();
+    }
+
+    public boolean containsKey(Object key) {
+        return this.sections.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        return this.sections.containsValue(value);
+    }
+
+    public Section get(Object key) {
+        return this.sections.get(key);
+    }
+
+    public Section put(String key, Section value) {
+        return this.sections.put(key, value);
+    }
+
+    public Section remove(Object key) {
+        return this.sections.remove(key);
+    }
+
+    public void putAll(Map<? extends String, ? extends Section> m) {
+        this.sections.putAll(m);
+    }
+
+    public void clear() {
+        this.sections.clear();
+    }
+
+    public Set<String> keySet() {
+        return Collections.unmodifiableSet(this.sections.keySet());
+    }
+
+    public Collection<Section> values() {
+        return Collections.unmodifiableCollection(this.sections.values());
+    }
+
+    public Set<Entry<String, Section>> entrySet() {
+        return Collections.unmodifiableSet(this.sections.entrySet());
+    }
+
+    /**
+     * An {@code Ini.Section} is String-key-to-String-value Map, identifiable by a
+     * {@link #getName() name} unique within an {@link Ini} instance.
+     */
+    public static class Section implements Map<String, String> {
+        private final String name;
+        private final Map<String, String> props;
+
+        private Section(String name) {
+            if (name == null) {
+                throw new NullPointerException("name");
+            }
+            this.name = name;
+            this.props = new LinkedHashMap<String, String>();
+        }
+
+        private Section(String name, String sectionContent) {
+            if (name == null) {
+                throw new NullPointerException("name");
+            }
+            this.name = name;
+            Map<String,String> props;
+            if (StringUtils.hasText(sectionContent) ) {
+                props = toMapProps(sectionContent);
+            } else {
+                props = new LinkedHashMap<String,String>();
+            }
+            if ( props != null ) {
+                this.props = props;
+            } else {
+                this.props = new LinkedHashMap<String,String>();
+            }
+        }
+
+        private Section(Section defaults) {
+            this(defaults.getName());
+            putAll(defaults.props);
+        }
+
+        //Protected to access in a test case - NOT considered part of Shiro's public API
+
+        protected static boolean isContinued(String line) {
+            if (!StringUtils.hasText(line)) {
+                return false;
+            }
+            int length = line.length();
+            //find the number of backslashes at the end of the line.  If an even number, the
+            //backslashes are considered escaped.  If an odd number, the line is considered continued on the next line
+            int backslashCount = 0;
+            for (int i = length - 1; i > 0; i--) {
+                if (line.charAt(i) == ESCAPE_TOKEN) {
+                    backslashCount++;
+                } else {
+                    break;
+                }
+            }
+            return backslashCount % 2 != 0;
+        }
+
+        private static boolean isKeyValueSeparatorChar(char c) {
+            return Character.isWhitespace(c) || c == ':' || c == '=';
+        }
+
+        private static boolean isCharEscaped(CharSequence s, int index) {
+            return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN;
+        }
+
+        //Protected to access in a test case - NOT considered part of Shiro's public API
+        protected static String[] splitKeyValue(String keyValueLine) {
+            String line = StringUtils.clean(keyValueLine);
+            if (line == null) {
+                return null;
+            }
+            StringBuilder keyBuffer = new StringBuilder();
+            StringBuilder valueBuffer = new StringBuilder();
+
+            boolean buildingKey = true; //we'll build the value next:
+
+            for (int i = 0; i < line.length(); i++) {
+                char c = line.charAt(i);
+
+                if (buildingKey) {
+                    if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
+                        buildingKey = false;//now start building the value
+                    } else {
+                        keyBuffer.append(c);
+                    }
+                } else {
+                    if (valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
+                        //swallow the separator chars before we start building the value
+                    } else {
+                        valueBuffer.append(c);
+                    }
+                }
+            }
+
+            String key = StringUtils.clean(keyBuffer.toString());
+            String value = StringUtils.clean(valueBuffer.toString());
+
+            if (key == null || value == null) {
+                String msg = "Line argument must contain a key and a value.  Only one string token was found.";
+                throw new IllegalArgumentException(msg);
+            }
+
+            log.trace("Discovered key/value pair: {}={}", key, value);
+
+            return new String[]{key, value};
+        }
+
+        private static Map<String, String> toMapProps(String content) {
+            Map<String, String> props = new LinkedHashMap<String, String>();
+            String line;
+            StringBuilder lineBuffer = new StringBuilder();
+            Scanner scanner = new Scanner(content);
+            while (scanner.hasNextLine()) {
+                line = StringUtils.clean(scanner.nextLine());
+                if (isContinued(line)) {
+                    //strip off the last continuation backslash:
+                    line = line.substring(0, line.length() - 1);
+                    lineBuffer.append(line);
+                    continue;
+                } else {
+                    lineBuffer.append(line);
+                }
+                line = lineBuffer.toString();
+                lineBuffer = new StringBuilder();
+                String[] kvPair = splitKeyValue(line);
+                props.put(kvPair[0], kvPair[1]);
+            }
+
+            return props;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public void clear() {
+            this.props.clear();
+        }
+
+        public boolean containsKey(Object key) {
+            return this.props.containsKey(key);
+        }
+
+        public boolean containsValue(Object value) {
+            return this.props.containsValue(value);
+        }
+
+        public Set<Entry<String, String>> entrySet() {
+            return this.props.entrySet();
+        }
+
+        public String get(Object key) {
+            return this.props.get(key);
+        }
+
+        public boolean isEmpty() {
+            return this.props.isEmpty();
+        }
+
+        public Set<String> keySet() {
+            return this.props.keySet();
+        }
+
+        public String put(String key, String value) {
+            return this.props.put(key, value);
+        }
+
+        public void putAll(Map<? extends String, ? extends String> m) {
+            this.props.putAll(m);
+        }
+
+        public String remove(Object key) {
+            return this.props.remove(key);
+        }
+
+        public int size() {
+            return this.props.size();
+        }
+
+        public Collection<String> values() {
+            return this.props.values();
+        }
+
+        public String toString() {
+            String name = getName();
+            if (DEFAULT_SECTION_NAME.equals(name)) {
+                return "<default>";
+            }
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Section) {
+                Section other = (Section) obj;
+                return getName().equals(other.getName()) && this.props.equals(other.props);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return this.name.hashCode() * 31 + this.props.hashCode();
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/config/IniFactorySupport.java b/core/src/main/java/org/apache/shiro/config/IniFactorySupport.java
new file mode 100644
index 0000000..ad91ee7
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/IniFactorySupport.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.AbstractFactory;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base support class for {@link Factory} implementations that generate their instance(s) based on
+ * {@link Ini} configuration.
+ *
+ * @since 1.0
+ */
+public abstract class IniFactorySupport<T> extends AbstractFactory<T> {
+
+    public static final String DEFAULT_INI_RESOURCE_PATH = "classpath:shiro.ini";
+
+    private static transient final Logger log = LoggerFactory.getLogger(IniFactorySupport.class);
+
+    private Ini ini;
+
+    protected IniFactorySupport() {
+    }
+
+    protected IniFactorySupport(Ini ini) {
+        this.ini = ini;
+    }
+
+    public Ini getIni() {
+        return ini;
+    }
+
+    public void setIni(Ini ini) {
+        this.ini = ini;
+    }
+
+    /**
+     * Returns a new Ini instance created from the default {@code classpath:shiro.ini} file, or {@code null} if
+     * the file does not exist.
+     *
+     * @return a new Ini instance created from the default {@code classpath:shiro.ini} file, or {@code null} if
+     *         the file does not exist.
+     */
+    public static Ini loadDefaultClassPathIni() {
+        Ini ini = null;
+        if (ResourceUtils.resourceExists(DEFAULT_INI_RESOURCE_PATH)) {
+            log.debug("Found shiro.ini at the root of the classpath.");
+            ini = new Ini();
+            ini.loadFromPath(DEFAULT_INI_RESOURCE_PATH);
+            if (CollectionUtils.isEmpty(ini)) {
+                log.warn("shiro.ini found at the root of the classpath, but it did not contain any data.");
+            }
+        }
+        return ini;
+    }
+
+    /**
+     * Tries to resolve the Ini instance to use for configuration.  This implementation functions as follows:
+     * <ol>
+     * <li>The {@code Ini} instance returned from {@link #getIni()} will be returned if it is not null or empty.</li>
+     * <li>If {@link #getIni()} is {@code null} or empty, this implementation will attempt to find and load the
+     * {@link #loadDefaultClassPathIni() default class path Ini}.</li>
+     * <li>If neither of the two attempts above returns an instance, {@code null} is returned</li>
+     * </ol>
+     *
+     * @return the Ini instance to use for configuration.
+     */
+    protected Ini resolveIni() {
+        Ini ini = getIni();
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("Null or empty Ini instance.  Falling back to the default {} file.", DEFAULT_INI_RESOURCE_PATH);
+            ini = loadDefaultClassPathIni();
+        }
+        return ini;
+    }
+
+    /**
+     * Creates a new object instance by using a configured INI source.  This implementation functions as follows:
+     * <ol>
+     * <li>{@link #resolveIni() Resolve} the {@code Ini} source to use for configuration.</li>
+     * <li>If there was no resolved Ini source, create and return a simple default instance via the
+     * {@link #createDefaultInstance()} method.</li>
+     * </ol>
+     *
+     * @return a new {@code SecurityManager} instance by using a configured INI source.
+     */
+    public T createInstance() {
+        Ini ini = resolveIni();
+
+        T instance;
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("No populated Ini available.  Creating a default instance.");
+            instance = createDefaultInstance();
+            if (instance == null) {
+                String msg = getClass().getName() + " implementation did not return a default instance in " +
+                        "the event of a null/empty Ini configuration.  This is required to support the " +
+                        "Factory interface.  Please check your implementation.";
+                throw new IllegalStateException(msg);
+            }
+        } else {
+            log.debug("Creating instance from Ini [" + ini + "]");
+            instance = createInstance(ini);
+            if (instance == null) {
+                String msg = getClass().getName() + " implementation did not return a constructed instance from " +
+                        "the createInstance(Ini) method implementation.";
+                throw new IllegalStateException(msg);
+            }
+        }
+
+        return instance;
+    }
+
+    protected abstract T createInstance(Ini ini);
+
+    protected abstract T createDefaultInstance();
+}
diff --git a/core/src/main/java/org/apache/shiro/config/IniSecurityManagerFactory.java b/core/src/main/java/org/apache/shiro/config/IniSecurityManagerFactory.java
new file mode 100644
index 0000000..5101b35
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/IniSecurityManagerFactory.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.RealmSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.RealmFactory;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Factory;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.Nameable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link Factory} that creates {@link SecurityManager} instances based on {@link Ini} configuration.
+ *
+ * @since 1.0
+ */
+public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {
+
+    public static final String MAIN_SECTION_NAME = "main";
+
+    public static final String SECURITY_MANAGER_NAME = "securityManager";
+    public static final String INI_REALM_NAME = "iniRealm";
+
+    private static transient final Logger log = LoggerFactory.getLogger(IniSecurityManagerFactory.class);
+
+    private ReflectionBuilder builder;
+
+    /**
+     * Creates a new instance.  See the {@link #getInstance()} JavaDoc for detailed explanation of how an INI
+     * source will be resolved to use to build the instance.
+     */
+    public IniSecurityManagerFactory() {
+    }
+
+    public IniSecurityManagerFactory(Ini config) {
+        setIni(config);
+    }
+
+    public IniSecurityManagerFactory(String iniResourcePath) {
+        this(Ini.fromResourcePath(iniResourcePath));
+    }
+
+    public Map<String, ?> getBeans() {
+        return this.builder != null ? Collections.unmodifiableMap(builder.getObjects()) : null;
+    }
+
+    private SecurityManager getSecurityManagerBean() {
+        return builder.getBean(SECURITY_MANAGER_NAME, SecurityManager.class);
+    }
+
+    protected SecurityManager createDefaultInstance() {
+        return new DefaultSecurityManager();
+    }
+
+    protected SecurityManager createInstance(Ini ini) {
+        if (CollectionUtils.isEmpty(ini)) {
+            throw new NullPointerException("Ini argument cannot be null or empty.");
+        }
+        SecurityManager securityManager = createSecurityManager(ini);
+        if (securityManager == null) {
+            String msg = SecurityManager.class + " instance cannot be null.";
+            throw new ConfigurationException(msg);
+        }
+        return securityManager;
+    }
+
+    private SecurityManager createSecurityManager(Ini ini) {
+        Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
+        if (CollectionUtils.isEmpty(mainSection)) {
+            //try the default:
+            mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
+        }
+        return createSecurityManager(ini, mainSection);
+    }
+
+    protected boolean isAutoApplyRealms(SecurityManager securityManager) {
+        boolean autoApply = true;
+        if (securityManager instanceof RealmSecurityManager) {
+            //only apply realms if they haven't been explicitly set by the user:
+            RealmSecurityManager realmSecurityManager = (RealmSecurityManager) securityManager;
+            Collection<Realm> realms = realmSecurityManager.getRealms();
+            if (!CollectionUtils.isEmpty(realms)) {
+                log.info("Realms have been explicitly set on the SecurityManager instance - auto-setting of " +
+                        "realms will not occur.");
+                autoApply = false;
+            }
+        }
+        return autoApply;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
+
+        Map<String, ?> defaults = createDefaults(ini, mainSection);
+        Map<String, ?> objects = buildInstances(mainSection, defaults);
+
+        SecurityManager securityManager = getSecurityManagerBean();
+
+        boolean autoApplyRealms = isAutoApplyRealms(securityManager);
+
+        if (autoApplyRealms) {
+            //realms and realm factory might have been created - pull them out first so we can
+            //initialize the securityManager:
+            Collection<Realm> realms = getRealms(objects);
+            //set them on the SecurityManager
+            if (!CollectionUtils.isEmpty(realms)) {
+                applyRealmsToSecurityManager(realms, securityManager);
+            }
+        }
+
+        return securityManager;
+    }
+
+    protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
+        Map<String, Object> defaults = new LinkedHashMap<String, Object>();
+
+        SecurityManager securityManager = createDefaultInstance();
+        defaults.put(SECURITY_MANAGER_NAME, securityManager);
+
+        if (shouldImplicitlyCreateRealm(ini)) {
+            Realm realm = createRealm(ini);
+            if (realm != null) {
+                defaults.put(INI_REALM_NAME, realm);
+            }
+        }
+
+        return defaults;
+    }
+
+    private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) {
+        this.builder = new ReflectionBuilder(defaults);
+        return this.builder.buildObjects(section);
+    }
+
+    private void addToRealms(Collection<Realm> realms, RealmFactory factory) {
+        LifecycleUtils.init(factory);
+        Collection<Realm> factoryRealms = factory.getRealms();
+        //SHIRO-238: check factoryRealms (was 'realms'):
+        if (!CollectionUtils.isEmpty(factoryRealms)) {
+            realms.addAll(factoryRealms);
+        }
+    }
+
+    private Collection<Realm> getRealms(Map<String, ?> instances) {
+
+        //realms and realm factory might have been created - pull them out first so we can
+        //initialize the securityManager:
+        List<Realm> realms = new ArrayList<Realm>();
+
+        //iterate over the map entries to pull out the realm factory(s):
+        for (Map.Entry<String, ?> entry : instances.entrySet()) {
+
+            String name = entry.getKey();
+            Object value = entry.getValue();
+
+            if (value instanceof RealmFactory) {
+                addToRealms(realms, (RealmFactory) value);
+            } else if (value instanceof Realm) {
+                Realm realm = (Realm) value;
+                //set the name if null:
+                String existingName = realm.getName();
+                if (existingName == null || existingName.startsWith(realm.getClass().getName())) {
+                    if (realm instanceof Nameable) {
+                        ((Nameable) realm).setName(name);
+                        log.debug("Applied name '{}' to Nameable realm instance {}", name, realm);
+                    } else {
+                        log.info("Realm does not implement the {} interface.  Configured name will not be applied.",
+                                Nameable.class.getName());
+                    }
+                }
+                realms.add(realm);
+            }
+        }
+
+        return realms;
+    }
+
+    private void assertRealmSecurityManager(SecurityManager securityManager) {
+        if (securityManager == null) {
+            throw new NullPointerException("securityManager instance cannot be null");
+        }
+        if (!(securityManager instanceof RealmSecurityManager)) {
+            String msg = "securityManager instance is not a " + RealmSecurityManager.class.getName() +
+                    " instance.  This is required to access or configure realms on the instance.";
+            throw new ConfigurationException(msg);
+        }
+    }
+
+    protected void applyRealmsToSecurityManager(Collection<Realm> realms, SecurityManager securityManager) {
+        assertRealmSecurityManager(securityManager);
+        ((RealmSecurityManager) securityManager).setRealms(realms);
+    }
+
+    /**
+     * Returns {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
+     * {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be implicitly
+     * created.
+     *
+     * @param ini the Ini instance to inspect for account data resulting in an implicitly created realm.
+     * @return {@code true} if the Ini contains account data and a {@code Realm} should be implicitly
+     *         {@link #createRealm(Ini) created} to reflect the account data, {@code false} if no realm should be
+     *         implicitly created.
+     */
+    protected boolean shouldImplicitlyCreateRealm(Ini ini) {
+        return !CollectionUtils.isEmpty(ini) &&
+                (!CollectionUtils.isEmpty(ini.getSection(IniRealm.ROLES_SECTION_NAME)) ||
+                        !CollectionUtils.isEmpty(ini.getSection(IniRealm.USERS_SECTION_NAME)));
+    }
+
+    /**
+     * Creates a {@code Realm} from the Ini instance containing account data.
+     *
+     * @param ini the Ini instance from which to acquire the account data.
+     * @return a new Realm instance reflecting the account data discovered in the {@code Ini}.
+     */
+    protected Realm createRealm(Ini ini) {
+        //IniRealm realm = new IniRealm(ini); changed to support SHIRO-322
+        IniRealm realm = new IniRealm();
+        realm.setName(INI_REALM_NAME);
+        realm.setIni(ini); //added for SHIRO-322
+        return realm;
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java b/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
new file mode 100644
index 0000000..6ca49c0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
@@ -0,0 +1,565 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+import org.apache.shiro.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.beans.PropertyDescriptor;
+import java.util.*;
+
+
+/**
+ * Object builder that uses reflection and Apache Commons BeanUtils to build objects given a
+ * map of "property values".  Typically these come from the Shiro INI configuration and are used
+ * to construct or modify the SecurityManager, its dependencies, and web-based security filters.
+ * <p/>
+ * Recognizes {@link Factory} implementations and will call
+ * {@link org.apache.shiro.util.Factory#getInstance() getInstance} to satisfy any reference to this bean.
+ *
+ * @since 0.9
+ */
+public class ReflectionBuilder {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(ReflectionBuilder.class);
+
+    private static final String OBJECT_REFERENCE_BEGIN_TOKEN = "$";
+    private static final String ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$";
+    private static final String GLOBAL_PROPERTY_PREFIX = "shiro";
+    private static final char MAP_KEY_VALUE_DELIMITER = ':';
+    private static final String HEX_BEGIN_TOKEN = "0x";
+    private static final String NULL_VALUE_TOKEN = "null";
+    private static final String EMPTY_STRING_VALUE_TOKEN = "\"\"";
+    private static final char STRING_VALUE_DELIMETER = '"';
+    private static final char MAP_PROPERTY_BEGIN_TOKEN = '[';
+    private static final char MAP_PROPERTY_END_TOKEN = ']';
+
+    private Map<String, ?> objects;
+
+    public ReflectionBuilder() {
+        this.objects = new LinkedHashMap<String, Object>();
+    }
+
+    public ReflectionBuilder(Map<String, ?> defaults) {
+        this.objects = CollectionUtils.isEmpty(defaults) ? new LinkedHashMap<String, Object>() : defaults;
+    }
+
+    public Map<String, ?> getObjects() {
+        return objects;
+    }
+
+    public void setObjects(Map<String, ?> objects) {
+        this.objects = CollectionUtils.isEmpty(objects) ? new LinkedHashMap<String, Object>() : objects;
+    }
+
+    public Object getBean(String id) {
+        return objects.get(id);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public <T> T getBean(String id, Class<T> requiredType) {
+        if (requiredType == null) {
+            throw new NullPointerException("requiredType argument cannot be null.");
+        }
+        Object bean = getBean(id);
+        if (bean == null) {
+            return null;
+        }
+        if (!requiredType.isAssignableFrom(bean.getClass())) {
+            throw new IllegalStateException("Bean with id [" + id + "] is not of the required type [" +
+                    requiredType.getName() + "].");
+        }
+        return (T) bean;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
+        if (kvPairs != null && !kvPairs.isEmpty()) {
+
+            // Separate key value pairs into object declarations and property assignment
+            // so that all objects can be created up front
+
+            //https://issues.apache.org/jira/browse/SHIRO-85 - need to use LinkedHashMaps here:
+            Map<String, String> instanceMap = new LinkedHashMap<String, String>();
+            Map<String, String> propertyMap = new LinkedHashMap<String, String>();
+
+            for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
+                if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) {
+                    instanceMap.put(entry.getKey(), entry.getValue());
+                } else {
+                    propertyMap.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            // Create all instances
+            for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
+                createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue());
+            }
+
+            // Set all properties
+            for (Map.Entry<String, String> entry : propertyMap.entrySet()) {
+                applyProperty(entry.getKey(), entry.getValue(), objects);
+            }
+        }
+
+        //SHIRO-413: init method must be called for constructed objects that are Initializable
+        LifecycleUtils.init(objects.values());
+
+        return objects;
+    }
+
+    protected void createNewInstance(Map<String, Object> objects, String name, String value) {
+
+        Object currentInstance = objects.get(name);
+        if (currentInstance != null) {
+            log.info("An instance with name '{}' already exists.  " +
+                    "Redefining this object as a new instance of type {}", name, value);
+        }
+
+        Object instance;//name with no property, assume right hand side of equals sign is the class name:
+        try {
+            instance = ClassUtils.newInstance(value);
+            if (instance instanceof Nameable) {
+                ((Nameable) instance).setName(name);
+            }
+        } catch (Exception e) {
+            String msg = "Unable to instantiate class [" + value + "] for object named '" + name + "'.  " +
+                    "Please ensure you've specified the fully qualified class name correctly.";
+            throw new ConfigurationException(msg, e);
+        }
+        objects.put(name, instance);
+    }
+
+    protected void applyProperty(String key, String value, Map objects) {
+
+        int index = key.indexOf('.');
+
+        if (index >= 0) {
+            String name = key.substring(0, index);
+            String property = key.substring(index + 1, key.length());
+
+            if (GLOBAL_PROPERTY_PREFIX.equalsIgnoreCase(name)) {
+                applyGlobalProperty(objects, property, value);
+            } else {
+                applySingleProperty(objects, name, property, value);
+            }
+
+        } else {
+            throw new IllegalArgumentException("All property keys must contain a '.' character. " +
+                    "(e.g. myBean.property = value)  These should already be separated out by buildObjects().");
+        }
+    }
+
+    protected void applyGlobalProperty(Map objects, String property, String value) {
+        for (Object instance : objects.values()) {
+            try {
+                PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(instance, property);
+                if (pd != null) {
+                    applyProperty(instance, property, value);
+                }
+            } catch (Exception e) {
+                String msg = "Error retrieving property descriptor for instance " +
+                        "of type [" + instance.getClass().getName() + "] " +
+                        "while setting property [" + property + "]";
+                throw new ConfigurationException(msg, e);
+            }
+        }
+    }
+
+    protected void applySingleProperty(Map objects, String name, String property, String value) {
+        Object instance = objects.get(name);
+        if (property.equals("class")) {
+            throw new IllegalArgumentException("Property keys should not contain 'class' properties since these " +
+                    "should already be separated out by buildObjects().");
+
+        } else if (instance == null) {
+            String msg = "Configuration error.  Specified object [" + name + "] with property [" +
+                    property + "] without first defining that object's class.  Please first " +
+                    "specify the class property first, e.g. myObject = fully_qualified_class_name " +
+                    "and then define additional properties.";
+            throw new IllegalArgumentException(msg);
+
+        } else {
+            applyProperty(instance, property, value);
+        }
+    }
+
+    protected boolean isReference(String value) {
+        return value != null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN);
+    }
+
+    protected String getId(String referenceToken) {
+        return referenceToken.substring(OBJECT_REFERENCE_BEGIN_TOKEN.length());
+    }
+
+    protected Object getReferencedObject(String id) {
+        Object o = objects != null && !objects.isEmpty() ? objects.get(id) : null;
+        if (o == null) {
+            String msg = "The object with id [" + id + "] has not yet been defined and therefore cannot be " +
+                    "referenced.  Please ensure objects are defined in the order in which they should be " +
+                    "created and made available for future reference.";
+            throw new UnresolveableReferenceException(msg);
+        }
+        return o;
+    }
+
+    protected String unescapeIfNecessary(String value) {
+        if (value != null && value.startsWith(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN)) {
+            return value.substring(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN.length() - 1);
+        }
+        return value;
+    }
+
+    protected Object resolveReference(String reference) {
+        String id = getId(reference);
+        log.debug("Encountered object reference '{}'.  Looking up object with id '{}'", reference, id);
+        final Object referencedObject = getReferencedObject(id);
+        if (referencedObject instanceof Factory) {
+            return ((Factory) referencedObject).getInstance();
+        }
+        return referencedObject;
+    }
+
+    protected boolean isTypedProperty(Object object, String propertyName, Class clazz) {
+        if (clazz == null) {
+            throw new NullPointerException("type (class) argument cannot be null.");
+        }
+        try {
+            PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, propertyName);
+            if (descriptor == null) {
+                String msg = "Property '" + propertyName + "' does not exist for object of " +
+                        "type " + object.getClass().getName() + ".";
+                throw new ConfigurationException(msg);
+            }
+            Class propertyClazz = descriptor.getPropertyType();
+            return clazz.isAssignableFrom(propertyClazz);
+        } catch (ConfigurationException ce) {
+            //let it propagate:
+            throw ce;
+        } catch (Exception e) {
+            String msg = "Unable to determine if property [" + propertyName + "] represents a " + clazz.getName();
+            throw new ConfigurationException(msg, e);
+        }
+    }
+
+    protected Set<?> toSet(String sValue) {
+        String[] tokens = StringUtils.split(sValue);
+        if (tokens == null || tokens.length <= 0) {
+            return null;
+        }
+
+        //SHIRO-423: check to see if the value is a referenced Set already, and if so, return it immediately:
+        if (tokens.length == 1 && isReference(tokens[0])) {
+            Object reference = resolveReference(tokens[0]);
+            if (reference instanceof Set) {
+                return (Set)reference;
+            }
+        }
+
+        Set<String> setTokens = new LinkedHashSet<String>(Arrays.asList(tokens));
+
+        //now convert into correct values and/or references:
+        Set<Object> values = new LinkedHashSet<Object>(setTokens.size());
+        for (String token : setTokens) {
+            Object value = resolveValue(token);
+            values.add(value);
+        }
+        return values;
+    }
+
+    protected Map<?, ?> toMap(String sValue) {
+        String[] tokens = StringUtils.split(sValue, StringUtils.DEFAULT_DELIMITER_CHAR,
+                StringUtils.DEFAULT_QUOTE_CHAR, StringUtils.DEFAULT_QUOTE_CHAR, true, true);
+        if (tokens == null || tokens.length <= 0) {
+            return null;
+        }
+
+        //SHIRO-423: check to see if the value is a referenced Map already, and if so, return it immediately:
+        if (tokens.length == 1 && isReference(tokens[0])) {
+            Object reference = resolveReference(tokens[0]);
+            if (reference instanceof Map) {
+                return (Map)reference;
+            }
+        }
+
+        Map<String, String> mapTokens = new LinkedHashMap<String, String>(tokens.length);
+        for (String token : tokens) {
+            String[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER);
+            if (kvPair == null || kvPair.length != 2) {
+                String msg = "Map property value [" + sValue + "] contained key-value pair token [" +
+                        token + "] that does not properly split to a single key and pair.  This must be the " +
+                        "case for all map entries.";
+                throw new ConfigurationException(msg);
+            }
+            mapTokens.put(kvPair[0], kvPair[1]);
+        }
+
+        //now convert into correct values and/or references:
+        Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size());
+        for (Map.Entry<String, String> entry : mapTokens.entrySet()) {
+            Object key = resolveValue(entry.getKey());
+            Object value = resolveValue(entry.getValue());
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    // @since 1.2.2
+    // TODO: make protected in 1.3+
+    private Collection<?> toCollection(String sValue) {
+
+        String[] tokens = StringUtils.split(sValue);
+        if (tokens == null || tokens.length <= 0) {
+            return null;
+        }
+
+        //SHIRO-423: check to see if the value is a referenced Collection already, and if so, return it immediately:
+        if (tokens.length == 1 && isReference(tokens[0])) {
+            Object reference = resolveReference(tokens[0]);
+            if (reference instanceof Collection) {
+                return (Collection)reference;
+            }
+        }
+
+        //now convert into correct values and/or references:
+        List<Object> values = new ArrayList<Object>(tokens.length);
+        for (String token : tokens) {
+            Object value = resolveValue(token);
+            values.add(value);
+        }
+        return values;
+    }
+
+    protected List<?> toList(String sValue) {
+        String[] tokens = StringUtils.split(sValue);
+        if (tokens == null || tokens.length <= 0) {
+            return null;
+        }
+
+        //SHIRO-423: check to see if the value is a referenced List already, and if so, return it immediately:
+        if (tokens.length == 1 && isReference(tokens[0])) {
+            Object reference = resolveReference(tokens[0]);
+            if (reference instanceof List) {
+                return (List)reference;
+            }
+        }
+
+        //now convert into correct values and/or references:
+        List<Object> values = new ArrayList<Object>(tokens.length);
+        for (String token : tokens) {
+            Object value = resolveValue(token);
+            values.add(value);
+        }
+        return values;
+    }
+
+    protected byte[] toBytes(String sValue) {
+        if (sValue == null) {
+            return null;
+        }
+        byte[] bytes;
+        if (sValue.startsWith(HEX_BEGIN_TOKEN)) {
+            String hex = sValue.substring(HEX_BEGIN_TOKEN.length());
+            bytes = Hex.decode(hex);
+        } else {
+            //assume base64 encoded:
+            bytes = Base64.decode(sValue);
+        }
+        return bytes;
+    }
+
+    protected Object resolveValue(String stringValue) {
+        Object value;
+        if (isReference(stringValue)) {
+            value = resolveReference(stringValue);
+        } else {
+            value = unescapeIfNecessary(stringValue);
+        }
+        return value;
+    }
+
+    protected String checkForNullOrEmptyLiteral(String stringValue) {
+        if (stringValue == null) {
+            return null;
+        }
+        //check if the value is the actual literal string 'null' (expected to be wrapped in quotes):
+        if (stringValue.equals("\"null\"")) {
+            return NULL_VALUE_TOKEN;
+        }
+        //or the actual literal string of two quotes '""' (expected to be wrapped in quotes):
+        else if (stringValue.equals("\"\"\"\"")) {
+            return EMPTY_STRING_VALUE_TOKEN;
+        } else {
+            return stringValue;
+        }
+    }
+    
+    protected void applyProperty(Object object, String propertyPath, Object value) {
+
+        int mapBegin = propertyPath.indexOf(MAP_PROPERTY_BEGIN_TOKEN);
+        int mapEnd = -1;
+        String mapPropertyPath = null;
+        String keyString = null;
+
+        String remaining = null;
+        
+        if (mapBegin >= 0) {
+            //a map is being referenced in the overall property path.  Find just the map's path:
+            mapPropertyPath = propertyPath.substring(0, mapBegin);
+            //find the end of the map reference:
+            mapEnd = propertyPath.indexOf(MAP_PROPERTY_END_TOKEN, mapBegin);
+            //find the token in between the [ and the ] (the map/array key or index):
+            keyString = propertyPath.substring(mapBegin+1, mapEnd);
+
+            //find out if there is more path reference to follow.  If not, we're at a terminal of the OGNL expression
+            if (propertyPath.length() > (mapEnd+1)) {
+                remaining = propertyPath.substring(mapEnd+1);
+                if (remaining.startsWith(".")) {
+                    remaining = StringUtils.clean(remaining.substring(1));
+                }
+            }
+        }
+        
+        if (remaining == null) {
+            //we've terminated the OGNL expression.  Check to see if we're assigning a property or a map entry:
+            if (keyString == null) {
+                //not a map or array value assignment - assign the property directly:
+                setProperty(object, propertyPath, value);
+            } else {
+                //we're assigning a map or array entry.  Check to see which we should call:
+                if (isTypedProperty(object, mapPropertyPath, Map.class)) {
+                    Map map = (Map)getProperty(object, mapPropertyPath);
+                    Object mapKey = resolveValue(keyString);
+                    //noinspection unchecked
+                    map.put(mapKey, value);
+                } else {
+                    //must be an array property.  Convert the key string to an index:
+                    int index = Integer.valueOf(keyString);
+                    setIndexedProperty(object, mapPropertyPath, index, value);
+                }
+            }
+        } else {
+            //property is being referenced as part of a nested path.  Find the referenced map/array entry and
+            //recursively call this method with the remaining property path
+            Object referencedValue = null;
+            if (isTypedProperty(object, mapPropertyPath, Map.class)) {
+                Map map = (Map)getProperty(object, mapPropertyPath);
+                Object mapKey = resolveValue(keyString);
+                referencedValue = map.get(mapKey);
+            } else {
+                //must be an array property:
+                int index = Integer.valueOf(keyString);
+                referencedValue = getIndexedProperty(object, mapPropertyPath, index);
+            }
+
+            if (referencedValue == null) {
+                throw new ConfigurationException("Referenced map/array value '" + mapPropertyPath + "[" +
+                keyString + "]' does not exist.");
+            }
+
+            applyProperty(referencedValue, remaining, value);
+        }
+    }
+    
+    private void setProperty(Object object, String propertyPath, Object value) {
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("Applying property [{}] value [{}] on object of type [{}]",
+                        new Object[]{propertyPath, value, object.getClass().getName()});
+            }
+            BeanUtils.setProperty(object, propertyPath, value);
+        } catch (Exception e) {
+            String msg = "Unable to set property '" + propertyPath + "' with value [" + value + "] on object " +
+                    "of type " + (object != null ? object.getClass().getName() : null) + ".  If " +
+                    "'" + value + "' is a reference to another (previously defined) object, prefix it with " +
+                    "'" + OBJECT_REFERENCE_BEGIN_TOKEN + "' to indicate that the referenced " +
+                    "object should be used as the actual value.  " +
+                    "For example, " + OBJECT_REFERENCE_BEGIN_TOKEN + value;
+            throw new ConfigurationException(msg, e);
+        }
+    }
+    
+    private Object getProperty(Object object, String propertyPath) {
+        try {
+            return PropertyUtils.getProperty(object, propertyPath);
+        } catch (Exception e) {
+            throw new ConfigurationException("Unable to access property '" + propertyPath + "'", e);
+        }
+    }
+    
+    private void setIndexedProperty(Object object, String propertyPath, int index, Object value) {
+        try {
+            PropertyUtils.setIndexedProperty(object, propertyPath, index, value);
+        } catch (Exception e) {
+            throw new ConfigurationException("Unable to set array property '" + propertyPath + "'", e);
+        }
+    }
+    
+    private Object getIndexedProperty(Object object, String propertyPath, int index) {
+        try {
+            return PropertyUtils.getIndexedProperty(object, propertyPath, index);
+        } catch (Exception e) {
+            throw new ConfigurationException("Unable to acquire array property '" + propertyPath + "'", e);
+        }
+    }
+    
+    protected boolean isIndexedPropertyAssignment(String propertyPath) {
+        return propertyPath.endsWith("" + MAP_PROPERTY_END_TOKEN);
+    }
+
+    protected void applyProperty(Object object, String propertyName, String stringValue) {
+
+        Object value;
+
+        if (NULL_VALUE_TOKEN.equals(stringValue)) {
+            value = null;
+        } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) {
+            value = StringUtils.EMPTY_STRING;
+        } else if (isIndexedPropertyAssignment(propertyName)) {
+            String checked = checkForNullOrEmptyLiteral(stringValue);
+            value = resolveValue(checked);
+        } else if (isTypedProperty(object, propertyName, Set.class)) {
+            value = toSet(stringValue);
+        } else if (isTypedProperty(object, propertyName, Map.class)) {
+            value = toMap(stringValue);
+        } else if (isTypedProperty(object, propertyName, List.class)) {
+            value = toList(stringValue);
+        } else if (isTypedProperty(object, propertyName, Collection.class)) {
+            value = toCollection(stringValue);
+        } else if (isTypedProperty(object, propertyName, byte[].class)) {
+            value = toBytes(stringValue);
+        } else if (isTypedProperty(object, propertyName, ByteSource.class)) {
+            byte[] bytes = toBytes(stringValue);
+            value = ByteSource.Util.bytes(bytes);
+        } else {
+            String checked = checkForNullOrEmptyLiteral(stringValue);
+            value = resolveValue(checked);
+        }
+
+        applyProperty(object, propertyName, value);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java b/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java
new file mode 100644
index 0000000..848438b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/ResourceConfigurable.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+/**
+ * Interface implemented by components that can be configured by resource locations (paths).
+ *
+ * @since 1.2
+ */
+public interface ResourceConfigurable {
+
+    /**
+     * Convenience method that accepts a comma-delimited string of config locations (resource paths).
+     *
+     * @param locations comma-delimited list of config locations (resource paths).
+     */
+    void setConfigLocations(String locations);
+
+    /**
+     * Sets the configuration locations (resource paths) that will be used to configure the instance.
+     *
+     * @param locations the configuration locations (resource paths) that will be used to configure the instance.
+     */
+    void setConfigLocations(String[] locations);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/config/UnresolveableReferenceException.java b/core/src/main/java/org/apache/shiro/config/UnresolveableReferenceException.java
new file mode 100644
index 0000000..ac6d90d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/UnresolveableReferenceException.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+/**
+ * Exception thrown when a reference to an object is made, but that object cannot be found.  This is most likely
+ * thrown due to a configuration line that references an object that hasn't been defined yet.
+ *
+ * @since 0.9 RC2
+ */
+public class UnresolveableReferenceException extends ConfigurationException {
+
+    /**
+     * Creates a new UnresolveableReferenceException.
+     */
+    public UnresolveableReferenceException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnresolveableReferenceException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnresolveableReferenceException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnresolveableReferenceException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnresolveableReferenceException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnresolveableReferenceException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnresolveableReferenceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/config/package-info.java b/core/src/main/java/org/apache/shiro/config/package-info.java
new file mode 100644
index 0000000..01b6c21
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/config/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components that support configuring Shiro in any application.
+ */
+package org.apache.shiro.config;
diff --git a/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java b/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java
new file mode 100644
index 0000000..bde36a0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import javax.crypto.KeyGenerator;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Base abstract class for supporting symmetric key cipher algorithms.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractSymmetricCipherService extends JcaCipherService {
+
+    protected AbstractSymmetricCipherService(String algorithmName) {
+        super(algorithmName);
+    }
+
+    /**
+     * Generates a new {@link java.security.Key Key} suitable for this CipherService's {@link #getAlgorithmName() algorithm}
+     * by calling {@link #generateNewKey(int) generateNewKey(128)} (uses a 128 bit size by default).
+     *
+     * @return a new {@link java.security.Key Key}, 128 bits in length.
+     */
+    public Key generateNewKey() {
+        return generateNewKey(getKeySize());
+    }
+
+    /**
+     * Generates a new {@link Key Key} of the specified size suitable for this CipherService
+     * (based on the {@link #getAlgorithmName() algorithmName} using the JDK {@link javax.crypto.KeyGenerator KeyGenerator}.
+     *
+     * @param keyBitSize the bit size of the key to create
+     * @return the created key suitable for use with this CipherService
+     */
+    public Key generateNewKey(int keyBitSize) {
+        KeyGenerator kg;
+        try {
+            kg = KeyGenerator.getInstance(getAlgorithmName());
+        } catch (NoSuchAlgorithmException e) {
+            String msg = "Unable to acquire " + getAlgorithmName() + " algorithm.  This is required to function.";
+            throw new IllegalStateException(msg, e);
+        }
+        kg.init(keyBitSize);
+        return kg.generateKey();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java b/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java
new file mode 100644
index 0000000..0c2f5cc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * {@code CipherService} using the {@code AES} cipher algorithm for all encryption, decryption, and key operations.
+ * <p/>
+ * The AES algorithm can support key sizes of {@code 128}, {@code 192} and {@code 256} bits<b>*</b>.  This implementation
+ * defaults to 128 bits.
+ * <p/>
+ * Note that this class retains the parent class's default {@link OperationMode#CBC CBC} mode of operation
+ * instead of the typical JDK default of {@link OperationMode#ECB ECB}.  {@code ECB} should not be used in
+ * security-sensitive environments because {@code ECB} does not allow for initialization vectors, which are
+ * considered necessary for strong encryption.  See the {@link DefaultBlockCipherService parent class}'s JavaDoc and the
+ * {@link JcaCipherService JcaCipherService} JavaDoc for more on why the JDK default should not be used and is not
+ * used in this implementation.
+ * <p/>
+ * <b>*</b> Generating and using AES key sizes greater than 128 require installation of the
+ * <a href="http://java.sun.com/javase/downloads/index.jsp">Java Cryptography Extension (JCE) Unlimited Strength
+ * Jurisdiction Policy files</a>.
+ *
+ * @since 1.0
+ */
+public class AesCipherService extends DefaultBlockCipherService {
+
+    private static final String ALGORITHM_NAME = "AES";
+
+    /**
+     * Creates a new {@link CipherService} instance using the {@code AES} cipher algorithm with the following
+     * important cipher default attributes:
+     * <table>
+     * <tr>
+     * <th>Attribute</th>
+     * <th>Value</th>
+     * </tr>
+     * <tr>
+     * <td>{@link #setKeySize keySize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setBlockSize blockSize}</td>
+     * <td>{@code 128} bits (required for {@code AES}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setMode mode}</td>
+     * <td>{@link OperationMode#CBC CBC}<b>*</b></td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setPaddingScheme paddingScheme}</td>
+     * <td>{@link PaddingScheme#PKCS5 PKCS5}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setInitializationVectorSize(int) initializationVectorSize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}</td>
+     * <td>{@code true}<b>**</b></td>
+     * </tr>
+     * </table>
+     * <p/>
+     * <b>*</b> The {@link OperationMode#CBC CBC} operation mode is used instead of the JDK default {@code ECB} to
+     * ensure strong encryption.  {@code ECB} should not be used in security-sensitive environments - see the
+     * {@link DefaultBlockCipherService DefaultBlockCipherService} class JavaDoc's "Operation Mode" section
+     * for more.
+     * <p/>
+     * <b>**</b>In conjunction with the default {@code CBC} operation mode, initialization vectors are generated by
+     * default to ensure strong encryption.  See the {@link JcaCipherService JcaCipherService} class JavaDoc for more.
+     */
+    public AesCipherService() {
+        super(ALGORITHM_NAME);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java b/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java
new file mode 100644
index 0000000..449874b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * {@code CipherService} using the {@code Blowfish} cipher algorithm for all encryption, decryption, and key operations.
+ * <p/>
+ * The Blowfish algorithm can support key sizes between {@code 32} and {@code 448} bits<b>*</b>, inclusive.  However,
+ * modern cryptanalysis techniques render keys of 80 bits or less mostly worthless - use {@code 128} or more whenever
+ * possible.
+ * <p/>
+ * Note that this class retains the parent class's default {@link OperationMode#CBC CBC} mode of operation
+ * instead of the typical JDK default of {@link OperationMode#ECB ECB}.  {@code ECB} should not be used in
+ * security-sensitive environments because {@code ECB} does not allow for initialization vectors, which are
+ * considered necessary for strong encryption.  See the {@link DefaultBlockCipherService parent class}'s JavaDoc and the
+ * {@link JcaCipherService JcaCipherService} JavaDoc for more on why the JDK default should not be used and is not
+ * used in this implementation.
+ * <p/>
+ * <b>*</b> Generating and using Blowfish key sizes greater than 128 require installation of the
+ * <a href="http://java.sun.com/javase/downloads/index.jsp">Java Cryptography Extension (JCE) Unlimited Strength
+ * Jurisdiction Policy files</a>.
+ *
+ * @since 1.0
+ */
+public class BlowfishCipherService extends DefaultBlockCipherService {
+
+    private static final String ALGORITHM_NAME = "Blowfish";
+    private static final int BLOCK_SIZE = 64;
+
+    /**
+     * Creates a new {@link CipherService} instance using the {@code Blowfish} cipher algorithm with the following
+     * important cipher default attributes:
+     * <table>
+     * <tr>
+     * <th>Attribute</th>
+     * <th>Value</th>
+     * </tr>
+     * <tr>
+     * <td>{@link #setKeySize keySize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setBlockSize blockSize}</td>
+     * <td>{@code 64} bits (required for {@code Blowfish})</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setMode mode}</td>
+     * <td>{@link OperationMode#CBC CBC}<b>*</b></td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setPaddingScheme paddingScheme}</td>
+     * <td>{@link PaddingScheme#PKCS5 PKCS5}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setInitializationVectorSize(int) initializationVectorSize}</td>
+     * <td>{@code 64} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}</td>
+     * <td>{@code true}<b>**</b></td>
+     * </tr>
+     * </table>
+     * <p/>
+     * <b>*</b> The {@link OperationMode#CBC CBC} operation mode is used instead of the JDK default {@code ECB} to
+     * ensure strong encryption.  {@code ECB} should not be used in security-sensitive environments - see the
+     * {@link DefaultBlockCipherService DefaultBlockCipherService} class JavaDoc's "Operation Mode" section
+     * for more.
+     * <p/>
+     * <b>**</b>In conjunction with the default {@code CBC} operation mode, initialization vectors are generated by
+     * default to ensure strong encryption.  See the {@link JcaCipherService JcaCipherService} class JavaDoc for more.
+     */
+    public BlowfishCipherService() {
+        super(ALGORITHM_NAME);
+        setInitializationVectorSize(BLOCK_SIZE); //like most block ciphers, the IV size is the same as the block size
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/CipherService.java b/core/src/main/java/org/apache/shiro/crypto/CipherService.java
new file mode 100644
index 0000000..8dda1ac
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/CipherService.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@code CipherService} uses a cryptographic algorithm called a
+ * <a href="http://en.wikipedia.org/wiki/Cipher">Cipher</a> to convert an original input source using a {@code key} to
+ * an uninterpretable format.  The resulting encrypted output is only able to be converted back to original form with
+ * a {@code key} as well.  {@code CipherService}s can perform both encryption and decryption.
+ * <h2>Cipher Basics</h2>
+ * For what is known as <em>Symmetric</em> {@code Cipher}s, the {@code Key} used to encrypt the source is the same
+ * as (or trivially similar to) the {@code Key} used to decrypt it.
+ * <p/>
+ * For <em>Asymmetric</em> {@code Cipher}s, the encryption {@code Key} is not the same as the decryption {@code Key}.
+ * The most common type of Asymmetric Ciphers are based on what is called public/private key pairs:
+ * <p/>
+ * A <em>private</em> key is known only to a single party, and as its name implies, is supposed be kept very private
+ * and secure.  A <em>public</em> key that is associated with the private key can be disseminated freely to anyone.
+ * Then data encrypted by the public key can only be decrypted by the private key and vice versa, but neither party
+ * need share their private key with anyone else.  By not sharing a private key, you can guarantee no 3rd party can
+ * intercept the key and therefore use it to decrypt a message.
+ * <p/>
+ * This asymmetric key technology was created as a
+ * more secure alternative to symmetric ciphers that sometimes suffer from man-in-the-middle attacks since, for
+ * data shared between two parties, the same Key must also be shared and may be compromised.
+ * <p/>
+ * Note that a symmetric cipher is perfectly fine to use if you just want to encode data in a format no one else
+ * can understand and you never give away the key.  Shiro uses a symmetric cipher when creating certain
+ * HTTP Cookies for example - because it is often undesirable to have user's identity stored in a plain-text cookie,
+ * that identity can be converted via a symmetric cipher.  Since the the same exact Shiro application will receive
+ * the cookie, it can decrypt it via the same {@code Key} and there is no potential for discovery since that Key
+ * is never shared with anyone.
+ * <h2>{@code CipherService}s vs JDK {@link javax.crypto.Cipher Cipher}s</h2>
+ * Shiro {@code CipherService}s essentially do the same things as JDK {@link javax.crypto.Cipher Cipher}s, but in
+ * simpler and easier-to-use ways for most application developers.  When thinking about encrypting and decrypting data
+ * in an application, most app developers want what a {@code CipherService} provides, rather than having to manage the
+ * lower-level intricacies of the JDK's {@code Cipher} API.  Here are a few reasons why most people prefer
+ * {@code CipherService}s:
+ * <ul>
+ * <li><b>Stateless Methods</b> - {@code CipherService} method calls do not retain state between method invocations.
+ * JDK {@code Cipher} instances do retain state across invocations, requiring its end-users to manage the instance
+ * and its state themselves.</li>
+ * <li><b>Thread Safety</b> - {@code CipherService} instances are thread-safe inherently because no state is
+ * retained across method invocations.  JDK {@code Cipher} instances retain state and cannot be used by multiple
+ * threads concurrently.</li>
+ * <li><b>Single Operation</b> - {@code CipherService} method calls are single operation methods: encryption or
+ * decryption in their entirety are done as a single method call.  This is ideal for the large majority of developer
+ * needs where you have something unencrypted and just want it decrypted (or vice versa) in a single method call.  In
+ * contrast, JDK {@code Cipher} instances can support encrypting/decrypting data in chunks over time (because it
+ * retains state), but this often introduces API clutter and confusion for most application developers.</li>
+ * <li><b>Type Safe</b> - There are {@code CipherService} implementations for different Cipher algorithms
+ * ({@code AesCipherService}, {@code BlowfishCipherService}, etc).  There is only one JDK {@code Cipher} class to
+ * represent all cipher algorithms/instances.
+ * <li><b>Simple Construction</b> - Because {@code CipherService} instances are type-safe, instantiating and using
+ * one is often as simple as calling the default constructor, for example, <code>new AesCipherService();</code>.  The
+ * JDK {@code Cipher} class however requires using a procedural factory method with String arguments to indicate how
+ * the instance should be created.  The String arguments themselves are somewhat cryptic and hard to
+ * understand unless you're a security expert.  Shiro hides these details from you, but allows you to configure them
+ * if you want.</li>
+ * </ul>
+ *
+ * @see BlowfishCipherService
+ * @see AesCipherService
+ * @since 1.0
+ */
+public interface CipherService {
+
+    /**
+     * Decrypts encrypted data via the specified cipher key and returns the original (pre-encrypted) data.
+     * Note that the key must be in a format understood by the CipherService implementation.
+     *
+     * @param encrypted     the previously encrypted data to decrypt
+     * @param decryptionKey the cipher key used during decryption.
+     * @return a byte source representing the original form of the specified encrypted data.
+     * @throws CryptoException if there is an error during decryption
+     */
+    ByteSource decrypt(byte[] encrypted, byte[] decryptionKey) throws CryptoException;
+
+    /**
+     * Receives encrypted data from the given {@code InputStream}, decrypts it, and sends the resulting decrypted data
+     * to the given {@code OutputStream}.
+     * <p/>
+     * <b>NOTE:</b> This method <em>does NOT</em> flush or close either stream prior to returning - the caller must
+     * do so when they are finished with the streams.  For example:
+     * <pre>
+     * try {
+     *     InputStream in = ...
+     *     OutputStream out = ...
+     *     cipherService.decrypt(in, out, decryptionKey);
+     * } finally {
+     *     if (in != null) {
+     *         try {
+     *             in.close();
+     *         } catch (IOException ioe1) { ... log, trigger event, etc }
+     *     }
+     *     if (out != null) {
+     *         try {
+     *             out.close();
+     *         } catch (IOException ioe2) { ... log, trigger event, etc }
+     *     }
+     * }
+     * </pre>
+     *
+     * @param in            the stream supplying the data to decrypt
+     * @param out           the stream to send the decrypted data
+     * @param decryptionKey the cipher key to use for decryption
+     * @throws CryptoException if there is any problem during decryption.
+     */
+    void decrypt(InputStream in, OutputStream out, byte[] decryptionKey) throws CryptoException;
+
+    /**
+     * Encrypts data via the specified cipher key.  Note that the key must be in a format understood by
+     * the {@code CipherService} implementation.
+     *
+     * @param raw           the data to encrypt
+     * @param encryptionKey the cipher key used during encryption.
+     * @return a byte source with the encrypted representation of the specified raw data.
+     * @throws CryptoException if there is an error during encryption
+     */
+    ByteSource encrypt(byte[] raw, byte[] encryptionKey) throws CryptoException;
+
+    /**
+     * Receives the data from the given {@code InputStream}, encrypts it, and sends the resulting encrypted data to the
+     * given {@code OutputStream}.
+     * <p/>
+     * <b>NOTE:</b> This method <em>does NOT</em> flush or close either stream prior to returning - the caller must
+     * do so when they are finished with the streams.  For example:
+     * <pre>
+     * try {
+     *     InputStream in = ...
+     *     OutputStream out = ...
+     *     cipherService.encrypt(in, out, encryptionKey);
+     * } finally {
+     *     if (in != null) {
+     *         try {
+     *             in.close();
+     *         } catch (IOException ioe1) { ... log, trigger event, etc }
+     *     }
+     *     if (out != null) {
+     *         try {
+     *             out.close();
+     *         } catch (IOException ioe2) { ... log, trigger event, etc }
+     *     }
+     * }
+     * </pre>
+     *
+     * @param in            the stream supplying the data to encrypt
+     * @param out           the stream to send the encrypted data
+     * @param encryptionKey the cipher key to use for encryption
+     * @throws CryptoException if there is any problem during encryption.
+     */
+    void encrypt(InputStream in, OutputStream out, byte[] encryptionKey) throws CryptoException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/CryptoException.java b/core/src/main/java/org/apache/shiro/crypto/CryptoException.java
new file mode 100644
index 0000000..a7e6c67
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/CryptoException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Base Shiro exception for problems encountered during cryptographic operations.
+ *
+ * @since 1.0
+ */
+public class CryptoException extends ShiroException {
+
+    public CryptoException(String message) {
+        super(message);
+    }
+
+    public CryptoException(Throwable cause) {
+        super(cause);
+    }
+
+    public CryptoException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java b/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java
new file mode 100644
index 0000000..0860397
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java
@@ -0,0 +1,531 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * Base abstract class for block cipher algorithms.
+ *
+ * <h2>Usage</h2>
+ * Note that this class exists mostly to simplify algorithm-specific subclasses.  Unless you understand the concepts of
+ * cipher modes of operation, block sizes, and padding schemes, and you want direct control of these things, you should
+ * typically not uses instances of this class directly.  Instead, algorithm-specific subclasses, such as
+ * {@link AesCipherService}, {@link BlowfishCipherService}, and others are usually better suited for regular use.
+ * <p/>
+ * However, if you have the need to create a custom block cipher service where no sufficient algorithm-specific subclass
+ * exists in Shiro, this class would be very useful.
+ *
+ * <h2>Configuration</h2>
+ * Block ciphers can accept configuration parameters that direct how they operate.  These parameters concatenated
+ * together in a single String comprise what the JDK JCA documentation calls a
+ * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#trans">transformation
+ * string</a>.  We think that it is better for Shiro to construct this transformation string automatically based on its
+ * constituent parts instead of having the end-user construct the string manually, which may be error prone or
+ * confusing.  To that end, Shiro {@link DefaultBlockCipherService}s have attributes that can be set individually in
+ * a type-safe manner based on your configuration needs, and Shiro will build the transformation string for you.
+ * <p/>
+ * The following sections typically document the configuration options for block (byte array)
+ * {@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])} method invocations.  Streaming configuration
+ * for those same attributes are done via mirrored {@code streaming}* attributes, and their purpose is identical, but
+ * they're only used during streaming {@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+ * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])} methods.  See the "Streaming"
+ * section below for more.
+ *
+ * <h3>Block Size</h3>
+ * The block size specifies the number of bits (not bytes) that the cipher operates on when performing an operation.
+ * It can be specified explicitly via the {@link #setBlockSize blockSize} attribute.  If not set, the JCA Provider
+ * default will be used based on the cipher algorithm.  Block sizes are usually very algorithm specific, so set this
+ * value only if you know you don't want the JCA Provider's default for the desired algorithm.  For example, the
+ * AES algorithm's Rijndael implementation <em>only</em> supports a 128 bit block size and will not work with any other
+ * size.
+ * <p/>
+ * Also note that the {@link #setInitializationVectorSize initializationVectorSize} is usually the same as the
+ * {@link #setBlockSize blockSize} in block ciphers.  If you change either attribute, you should ensure that the other
+ * attribute is correct for the target cipher algorithm.
+ *
+ * <h3>Operation Mode</h3>
+ * You may set the block cipher's<a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">mode of
+ * operation</a> via the {@link #setMode(OperationMode) mode} attribute, which accepts a type-safe
+ * {@link OperationMode OperationMode} enum instance.  This type safety helps avoid typos when specifying the mode and
+ * guarantees that the mode name will be recognized by the underlying JCA Provider.
+ * <p/>
+ * <b>*</b>If no operation mode is specified, Shiro defaults all of its block {@code CipherService} instances to the
+ * {@link OperationMode#CBC CBC} mode, specifically to support auto-generation of initialization vectors during
+ * encryption.  This is different than the JDK's default {@link OperationMode#ECB ECB} mode because {@code ECB} does
+ * not support initialization vectors, which are necessary for strong encryption.  See  the
+ * {@link org.apache.shiro.crypto.JcaCipherService JcaCipherService parent class} class JavaDoc for an extensive
+ * explanation on why we do this and why we do not use the Sun {@code ECB} default.  You also might also want read
+ * the <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29">Wikipedia
+ * section on ECB<a/> and look at the encrypted image to see an example of why {@code ECB} should not be used in
+ * security-sensitive environments.
+ * <p/>
+ * In the rare case that you need to override the default with a mode not represented
+ * by the {@link OperationMode} enum, you may specify the raw mode name string that will be recognized by your JCA
+ * provider via the {@link #setModeName modeName} attribute.  Because this is not type-safe, it is recommended only to
+ * use this attribute if the {@link OperationMode} enum does not represent your desired mode.
+ * <p/>
+ * <b>NOTE:</b> If you change the mode to one that does not support initialization vectors (such as
+ * {@link OperationMode#ECB ECB} or {@link OperationMode#NONE NONE}), you <em>must</em> turn off auto-generated
+ * initialization vectors by setting {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}
+ * to {@code false}.  Abandoning initialization vectors significantly weakens encryption, so think twice before
+ * disabling this feature.
+ *
+ * <h3>Padding Scheme</h3>
+ * Because block ciphers process messages in fixed-length blocks, if the final block in a message is not equal to the
+ * block length, <a href="http://en.wikipedia.org/wiki/Padding_(cryptography)">padding</a> is applied to match that
+ * size to maintain the total length of the message.  This is good because it protects data patterns from being
+ * identified  - when all chunks look the same length, it is much harder to infer what that data might be.
+ * <p/>
+ * You may set a padding scheme via the {@link #setPaddingScheme(PaddingScheme) paddingScheme} attribute, which
+ * accepts a type-safe {@link PaddingScheme PaddingScheme} enum instance.  Like the {@link OperationMode} enum,
+ * this enum offers type safety to help avoid typos and guarantees that the mode will be recongized by the underlying
+ * JCA provider.
+ * <p/>
+ * <b>*</b>If no padding scheme is specified, this class defaults to the {@link PaddingScheme#PKCS5} scheme, specifically
+ * to be compliant with the default behavior of auto-generating initialization vectors during encryption (see the
+ * {@link org.apache.shiro.crypto.JcaCipherService JcaCipherService parent class} class JavaDoc for why).
+ * <p/>
+ * In the rare case that you need to override the default with a scheme not represented by the {@link PaddingScheme}
+ * enum, you may specify the raw padding scheme name string that will be recognized by your JCA provider via the
+ * {@link #setPaddingScheme paddingSchemeName} attribute.  Because this is not type-safe, it is recommended only to
+ * use this attribute if the {@link PaddingScheme} enum does not represent your desired scheme.
+ *
+ * <h2>Streaming</h2>
+ * Most people don't think of using block ciphers as stream ciphers, since their name implies working
+ * with block data (i.e. byte arrays) only.  However, block ciphers can be turned into byte-oriented stream ciphers by
+ * using an appropriate {@link OperationMode operation mode} with a {@link #getStreamingBlockSize() streaming block size}
+ * of 8 bits.  This is why the {@link CipherService} interface provides both block and streaming operations.
+ * <p/>
+ * Because this streaming 8-bit block size rarely changes across block-cipher algorithms, default values have been set
+ * for all three streaming configuration parameters.  The defaults are:
+ * <ul>
+ * <li>{@link #setStreamingBlockSize(int) streamingBlockSize} = {@code 8} (bits)</li>
+ * <li>{@link #setStreamingMode streamingMode} = {@link OperationMode#CBC CBC}</li>
+ * <li>{@link #setStreamingPaddingScheme(PaddingScheme) streamingPaddingScheme} = {@link PaddingScheme#PKCS5 PKCS5}</li>
+ * </ul>
+ * <p/>
+ * These attributes have the same meaning as the {@code mode}, {@code blockSize}, and {@code paddingScheme} attributes
+ * described above, but they are applied during streaming method invocations only ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])}
+ * and {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+ *
+ * @see BlowfishCipherService
+ * @see AesCipherService
+ * @see <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">Wikipedia: Block Cipher Modes of Operation</a>
+ * @since 1.0
+ */
+public class DefaultBlockCipherService extends AbstractSymmetricCipherService {
+
+    private static final int DEFAULT_BLOCK_SIZE = 0;
+
+    private static final String TRANSFORMATION_STRING_DELIMITER = "/";
+    private static final int DEFAULT_STREAMING_BLOCK_SIZE = 8; //8 bits (1 byte)
+
+    private String modeName;
+    private int blockSize; //size in bits (not bytes) - i.e. a blockSize of 8 equals 1 byte. negative or zero value = use system default
+    private String paddingSchemeName;
+
+    private String streamingModeName;
+    private int streamingBlockSize;
+    private String streamingPaddingSchemeName;
+
+    private String transformationString; //cached value - rebuilt whenever any of its constituent parts change
+    private String streamingTransformationString; //cached value - rebuilt whenever any of its constituent parts change
+
+
+    /**
+     * Creates a new {@link DefaultBlockCipherService} using the specified block cipher {@code algorithmName}.  Per this
+     * class's JavaDoc, this constructor also sets the following defaults:
+     * <ul>
+     * <li>{@code streamingMode} = {@link OperationMode#CBC CBC}</li>
+     * <li>{@code streamingPaddingScheme} = {@link PaddingScheme#NONE none}</li>
+     * <li>{@code streamingBlockSize} = 8</li>
+     * </ul>
+     * All other attributes are null/unset, indicating the JCA Provider defaults will be used.
+     *
+     * @param algorithmName the block cipher algorithm to use when encrypting and decrypting
+     */
+    public DefaultBlockCipherService(String algorithmName) {
+        super(algorithmName);
+
+        this.modeName = OperationMode.CBC.name();
+        this.paddingSchemeName = PaddingScheme.PKCS5.getTransformationName();
+        this.blockSize = DEFAULT_BLOCK_SIZE; //0 = use the JCA provider's default
+
+        this.streamingModeName = OperationMode.CBC.name();
+        this.streamingPaddingSchemeName = PaddingScheme.PKCS5.getTransformationName();
+        this.streamingBlockSize = DEFAULT_STREAMING_BLOCK_SIZE;
+    }
+
+    /**
+     * Returns the cipher operation mode name (as a String) to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingModeName() streamingModeName} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     *
+     * @return the cipher operation mode name (as a String) to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default
+     *         mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public String getModeName() {
+        return modeName;
+    }
+
+    /**
+     * Sets the cipher operation mode name to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string.  A {@code null} value indicates that the JCA Provider
+     * default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingModeName() streamingModeName} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> most standard mode names are represented by the {@link OperationMode OperationMode} enum.  That enum
+     * should be used with the {@link #setMode mode} attribute when possible to retain type-safety and reduce the
+     * possibility of errors.  This method is better used if the {@link OperationMode} enum does not represent the
+     * necessary mode.
+     *
+     * @param modeName the cipher operation mode name to be used when constructing
+     *                 {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *                 default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * @see #setMode
+     */
+    public void setModeName(String modeName) {
+        this.modeName = modeName;
+        //clear out the transformation string so the next invocation will rebuild it with the new mode:
+        this.transformationString = null;
+    }
+
+    /**
+     * Sets the cipher operation mode of operation to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string.  A {@code null} value indicates that the JCA Provider
+     * default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #setStreamingMode streamingMode} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * If the {@link OperationMode} enum cannot represent your desired mode, you can set the name explicitly
+     * via the {@link #setModeName modeName} attribute directly.  However, because {@link OperationMode} represents all
+     * standard JDK mode names already, ensure that your underlying JCA Provider supports the non-standard name first.
+     *
+     * @param mode the cipher operation mode to be used when constructing
+     *             {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *             default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setMode(OperationMode mode) {
+        setModeName(mode.name());
+    }
+
+    /**
+     * Returns the cipher algorithm padding scheme name (as a String) to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingPaddingSchemeName() streamingPaddingSchemeName} attribute is used when the block cipher is
+     * used for streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     *
+     * @return the padding scheme name (as a String) to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default
+     *         padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public String getPaddingSchemeName() {
+        return paddingSchemeName;
+    }
+
+    /**
+     * Sets the padding scheme name to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingPaddingSchemeName() streamingPaddingSchemeName} attribute is used when the block cipher is
+     * used for streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> most standard padding schemes are represented by the {@link PaddingScheme PaddingScheme} enum.
+     * That enum should be used with the {@link #setPaddingScheme paddingScheme} attribute when possible to retain
+     * type-safety and reduce the possibility of errors.  Calling this method however is suitable if the
+     * {@code PaddingScheme} enum does not represent the desired scheme.
+     *
+     * @param paddingSchemeName the padding scheme name to be used when constructing
+     *                          {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA
+     *                          Provider default padding scheme for the specified {@link #getAlgorithmName() algorithm}
+     *                          should be used.
+     * @see #setPaddingScheme
+     */
+    public void setPaddingSchemeName(String paddingSchemeName) {
+        this.paddingSchemeName = paddingSchemeName;
+        //clear out the transformation string so the next invocation will rebuild it with the new padding scheme:
+        this.transformationString = null;
+    }
+
+    /**
+     * Sets the padding scheme to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string. A {@code null} value indicates that the JCA Provider
+     * default padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #setStreamingPaddingScheme streamingPaddingScheme} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * If the {@link PaddingScheme PaddingScheme} enum does represent your desired scheme, you can set the name explicitly
+     * via the {@link #setPaddingSchemeName paddingSchemeName} attribute directly.  However, because
+     * {@code PaddingScheme} represents all standard JDK scheme names already, ensure that your underlying JCA Provider
+     * supports the non-standard name first.
+     *
+     * @param paddingScheme the padding scheme to be used when constructing
+     *                      {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *                      default padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setPaddingScheme(PaddingScheme paddingScheme) {
+        setPaddingSchemeName(paddingScheme.getTransformationName());
+    }
+
+    /**
+     * Returns the block cipher's block size to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code 0} if the JCA Provider default block size
+     * for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingBlockSize() streamingBlockSize} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code 0} which retains the JCA Provider default.
+     *
+     * @return the block cipher block size to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code 0} if the JCA Provider default
+     *         block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * Sets the block cipher's block size to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string.  {@code 0} indicates that the JCA Provider default
+     * block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingBlockSize() streamingBlockSize} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code 0} which retains the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> block cipher block sizes are very algorithm-specific.  If you change this value, ensure that it
+     * will work with the specified {@link #getAlgorithmName() algorithm}.
+     *
+     * @param blockSize the block cipher block size to be used when constructing the
+     *                  {@link javax.crypto.Cipher Cipher} transformation string, or {@code 0} if the JCA Provider
+     *                  default block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setBlockSize(int blockSize) {
+        this.blockSize = Math.max(DEFAULT_BLOCK_SIZE, blockSize);
+        //clear out the transformation string so the next invocation will rebuild it with the new block size:
+        this.transformationString = null;
+    }
+
+    /**
+     * Same purpose as the {@link #getModeName modeName} attribute, but is used instead only for for streaming
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Note that unlike the {@link #getModeName modeName} attribute, the default value of this attribute is not
+     * {@code null} - it is {@link OperationMode#CBC CBC} for reasons described in the class-level JavaDoc in the
+     * {@code Streaming} section.
+     *
+     * @return the transformation string mode name to be used for streaming operations only.
+     */
+    public String getStreamingModeName() {
+        return streamingModeName;
+    }
+
+    private boolean isModeStreamingCompatible(String modeName) {
+        return modeName != null &&
+                !modeName.equalsIgnoreCase(OperationMode.ECB.name()) &&
+                !modeName.equalsIgnoreCase(OperationMode.NONE.name());
+    }
+
+    /**
+     * Sets the transformation string mode name to be used for streaming operations only.  The default value is
+     * {@link OperationMode#CBC CBC} for reasons described in the class-level JavaDoc in the {@code Streaming} section.
+     *
+     * @param streamingModeName transformation string mode name to be used for streaming operations only
+     */
+    public void setStreamingModeName(String streamingModeName) {
+        if (!isModeStreamingCompatible(streamingModeName)) {
+            String msg = "mode [" + streamingModeName + "] is not a valid operation mode for block cipher streaming.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.streamingModeName = streamingModeName;
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new mode:
+        this.streamingTransformationString = null;
+    }
+
+    /**
+     * Sets the transformation string mode to be used for streaming operations only.  The default value is
+     * {@link OperationMode#CBC CBC} for reasons described in the class-level JavaDoc in the {@code Streaming} section.
+     *
+     * @param mode the transformation string mode to be used for streaming operations only
+     */
+    public void setStreamingMode(OperationMode mode) {
+        setStreamingModeName(mode.name());
+    }
+
+    public String getStreamingPaddingSchemeName() {
+        return streamingPaddingSchemeName;
+    }
+
+    public void setStreamingPaddingSchemeName(String streamingPaddingSchemeName) {
+        this.streamingPaddingSchemeName = streamingPaddingSchemeName;
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new scheme:
+        this.streamingTransformationString = null;
+    }
+
+    public void setStreamingPaddingScheme(PaddingScheme scheme) {
+        setStreamingPaddingSchemeName(scheme.getTransformationName());
+    }
+
+    public int getStreamingBlockSize() {
+        return streamingBlockSize;
+    }
+
+    public void setStreamingBlockSize(int streamingBlockSize) {
+        this.streamingBlockSize = Math.max(DEFAULT_BLOCK_SIZE, streamingBlockSize);
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new block size:
+        this.streamingTransformationString = null;
+    }
+
+    /**
+     * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} call.  If
+     * {@code streaming} is {@code true}, a block-cipher transformation string compatible with streaming operations will
+     * be constructed and cached for re-use later (see the class-level JavaDoc for more on using block ciphers
+     * for streaming).  If {@code streaming} is {@code false} a normal block-cipher transformation string will
+     * be constructed and cached for later re-use.
+     *
+     * @param streaming if the transformation string is going to be used for a Cipher performing stream-based encryption or not.
+     * @return the transformation string
+     */
+    protected String getTransformationString(boolean streaming) {
+        if (streaming) {
+            if (this.streamingTransformationString == null) {
+                this.streamingTransformationString = buildStreamingTransformationString();
+            }
+            return this.streamingTransformationString;
+        } else {
+            if (this.transformationString == null) {
+                this.transformationString = buildTransformationString();
+            }
+            return this.transformationString;
+        }
+    }
+
+    private String buildTransformationString() {
+        return buildTransformationString(getModeName(), getPaddingSchemeName(), getBlockSize());
+    }
+
+    private String buildStreamingTransformationString() {
+        return buildTransformationString(getStreamingModeName(), getStreamingPaddingSchemeName(), 0);
+    }
+
+    private String buildTransformationString(String modeName, String paddingSchemeName, int blockSize) {
+        StringBuilder sb = new StringBuilder(getAlgorithmName());
+        if (StringUtils.hasText(modeName)) {
+            sb.append(TRANSFORMATION_STRING_DELIMITER).append(modeName);
+        }
+        if (blockSize > 0) {
+            sb.append(blockSize);
+        }
+        if (StringUtils.hasText(paddingSchemeName)) {
+            sb.append(TRANSFORMATION_STRING_DELIMITER).append(paddingSchemeName);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns {@code true} if the specified cipher operation mode name supports initialization vectors,
+     * {@code false} otherwise.
+     *
+     * @param modeName the raw text name of the mode of operation
+     * @return {@code true} if the specified cipher operation mode name supports initialization vectors,
+     *         {@code false} otherwise.
+     */
+    private boolean isModeInitializationVectorCompatible(String modeName) {
+        return modeName != null &&
+                !modeName.equalsIgnoreCase(OperationMode.ECB.name()) &&
+                !modeName.equalsIgnoreCase(OperationMode.NONE.name());
+    }
+
+    /**
+     * Overrides the parent implementation to ensure initialization vectors are always generated if streaming is
+     * enabled (block ciphers <em>must</em> use initialization vectors if they are to be used as a stream cipher).  If
+     * not being used as a stream cipher, then the value is computed based on whether or not the currently configured
+     * {@link #getModeName modeName} is compatible with initialization vectors as well as the result of the configured
+     * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} value.
+     *
+     * @param streaming whether or not streaming is being performed
+     * @return {@code true} if streaming or a value computed based on if the currently configured mode is compatible
+     *         with initialization vectors.
+     */
+    @Override
+    protected boolean isGenerateInitializationVectors(boolean streaming) {
+        return streaming || super.isGenerateInitializationVectors() && isModeInitializationVectorCompatible(getModeName());
+    }
+
+    @Override
+    protected byte[] generateInitializationVector(boolean streaming) {
+        if (streaming) {
+            String streamingModeName = getStreamingModeName();
+            if (!isModeInitializationVectorCompatible(streamingModeName)) {
+                String msg = "streamingMode attribute value [" + streamingModeName + "] does not support " +
+                        "Initialization Vectors.  Ensure the streamingMode value represents an operation mode " +
+                        "that is compatible with initialization vectors.";
+                throw new IllegalStateException(msg);
+            }
+        } else {
+            String modeName = getModeName();
+            if (!isModeInitializationVectorCompatible(modeName)) {
+                String msg = "mode attribute value [" + modeName + "] does not support " +
+                        "Initialization Vectors.  Ensure the mode value represents an operation mode " +
+                        "that is compatible with initialization vectors.";
+                throw new IllegalStateException(msg);
+            }
+        }
+        return super.generateInitializationVector(streaming);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java b/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java
new file mode 100644
index 0000000..bb21556
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java
@@ -0,0 +1,602 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.CipherInputStream;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Abstract {@code CipherService} implementation utilizing Java's JCA APIs.
+ * <h2>Auto-generated Initialization Vectors</h2>
+ * Shiro does something by default for all of its {@code CipherService} implementations that the JCA
+ * {@link javax.crypto.Cipher Cipher} does not do:  by default,
+ * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly
+ * generated and prepended to encrypted data before returning from the {@code encrypt} methods.  That is, the returned
+ * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual
+ * encrypted data byte array.  The {@code decrypt} methods in turn know to read this prepended initialization vector
+ * before decrypting the real data that follows.
+ * <p/>
+ * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted
+ * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>.
+ * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources
+ * that are the same or similar.
+ * <p/>
+ * You can turn off this behavior by setting the
+ * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it
+ * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing
+ * a critical security feature.
+ * <h3>Initialization Vector Size</h3>
+ * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to
+ * {@code 128} bits, a fairly common size.  Initialization vector sizes are very algorithm specific however, so subclass
+ * implementations will often override this value in their constructor if necessary.
+ * <p/>
+ * Also note that {@code initializationVectorSize} values are specified in the number of
+ * bits (not bytes!) to match common references in most cryptography documentation.  In practice though, initialization
+ * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple
+ * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the
+ * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this).
+ *
+ * @since 1.0
+ */
+public abstract class JcaCipherService implements CipherService {
+
+    /**
+     * Internal private log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class);
+
+    /**
+     * Default key size (in bits) for generated keys.
+     */
+    private static final int DEFAULT_KEY_SIZE = 128;
+
+    /**
+     * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations
+     */
+    private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
+
+    private static final int BITS_PER_BYTE = 8;
+
+    /**
+     * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance.
+     */
+    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
+
+    /**
+     * The name of the cipher algorithm to use for all encryption, decryption, and key operations
+     */
+    private String algorithmName;
+
+    /**
+     * The size in bits (not bytes) of generated cipher keys
+     */
+    private int keySize;
+
+    /**
+     * The size of the internal buffer (in bytes) used to transfer data from one stream to another during stream operations
+     */
+    private int streamingBufferSize;
+
+    private boolean generateInitializationVectors;
+    private int initializationVectorSize;
+
+
+    private SecureRandom secureRandom;
+
+    /**
+     * Creates a new {@code JcaCipherService} instance which will use the specified cipher {@code algorithmName}
+     * for all encryption, decryption, and key operations.  Also, the following defaults are set:
+     * <ul>
+     * <li>{@link #setKeySize keySize} = 128 bits</li>
+     * <li>{@link #setInitializationVectorSize(int) initializationVectorSize} = 128 bits</li>
+     * <li>{@link #setStreamingBufferSize(int) streamingBufferSize} = 512 bytes</li>
+     * </ul>
+     *
+     * @param algorithmName the name of the cipher algorithm to use for all encryption, decryption, and key operations
+     */
+    protected JcaCipherService(String algorithmName) {
+        if (!StringUtils.hasText(algorithmName)) {
+            throw new IllegalArgumentException("algorithmName argument cannot be null or empty.");
+        }
+        this.algorithmName = algorithmName;
+        this.keySize = DEFAULT_KEY_SIZE;
+        this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice)
+        this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
+        this.generateInitializationVectors = true;
+    }
+
+    /**
+     * Returns the cipher algorithm name that will be used for all encryption, decryption, and key operations (for
+     * example, 'AES', 'Blowfish', 'RSA', 'DSA', 'TripleDES', etc).
+     *
+     * @return the cipher algorithm name that will be used for all encryption, decryption, and key operations
+     */
+    public String getAlgorithmName() {
+        return algorithmName;
+    }
+
+    /**
+     * Returns the size in bits (not bytes) of generated cipher keys.
+     *
+     * @return the size in bits (not bytes) of generated cipher keys.
+     */
+    public int getKeySize() {
+        return keySize;
+    }
+
+    /**
+     * Sets the size in bits (not bytes) of generated cipher keys.
+     *
+     * @param keySize the size in bits (not bytes) of generated cipher keys.
+     */
+    public void setKeySize(int keySize) {
+        this.keySize = keySize;
+    }
+
+    public boolean isGenerateInitializationVectors() {
+        return generateInitializationVectors;
+    }
+
+    public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
+        this.generateInitializationVectors = generateInitializationVectors;
+    }
+
+    /**
+     * Returns the algorithm-specific size in bits of generated initialization vectors.
+     *
+     * @return the algorithm-specific size in bits of generated initialization vectors.
+     */
+    public int getInitializationVectorSize() {
+        return initializationVectorSize;
+    }
+
+    /**
+     * Sets the algorithm-specific initialization vector size in bits (not bytes!) to be used when generating
+     * initialization vectors.  The  value must be a multiple of {@code 8} to ensure that the IV can be represented
+     * as a byte array.
+     *
+     * @param initializationVectorSize the size in bits (not bytes) of generated initialization vectors.
+     * @throws IllegalArgumentException if the size is not a multiple of {@code 8}.
+     */
+    public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
+        if (initializationVectorSize % BITS_PER_BYTE != 0) {
+            String msg = "Initialization vector sizes are specified in bits, but must be a multiple of 8 so they " +
+                    "can be easily represented as a byte array.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.initializationVectorSize = initializationVectorSize;
+    }
+
+    protected boolean isGenerateInitializationVectors(boolean streaming) {
+        return isGenerateInitializationVectors();
+    }
+
+    /**
+     * Returns the size in bytes of the internal buffer used to transfer data from one stream to another during stream
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Default size is {@code 512} bytes.
+     *
+     * @return the size of the internal buffer used to transfer data from one stream to another during stream
+     *         operations
+     */
+    public int getStreamingBufferSize() {
+        return streamingBufferSize;
+    }
+
+    /**
+     * Sets the size in bytes of the internal buffer used to transfer data from one stream to another during stream
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Default size is {@code 512} bytes.
+     *
+     * @param streamingBufferSize the size of the internal buffer used to transfer data from one stream to another
+     *                            during stream operations
+     */
+    public void setStreamingBufferSize(int streamingBufferSize) {
+        this.streamingBufferSize = streamingBufferSize;
+    }
+
+    /**
+     * Returns a source of randomness for encryption operations.  If one is not configured, and the underlying
+     * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     *
+     * @return a source of randomness for encryption operations.  If one is not configured, and the underlying
+     *         algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     */
+    public SecureRandom getSecureRandom() {
+        return secureRandom;
+    }
+
+    /**
+     * Sets a source of randomness for encryption operations.  If one is not configured, and the underlying
+     * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     *
+     * @param secureRandom a source of randomness for encryption operations.  If one is not configured, and the
+     *                     underlying algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     */
+    public void setSecureRandom(SecureRandom secureRandom) {
+        this.secureRandom = secureRandom;
+    }
+
+    protected static SecureRandom getDefaultSecureRandom() {
+        try {
+            return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
+        } catch (java.security.NoSuchAlgorithmException e) {
+            log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
+                    "platform's default SecureRandom algorithm.", e);
+            return new java.security.SecureRandom();
+        }
+    }
+
+    protected SecureRandom ensureSecureRandom() {
+        SecureRandom random = getSecureRandom();
+        if (random == null) {
+            random = getDefaultSecureRandom();
+        }
+        return random;
+    }
+
+    /**
+     * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
+     * creating a new {@code Cipher} instance.  This default implementation always returns
+     * {@link #getAlgorithmName() getAlgorithmName()}.  Block cipher implementations will want to override this method
+     * to support appending cipher operation modes and padding schemes.
+     *
+     * @param streaming if the transformation string is going to be used for a Cipher for stream-based encryption or not.
+     * @return the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
+     *         creating a new {@code Cipher} instance.
+     */
+    protected String getTransformationString(boolean streaming) {
+        return getAlgorithmName();
+    }
+
+    protected byte[] generateInitializationVector(boolean streaming) {
+        int size = getInitializationVectorSize();
+        if (size <= 0) {
+            String msg = "initializationVectorSize property must be greater than zero.  This number is " +
+                    "typically set in the " + CipherService.class.getSimpleName() + " subclass constructor.  " +
+                    "Also check your configuration to ensure that if you are setting a value, it is positive.";
+            throw new IllegalStateException(msg);
+        }
+        if (size % BITS_PER_BYTE != 0) {
+            String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
+            throw new IllegalStateException(msg);
+        }
+        int sizeInBytes = size / BITS_PER_BYTE;
+        byte[] ivBytes = new byte[sizeInBytes];
+        SecureRandom random = ensureSecureRandom();
+        random.nextBytes(ivBytes);
+        return ivBytes;
+    }
+
+    public ByteSource encrypt(byte[] plaintext, byte[] key) {
+        byte[] ivBytes = null;
+        boolean generate = isGenerateInitializationVectors(false);
+        if (generate) {
+            ivBytes = generateInitializationVector(false);
+            if (ivBytes == null || ivBytes.length == 0) {
+                throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
+                        "cannot be null or empty.");
+            }
+        }
+        return encrypt(plaintext, key, ivBytes, generate);
+    }
+
+    private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
+
+        final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
+
+        byte[] output;
+
+        if (prependIv && iv != null && iv.length > 0) {
+
+            byte[] encrypted = crypt(plaintext, key, iv, MODE);
+
+            output = new byte[iv.length + encrypted.length];
+
+            //now copy the iv bytes + encrypted bytes into one output array:
+
+            // iv bytes:
+            System.arraycopy(iv, 0, output, 0, iv.length);
+
+            // + encrypted bytes:
+            System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
+        } else {
+            output = crypt(plaintext, key, iv, MODE);
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
+                    "byte array is size " + (output != null ? output.length : 0));
+        }
+
+        return ByteSource.Util.bytes(output);
+    }
+
+    public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
+
+        byte[] encrypted = ciphertext;
+
+        //No IV, check if we need to read the IV from the stream:
+        byte[] iv = null;
+
+        if (isGenerateInitializationVectors(false)) {
+            try {
+                //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
+                //is:
+                // - the first N bytes is the initialization vector, where N equals the value of the
+                // 'initializationVectorSize' attribute.
+                // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.
+
+                //So we need to chunk the method argument into its constituent parts to find the IV and then use
+                //the IV to decrypt the real ciphertext:
+
+                int ivSize = getInitializationVectorSize();
+                int ivByteSize = ivSize / BITS_PER_BYTE;
+
+                //now we know how large the iv is, so extract the iv bytes:
+                iv = new byte[ivByteSize];
+                System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
+
+                //remaining data is the actual encrypted ciphertext.  Isolate it:
+                int encryptedSize = ciphertext.length - ivByteSize;
+                encrypted = new byte[encryptedSize];
+                System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
+            } catch (Exception e) {
+                String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
+                throw new CryptoException(msg, e);
+            }
+        }
+
+        return decrypt(encrypted, key, iv);
+    }
+
+    private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
+        if (log.isTraceEnabled()) {
+            log.trace("Attempting to decrypt incoming byte array of length " +
+                    (ciphertext != null ? ciphertext.length : 0));
+        }
+        byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
+        return decrypted == null ? null : ByteSource.Util.bytes(decrypted);
+    }
+
+    /**
+     * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations.  The
+     * Cipher's {@code transformationString} for the {@code Cipher}.{@link javax.crypto.Cipher#getInstance getInstance}
+     * call is obtaind via the {@link #getTransformationString(boolean) getTransformationString} method.
+     *
+     * @param streaming {@code true} if the cipher instance will be used as a stream cipher, {@code false} if it will be
+     *                  used as a block cipher.
+     * @return a new JDK {@code Cipher} instance.
+     * @throws CryptoException if a new Cipher instance cannot be constructed based on the
+     *                         {@link #getTransformationString(boolean) getTransformationString} value.
+     */
+    private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException {
+        String transformationString = getTransformationString(streaming);
+        try {
+            return javax.crypto.Cipher.getInstance(transformationString);
+        } catch (Exception e) {
+            String msg = "Unable to acquire a Java JCA Cipher instance using " +
+                    javax.crypto.Cipher.class.getName() + ".getInstance( \"" + transformationString + "\" ). " +
+                    getAlgorithmName() + " under this configuration is required for the " +
+                    getClass().getName() + " instance to function.";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+    /**
+     * Functions as follows:
+     * <ol>
+     * <li>Creates a {@link #newCipherInstance(boolean) new JDK cipher instance}</li>
+     * <li>Converts the specified key bytes into an {@link #getAlgorithmName() algorithm}-compatible JDK
+     * {@link Key key} instance</li>
+     * <li>{@link #init(javax.crypto.Cipher, int, java.security.Key, AlgorithmParameterSpec, SecureRandom) Initializes}
+     * the JDK cipher instance with the JDK key</li>
+     * <li>Calls the {@link #crypt(javax.crypto.Cipher, byte[]) crypt(cipher,bytes)} method to either encrypt or
+     * decrypt the data based on the specified Cipher behavior mode
+     * ({@link javax.crypto.Cipher#ENCRYPT_MODE Cipher.ENCRYPT_MODE} or
+     * {@link javax.crypto.Cipher#DECRYPT_MODE Cipher.DECRYPT_MODE})</li>
+     * </ol>
+     *
+     * @param bytes the bytes to crypt
+     * @param key   the key to use to perform the encryption or decryption.
+     * @param iv    the initialization vector to use for the crypt operation (optional, may be {@code null}).
+     * @param mode  the JDK Cipher behavior mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE).
+     * @return the resulting crypted byte array
+     * @throws IllegalArgumentException if {@code bytes} are null or empty.
+     * @throws CryptoException          if Cipher initialization or the crypt operation fails
+     */
+    private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
+        if (key == null || key.length == 0) {
+            throw new IllegalArgumentException("key argument cannot be null or empty.");
+        }
+        javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
+        return crypt(cipher, bytes);
+    }
+
+    /**
+     * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that
+     * might arise in an {@link CryptoException}
+     *
+     * @param cipher the JDK Cipher to finalize (perform the actual cryption)
+     * @param bytes  the bytes to crypt
+     * @return the resulting crypted byte array.
+     * @throws CryptoException if there is an illegal block size or bad padding
+     */
+    private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException {
+        try {
+            return cipher.doFinal(bytes);
+        } catch (Exception e) {
+            String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+    /**
+     * Initializes the JDK Cipher with the specified mode and key.  This is primarily a utility method to catch any
+     * potential {@link java.security.InvalidKeyException InvalidKeyException} that might arise.
+     *
+     * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}.
+     * @param mode   the Cipher mode
+     * @param key    the Cipher's Key
+     * @param spec   the JDK AlgorithmParameterSpec for cipher initialization (optional, may be null).
+     * @param random the SecureRandom to use for cipher initialization (optional, may be null).
+     * @throws CryptoException if the key is invalid
+     */
+    private void init(javax.crypto.Cipher cipher, int mode, java.security.Key key,
+                      AlgorithmParameterSpec spec, SecureRandom random) throws CryptoException {
+        try {
+            if (random != null) {
+                if (spec != null) {
+                    cipher.init(mode, key, spec, random);
+                } else {
+                    cipher.init(mode, key, random);
+                }
+            } else {
+                if (spec != null) {
+                    cipher.init(mode, key, spec);
+                } else {
+                    cipher.init(mode, key);
+                }
+            }
+        } catch (Exception e) {
+            String msg = "Unable to init cipher instance.";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+
+    public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
+        byte[] iv = null;
+        boolean generate = isGenerateInitializationVectors(true);
+        if (generate) {
+            iv = generateInitializationVector(true);
+            if (iv == null || iv.length == 0) {
+                throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
+                        "cannot be null or empty.");
+            }
+        }
+        encrypt(in, out, key, iv, generate);
+    }
+
+    private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
+        if (prependIv && iv != null && iv.length > 0) {
+            try {
+                //first write the IV:
+                out.write(iv);
+            } catch (IOException e) {
+                throw new CryptoException(e);
+            }
+        }
+
+        crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
+    }
+
+    public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
+        decrypt(in, out, key, isGenerateInitializationVectors(true));
+    }
+
+    private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
+
+        byte[] iv = null;
+        //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream:
+        if (ivPrepended) {
+            //we are generating IVs, so we need to read the previously-generated IV from the stream before
+            //we decrypt the rest of the stream (we need the IV to decrypt):
+            int ivSize = getInitializationVectorSize();
+            int ivByteSize = ivSize / BITS_PER_BYTE;
+            iv = new byte[ivByteSize];
+            int read;
+
+            try {
+                read = in.read(iv);
+            } catch (IOException e) {
+                String msg = "Unable to correctly read the Initialization Vector from the input stream.";
+                throw new CryptoException(msg, e);
+            }
+
+            if (read != ivByteSize) {
+                throw new CryptoException("Unable to read initialization vector bytes from the InputStream.  " +
+                        "This is required when initialization vectors are autogenerated during an encryption " +
+                        "operation.");
+            }
+        }
+
+        decrypt(in, out, key, iv);
+    }
+
+    private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
+        crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
+    }
+
+    private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
+        if (in == null) {
+            throw new NullPointerException("InputStream argument cannot be null.");
+        }
+        if (out == null) {
+            throw new NullPointerException("OutputStream argument cannot be null.");
+        }
+
+        javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
+
+        CipherInputStream cis = new CipherInputStream(in, cipher);
+
+        int bufSize = getStreamingBufferSize();
+        byte[] buffer = new byte[bufSize];
+
+        int bytesRead;
+        try {
+            while ((bytesRead = cis.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            throw new CryptoException(e);
+        }
+    }
+
+    private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
+            throws CryptoException {
+
+        javax.crypto.Cipher cipher = newCipherInstance(streaming);
+        java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
+        IvParameterSpec ivSpec = null;
+        if (iv != null && iv.length > 0) {
+            ivSpec = new IvParameterSpec(iv);
+        }
+
+        init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
+
+        return cipher;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/OperationMode.java b/core/src/main/java/org/apache/shiro/crypto/OperationMode.java
new file mode 100644
index 0000000..3ac56ab
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/OperationMode.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * A cipher <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">mode of operation</a>
+ * directs a cipher algorithm how to convert data during the encryption or decryption process.  This enum represents
+ * all JDK-standard Cipher operation mode names as defined in
+ * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html">JDK Security Standard
+ * Names</a>, as well as a few more that are well-known and supported by other JCA Providers.
+ * <p/>
+ * This {@code enum} exists to provide Shiro end-users type-safety when declaring an operation mode.  This helps reduce
+ * error by providing a compile-time mechanism to specify a mode and guarantees a valid name that will be
+ * recognized by an underlying JCA Provider.
+ * <h2>Standard or Non-Standard?</h2>
+ * All modes listed specify whether they are a JDK standard mode or a non-standard mode.  Standard modes are included
+ * in all JDK distributions.  Non-standard modes can
+ * sometimes result in better performance or more secure output, but may not be available on the target JDK
+ * platform and rely on an external JCA Provider to be installed.  Some providers
+ * (like <a href="http://www.bouncycastle.org">Bouncy Castle</a>) may support these modes however.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">Block Cipher Modes of Operation<a/>
+ * @since 1.0
+ */
+public enum OperationMode {
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29">
+     * Cipher-block Chaining</a> mode, defined in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS
+     * PUB 81</a>.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    CBC,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/CCM_mode">Counter with CBC-MAC</a> mode<b>*</b> - for block ciphers with
+     * 128 bit block-size only. See <a href="http://www.ietf.org/rfc/rfc3610.txt">RFC 3610</a> for AES Ciphers.
+     * This mode has essentially been replaced by the more-capable {@link #EAX EAX} mode.
+     * <p/>
+     * <b>*THIS IS A NON-STANDARD MODE</b>. It is not guaranteed to be supported across JDK installations.  You must
+     * ensure you have a JCA Provider that can support this cipher operation mode.
+     * <a href="http://www.bouncycastle.org">Bouncy Castle</a> <em>may</em> be one such provider.
+     */
+    CCM,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29">Cipher
+     * Feedback<a/> mode, defined in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS PUB 81</a>.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    CFB,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29">Counter Mode</a>, aka
+     * Integer Counter Mode (ICM) and Segmented Integer Counter (SIC).  Counter is a simplification of {@link #OFB OFB}
+     * and updates the input block as a counter.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    CTR,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/EAX_mode">EAX Mode</a><b>*</b>.  This is a patent-free but less-effecient
+     * alternative to {@link #OCB OCB} and has capabilities beyond what {@link #CCM CCM} can provide.
+     * <p/>
+     * <b>*THIS IS A NON-STANDARD MODE</b>. It is not guaranteed to be supported across JDK installations.  You must
+     * ensure you have a JCA Provider that can support this cipher operation mode.
+     * <a href="http://www.bouncycastle.org">Bouncy Castle</a> <em>may</em> be one such provider.
+     */
+    EAX,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29">Electronic
+     * Codebook</a> mode, defined in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS PUB 81</a>.
+     * ECB is the only mode that does <em>not</em> require an Initialization Vector, but because of this, can be seen
+     * as less secure than operation modes that require an IV.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    ECB,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/GCM_mode">Galois/Counter</a> mode<b>*</b> - for block ciphers with 128
+     * bit block-size only.
+     * <p/>
+     * <b>*THIS IS A NON-STANDARD MODE</b>. It is not guaranteed to be supported across JDK installations.  You must
+     * ensure you have a JCA Provider that can support this cipher operation mode.
+     * <a href="http://www.bouncycastle.org">Bouncy Castle</a> <em>may</em> be one such provider.
+     */
+    GCM,
+
+    /**
+     * No mode.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    NONE,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/OCB_mode">Offset Codebook</a> mode<b>*</b>.  Parallel mode that provides
+     * both message privacy and authenticity in a single pass.  This is a very efficient mode, but is patent-encumbered.
+     * A less-efficient (two pass) alternative is available by using {@link #EAX EAX} mode.
+     * <p/>
+     * <b>*THIS IS A NON-STANDARD MODE</b>. It is not guaranteed to be supported across JDK installations.  You must
+     * ensure you have a JCA Provider that can support this cipher operation mode.
+     * <a href="http://www.bouncycastle.org">Bouncy Castle</a> <em>may</em> be one such provider.
+     */
+    OCB,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29">Output
+     * Feedback</a> mode, defined in <a href="http://csrc.nist.gov/publications/fips/index.html">FIPS PUB 81</a>.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    OFB,
+
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Propagating_cipher-block_chaining_.28PCBC.29">
+     * Propagating Cipher Block Chaining</a> mode, defined in <a href="http://web.mit.edu/kerberos/">Kerberos version 4<a/>.
+     * <p/>
+     * This is a standard JDK operation mode and should be supported by all JDK environments.
+     */
+    PCBC
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/PaddingScheme.java b/core/src/main/java/org/apache/shiro/crypto/PaddingScheme.java
new file mode 100644
index 0000000..0cc1e0e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/PaddingScheme.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * A {@code CipherPaddingScheme} represents well-known
+ * <a href="http://en.wikipedia.org/wiki/Padding_(cryptography)">padding schemes</a> supported by JPA providers in a
+ * type-safe manner.
+ * <p/>
+ * When encrypted data is transferred, it is usually desirable to ensure that all 'chunks' transferred are a fixed-length:
+ * different length blocks might give cryptanalysts clues about what the data might be, among other reasons.  Of course
+ * not all data will convert to neat fixed-length blocks, so padding schemes are used to 'fill in' (pad) any remaining
+ * space with unintelligible data.
+ * <p/>
+ * Padding schemes can be used in both asymmetric key ciphers as well as symmetric key ciphers (e.g. block ciphers).
+ * Block-ciphers especially regularly use padding schemes as they are based on the notion of fixed-length block sizes.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Padding_(cryptography)">Wikipedia: Cryptographic Padding</a>
+ * @since 1.0
+ */
+public enum PaddingScheme {
+
+    /**
+     * No padding.  Useful when the block size is 8 bits for block cipher streaming operations. (Because
+     * a byte is the most primitive block size, there is nothing to pad).
+     */
+    NONE("NoPadding"),
+
+    /**
+     * Padding scheme as defined in the W3C's "XML Encryption Syntax and Processing" document,
+     * <a href="http://www.w3.org/TR/xmlenc-core/#sec-Alg-Block">Section 5.2 - Block Encryption Algorithms</a>.
+     */
+    ISO10126("ISO10126Padding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding defined in RSA's <a href="http://en.wikipedia.org/wiki/PKCS1">PKSC#1
+     * standard</a> (aka <a href="http://tools.ietf.org/html/rfc3447">RFC 3447</a>).
+     * <p/>
+     * <b>NOTE:</b> using this padding requires initializing {@link javax.crypto.Cipher Cipher} instances with a
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object which provides the 1) message digest and
+     * 2) mask generation function to use for the scheme.
+     * <h3>Convenient Alternatives</h3>
+     * While using this scheme enables you full customization of the message digest + mask generation function
+     * combination, it does require the extra burden of providing your own {@code OAEPParameterSpec} object.  This is
+     * often unnecessary, because most combinations are fairly standard.  These common combinations are pre-defined
+     * in this enum in the {@code OAEP}* variants.
+     * <p/>
+     * If you find that these common combinations still do not meet your needs, then you will need to
+     * specify your own message digest and mask generation function, either as an {@code OAEPParameterSpec} object
+     * during Cipher initialization or, maybe more easily, in the scheme name directly.  If you want to use scheme name
+     * approach, the name format is specified in the
+     * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html">Standard Names</a>
+     * document in the <code>Cipher Algorithm Padding</code> section.
+     *
+     * @see #OAEPWithMd5AndMgf1
+     * @see #OAEPWithSha1AndMgf1
+     * @see #OAEPWithSha256AndMgf1
+     * @see #OAEPWithSha384AndMgf1
+     * @see #OAEPWithSha512AndMgf1
+     */
+    OAEP("OAEPPadding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding with {@code MD5} message digest and {@code MGF1} mask generation function.
+     * <p/>
+     * This is a convenient pre-defined OAEP padding scheme that embeds the message digest and mask generation function.
+     * When using this padding scheme, there is no need to init the {@code Cipher} instance with an
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object, as it is already 'built in' to the scheme
+     * name (unlike the {@link #OAEP OAEP} scheme, which requires a bit more work).
+     */
+    OAEPWithMd5AndMgf1("OAEPWithMD5AndMGF1Padding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding with {@code SHA-1} message digest and {@code MGF1} mask generation function.
+     * <p/>
+     * This is a convenient pre-defined OAEP padding scheme that embeds the message digest and mask generation function.
+     * When using this padding scheme, there is no need to init the {@code Cipher} instance with an
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object, as it is already 'built in' to the scheme
+     * name (unlike the {@link #OAEP OAEP} scheme, which requires a bit more work).
+     */
+    OAEPWithSha1AndMgf1("OAEPWithSHA-1AndMGF1Padding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding with {@code SHA-256} message digest and {@code MGF1} mask generation function.
+     * <p/>
+     * This is a convenient pre-defined OAEP padding scheme that embeds the message digest and mask generation function.
+     * When using this padding scheme, there is no need to init the {@code Cipher} instance with an
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object, as it is already 'built in' to the scheme
+     * name (unlike the {@link #OAEP OAEP} scheme, which requires a bit more work).
+     */
+    OAEPWithSha256AndMgf1("OAEPWithSHA-256AndMGF1Padding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding with {@code SHA-384} message digest and {@code MGF1} mask generation function.
+     * <p/>
+     * This is a convenient pre-defined OAEP padding scheme that embeds the message digest and mask generation function.
+     * When using this padding scheme, there is no need to init the {@code Cipher} instance with an
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object, as it is already 'built in' to the scheme
+     * name (unlike the {@link #OAEP OAEP} scheme, which requires a bit more work).
+     */
+    OAEPWithSha384AndMgf1("OAEPWithSHA-384AndMGF1Padding"),
+
+    /**
+     * Optimal Asymmetric Encryption Padding with {@code SHA-512} message digest and {@code MGF1} mask generation function.
+     * <p/>
+     * This is a convenient pre-defined OAEP padding scheme that embeds the message digest and mask generation function.
+     * When using this padding scheme, there is no need to init the {@code Cipher} instance with an
+     * {@link javax.crypto.spec.OAEPParameterSpec OAEPParameterSpec} object, as it is already 'built in' to the scheme
+     * name (unlike the {@link #OAEP OAEP} scheme, which requires a bit more work).
+     */
+    OAEPWithSha512AndMgf1("OAEPWithSHA-512AndMGF1Padding"),
+
+    /**
+     * Padding scheme used with the {@code RSA} algorithm defined in RSA's
+     * <a href="http://en.wikipedia.org/wiki/PKCS1">PKSC#1 standard</a> (aka
+     * <a href="http://tools.ietf.org/html/rfc3447">RFC 3447</a>).
+     */
+    PKCS1("PKCS1Padding"),
+
+    /**
+     * Padding scheme defined in RSA's <a href="http://www.rsa.com/rsalabs/node.asp?id=2127">Password-Based
+     * Cryptography Standard</a>.
+     */
+    PKCS5("PKCS5Padding"),
+
+    /**
+     * Padding scheme defined in the <a href="http://www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt">SSL
+     * 3.0 specification</a>, section <code>5.2.3.2 (CBC block cipher)</code>.
+     */
+    SSL3("SSL3Padding");
+
+    private final String transformationName;
+
+    private PaddingScheme(String transformationName) {
+        this.transformationName = transformationName;
+    }
+
+    /**
+     * Returns the actual string name to use when building the {@link javax.crypto.Cipher Cipher}
+     * {@code transformation string}.
+     *
+     * @return the actual string name to use when building the {@link javax.crypto.Cipher Cipher}
+     *         {@code transformation string}.
+     * @see javax.crypto.Cipher#getInstance(String)
+     */
+    public String getTransformationName() {
+        return this.transformationName;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java b/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java
new file mode 100644
index 0000000..4358537
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A component that can generate random number/byte values as needed.  Useful in cryptography or security scenarios
+ * where random byte arrays are needed, such as for password salts, nonces, initialization vectors and other seeds.
+ * <p/>
+ * This is essentially the same as a {@link java.security.SecureRandom SecureRandom}, and indeed implementations
+ * of this interface will probably all use {@link java.security.SecureRandom SecureRandom} instances, but this
+ * interface provides a few additional benefits to end-users:
+ * <ul>
+ * <li>It is an interface rather than the JDK's {@code SecureRandom} concrete implementation.  Implementation details
+ * can be customized as necessary based on the application's needs</li>
+ * <li>Default per-instance behavior can be customized on implementations, typically via JavaBeans mutators.</li>
+ * <li>Perhaps most important for Shiro end-users, tt can more easily be used as a source of cryptographic seed data,
+ * and the data returned is already in a more convenient {@link ByteSource ByteSource} format in case that data needs
+ * to be {@link org.apache.shiro.util.ByteSource#toHex() hex} or
+ * {@link org.apache.shiro.util.ByteSource#toBase64() base64}-encoded.</li>
+ * </ul>
+ * For example, consider the following example generating password salts for new user accounts:
+ * <pre>
+ * RandomNumberGenerator saltGenerator = new {@link org.apache.shiro.crypto.SecureRandomNumberGenerator SecureRandomNumberGenerator}();
+ * User user = new User();
+ * user.setPasswordSalt(saltGenerator.nextBytes().toBase64());
+ * userDAO.save(user);
+ * </pre>
+ *
+ * @since 1.1
+ */
+public interface RandomNumberGenerator {
+
+    /**
+     * Generates a byte array of fixed length filled with random data, often useful for generating salts,
+     * initialization vectors or other seed data.  The length is specified as a configuration
+     * value on the underlying implementation.
+     * <p/>
+     * If you'd like per-invocation control the number of bytes generated, use the
+     * {@link #nextBytes(int) nextBytes(int)} method instead.
+     *
+     * @return a byte array of fixed length filled with random data.
+     * @see #nextBytes(int)
+     */
+    ByteSource nextBytes();
+
+    /**
+     * Generates a byte array of the specified length filled with random data.
+     *
+     * @param numBytes the number of bytes to be populated with random data.
+     * @return a byte array of the specified length filled with random data.
+     * @see #nextBytes()
+     */
+    ByteSource nextBytes(int numBytes);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java b/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java
new file mode 100644
index 0000000..5c132d0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+
+import java.security.SecureRandom;
+
+/**
+ * Default implementation of the {@link RandomNumberGenerator RandomNumberGenerator} interface, backed by a
+ * {@link SecureRandom SecureRandom} instance.
+ * <p/>
+ * This class is a little easier to use than using the JDK's {@code SecureRandom} class directly.  It also
+ * allows for JavaBeans-style of customization, convenient for Shiro's INI configuration or other IoC configuration
+ * mechanism.
+ *
+ * @since 1.1
+ */
+public class SecureRandomNumberGenerator implements RandomNumberGenerator {
+
+    protected static final int DEFAULT_NEXT_BYTES_SIZE = 16; //16 bytes == 128 bits (a common number in crypto)
+
+    private int defaultNextBytesSize;
+    private SecureRandom secureRandom;
+
+    /**
+     * Creates a new instance with a default backing {@link SecureRandom SecureRandom} and a
+     * {@link #getDefaultNextBytesSize() defaultNextBytesSize} of {@code 16}, which equals 128 bits, a size commonly
+     * used in cryptographic algorithms.
+     */
+    public SecureRandomNumberGenerator() {
+        this.defaultNextBytesSize = DEFAULT_NEXT_BYTES_SIZE;
+        this.secureRandom = new SecureRandom();
+    }
+
+    /**
+     * Seeds the backing {@link SecureRandom SecureRandom} instance with additional seed data.
+     *
+     * @param bytes the seed bytes
+     * @see SecureRandom#setSeed(byte[])
+     */
+    public void setSeed(byte[] bytes) {
+        this.secureRandom.setSeed(bytes);
+    }
+
+    /**
+     * Returns the {@link SecureRandom SecureRandom} backing this instance.
+     *
+     * @return the {@link SecureRandom SecureRandom} backing this instance.
+     */
+    public SecureRandom getSecureRandom() {
+        return secureRandom;
+    }
+
+    /**
+     * Sets the {@link SecureRandom SecureRandom} to back this instance.
+     *
+     * @param random the {@link SecureRandom SecureRandom} to back this instance.
+     * @throws NullPointerException if the method argument is null
+     */
+    public void setSecureRandom(SecureRandom random) throws NullPointerException {
+        if (random == null) {
+            throw new NullPointerException("SecureRandom argument cannot be null.");
+        }
+        this.secureRandom = random;
+    }
+
+    /**
+     * Returns the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.  Defaults to
+     * {@code 16}, which equals 128 bits, a size commonly used in cryptographic algorithms.
+     *
+     * @return the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
+     */
+    public int getDefaultNextBytesSize() {
+        return defaultNextBytesSize;
+    }
+
+    /**
+     * Sets the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}. Defaults to
+     * {@code 16}, which equals 128 bits, a size commonly used in cryptographic algorithms.
+     *
+     * @param defaultNextBytesSize the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
+     * @throws IllegalArgumentException if the argument is 0 or negative
+     */
+    public void setDefaultNextBytesSize(int defaultNextBytesSize) throws IllegalArgumentException {
+        if ( defaultNextBytesSize <= 0) {
+            throw new IllegalArgumentException("size value must be a positive integer (1 or larger)");
+        }
+        this.defaultNextBytesSize = defaultNextBytesSize;
+    }
+
+    public ByteSource nextBytes() {
+        return nextBytes(getDefaultNextBytesSize());
+    }
+
+    public ByteSource nextBytes(int numBytes) {
+        if (numBytes <= 0) {
+            throw new IllegalArgumentException("numBytes argument must be a positive integer (1 or larger)");
+        }
+        byte[] bytes = new byte[numBytes];
+        this.secureRandom.nextBytes(bytes);
+        return ByteSource.Util.bytes(bytes);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/UnknownAlgorithmException.java b/core/src/main/java/org/apache/shiro/crypto/UnknownAlgorithmException.java
new file mode 100644
index 0000000..089bfda
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/UnknownAlgorithmException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * Exception thrown when attempting to lookup or use a cryptographic algorithm that does not exist in the current
+ * JVM environment.
+ *
+ * @since 1.2
+ */
+public class UnknownAlgorithmException extends CryptoException {
+
+    public UnknownAlgorithmException(String message) {
+        super(message);
+    }
+
+    public UnknownAlgorithmException(Throwable cause) {
+        super(cause);
+    }
+
+    public UnknownAlgorithmException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java b/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java
new file mode 100644
index 0000000..5ec16da
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java
@@ -0,0 +1,361 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.CodecException;
+import org.apache.shiro.codec.CodecSupport;
+import org.apache.shiro.codec.Hex;
+import org.apache.shiro.crypto.UnknownAlgorithmException;
+
+import java.io.Serializable;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * Provides a base for all Shiro Hash algorithms with support for salts and multiple hash iterations.
+ * <p/>
+ * Read
+ * <a href="http://www.owasp.org/index.php/Hashing_Java" target="blank">http://www.owasp.org/index.php/Hashing_Java</a>
+ * for a good article on the benefits of hashing, including what a 'salt' is as well as why it and multiple hash
+ * iterations can be useful.
+ * <p/>
+ * This class and its subclasses support hashing with additional capabilities of salting and multiple iterations via
+ * overloaded constructors.
+ *
+ * @since 0.9
+ * @deprecated in Shiro 1.1 in favor of using the concrete {@link SimpleHash} implementation directly.
+ */
+ at Deprecated
+public abstract class AbstractHash extends CodecSupport implements Hash, Serializable {
+
+    /**
+     * The hashed data
+     */
+    private byte[] bytes = null;
+
+    /**
+     * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
+     */
+    private transient String hexEncoded = null;
+    /**
+     * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
+     */
+    private transient String base64Encoded = null;
+
+    /**
+     * Creates an new instance without any of its properties set (no hashing is performed).
+     * <p/>
+     * Because all constructors in this class (except this one) hash the {@code source} constructor argument, this
+     * default, no-arg constructor is useful in scenarios when you have a byte array that you know is already hashed and
+     * just want to set the bytes in their raw form directly on an instance.  After instantiating the instance with
+     * this default, no-arg constructor, you can then immediately call {@link #setBytes setBytes} to have a
+     * fully-initialized instance.
+     */
+    public AbstractHash() {
+    }
+
+    /**
+     * Creates a hash of the specified {@code source} with no {@code salt} using a single hash iteration.
+     * <p/>
+     * It is a convenience constructor that merely executes <code>this( source, null, 1);</code>.
+     * <p/>
+     * Please see the
+     * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
+     * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
+     * types.
+     *
+     * @param source the object to be hashed.
+     * @throws CodecException if the specified {@code source} cannot be converted into a byte array (byte[]).
+     */
+    public AbstractHash(Object source) throws CodecException {
+        this(source, null, 1);
+    }
+
+    /**
+     * Creates a hash of the specified {@code source} using the given {@code salt} using a single hash iteration.
+     * <p/>
+     * It is a convenience constructor that merely executes <code>this( source, salt, 1);</code>.
+     * <p/>
+     * Please see the
+     * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
+     * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
+     * types.
+     *
+     * @param source the source object to be hashed.
+     * @param salt   the salt to use for the hash
+     * @throws CodecException if either constructor argument cannot be converted into a byte array.
+     */
+    public AbstractHash(Object source, Object salt) throws CodecException {
+        this(source, salt, 1);
+    }
+
+    /**
+     * Creates a hash of the specified {@code source} using the given {@code salt} a total of
+     * {@code hashIterations} times.
+     * <p/>
+     * By default, this class only supports Object method arguments of
+     * type {@code byte[]}, {@code char[]}, {@link String}, {@link java.io.File File}, or
+     * {@link java.io.InputStream InputStream}.  If either argument is anything other than these
+     * types a {@link org.apache.shiro.codec.CodecException CodecException} will be thrown.
+     * <p/>
+     * If you want to be able to hash other object types, or use other salt types, you need to override the
+     * {@link #toBytes(Object) toBytes(Object)} method to support those specific types.  Your other option is to
+     * convert your arguments to one of the default three supported types first before passing them in to this
+     * constructor}.
+     *
+     * @param source         the source object to be hashed.
+     * @param salt           the salt to use for the hash
+     * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
+     * @throws CodecException if either Object constructor argument cannot be converted into a byte array.
+     */
+    public AbstractHash(Object source, Object salt, int hashIterations) throws CodecException {
+        byte[] sourceBytes = toBytes(source);
+        byte[] saltBytes = null;
+        if (salt != null) {
+            saltBytes = toBytes(salt);
+        }
+        byte[] hashedBytes = hash(sourceBytes, saltBytes, hashIterations);
+        setBytes(hashedBytes);
+    }
+
+    /**
+     * Implemented by subclasses, this specifies the {@link MessageDigest MessageDigest} algorithm name 
+     * to use when performing the hash.
+     *
+     * @return the {@link MessageDigest MessageDigest} algorithm name to use when performing the hash.
+     */
+    public abstract String getAlgorithmName();
+
+    public byte[] getBytes() {
+        return this.bytes;
+    }
+
+    /**
+     * Sets the raw bytes stored by this hash instance.
+     * <p/>
+     * The bytes are kept in raw form - they will not be hashed/changed.  This is primarily a utility method for
+     * constructing a Hash instance when the hashed value is already known.
+     *
+     * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
+     */
+    public void setBytes(byte[] alreadyHashedBytes) {
+        this.bytes = alreadyHashedBytes;
+        this.hexEncoded = null;
+        this.base64Encoded = null;
+    }
+
+    /**
+     * Returns the JDK MessageDigest instance to use for executing the hash.
+     *
+     * @param algorithmName the algorithm to use for the hash, provided by subclasses.
+     * @return the MessageDigest object for the specified {@code algorithm}.
+     * @throws UnknownAlgorithmException if the specified algorithm name is not available.
+     */
+    protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmException {
+        try {
+            return MessageDigest.getInstance(algorithmName);
+        } catch (NoSuchAlgorithmException e) {
+            String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM.";
+            throw new UnknownAlgorithmException(msg, e);
+        }
+    }
+
+    /**
+     * Hashes the specified byte array without a salt for a single iteration.
+     *
+     * @param bytes the bytes to hash.
+     * @return the hashed bytes.
+     */
+    protected byte[] hash(byte[] bytes) {
+        return hash(bytes, null, 1);
+    }
+
+    /**
+     * Hashes the specified byte array using the given {@code salt} for a single iteration.
+     *
+     * @param bytes the bytes to hash
+     * @param salt  the salt to use for the initial hash
+     * @return the hashed bytes
+     */
+    protected byte[] hash(byte[] bytes, byte[] salt) {
+        return hash(bytes, salt, 1);
+    }
+
+    /**
+     * Hashes the specified byte array using the given {@code salt} for the specified number of iterations.
+     *
+     * @param bytes          the bytes to hash
+     * @param salt           the salt to use for the initial hash
+     * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency).
+     * @return the hashed bytes.
+     * @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available.
+     */
+    protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
+        MessageDigest digest = getDigest(getAlgorithmName());
+        if (salt != null) {
+            digest.reset();
+            digest.update(salt);
+        }
+        byte[] hashed = digest.digest(bytes);
+        int iterations = hashIterations - 1; //already hashed once above
+        //iterate remaining number:
+        for (int i = 0; i < iterations; i++) {
+            digest.reset();
+            hashed = digest.digest(hashed);
+        }
+        return hashed;
+    }
+
+    /**
+     * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
+     * <p/>
+     * This implementation caches the resulting hex string so multiple calls to this method remain efficient.
+     * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
+     * next time this method is called.
+     *
+     * @return a hex-encoded string of the underlying {@link #getBytes byte array}.
+     */
+    public String toHex() {
+        if (this.hexEncoded == null) {
+            this.hexEncoded = Hex.encodeToString(getBytes());
+        }
+        return this.hexEncoded;
+    }
+
+    /**
+     * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}.
+     * <p/>
+     * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient.
+     * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
+     * next time this method is called.
+     *
+     * @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
+     */
+    public String toBase64() {
+        if (this.base64Encoded == null) {
+            //cache result in case this method is called multiple times.
+            this.base64Encoded = Base64.encodeToString(getBytes());
+        }
+        return this.base64Encoded;
+    }
+
+    /**
+     * Simple implementation that merely returns {@link #toHex() toHex()}.
+     *
+     * @return the {@link #toHex() toHex()} value.
+     */
+    public String toString() {
+        return toHex();
+    }
+
+    /**
+     * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
+     * this Hash's byte array, {@code false} otherwise.
+     *
+     * @param o the object (Hash) to check for equality.
+     * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
+     *         this Hash's byte array, {@code false} otherwise.
+     */
+    public boolean equals(Object o) {
+        if (o instanceof Hash) {
+            Hash other = (Hash) o;
+            return Arrays.equals(getBytes(), other.getBytes());
+        }
+        return false;
+    }
+
+    /**
+     * Simply returns toHex().hashCode();
+     *
+     * @return toHex().hashCode()
+     */
+    public int hashCode() {
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
+    }
+
+    private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) {
+        System.out.println("Prints an " + type + " hash value.");
+        System.out.println("Usage: java " + clazz.getName() + " [-base64] [-salt <saltValue>] [-times <N>] <valueToHash>");
+        System.out.println("Options:");
+        System.out.println("\t-base64\t\tPrints the hash value as a base64 String instead of the default hex.");
+        System.out.println("\t-salt\t\tSalts the hash with the specified <saltValue>");
+        System.out.println("\t-times\t\tHashes the input <N> number of times");
+    }
+
+    private static boolean isReserved(String arg) {
+        return "-base64".equals(arg) || "-times".equals(arg) || "-salt".equals(arg);
+    }
+
+    static int doMain(Class<? extends AbstractHash> clazz, String[] args) {
+        String simple = clazz.getSimpleName();
+        int index = simple.indexOf("Hash");
+        String type = simple.substring(0, index).toUpperCase();
+
+        if (args == null || args.length < 1 || args.length > 7) {
+            printMainUsage(clazz, type);
+            return -1;
+        }
+        boolean hex = true;
+        String salt = null;
+        int times = 1;
+        String text = args[args.length - 1];
+        for (int i = 0; i < args.length; i++) {
+            String arg = args[i];
+            if (arg.equals("-base64")) {
+                hex = false;
+            } else if (arg.equals("-salt")) {
+                if ((i + 1) >= (args.length - 1)) {
+                    String msg = "Salt argument must be followed by a salt value.  The final argument is " +
+                            "reserved for the value to hash.";
+                    System.out.println(msg);
+                    printMainUsage(clazz, type);
+                    return -1;
+                }
+                salt = args[i + 1];
+            } else if (arg.equals("-times")) {
+                if ((i + 1) >= (args.length - 1)) {
+                    String msg = "Times argument must be followed by an integer value.  The final argument is " +
+                            "reserved for the value to hash";
+                    System.out.println(msg);
+                    printMainUsage(clazz, type);
+                    return -1;
+                }
+                try {
+                    times = Integer.valueOf(args[i + 1]);
+                } catch (NumberFormatException e) {
+                    String msg = "Times argument must be followed by an integer value.";
+                    System.out.println(msg);
+                    printMainUsage(clazz, type);
+                    return -1;
+                }
+            }
+        }
+
+        Hash hash = new Md2Hash(text, salt, times);
+        String hashed = hex ? hash.toHex() : hash.toBase64();
+        System.out.print(hex ? "Hex: " : "Base64: ");
+        System.out.println(hashed);
+        return 0;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java b/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java
new file mode 100644
index 0000000..38c8b46
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code HashService} that allows configuration of its strategy via JavaBeans-compatible setter methods.
+ *
+ * @since 1.2
+ */
+public interface ConfigurableHashService extends HashService {
+
+    /**
+     * Sets the 'private' (internal) salt to be paired with a 'public' (random or supplied) salt during hash computation.
+     *
+     * @param privateSalt the 'private' internal salt to be paired with a 'public' (random or supplied) salt during
+     *                    hash computation.
+     */
+    void setPrivateSalt(ByteSource privateSalt);
+
+    /**
+     * Sets the number of hash iterations that will be performed during hash computation.
+     *
+     * @param iterations the number of hash iterations that will be performed during hash computation.
+     */
+    void setHashIterations(int iterations);
+
+    /**
+     * Sets the name of the {@link java.security.MessageDigest MessageDigest} algorithm that will be used to compute
+     * hashes.
+     *
+     * @param name the name of the {@link java.security.MessageDigest MessageDigest} algorithm that will be used to
+     *             compute hashes.
+     */
+    void setHashAlgorithmName(String name);
+
+    /**
+     * Sets a source of randomness used to generate public salts that will in turn be used during hash computation.
+     *
+     * @param rng a source of randomness used to generate public salts that will in turn be used during hash computation.
+     */
+    void setRandomNumberGenerator(RandomNumberGenerator rng);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java b/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java
new file mode 100644
index 0000000..c411370
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Default implementation of the {@link HashService} interface, supporting a customizable hash algorithm name,
+ * secure-random salt generation, multiple hash iterations and an optional internal
+ * {@link #setPrivateSalt(ByteSource) privateSalt}.
+ * <h2>Hash Algorithm</h2>
+ * You may specify a hash algorithm via the {@link #setHashAlgorithmName(String)} property.  Any algorithm name
+ * understood by the JDK
+ * {@link java.security.MessageDigest#getInstance(String) MessageDigest.getInstance(String algorithmName)} method
+ * will work.  The default is {@code SHA-512}.
+ * <h2>Random Salts</h2>
+ * When a salt is not specified in a request, this implementation generates secure random salts via its
+ * {@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} property.
+ * Random salts (and potentially combined with the internal {@link #getPrivateSalt() privateSalt}) is a very strong
+ * salting strategy, as salts should ideally never be based on known/guessable data.  The default instance is a
+ * {@link SecureRandomNumberGenerator}.
+ * <h2>Hash Iterations</h2>
+ * Secure hashing strategies often employ multiple hash iterations to slow down the hashing process.  This technique
+ * is usually used for password hashing, since the longer it takes to compute a password hash, the longer it would
+ * take for an attacker to compromise a password.  This
+ * <a href="http://www.katasoft.com/blog/2011/04/04/strong-password-hashing-apache-shiro">Katasoft blog article</a>
+ * explains in greater detail why this is useful, as well as information on how many iterations is 'enough'.
+ * <p/>
+ * You may set the number of hash iterations via the {@link #setHashIterations(int)} property.  The default is
+ * {@code 1}, but should be increased significantly if the {@code HashService} is intended to be used for password
+ * hashing. See the linked blog article for more info.
+ * <h2>Private Salt</h2>
+ * If using this implementation as part of a password hashing strategy, it might be desirable to configure a
+ * {@link #setPrivateSalt(ByteSource) private salt}:
+ * <p/>
+ * A hash and the salt used to compute it are often stored together.  If an attacker is ever able to access
+ * the hash (e.g. during password cracking) and it has the full salt value, the attacker has all of the input necessary
+ * to try to brute-force crack the hash (source + complete salt).
+ * <p/>
+ * However, if part of the salt is not available to the attacker (because it is not stored with the hash), it is
+ * <em>much</em> harder to crack the hash value since the attacker does not have the complete inputs necessary.
+ * <p/>
+ * The {@link #getPrivateSalt() privateSalt} property exists to satisfy this private-and-not-shared part of the salt.
+ * If you configure this attribute, you can obtain this additional very important safety feature.
+ * <p/>
+ * <b>*</b>By default, the {@link #getPrivateSalt() privateSalt} is null, since a sensible default cannot be used that
+ * isn't easily compromised (because Shiro is an open-source project and any default could be easily seen and used).
+ *
+ * @since 1.2
+ */
+public class DefaultHashService implements ConfigurableHashService {
+
+    /**
+     * The RandomNumberGenerator to use to randomly generate the public part of the hash salt.
+     */
+    private RandomNumberGenerator rng;
+
+    /**
+     * The MessageDigest name of the hash algorithm to use for computing hashes.
+     */
+    private String algorithmName;
+
+    /**
+     * The 'private' part of the hash salt.
+     */
+    private ByteSource privateSalt;
+
+    /**
+     * The number of hash iterations to perform when computing hashes.
+     */
+    private int iterations;
+
+    /**
+     * Whether or not to generate public salts if a request does not provide one.
+     */
+    private boolean generatePublicSalt;
+
+    /**
+     * Constructs a new {@code DefaultHashService} instance with the following defaults:
+     * <ul>
+     * <li>{@link #setHashAlgorithmName(String) hashAlgorithmName} = {@code SHA-512}</li>
+     * <li>{@link #setHashIterations(int) hashIterations} = {@code 1}</li>
+     * <li>{@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} =
+     * new {@link SecureRandomNumberGenerator}()</li>
+     * <li>{@link #setGeneratePublicSalt(boolean) generatePublicSalt} = {@code false}</li>
+     * </ul>
+     * <p/>
+     * If this hashService will be used for password hashing it is recommended to set the
+     * {@link #setPrivateSalt(ByteSource) privateSalt} and significantly increase the number of
+     * {@link #setHashIterations(int) hashIterations}.  See the class-level JavaDoc for more information.
+     */
+    public DefaultHashService() {
+        this.algorithmName = "SHA-512";
+        this.iterations = 1;
+        this.generatePublicSalt = false;
+        this.rng = new SecureRandomNumberGenerator();
+    }
+
+    /**
+     * Computes and responds with a hash based on the specified request.
+     * <p/>
+     * This implementation functions as follows:
+     * <ul>
+     * <li>If the request's {@link org.apache.shiro.crypto.hash.HashRequest#getSalt() salt} is null:
+     * <p/>
+     * A salt will be generated and used to compute the hash.  The salt is generated as follows:
+     * <ol>
+     * <li>Use the {@link #getRandomNumberGenerator() randomNumberGenerator} to generate a new random number.</li>
+     * <li>{@link #combine(ByteSource, ByteSource) combine} this random salt with any configured
+     * {@link #getPrivateSalt() privateSalt}
+     * </li>
+     * <li>Use the combined value as the salt used during hash computation</li>
+     * </ol>
+     * </li>
+     * <li>
+     * If the request salt is not null:
+     * <p/>
+     * This indicates that the hash computation is for comparison purposes (of a
+     * previously computed hash).  The request salt will be {@link #combine(ByteSource, ByteSource) combined} with any
+     * configured {@link #getPrivateSalt() privateSalt} and used as the complete salt during hash computation.
+     * </li>
+     * </ul>
+     * <p/>
+     * The returned {@code Hash}'s {@link Hash#getSalt() salt} property
+     * will contain <em>only</em> the 'public' part of the salt and <em>NOT</em> the privateSalt.  See the class-level
+     * JavaDoc explanation for more info.
+     *
+     * @param request the request to process
+     * @return the response containing the result of the hash computation, as well as any hash salt used that should be
+     *         exposed to the caller.
+     */
+    public Hash computeHash(HashRequest request) {
+        if (request == null || request.getSource() == null || request.getSource().isEmpty()) {
+            return null;
+        }
+
+        String algorithmName = getAlgorithmName(request);
+        ByteSource source = request.getSource();
+        int iterations = getIterations(request);
+
+        ByteSource publicSalt = getPublicSalt(request);
+        ByteSource privateSalt = getPrivateSalt();
+        ByteSource salt = combine(privateSalt, publicSalt);
+
+        Hash computed = new SimpleHash(algorithmName, source, salt, iterations);
+
+        SimpleHash result = new SimpleHash(algorithmName);
+        result.setBytes(computed.getBytes());
+        result.setIterations(iterations);
+        //Only expose the public salt - not the real/combined salt that might have been used:
+        result.setSalt(publicSalt);
+
+        return result;
+    }
+
+    protected String getAlgorithmName(HashRequest request) {
+        String name = request.getAlgorithmName();
+        if (name == null) {
+            name = getHashAlgorithmName();
+        }
+        return name;
+    }
+
+    protected int getIterations(HashRequest request) {
+        int iterations = Math.max(0, request.getIterations());
+        if (iterations < 1) {
+            iterations = Math.max(1, getHashIterations());
+        }
+        return iterations;
+    }
+
+    /**
+     * Returns the public salt that should be used to compute a hash based on the specified request or
+     * {@code null} if no public salt should be used.
+     * <p/>
+     * This implementation functions as follows:
+     * <ol>
+     * <li>If the request salt is not null and non-empty, this will be used, return it.</li>
+     * <li>If the request salt is null or empty:
+     * <ol>
+     * <li>If a private salt has been set <em>OR</em> {@link #isGeneratePublicSalt()} is {@code true},
+     * auto generate a random public salt via the configured
+     * {@link #getRandomNumberGenerator() randomNumberGenerator}.</li>
+     * <li>If a private salt has not been configured and {@link #isGeneratePublicSalt()} is {@code false},
+     * do nothing - return {@code null} to indicate a salt should not be used during hash computation.</li>
+     * </ol>
+     * </li>
+     * </ol>
+     *
+     * @param request request the request to process
+     * @return the public salt that should be used to compute a hash based on the specified request or
+     *         {@code null} if no public salt should be used.
+     */
+    protected ByteSource getPublicSalt(HashRequest request) {
+
+        ByteSource publicSalt = request.getSalt();
+
+        if (publicSalt != null && !publicSalt.isEmpty()) {
+            //a public salt was explicitly requested to be used - go ahead and use it:
+            return publicSalt;
+        }
+
+        publicSalt = null;
+
+        //check to see if we need to generate one:
+        ByteSource privateSalt = getPrivateSalt();
+        boolean privateSaltExists = privateSalt != null && !privateSalt.isEmpty();
+
+        //If a private salt exists, we must generate a public salt to protect the integrity of the private salt.
+        //Or generate it if the instance is explicitly configured to do so:
+        if (privateSaltExists || isGeneratePublicSalt()) {
+            publicSalt = getRandomNumberGenerator().nextBytes();
+        }
+
+        return publicSalt;
+    }
+
+    /**
+     * Combines the specified 'private' salt bytes with the specified additional extra bytes to use as the
+     * total salt during hash computation.  {@code privateSaltBytes} will be {@code null} }if no private salt has been
+     * configured.
+     *
+     * @param privateSalt the (possibly {@code null}) 'private' salt to combine with the specified extra bytes
+     * @param publicSalt  the extra bytes to use in addition to the given private salt.
+     * @return a combination of the specified private salt bytes and extra bytes that will be used as the total
+     *         salt during hash computation.
+     */
+    protected ByteSource combine(ByteSource privateSalt, ByteSource publicSalt) {
+
+        byte[] privateSaltBytes = privateSalt != null ? privateSalt.getBytes() : null;
+        int privateSaltLength = privateSaltBytes != null ? privateSaltBytes.length : 0;
+
+        byte[] publicSaltBytes = publicSalt != null ? publicSalt.getBytes() : null;
+        int extraBytesLength = publicSaltBytes != null ? publicSaltBytes.length : 0;
+
+        int length = privateSaltLength + extraBytesLength;
+
+        if (length <= 0) {
+            return null;
+        }
+
+        byte[] combined = new byte[length];
+
+        int i = 0;
+        for (int j = 0; j < privateSaltLength; j++) {
+            assert privateSaltBytes != null;
+            combined[i++] = privateSaltBytes[j];
+        }
+        for (int j = 0; j < extraBytesLength; j++) {
+            assert publicSaltBytes != null;
+            combined[i++] = publicSaltBytes[j];
+        }
+
+        return ByteSource.Util.bytes(combined);
+    }
+
+    public void setHashAlgorithmName(String name) {
+        this.algorithmName = name;
+    }
+
+    public String getHashAlgorithmName() {
+        return this.algorithmName;
+    }
+
+    public void setPrivateSalt(ByteSource privateSalt) {
+        this.privateSalt = privateSalt;
+    }
+
+    public ByteSource getPrivateSalt() {
+        return this.privateSalt;
+    }
+
+    public void setHashIterations(int count) {
+        this.iterations = count;
+    }
+
+    public int getHashIterations() {
+        return this.iterations;
+    }
+
+    public void setRandomNumberGenerator(RandomNumberGenerator rng) {
+        this.rng = rng;
+    }
+
+    public RandomNumberGenerator getRandomNumberGenerator() {
+        return this.rng;
+    }
+
+    /**
+     * Returns {@code true} if a public salt should be randomly generated and used to compute a hash if a
+     * {@link HashRequest} does not specify a salt, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code false} but should definitely be set to {@code true} if the
+     * {@code HashService} instance is being used for password hashing.
+     * <p/>
+     * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured.  If a
+     * private salt has been configured and a request does not provide a salt, a random salt will always be generated
+     * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
+     * which is undesirable).
+     *
+     * @return {@code true} if a public salt should be randomly generated and used to compute a hash if a
+     *         {@link HashRequest} does not specify a salt, {@code false} otherwise.
+     */
+    public boolean isGeneratePublicSalt() {
+        return generatePublicSalt;
+    }
+
+    /**
+     * Sets whether or not a public salt should be randomly generated and used to compute a hash if a
+     * {@link HashRequest} does not specify a salt.
+     * <p/>
+     * The default value is {@code false} but should definitely be set to {@code true} if the
+     * {@code HashService} instance is being used for password hashing.
+     * <p/>
+     * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured.  If a
+     * private salt has been configured and a request does not provide a salt, a random salt will always be generated
+     * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
+     * which is undesirable).
+     *
+     * @param generatePublicSalt whether or not a public salt should be randomly generated and used to compute a hash
+     *                           if a {@link HashRequest} does not specify a salt.
+     */
+    public void setGeneratePublicSalt(boolean generatePublicSalt) {
+        this.generatePublicSalt = generatePublicSalt;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java
new file mode 100644
index 0000000..8760895
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A Cryptographic {@code Hash} represents a one-way conversion algorithm that transforms an input source to an
+ * underlying byte array.  Hex and Base64-encoding output of the hashed bytes are automatically supported by the
+ * inherited {@link #toHex() toHex()} and {@link #toBase64() toBase64()} methods.
+ * <p/>
+ * The bytes returned by the parent interface's {@link #getBytes() getBytes()} are the hashed value of the
+ * original input source, also known as the 'checksum' or 'digest'.
+ *
+ * @see Md2Hash
+ * @see Md5Hash
+ * @see Sha1Hash
+ * @see Sha256Hash
+ * @see Sha384Hash
+ * @see Sha512Hash
+ * @since 0.9
+ */
+public interface Hash extends ByteSource {
+
+    /**
+     * Returns the name of the algorithm used to hash the input source, for example, {@code SHA-256}, {@code MD5}, etc.
+     * <p/>
+     * The name is expected to be a {@link java.security.MessageDigest MessageDigest} algorithm name.
+     *
+     * @return the the name of the algorithm used to hash the input source, for example, {@code SHA-256}, {@code MD5}, etc.
+     * @since 1.1
+     */
+    String getAlgorithmName();
+
+    /**
+     * Returns a salt used to compute the hash or {@code null} if no salt was used.
+     *
+     * @return a salt used to compute the hash or {@code null} if no salt was used.
+     * @since 1.2
+     */
+    ByteSource getSalt();
+
+    /**
+     * Returns the number of hash iterations used to compute the hash.
+     *
+     * @return the number of hash iterations used to compute the hash.
+     * @since 1.2
+     */
+    int getIterations();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java b/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java
new file mode 100644
index 0000000..82376ed
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code HashRequest} is composed of data that will be used by a {@link HashService} to compute a hash (aka
+ * 'digest').  While you can instantiate a concrete {@code HashRequest} class directly, most will find using the
+ * {@link HashRequest.Builder} more convenient.
+ *
+ * @see HashRequest.Builder
+ * @since 1.2
+ */
+public interface HashRequest {
+
+    /**
+     * Returns the source data that will be hashed by a {@link HashService}. For example, this might be a
+     * {@code ByteSource} representation of a password, or file, etc.
+     *
+     * @return the source data that will be hashed by a {@link HashService}.
+     */
+    ByteSource getSource();
+
+    /**
+     * Returns a salt to be used by the {@link HashService} during hash computation, or {@code null} if no salt is
+     * provided as part of the request.
+     * <p/>
+     * Note that a {@code null} value does not necessarily mean a salt won't be used at all - it just
+     * means that the request didn't include one.  The servicing {@link HashService} is free to provide a salting
+     * strategy for a request, even if the request did not specify one.
+     *
+     * @return a salt to be used by the {@link HashService} during hash computation, or {@code null} if no salt is
+     *         provided as part of the request.
+     */
+    ByteSource getSalt();
+
+    /**
+     * Returns the number of requested hash iterations to be performed when computing the final {@code Hash} result.
+     * A non-positive (0 or less) indicates that the {@code HashService}'s default iteration configuration should
+     * be used.  A positive value overrides the {@code HashService}'s configuration for a single request.
+     * <p/>
+     * Note that a {@code HashService} is free to ignore this number if it determines the number is not sufficient
+     * to meet a desired level of security.
+     *
+     * @return the number of requested hash iterations to be performed when computing the final {@code Hash} result.
+     */
+    int getIterations();
+
+    /**
+     * Returns the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}, or
+     * {@code null} if the default algorithm configuration of the {@code HashService} should be used.  A non-null value
+     * overrides the {@code HashService}'s configuration for a single request.
+     * <p/>
+     * Note that a {@code HashService} is free to ignore this value if it determines that the algorithm is not
+     * sufficient to meet a desired level of security.
+     *
+     * @return the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}, or
+     *         {@code null} if the default algorithm configuration of the {@code HashService} should be used.
+     */
+    String getAlgorithmName();
+
+    /**
+     * A Builder class representing the Builder design pattern for constructing {@link HashRequest} instances.
+     *
+     * @see SimpleHashRequest
+     * @since 1.2
+     */
+    public static class Builder {
+
+        private ByteSource source;
+        private ByteSource salt;
+        private int iterations;
+        private String algorithmName;
+
+        /**
+         * Default no-arg constructor.
+         */
+        public Builder() {
+            this.iterations = 0;
+        }
+
+        /**
+         * Sets the source data that will be hashed by a {@link HashService}. For example, this might be a
+         * {@code ByteSource} representation of a password, or file, etc.
+         *
+         * @param source the source data that will be hashed by a {@link HashService}.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getSource()
+         * @see #setSource(Object)
+         */
+        public Builder setSource(ByteSource source) {
+            this.source = source;
+            return this;
+        }
+
+        /**
+         * Sets the source data that will be hashed by a {@link HashService}.
+         * <p/>
+         * This is a convenience alternative to {@link #setSource(ByteSource)}: it will attempt to convert the
+         * argument into a {@link ByteSource} instance using Shiro's default conversion heuristics
+         * (as defined by {@link ByteSource.Util#isCompatible(Object) ByteSource.Util.isCompatible}.  If the object
+         * cannot be heuristically converted to a {@code ByteSource}, an {@code IllegalArgumentException} will be
+         * thrown.
+         *
+         * @param source the byte-backed source data that will be hashed by a {@link HashService}.
+         * @return this {@code Builder} instance for method chaining.
+         * @throws IllegalArgumentException if the argument cannot be heuristically converted to a {@link ByteSource}
+         *                                  instance.
+         * @see HashRequest#getSource()
+         * @see #setSource(ByteSource)
+         */
+        public Builder setSource(Object source) throws IllegalArgumentException {
+            this.source = ByteSource.Util.bytes(source);
+            return this;
+        }
+
+        /**
+         * Sets a salt to be used by the {@link HashService} during hash computation.
+         * <p/>
+         * <b>NOTE</b>: not calling this method does not necessarily mean a salt won't be used at all - it just
+         * means that the request didn't include a salt.  The servicing {@link HashService} is free to provide a salting
+         * strategy for a request, even if the request did not specify one.  You can always check the result
+         * {@code Hash} {@link Hash#getSalt() getSalt()} method to see what the actual
+         * salt was (if any), which may or may not match this request salt.
+         *
+         * @param salt a salt to be used by the {@link HashService} during hash computation
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getSalt()
+         */
+        public Builder setSalt(ByteSource salt) {
+            this.salt = salt;
+            return this;
+        }
+
+        /**
+         * Sets a salt to be used by the {@link HashService} during hash computation.
+         * <p/>
+         * This is a convenience alternative to {@link #setSalt(ByteSource)}: it will attempt to convert the
+         * argument into a {@link ByteSource} instance using Shiro's default conversion heuristics
+         * (as defined by {@link ByteSource.Util#isCompatible(Object) ByteSource.Util.isCompatible}.  If the object
+         * cannot be heuristically converted to a {@code ByteSource}, an {@code IllegalArgumentException} will be
+         * thrown.
+         *
+         * @param salt a salt to be used by the {@link HashService} during hash computation.
+         * @return this {@code Builder} instance for method chaining.
+         * @throws IllegalArgumentException if the argument cannot be heuristically converted to a {@link ByteSource}
+         *                                  instance.
+         * @see #setSalt(ByteSource)
+         * @see HashRequest#getSalt()
+         */
+        public Builder setSalt(Object salt) throws IllegalArgumentException {
+            this.salt = ByteSource.Util.bytes(salt);
+            return this;
+        }
+
+        /**
+         * Sets the number of requested hash iterations to be performed when computing the final {@code Hash} result.
+         * Not calling this method or setting a non-positive value (0 or less) indicates that the {@code HashService}'s
+         * default iteration configuration should be used.  A positive value overrides the {@code HashService}'s
+         * configuration for a single request.
+         * <p/>
+         * Note that a {@code HashService} is free to ignore this number if it determines the number is not sufficient
+         * to meet a desired level of security. You can always check the result
+         * {@code Hash} {@link Hash#getIterations() getIterations()} method to see what the actual
+         * number of iterations was, which may or may not match this request salt.
+         *
+         * @param iterations the number of requested hash iterations to be performed when computing the final
+         *                   {@code Hash} result.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getIterations()
+         */
+        public Builder setIterations(int iterations) {
+            this.iterations = iterations;
+            return this;
+        }
+
+        /**
+         * Sets the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}.
+         * Not calling this method or setting it to {@code null} indicates the the default algorithm configuration of
+         * the {@code HashService} should be used.  A non-null value
+         * overrides the {@code HashService}'s configuration for a single request.
+         * <p/>
+         * Note that a {@code HashService} is free to ignore this value if it determines that the algorithm is not
+         * sufficient to meet a desired level of security. You can always check the result
+         * {@code Hash} {@link Hash#getAlgorithmName() getAlgorithmName()} method to see what the actual
+         * algorithm was, which may or may not match this request salt.
+         *
+         * @param algorithmName the name of the hash algorithm the {@code HashService} should use when computing the
+         *                      {@link Hash}, or {@code null} if the default algorithm configuration of the
+         *                      {@code HashService} should be used.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getAlgorithmName()
+         */
+        public Builder setAlgorithmName(String algorithmName) {
+            this.algorithmName = algorithmName;
+            return this;
+        }
+
+        /**
+         * Builds a {@link HashRequest} instance reflecting the specified configuration.
+         *
+         * @return a {@link HashRequest} instance reflecting the specified configuration.
+         */
+        public HashRequest build() {
+            return new SimpleHashRequest(this.algorithmName, this.source, this.salt, this.iterations);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java b/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java
new file mode 100644
index 0000000..4dc5019
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+/**
+ * A {@code HashService} hashes input sources utilizing a particular hashing strategy.
+ * <p/>
+ * A {@code HashService} sits at a higher architectural level than Shiro's simple {@link Hash} classes:  it allows
+ * for salting and iteration-related strategies to be configured and internalized in a
+ * single component that can be re-used in multiple places in the application.
+ * <p/>
+ * For example, for the most secure hashes, it is highly recommended to use a randomly generated salt, potentially
+ * paired with an configuration-specific private salt, in addition to using multiple hash iterations.
+ * <p/>
+ * While one can do this easily enough using Shiro's {@link Hash} implementations directly, this direct approach could
+ * quickly lead to copy-and-paste behavior.  For example, consider this logic which might need to repeated in an
+ * application:
+ * <pre>
+ * int numHashIterations = ...
+ * ByteSource privateSalt = ...
+ * ByteSource randomSalt = {@link org.apache.shiro.crypto.RandomNumberGenerator randomNumberGenerator}.nextBytes();
+ * ByteSource combined = combine(privateSalt, randomSalt);
+ * Hash hash = Sha512Hash(source, combined, numHashIterations);
+ * save(hash);
+ * </pre>
+ * In this example, often only the input source will change during runtime, while the hashing strategy (how salts
+ * are generated or acquired, how many hash iterations will be performed, etc) usually remain consistent.  A HashService
+ * internalizes this logic so the above becomes simply this:
+ * <pre>
+ * HashRequest request = new HashRequest.Builder().source(source).build();
+ * Hash result = hashService.hash(request);
+ * save(result);
+ * </pre>
+ *
+ * @since 1.2
+ */
+public interface HashService {
+
+    /**
+     * Computes a hash based on the given request.
+     *
+     * <h3>Salt Notice</h3>
+     *
+     * If a salt accompanies the return value
+     * (i.e. <code>returnedHash.{@link org.apache.shiro.crypto.hash.Hash#getSalt() getSalt()} != null</code>), this
+     * same exact salt <b><em>MUST</em></b> be presented back to the {@code HashService} if hash
+     * comparison/verification will be performed at a later time (for example, for password hash or file checksum
+     * comparison).
+     * <p/>
+     * For additional security, the {@code HashService}'s internal implementation may use more complex salting
+     * strategies than what would be achieved by computing a {@code Hash} manually.
+     * <p/>
+     * In summary, if a {@link HashService} returns a salt in a returned Hash, it is expected that the same salt
+     * will be provided to the same {@code HashService} instance.
+     *
+     * @param request the request to process
+     * @return the hashed data
+     * @see Hash#getSalt()
+     */
+    Hash computeHash(HashRequest request);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java
new file mode 100644
index 0000000..c4ae2ff
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+
+/**
+ * Generates an MD2 Hash (RFC 1319) from a given input <tt>source</tt> with an optional <tt>salt</tt> and
+ * hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ *
+ * @since 0.9
+ */
+public class Md2Hash extends SimpleHash {
+
+    public static final String ALGORITHM_NAME = "MD2";
+
+    public Md2Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Md2Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Md2Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Md2Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Md2Hash fromHexString(String hex) {
+        Md2Hash hash = new Md2Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Md2Hash fromBase64String(String base64) {
+        Md2Hash hash = new Md2Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java
new file mode 100644
index 0000000..e7aa0b4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+/**
+ * Generates an MD5 Hash (RFC 1321) from a given input <tt>source</tt> with an optional <tt>salt</tt> and
+ * hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ *
+ * @since 0.9
+ */
+public class Md5Hash extends SimpleHash {
+
+    //TODO - complete JavaDoc
+
+    public static final String ALGORITHM_NAME = "MD5";
+
+    public Md5Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Md5Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Md5Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Md5Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Md5Hash fromHexString(String hex) {
+        Md5Hash hash = new Md5Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Md5Hash fromBase64String(String base64) {
+        Md5Hash hash = new Md5Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java
new file mode 100644
index 0000000..0acfdb9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+
+/**
+ * Generates an SHA-1 Hash (Secure Hash Standard, NIST FIPS 180-1) from a given input <tt>source</tt> with an
+ * optional <tt>salt</tt> and hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ *
+ * @since 0.9
+ */
+public class Sha1Hash extends SimpleHash {
+
+    //TODO - complete JavaDoc
+
+    public static final String ALGORITHM_NAME = "SHA-1";
+
+    public Sha1Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Sha1Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Sha1Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Sha1Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Sha1Hash fromHexString(String hex) {
+        Sha1Hash hash = new Sha1Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Sha1Hash fromBase64String(String base64) {
+        Sha1Hash hash = new Sha1Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Sha256Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Sha256Hash.java
new file mode 100644
index 0000000..b600a15
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Sha256Hash.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+/**
+ * Generates an SHA-256 Hash from a given input <tt>source</tt> with an optional <tt>salt</tt> and hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ * <p/>
+ * <b>JDK Version Note</b> - Attempting to instantiate this class on JREs prior to version 1.4.0 will throw
+ * an {@link IllegalStateException IllegalStateException}
+ *
+ * @since 0.9
+ */
+public class Sha256Hash extends SimpleHash {
+
+    //TODO - complete JavaDoc
+
+    public static final String ALGORITHM_NAME = "SHA-256";
+
+    public Sha256Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Sha256Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Sha256Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Sha256Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Sha256Hash fromHexString(String hex) {
+        Sha256Hash hash = new Sha256Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Sha256Hash fromBase64String(String base64) {
+        Sha256Hash hash = new Sha256Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Sha384Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Sha384Hash.java
new file mode 100644
index 0000000..8af2fdf
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Sha384Hash.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+
+/**
+ * Generates an SHA-384 Hash from a given input <tt>source</tt> with an optional <tt>salt</tt> and hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ * <p/>
+ * <b>JDK Version Note</b> - Attempting to instantiate this class on JREs prior to version 1.4.0 will throw
+ * an {@link IllegalStateException IllegalStateException}
+ *
+ * @since 0.9
+ */
+public class Sha384Hash extends SimpleHash {
+
+    //TODO - complete JavaDoc
+
+    public static final String ALGORITHM_NAME = "SHA-384";
+
+    public Sha384Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Sha384Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Sha384Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Sha384Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Sha384Hash fromHexString(String hex) {
+        Sha384Hash hash = new Sha384Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Sha384Hash fromBase64String(String base64) {
+        Sha384Hash hash = new Sha384Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/Sha512Hash.java b/core/src/main/java/org/apache/shiro/crypto/hash/Sha512Hash.java
new file mode 100644
index 0000000..5eb285e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/Sha512Hash.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+
+/**
+ * Generates an SHA-512 Hash from a given input <tt>source</tt> with an optional <tt>salt</tt> and hash iterations.
+ * <p/>
+ * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing
+ * techniques and how the overloaded constructors function.
+ * <p/>
+ * <b>JDK Version Note</b> - Attempting to instantiate this class on JREs prior to version 1.4.0 will throw
+ * an {@link IllegalStateException IllegalStateException}
+ *
+ * @since 0.9
+ */
+public class Sha512Hash extends SimpleHash {
+
+    //TODO - complete JavaDoc
+
+    public static final String ALGORITHM_NAME = "SHA-512";
+
+    public Sha512Hash() {
+        super(ALGORITHM_NAME);
+    }
+
+    public Sha512Hash(Object source) {
+        super(ALGORITHM_NAME, source);
+    }
+
+    public Sha512Hash(Object source, Object salt) {
+        super(ALGORITHM_NAME, source, salt);
+    }
+
+    public Sha512Hash(Object source, Object salt, int hashIterations) {
+        super(ALGORITHM_NAME, source, salt, hashIterations);
+    }
+
+    public static Sha512Hash fromHexString(String hex) {
+        Sha512Hash hash = new Sha512Hash();
+        hash.setBytes(Hex.decode(hex));
+        return hash;
+    }
+
+    public static Sha512Hash fromBase64String(String base64) {
+        Sha512Hash hash = new Sha512Hash();
+        hash.setBytes(Base64.decode(base64));
+        return hash;
+    }
+}
+
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java b/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java
new file mode 100644
index 0000000..7ee3c40
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java
@@ -0,0 +1,431 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.CodecException;
+import org.apache.shiro.codec.Hex;
+import org.apache.shiro.crypto.UnknownAlgorithmException;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.StringUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * A {@code Hash} implementation that allows any {@link java.security.MessageDigest MessageDigest} algorithm name to
+ * be used.  This class is a less type-safe variant than the other {@code AbstractHash} subclasses
+ * (e.g. {@link Sha512Hash}, etc), but it does allow for any algorithm name to be specified in case the other subclass
+ * implementations do not represent an algorithm that you may want to use.
+ * <p/>
+ * As of Shiro 1.1, this class effectively replaces the (now-deprecated) {@link AbstractHash} class.  It subclasses
+ * {@code AbstractHash} only to retain backwards-compatibility.
+ *
+ * @since 1.1
+ */
+public class SimpleHash extends AbstractHash {
+
+    private static final int DEFAULT_ITERATIONS = 1;
+
+    /**
+     * The {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
+     */
+    private final String algorithmName;
+
+    /**
+     * The hashed data
+     */
+    private byte[] bytes;
+
+    /**
+     * Supplied salt, if any.
+     */
+    private ByteSource salt;
+
+    /**
+     * Number of hash iterations to perform.  Defaults to 1 in the constructor.
+     */
+    private int iterations;
+
+    /**
+     * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
+     */
+    private transient String hexEncoded = null;
+
+    /**
+     * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
+     */
+    private transient String base64Encoded = null;
+
+    /**
+     * Creates an new instance with only its {@code algorithmName} set - no hashing is performed.
+     * <p/>
+     * Because all other constructors in this class hash the {@code source} constructor argument, this
+     * constructor is useful in scenarios when you have a byte array that you know is already hashed and
+     * just want to set the bytes in their raw form directly on an instance.  After using this constructor,
+     * you can then immediately call {@link #setBytes setBytes} to have a fully-initialized instance.
+     * <p/>
+     * <b>N.B.</b>The algorithm identified by the {@code algorithmName} parameter must be available on the JVM.  If it
+     * is not, a {@link UnknownAlgorithmException} will be thrown when the hash is performed (not at instantiation).
+     *
+     * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
+     *                      performing the hash.
+     * @see UnknownAlgorithmException
+     */
+    public SimpleHash(String algorithmName) {
+        this.algorithmName = algorithmName;
+        this.iterations = DEFAULT_ITERATIONS;
+    }
+
+    /**
+     * Creates an {@code algorithmName}-specific hash of the specified {@code source} with no {@code salt} using a
+     * single hash iteration.
+     * <p/>
+     * This is a convenience constructor that merely executes <code>this( algorithmName, source, null, 1);</code>.
+     * <p/>
+     * Please see the
+     * {@link #SimpleHash(String algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)}
+     * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
+     * types.
+     *
+     * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
+     *                      performing the hash.
+     * @param source        the object to be hashed.
+     * @throws org.apache.shiro.codec.CodecException
+     *                                   if the specified {@code source} cannot be converted into a byte array (byte[]).
+     * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
+     */
+    public SimpleHash(String algorithmName, Object source) throws CodecException, UnknownAlgorithmException {
+        //noinspection NullableProblems
+        this(algorithmName, source, null, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given {@code salt}
+     * using a single hash iteration.
+     * <p/>
+     * It is a convenience constructor that merely executes <code>this( algorithmName, source, salt, 1);</code>.
+     * <p/>
+     * Please see the
+     * {@link #SimpleHash(String algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)}
+     * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
+     * types.
+     *
+     * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
+     *                      performing the hash.
+     * @param source        the source object to be hashed.
+     * @param salt          the salt to use for the hash
+     * @throws CodecException            if either constructor argument cannot be converted into a byte array.
+     * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
+     */
+    public SimpleHash(String algorithmName, Object source, Object salt) throws CodecException, UnknownAlgorithmException {
+        this(algorithmName, source, salt, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given
+     * {@code salt} a total of {@code hashIterations} times.
+     * <p/>
+     * By default, this class only supports Object method arguments of
+     * type {@code byte[]}, {@code char[]}, {@link String}, {@link java.io.File File},
+     * {@link java.io.InputStream InputStream} or {@link org.apache.shiro.util.ByteSource ByteSource}.  If either
+     * argument is anything other than these types a {@link org.apache.shiro.codec.CodecException CodecException}
+     * will be thrown.
+     * <p/>
+     * If you want to be able to hash other object types, or use other salt types, you need to override the
+     * {@link #toBytes(Object) toBytes(Object)} method to support those specific types.  Your other option is to
+     * convert your arguments to one of the default supported types first before passing them in to this
+     * constructor}.
+     *
+     * @param algorithmName  the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
+     *                       performing the hash.
+     * @param source         the source object to be hashed.
+     * @param salt           the salt to use for the hash
+     * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
+     * @throws CodecException            if either Object constructor argument cannot be converted into a byte array.
+     * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
+     */
+    public SimpleHash(String algorithmName, Object source, Object salt, int hashIterations)
+            throws CodecException, UnknownAlgorithmException {
+        if (!StringUtils.hasText(algorithmName)) {
+            throw new NullPointerException("algorithmName argument cannot be null or empty.");
+        }
+        this.algorithmName = algorithmName;
+        this.iterations = Math.max(DEFAULT_ITERATIONS, hashIterations);
+        ByteSource saltBytes = null;
+        if (salt != null) {
+            saltBytes = convertSaltToBytes(salt);
+            this.salt = saltBytes;
+        }
+        ByteSource sourceBytes = convertSourceToBytes(source);
+        hash(sourceBytes, saltBytes, hashIterations);
+    }
+
+    /**
+     * Acquires the specified {@code source} argument's bytes and returns them in the form of a {@code ByteSource} instance.
+     * <p/>
+     * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
+     * conversion.  Can be overridden by subclasses for source-specific conversion.
+     *
+     * @param source the source object to be hashed.
+     * @return the source's bytes in the form of a {@code ByteSource} instance.
+     * @since 1.2
+     */
+    protected ByteSource convertSourceToBytes(Object source) {
+        return toByteSource(source);
+    }
+
+    /**
+     * Acquires the specified {@code salt} argument's bytes and returns them in the form of a {@code ByteSource} instance.
+     * <p/>
+     * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
+     * conversion.  Can be overridden by subclasses for salt-specific conversion.
+     *
+     * @param salt the salt to be use for the hash.
+     * @return the salt's bytes in the form of a {@code ByteSource} instance.
+     * @since 1.2
+     */
+    protected ByteSource convertSaltToBytes(Object salt) {
+        return toByteSource(salt);
+    }
+
+    /**
+     * Converts a given object into a {@code ByteSource} instance.  Assumes the object can be converted to bytes.
+     *
+     * @param o the Object to convert into a {@code ByteSource} instance.
+     * @return the {@code ByteSource} representation of the specified object's bytes.
+     * @since 1.2
+     */
+    protected ByteSource toByteSource(Object o) {
+        if (o == null) {
+            return null;
+        }
+        if (o instanceof ByteSource) {
+            return (ByteSource) o;
+        }
+        byte[] bytes = toBytes(o);
+        return ByteSource.Util.bytes(bytes);
+    }
+
+    private void hash(ByteSource source, ByteSource salt, int hashIterations) throws CodecException, UnknownAlgorithmException {
+        byte[] saltBytes = salt != null ? salt.getBytes() : null;
+        byte[] hashedBytes = hash(source.getBytes(), saltBytes, hashIterations);
+        setBytes(hashedBytes);
+    }
+
+    /**
+     * Returns the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
+     *
+     * @return the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
+     */
+    public String getAlgorithmName() {
+        return this.algorithmName;
+    }
+
+    public ByteSource getSalt() {
+        return this.salt;
+    }
+
+    public int getIterations() {
+        return this.iterations;
+    }
+
+    public byte[] getBytes() {
+        return this.bytes;
+    }
+
+    /**
+     * Sets the raw bytes stored by this hash instance.
+     * <p/>
+     * The bytes are kept in raw form - they will not be hashed/changed.  This is primarily a utility method for
+     * constructing a Hash instance when the hashed value is already known.
+     *
+     * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
+     */
+    public void setBytes(byte[] alreadyHashedBytes) {
+        this.bytes = alreadyHashedBytes;
+        this.hexEncoded = null;
+        this.base64Encoded = null;
+    }
+
+    /**
+     * Sets the iterations used to previously compute AN ALREADY GENERATED HASH.
+     * <p/>
+     * This is provided <em>ONLY</em> to reconstitute an already-created Hash instance.  It should ONLY ever be
+     * invoked when re-constructing a hash instance from an already-hashed value.
+     *
+     * @param iterations the number of hash iterations used to previously create the hash/digest.
+     * @since 1.2
+     */
+    public void setIterations(int iterations) {
+        this.iterations = Math.max(DEFAULT_ITERATIONS, iterations);
+    }
+
+    /**
+     * Sets the salt used to previously compute AN ALREADY GENERATED HASH.
+     * <p/>
+     * This is provided <em>ONLY</em> to reconstitute a Hash instance that has already been computed.  It should ONLY
+     * ever be invoked when re-constructing a hash instance from an already-hashed value.
+     *
+     * @param salt the salt used to previously create the hash/digest.
+     * @since 1.2
+     */
+    public void setSalt(ByteSource salt) {
+        this.salt = salt;
+    }
+
+    /**
+     * Returns the JDK MessageDigest instance to use for executing the hash.
+     *
+     * @param algorithmName the algorithm to use for the hash, provided by subclasses.
+     * @return the MessageDigest object for the specified {@code algorithm}.
+     * @throws UnknownAlgorithmException if the specified algorithm name is not available.
+     */
+    protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmException {
+        try {
+            return MessageDigest.getInstance(algorithmName);
+        } catch (NoSuchAlgorithmException e) {
+            String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM.";
+            throw new UnknownAlgorithmException(msg, e);
+        }
+    }
+
+    /**
+     * Hashes the specified byte array without a salt for a single iteration.
+     *
+     * @param bytes the bytes to hash.
+     * @return the hashed bytes.
+     * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
+     */
+    protected byte[] hash(byte[] bytes) throws UnknownAlgorithmException {
+        return hash(bytes, null, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Hashes the specified byte array using the given {@code salt} for a single iteration.
+     *
+     * @param bytes the bytes to hash
+     * @param salt  the salt to use for the initial hash
+     * @return the hashed bytes
+     * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
+     */
+    protected byte[] hash(byte[] bytes, byte[] salt) throws UnknownAlgorithmException {
+        return hash(bytes, salt, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Hashes the specified byte array using the given {@code salt} for the specified number of iterations.
+     *
+     * @param bytes          the bytes to hash
+     * @param salt           the salt to use for the initial hash
+     * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency).
+     * @return the hashed bytes.
+     * @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available.
+     */
+    protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
+        MessageDigest digest = getDigest(getAlgorithmName());
+        if (salt != null) {
+            digest.reset();
+            digest.update(salt);
+        }
+        byte[] hashed = digest.digest(bytes);
+        int iterations = hashIterations - DEFAULT_ITERATIONS; //already hashed once above
+        //iterate remaining number:
+        for (int i = 0; i < iterations; i++) {
+            digest.reset();
+            hashed = digest.digest(hashed);
+        }
+        return hashed;
+    }
+
+    public boolean isEmpty() {
+        return this.bytes == null || this.bytes.length == 0;
+    }
+
+    /**
+     * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
+     * <p/>
+     * This implementation caches the resulting hex string so multiple calls to this method remain efficient.
+     * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
+     * next time this method is called.
+     *
+     * @return a hex-encoded string of the underlying {@link #getBytes byte array}.
+     */
+    public String toHex() {
+        if (this.hexEncoded == null) {
+            this.hexEncoded = Hex.encodeToString(getBytes());
+        }
+        return this.hexEncoded;
+    }
+
+    /**
+     * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}.
+     * <p/>
+     * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient.
+     * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
+     * next time this method is called.
+     *
+     * @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
+     */
+    public String toBase64() {
+        if (this.base64Encoded == null) {
+            //cache result in case this method is called multiple times.
+            this.base64Encoded = Base64.encodeToString(getBytes());
+        }
+        return this.base64Encoded;
+    }
+
+    /**
+     * Simple implementation that merely returns {@link #toHex() toHex()}.
+     *
+     * @return the {@link #toHex() toHex()} value.
+     */
+    public String toString() {
+        return toHex();
+    }
+
+    /**
+     * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
+     * this Hash's byte array, {@code false} otherwise.
+     *
+     * @param o the object (Hash) to check for equality.
+     * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
+     *         this Hash's byte array, {@code false} otherwise.
+     */
+    public boolean equals(Object o) {
+        if (o instanceof Hash) {
+            Hash other = (Hash) o;
+            return Arrays.equals(getBytes(), other.getBytes());
+        }
+        return false;
+    }
+
+    /**
+     * Simply returns toHex().hashCode();
+     *
+     * @return toHex().hashCode()
+     */
+    public int hashCode() {
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java b/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
new file mode 100644
index 0000000..0e528a6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Simple implementation of {@link HashRequest} that can be used when interacting with a {@link HashService}.
+ *
+ * @since 1.2
+ */
+public class SimpleHashRequest implements HashRequest {
+
+    private final ByteSource source; //cannot be null - this is the source to hash.
+    private final ByteSource salt; //null = no salt specified
+    private final int iterations; //0 = not specified by the requestor; let the HashService decide.
+    private final String algorithmName; //null = let the HashService decide.
+
+    /**
+     * Creates a new SimpleHashRequest instance.
+     *
+     * @param algorithmName the name of the hash algorithm to use.  This is often null as the
+     * {@link HashService} implementation is usually configured with an appropriate algorithm name, but this
+     * can be non-null if the hash service's algorithm should be overridden with a specific one for the duration
+     * of the request.
+     *
+     * @param source the source to be hashed
+     * @param salt any public salt which should be used when computing the hash
+     * @param iterations the number of hash iterations to execute.  Zero (0) indicates no iterations were specified
+     * for the request, at which point the number of iterations is decided by the {@code HashService}
+     * @throws NullPointerException if {@code source} is null or empty.
+     */
+    public SimpleHashRequest(String algorithmName, ByteSource source, ByteSource salt, int iterations) {
+        if (source == null) {
+            throw new NullPointerException("source argument cannot be null");
+        }
+        this.source = source;
+        this.salt = salt;
+        this.algorithmName = algorithmName;
+        this.iterations = Math.max(0, iterations);
+    }
+
+    public ByteSource getSource() {
+        return this.source;
+    }
+
+    public ByteSource getSalt() {
+        return this.salt;
+    }
+
+    public int getIterations() {
+        return iterations;
+    }
+
+    public String getAlgorithmName() {
+        return algorithmName;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java
new file mode 100644
index 0000000..78742c0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * {@code HashFormat} that outputs <em>only</em> the hash's digest bytes in Base64 format.  It does not print out
+ * anything else (salt, iterations, etc).  This implementation is mostly provided as a convenience for
+ * command-line hashing.
+ *
+ * @since 1.2
+ */
+public class Base64Format implements HashFormat {
+
+    /**
+     * Returns {@code hash != null ? hash.toBase64() : null}.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return {@code hash != null ? hash.toBase64() : null}.
+     */
+    public String format(Hash hash) {
+        return hash != null ? hash.toBase64() : null;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java
new file mode 100644
index 0000000..1eca5cc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.util.UnknownClassException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This default {@code HashFormatFactory} implementation heuristically determines a {@code HashFormat} class to
+ * instantiate based on the input argument and returns a new instance of the discovered class.  The heuristics are
+ * detailed in the {@link #getInstance(String) getInstance} method documentation.
+ *
+ * @since 1.2
+ */
+public class DefaultHashFormatFactory implements HashFormatFactory {
+
+    private Map<String, String> formatClassNames; //id - to - fully qualified class name
+
+    private Set<String> searchPackages; //packages to search for HashFormat implementations
+
+    public DefaultHashFormatFactory() {
+        this.searchPackages = new HashSet<String>();
+        this.formatClassNames = new HashMap<String, String>();
+    }
+
+    /**
+     * Returns a {@code hashFormatAlias}-to-<code>fullyQualifiedHashFormatClassNameImplementation</code> map.
+     * <p/>
+     * This map will be used by the {@link #getInstance(String) getInstance} implementation:  that method's argument
+     * will be used as a lookup key to this map.  If the map returns a value, that value will be used to instantiate
+     * and return a new {@code HashFormat} instance.
+     * <h3>Defaults</h3>
+     * Shiro's default HashFormat implementations (as listed by the {@link ProvidedHashFormat} enum) will
+     * be searched automatically independently of this map.  You only need to populate this map with custom
+     * {@code HashFormat} implementations that are <em>not</em> already represented by a {@code ProvidedHashFormat}.
+     * <h3>Efficiency</h3>
+     * Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages},
+     * but search packages may be more convenient depending on the number of {@code HashFormat} implementations that
+     * need to be supported by this factory.
+     *
+     * @return a {@code hashFormatAlias}-to-<code>fullyQualifiedHashFormatClassNameImplementation</code> map.
+     */
+    public Map<String, String> getFormatClassNames() {
+        return formatClassNames;
+    }
+
+    /**
+     * Sets the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation} map to be used in
+     * the {@link #getInstance(String)} implementation.  See the {@link #getFormatClassNames()} JavaDoc for more
+     * information.
+     * <h3>Efficiency</h3>
+     * Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages},
+     * but search packages may be more convenient depending on the number of {@code HashFormat} implementations that
+     * need to be supported by this factory.
+     *
+     * @param formatClassNames the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation}
+     *                         map to be used in the {@link #getInstance(String)} implementation.
+     */
+    public void setFormatClassNames(Map<String, String> formatClassNames) {
+        this.formatClassNames = formatClassNames;
+    }
+
+    /**
+     * Returns a set of package names that can be searched for {@link HashFormat} implementations according to
+     * heuristics defined in the {@link #getHashFormatClass(String, String) getHashFormat(packageName, token)} JavaDoc.
+     * <h3>Efficiency</h3>
+     * Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames}
+     * map, but it may be more convenient depending on the number of {@code HashFormat} implementations that
+     * need to be supported by this factory.
+     *
+     * @return a set of package names that can be searched for {@link HashFormat} implementations
+     * @see #getHashFormatClass(String, String)
+     */
+    public Set<String> getSearchPackages() {
+        return searchPackages;
+    }
+
+    /**
+     * Sets a set of package names that can be searched for {@link HashFormat} implementations according to
+     * heuristics defined in the {@link #getHashFormatClass(String, String) getHashFormat(packageName, token)} JavaDoc.
+     * <h3>Efficiency</h3>
+     * Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames}
+     * map, but it may be more convenient depending on the number of {@code HashFormat} implementations that
+     * need to be supported by this factory.
+     *
+     * @param searchPackages a set of package names that can be searched for {@link HashFormat} implementations
+     */
+    public void setSearchPackages(Set<String> searchPackages) {
+        this.searchPackages = searchPackages;
+    }
+
+    public HashFormat getInstance(String in) {
+        if (in == null) {
+            return null;
+        }
+
+        HashFormat hashFormat = null;
+        Class clazz = null;
+
+        //NOTE: this code block occurs BEFORE calling getHashFormatClass(in) on purpose as a performance
+        //optimization.  If the input arg is an MCF-formatted string, there will be many unnecessary ClassLoader
+        //misses which can be slow.  By checking the MCF-formatted option, we can significantly improve performance
+        if (in.startsWith(ModularCryptFormat.TOKEN_DELIMITER)) {
+            //odds are high that the input argument is not a fully qualified class name or a format key (e.g. 'hex',
+            //base64' or 'shiro1').  Try to find the key and lookup via that:
+            String test = in.substring(ModularCryptFormat.TOKEN_DELIMITER.length());
+            String[] tokens = test.split("\\" + ModularCryptFormat.TOKEN_DELIMITER);
+            //the MCF ID is always the first token in the delimited string:
+            String possibleMcfId = (tokens != null && tokens.length > 0) ? tokens[0] : null;
+            if (possibleMcfId != null) {
+                //found a possible MCF ID - test it using our heuristics to see if we can find a corresponding class:
+                clazz = getHashFormatClass(possibleMcfId);
+            }
+        }
+
+        if (clazz == null) {
+            //not an MCF-formatted string - use the unaltered input arg and go through our heuristics:
+            clazz = getHashFormatClass(in);
+        }
+
+        if (clazz != null) {
+            //we found a HashFormat class - instantiate it:
+            hashFormat = newHashFormatInstance(clazz);
+        }
+
+        return hashFormat;
+    }
+
+    /**
+     * Heuristically determine the fully qualified HashFormat implementation class name based on the specified
+     * token.
+     * <p/>
+     * This implementation functions as follows (in order):
+     * <ol>
+     * <li>See if the argument can be used as a lookup key in the {@link #getFormatClassNames() formatClassNames}
+     * map.  If a value (a fully qualified class name {@link HashFormat HashFormat} implementation) is found,
+     * {@link ClassUtils#forName(String) lookup} the class and return it.</li>
+     * <li>
+     * Check to see if the token argument is a
+     * {@link ProvidedHashFormat} enum value.  If so, acquire the corresponding {@code HashFormat} class and
+     * return it.
+     * </li>
+     * <li>
+     * Check to see if the token argument is itself a fully qualified class name.  If so, try to load the class
+     * and return it.
+     * </li>
+     * <li>If the above options do not result in a discovered class, search all all configured
+     * {@link #getSearchPackages() searchPackages} using heuristics defined in the
+     * {@link #getHashFormatClass(String, String) getHashFormatClass(packageName, token)} method documentation
+     * (relaying the {@code token} argument to that method for each configured package).
+     * </li>
+     * </ol>
+     * <p/>
+     * If a class is not discovered via any of the above means, {@code null} is returned to indicate the class
+     * could not be found.
+     *
+     * @param token the string token from which a class name will be heuristically determined.
+     * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
+     */
+    protected Class getHashFormatClass(String token) {
+
+        Class clazz = null;
+
+        //check to see if the token is a configured FQCN alias.  This is faster than searching packages,
+        //so we try this first:
+        if (this.formatClassNames != null) {
+            String value = this.formatClassNames.get(token);
+            if (value != null) {
+                //found an alias - see if the value is a class:
+                clazz = lookupHashFormatClass(value);
+            }
+        }
+
+        //check to see if the token is one of Shiro's provided FQCN aliases (again, faster than searching):
+        if (clazz == null) {
+            ProvidedHashFormat provided = ProvidedHashFormat.byId(token);
+            if (provided != null) {
+                clazz = provided.getHashFormatClass();
+            }
+        }
+
+        if (clazz == null) {
+            //check to see if 'token' was a FQCN itself:
+            clazz = lookupHashFormatClass(token);
+        }
+
+        if (clazz == null) {
+            //token wasn't a FQCN or a FQCN alias - try searching in configured packages:
+            if (this.searchPackages != null) {
+                for (String packageName : this.searchPackages) {
+                    clazz = getHashFormatClass(packageName, token);
+                    if (clazz != null) {
+                        //found it:
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (clazz != null) {
+            assertHashFormatImpl(clazz);
+        }
+
+        return clazz;
+    }
+
+    /**
+     * Heuristically determine the fully qualified {@code HashFormat} implementation class name in the specified
+     * package based on the provided token.
+     * <p/>
+     * The token is expected to be a relevant fragment of an unqualified class name in the specified package.
+     * A 'relevant fragment' can be one of the following:
+     * <ul>
+     * <li>The {@code HashFormat} implementation unqualified class name</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code Format}.  The first character of
+     * this prefix can be upper or lower case and both options will be tried.</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code HashFormat}.  The first character of
+     * this prefix can be upper or lower case and both options will be tried.</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code CryptoFormat}.  The first character
+     * of this prefix can be upper or lower case and both options will be tried.</li>
+     * </ul>
+     * <p/>
+     * Some examples:
+     * <table>
+     * <tr>
+     * <th>Package Name</th>
+     * <th>Token</th>
+     * <th>Expected Output Class</th>
+     * <th>Notes</th>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code MyBarFormat}</td>
+     * <td>{@code com.foo.whatever.MyBarFormat}</td>
+     * <td>Token is a complete unqualified class name</td>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code Bar}</td>
+     * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em>
+     * {@code com.foo.whatever.BarCryptFormat}</td>
+     * <td>The token is only part of the unqualified class name - i.e. all characters in front of the {@code *Format}
+     * {@code *HashFormat} or {@code *CryptFormat} suffix.  Note that the {@code *Format} variant will be tried before
+     * {@code *HashFormat} and then finally {@code *CryptFormat}</td>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code bar}</td>
+     * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em>
+     * {@code com.foo.whatever.BarCryptFormat}</td>
+     * <td>Exact same output as the above {@code Bar} input example. (The token differs only by the first character)</td>
+     * </tr>
+     * </table>
+     *
+     * @param packageName the package to search for matching {@code HashFormat} implementations.
+     * @param token       the string token from which a class name will be heuristically determined.
+     * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
+     */
+    protected Class getHashFormatClass(String packageName, String token) {
+        String test = token;
+        Class clazz = null;
+        String pkg = packageName == null ? "" : packageName;
+
+        //1. Assume the arg is a fully qualified class name in the classpath:
+        clazz = lookupHashFormatClass(test);
+
+        if (clazz == null) {
+            test = pkg + "." + token;
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "Format";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "Format";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "HashFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "HashFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "CryptFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "CryptFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            return null; //ran out of options
+        }
+
+        assertHashFormatImpl(clazz);
+
+        return clazz;
+    }
+
+    protected Class lookupHashFormatClass(String name) {
+        try {
+            return ClassUtils.forName(name);
+        } catch (UnknownClassException ignored) {
+        }
+
+        return null;
+    }
+
+    protected final void assertHashFormatImpl(Class clazz) {
+        if (!HashFormat.class.isAssignableFrom(clazz) || clazz.isInterface()) {
+            throw new IllegalArgumentException("Discovered class [" + clazz.getName() + "] is not a " +
+                    HashFormat.class.getName() + " implementation.");
+        }
+
+    }
+
+    protected final HashFormat newHashFormatInstance(Class clazz) {
+        assertHashFormatImpl(clazz);
+        return (HashFormat) ClassUtils.newInstance(clazz);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java
new file mode 100644
index 0000000..c65ae78
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * A {@code HashFormat} is able to format a {@link Hash} instance into a well-defined formatted String.
+ * <p/>
+ * Note that not all HashFormat algorithms are reversible.  That is, they can't be parsed and reconstituted to the
+ * original Hash instance.  The traditional <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">
+ * Unix crypt(3)</a> is one such format.
+ * <p/>
+ * The formats that <em>are</em> reversible however will be represented as {@link ParsableHashFormat} instances.
+ *
+ * @see ParsableHashFormat
+ *
+ * @since 1.2
+ */
+public interface HashFormat {
+
+    /**
+     * Returns a formatted string representing the specified Hash instance.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return a formatted string representing the specified Hash instance.
+     */
+    String format(Hash hash);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java
new file mode 100644
index 0000000..fa52691
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+/**
+ * @since 1.2
+ */
+public interface HashFormatFactory {
+
+    HashFormat getInstance(String token);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java
new file mode 100644
index 0000000..5730ac9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * {@code HashFormat} that outputs <em>only</em> The hash's digest bytes in hex format.  It does not print out
+ * anything else (salt, iterations, etc).  This implementation is mostly provided as a convenience for
+ * command-line hashing.
+ *
+ * @since 1.2
+ */
+public class HexFormat implements HashFormat {
+
+    /**
+     * Returns {@code hash != null ? hash.toHex() : null}.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return {@code hash != null ? hash.toHex() : null}.
+     */
+    public String format(Hash hash) {
+        return hash != null ? hash.toHex() : null;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java
new file mode 100644
index 0000000..ce49175
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+/**
+ * A {@code HashFormat} that supports
+ * <a href="http://packages.python.org/passlib/modular_crypt_format.html">Modular Crypt Format</a> token rules.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Crypt (unix)</a>
+ * @see <a href="http://www.tummy.com/journals/entries/jafo_20110117_054918">MCF Journal Entry</a>
+ * @since 1.2
+ */
+public interface ModularCryptFormat extends HashFormat {
+
+    public static final String TOKEN_DELIMITER = "$";
+
+    /**
+     * Returns the Modular Crypt Format identifier that indicates how the formatted String should be parsed.  This id
+     * is always in the MCF-formatted string's first token.
+     * <p/>
+     * Example values are {@code md5}, {@code 1}, {@code 2}, {@code apr1}, etc.
+     *
+     * @return the Modular Crypt Format identifier that indicates how the formatted String should be parsed.
+     */
+    String getId();
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java
new file mode 100644
index 0000000..0457568
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * A {@code ParsableHashFormat} is able to parse a formatted string and convert it into a {@link Hash} instance.
+ * <p/>
+ * This interface exists to represent {@code HashFormat}s that can offer two-way conversion
+ * (Hash -> String, String -> Hash) capabilities.  Some HashFormats, such as many {@link ModularCryptFormat}s
+ * (like Unix Crypt(3)) only support one way conversion and therefore wouldn't implement this interface.
+ *
+ * @see Shiro1CryptFormat
+ *
+ * @since 1.2
+ */
+public interface ParsableHashFormat extends HashFormat {
+
+    /**
+     * Parses the specified formatted string and returns the corresponding Hash instance.
+     *
+     * @param formatted the formatted string representing a Hash.
+     * @return the corresponding Hash instance.
+     */
+    Hash parse(String formatted);
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
new file mode 100644
index 0000000..bfd90a5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+/**
+ * An enum representing Shiro's default provided {@link HashFormat} implementations.
+ *
+ * @since 1.2
+ */
+public enum ProvidedHashFormat {
+
+    /**
+     * Value representing the {@link HexFormat} implementation.
+     */
+    HEX(HexFormat.class),
+
+    /**
+     * Value representing the {@link Base64Format} implementation.
+     */
+    BASE64(Base64Format.class),
+
+    /**
+     * Value representing the {@link Shiro1CryptFormat} implementation.
+     */
+    SHIRO1(Shiro1CryptFormat.class);
+
+    private final Class<? extends HashFormat> clazz;
+
+    private ProvidedHashFormat(Class<? extends HashFormat> clazz) {
+        this.clazz = clazz;
+    }
+
+    Class<? extends HashFormat> getHashFormatClass() {
+        return this.clazz;
+    }
+
+    public static ProvidedHashFormat byId(String id) {
+        if (id == null) {
+            return null;
+        }
+        try {
+            return valueOf(id.toUpperCase());
+        } catch (IllegalArgumentException ignored) {
+            return null;
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java b/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java
new file mode 100644
index 0000000..5f78828
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * The {@code Shiro1CryptFormat} is a fully reversible
+ * <a href="http://packages.python.org/passlib/modular_crypt_format.html">Modular Crypt Format</a> (MCF).  Because it is
+ * fully reversible (i.e. Hash -> String, String -> Hash), it does NOT use the traditional MCF encoding alphabet
+ * (the traditional MCF encoding, aka H64, is bit-destructive and cannot be reversed).  Instead, it uses fully
+ * reversible Base64 encoding for the Hash digest and any salt value.
+ * <h2>Format</h2>
+ * <p>Hash instances formatted with this implementation will result in a String with the following dollar-sign ($)
+ * delimited format:</p>
+ * <pre>
+ * <b>$</b>mcfFormatId<b>$</b>algorithmName<b>$</b>iterationCount<b>$</b>base64EncodedSalt<b>$</b>base64EncodedDigest
+ * </pre>
+ * <p>Each token is defined as follows:</p>
+ * <table>
+ *     <tr>
+ *         <th>Position</th>
+ *         <th>Token</th>
+ *         <th>Description</th>
+ *         <th>Required?</th>
+ *     </tr>
+ *     <tr>
+ *         <td>1</td>
+ *         <td>{@code mcfFormatId}</td>
+ *         <td>The Modular Crypt Format identifier for this implementation, equal to <b>{@code shiro1}</b>.
+ *             ( This implies that all {@code shiro1} MCF-formatted strings will always begin with the prefix
+ *             {@code $shiro1$} ).</td>
+ *         <td>true</td>
+ *     </tr>
+ *     <tr>
+ *         <td>2</td>
+ *         <td>{@code algorithmName}</td>
+ *         <td>The name of the hash algorithm used to perform the hash.  This is an algorithm name understood by
+ *         {@code MessageDigest}.{@link java.security.MessageDigest#getInstance(String) getInstance}, for example
+ *         {@code MD5}, {@code SHA-256}, {@code SHA-256}, etc.</td>
+ *         <td>true</td>
+ *     </tr>
+ *     <tr>
+ *         <td>3</td>
+ *         <td>{@code iterationCount}</td>
+ *         <td>The number of hash iterations performed.</td>
+ *         <td>true (1 <= N <= Integer.MAX_VALUE)</td>
+ *     </tr>
+ *     <tr>
+ *         <td>4</td>
+ *         <td>{@code base64EncodedSalt}</td>
+ *         <td>The Base64-encoded salt byte array.  This token only exists if a salt was used to perform the hash.</td>
+ *         <td>false</td>
+ *     </tr>
+ *     <tr>
+ *         <td>5</td>
+ *         <td>{@code base64EncodedDigest}</td>
+ *         <td>The Base64-encoded digest byte array.  This is the actual hash result.</td>
+ *         <td>true</td>
+ *     </tr>
+ * </table>
+ *
+ * @see ModularCryptFormat
+ * @see ParsableHashFormat
+ *
+ * @since 1.2
+ */
+public class Shiro1CryptFormat implements ModularCryptFormat, ParsableHashFormat {
+
+    public static final String ID = "shiro1";
+    public static final String MCF_PREFIX = TOKEN_DELIMITER + ID + TOKEN_DELIMITER;
+
+    public Shiro1CryptFormat() {
+    }
+
+    public String getId() {
+        return ID;
+    }
+
+    public String format(Hash hash) {
+        if (hash == null) {
+            return null;
+        }
+
+        String algorithmName = hash.getAlgorithmName();
+        ByteSource salt = hash.getSalt();
+        int iterations = hash.getIterations();
+        StringBuilder sb = new StringBuilder(MCF_PREFIX).append(algorithmName).append(TOKEN_DELIMITER).append(iterations).append(TOKEN_DELIMITER);
+
+        if (salt != null) {
+            sb.append(salt.toBase64());
+        }
+
+        sb.append(TOKEN_DELIMITER);
+        sb.append(hash.toBase64());
+
+        return sb.toString();
+    }
+
+    public Hash parse(String formatted) {
+        if (formatted == null) {
+            return null;
+        }
+        if (!formatted.startsWith(MCF_PREFIX)) {
+            //TODO create a HashFormatException class
+            String msg = "The argument is not a valid '" + ID + "' formatted hash.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        String suffix = formatted.substring(MCF_PREFIX.length());
+        String[] parts = suffix.split("\\$");
+
+        //last part is always the digest/checksum, Base64-encoded:
+        int i = parts.length-1;
+        String digestBase64 = parts[i--];
+        //second-to-last part is always the salt, Base64-encoded:
+        String saltBase64 = parts[i--];
+        String iterationsString = parts[i--];
+        String algorithmName = parts[i];
+
+        byte[] digest = Base64.decode(digestBase64);
+        ByteSource salt = null;
+
+        if (StringUtils.hasLength(saltBase64)) {
+            byte[] saltBytes = Base64.decode(saltBase64);
+            salt = ByteSource.Util.bytes(saltBytes);
+        }
+
+        int iterations;
+        try {
+            iterations = Integer.parseInt(iterationsString);
+        } catch (NumberFormatException e) {
+            String msg = "Unable to parse formatted hash string: " + formatted;
+            throw new IllegalArgumentException(msg, e);
+        }
+
+        SimpleHash hash = new SimpleHash(algorithmName);
+        hash.setBytes(digest);
+        if (salt != null) {
+            hash.setSalt(salt);
+        }
+        hash.setIterations(iterations);
+
+        return hash;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/crypto/hash/package-info.java b/core/src/main/java/org/apache/shiro/crypto/hash/package-info.java
new file mode 100644
index 0000000..7d0e4c5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/hash/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Cryptographic Hashing components that greatly simplify one-way data hashing in an application.
+ * <p/>
+ * The {@link org.apache.shiro.crypto.hash.Hash Hash} interface and its implementations are significantly
+ * easier to understand and use compared to the JDK's <code>MessageDigest</code> mechanism.
+ */
+package org.apache.shiro.crypto.hash;
diff --git a/core/src/main/java/org/apache/shiro/crypto/package-info.java b/core/src/main/java/org/apache/shiro/crypto/package-info.java
new file mode 100644
index 0000000..f84853d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/crypto/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Cryptography Cipher and Hashing components that greatly simplify the JDK's cryptography concepts and
+ * add additional convenient behavior.
+ * <p/>
+ * The most important interface in this package is the {@link org.apache.shiro.crypto.CipherService CipherService}
+ * interface, which allows one to encrypt and decrypt sensitive data.
+ */
+package org.apache.shiro.crypto;
diff --git a/core/src/main/java/org/apache/shiro/dao/DataAccessException.java b/core/src/main/java/org/apache/shiro/dao/DataAccessException.java
new file mode 100644
index 0000000..84ce736
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/dao/DataAccessException.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.dao;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Generic exception representing a problem when attempting to access data.
+ * <p/>
+ * The idea was borrowed from the Spring Framework, which has a nice model for a generic DAO exception hierarchy.
+ * Unfortunately we can't use it as we can't force a Spring API usage on all Shiro end-users.
+ *
+ * @since 1.2
+ */
+public abstract class DataAccessException extends ShiroException {
+
+    /**
+     * Constructs a DataAccessException with a message explaining the cause of the exception.
+     *
+     * @param message the message explaining the cause of the exception
+     */
+    public DataAccessException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a DataAccessException with a message explaining the cause of the exception.
+     *
+     * @param message the explanation
+     * @param cause   the root cause of the exception, typically an API-specific exception
+     */
+    public DataAccessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/dao/InvalidResourceUsageException.java b/core/src/main/java/org/apache/shiro/dao/InvalidResourceUsageException.java
new file mode 100644
index 0000000..95a25cc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/dao/InvalidResourceUsageException.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.dao;
+
+/**
+ * Root exception indicating invalid or incorrect usage of a data access resource.  This is thrown
+ * typically when incorrectly using the resource or its API.
+ *
+ * @since 1.2
+ */
+public class InvalidResourceUsageException extends DataAccessException {
+
+    /**
+     * Constructs an InvalidResourceUsageException with a message explaining the cause of the exception.
+     *
+     * @param message the message explaining the cause of the exception
+     */
+    public InvalidResourceUsageException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a InvalidResourceUsageException with a message explaining the cause of the exception.
+     *
+     * @param message the explanation
+     * @param cause   the root cause of the exception, typically an API-specific exception
+     */
+    public InvalidResourceUsageException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/dao/package-info.java b/core/src/main/java/org/apache/shiro/dao/package-info.java
new file mode 100644
index 0000000..d2798cd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/dao/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Package containing various components useful when building Data Access Objects (DAOs), including a generic
+ * Data Access Exception hierarchy.  As {@link org.apache.shiro.realm.Realm Realm} instances are typically viewed as
+ * security-specific DAOs, this package is often useful when implementing Realms.
+ *
+ * @since 1.2
+ */
+package org.apache.shiro.dao;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java b/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
new file mode 100644
index 0000000..13843ed
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Simple/default {@code Environment} implementation that stores Shiro objects as key-value pairs in a
+ * {@link java.util.Map Map} instance.  The key is the object name, the value is the object itself.
+ *
+ * @since 1.2
+ */
+public class DefaultEnvironment implements NamedObjectEnvironment, Destroyable {
+
+    /**
+     * The default name under which the application's {@code SecurityManager} instance may be acquired, equal to
+     * {@code securityManager}.
+     */
+    public static final String DEFAULT_SECURITY_MANAGER_KEY = "securityManager";
+
+    protected final Map<String, Object> objects;
+    private String securityManagerName;
+
+    /**
+     * Creates a new instance with a thread-safe {@link ConcurrentHashMap} backing map.
+     */
+    public DefaultEnvironment() {
+        this(new ConcurrentHashMap<String, Object>());
+    }
+
+    /**
+     * Creates a new instance with the specified backing map.
+     *
+     * @param seed backing map to use to maintain Shiro objects.
+     */
+    @SuppressWarnings({"unchecked"})
+    public DefaultEnvironment(Map<String, ?> seed) {
+        this.securityManagerName = DEFAULT_SECURITY_MANAGER_KEY;
+        if (seed == null) {
+            throw new IllegalArgumentException("Backing map cannot be null.");
+        }
+        this.objects = (Map<String, Object>) seed;
+    }
+
+    /**
+     * Returns the application's {@code SecurityManager} instance accessible in the backing map using the
+     * {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
+     * <p/>
+     * This implementation guarantees that a non-null instance is always returned, as this is expected for
+     * Environment API end-users.  If subclasses have the need to perform the map lookup without this guarantee
+     * (for example, during initialization when the instance may not have been added to the map yet), the
+     * {@link #lookupSecurityManager()} method is provided as an alternative.
+     *
+     * @return the application's {@code SecurityManager} instance accessible in the backing map using the
+     *         {@link #getSecurityManagerName() securityManagerName} property as the lookup key.
+     */
+    public SecurityManager getSecurityManager() throws IllegalStateException {
+        SecurityManager securityManager = lookupSecurityManager();
+        if (securityManager == null) {
+            throw new IllegalStateException("No SecurityManager found in Environment.  This is an invalid " +
+                    "environment state.");
+        }
+        return securityManager;
+    }
+
+    public void setSecurityManager(SecurityManager securityManager) {
+        if (securityManager == null) {
+            throw new IllegalArgumentException("Null SecurityManager instances are not allowed.");
+        }
+        String name = getSecurityManagerName();
+        setObject(name, securityManager);
+    }
+
+    /**
+     * Looks up the {@code SecurityManager} instance in the backing map without performing any non-null guarantees.
+     *
+     * @return the {@code SecurityManager} in the backing map, or {@code null} if it has not yet been populated.
+     */
+    protected SecurityManager lookupSecurityManager() {
+        String name = getSecurityManagerName();
+        return getObject(name, SecurityManager.class);
+    }
+
+    /**
+     * Returns the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     * instance.  Unless set otherwise, the default is {@code securityManager}.
+     *
+     * @return the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     *         instance.
+     */
+    public String getSecurityManagerName() {
+        return securityManagerName;
+    }
+
+    /**
+     * Sets the name of the {@link SecurityManager} instance in the backing map.  Used as a key to lookup the
+     * instance.  Unless set otherwise, the default is {@code securityManager}.
+     *
+     * @param securityManagerName the name of the {@link SecurityManager} instance in the backing map.  Used as a key
+     *                            to lookup the instance. 
+     */
+    public void setSecurityManagerName(String securityManagerName) {
+        this.securityManagerName = securityManagerName;
+    }
+
+    /**
+     * Returns the live (modifiable) internal objects collection.
+     *
+     * @return the live (modifiable) internal objects collection.
+     */
+    public Map<String,Object> getObjects() {
+        return this.objects;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public <T> T getObject(String name, Class<T> requiredType) throws RequiredTypeException {
+        if (name == null) {
+            throw new NullPointerException("name parameter cannot be null.");
+        }
+        if (requiredType == null) {
+            throw new NullPointerException("requiredType parameter cannot be null.");
+        }
+        Object o = this.objects.get(name);
+        if (o == null) {
+            return null;
+        }
+        if (!requiredType.isInstance(o)) {
+            String msg = "Object named '" + name + "' is not of required type [" + requiredType.getName() + "].";
+            throw new RequiredTypeException(msg);
+        }
+        return (T)o;
+    }
+
+    public void setObject(String name, Object instance) {
+        if (name == null) {
+            throw new NullPointerException("name parameter cannot be null.");
+        }
+        if (instance == null) {
+            this.objects.remove(name);
+        } else {
+            this.objects.put(name, instance);
+        }
+    }
+
+
+    public void destroy() throws Exception {
+        LifecycleUtils.destroy(this.objects.values());
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/env/Environment.java b/core/src/main/java/org/apache/shiro/env/Environment.java
new file mode 100644
index 0000000..c7c4551
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/Environment.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+import org.apache.shiro.mgt.SecurityManager;
+
+/**
+ * An {@code Environment} instance encapsulates all of the objects that Shiro requires to function.  It is essentially
+ * a 'meta' object from which all Shiro components can be obtained for an application.
+ * <p/>
+ * An {@code Environment} instance is usually created as a result of parsing a Shiro configuration file.  The
+ * environment instance can be stored in any place the application deems necessary, and from it, can retrieve any
+ * of Shiro's components that might be necessary in implementing security behavior.
+ * <p/>
+ * For example, the most obvious component accessible via an {@code Environment} instance is the application's
+ * {@link #getSecurityManager() securityManager}.
+ *
+ * @since 1.2
+ */
+public interface Environment {
+
+    /**
+     * Returns the application's {@code SecurityManager} instance.
+     *
+     * @return the application's {@code SecurityManager} instance.
+     */
+    SecurityManager getSecurityManager();
+}
diff --git a/core/src/main/java/org/apache/shiro/env/EnvironmentException.java b/core/src/main/java/org/apache/shiro/env/EnvironmentException.java
new file mode 100644
index 0000000..1ab5158
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/EnvironmentException.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Exception thrown for errors related to {@link Environment} instances or configuration.
+ *
+ * @since 1.2
+ */
+public class EnvironmentException extends ShiroException {
+
+    public EnvironmentException(String message) {
+        super(message);
+    }
+
+    public EnvironmentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java b/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java
new file mode 100644
index 0000000..1d6aa34
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+/**
+ * An environment that supports object lookup by name.
+ *
+ * @since 1.2
+ */
+public interface NamedObjectEnvironment extends Environment {
+
+    /**
+     * Returns the object in Shiro's environment with the specified name and type or {@code null} if
+     * no object with that name was found.
+     *
+     * @param name the assigned name of the object.
+     * @param requiredType the class to which the discovered object must be assignable.
+     * @param <T> the type of the class
+     * @throws RequiredTypeException if the discovered object does not equal, extend, or implement the specified class.
+     * @return the object in Shiro's environment with the specified name (of the specified type) or {@code null} if
+     * no object with that name was found.
+     */
+    <T> T getObject(String name, Class<T> requiredType) throws RequiredTypeException;
+}
diff --git a/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java b/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java
new file mode 100644
index 0000000..f6e0b7c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.env;
+
+/**
+ * Exception thrown when attempting to acquire an object of a required type and that object does not equal, extend, or
+ * implement a specified {@code Class}.
+ *
+ * @since 1.2
+ */
+public class RequiredTypeException extends EnvironmentException {
+
+    public RequiredTypeException(String message) {
+        super(message);
+    }
+
+    public RequiredTypeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/env/package-info.java b/core/src/main/java/org/apache/shiro/env/package-info.java
new file mode 100644
index 0000000..6ad3bd3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/env/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Concepts used to represent Shiro's aggregate state in an application.  An {@link Environment} instance represents
+ * everything Shiro needs to function in an application.
+ *
+ * @see Environment
+ */
+package org.apache.shiro.env;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/functor/Translator.java b/core/src/main/java/org/apache/shiro/functor/Translator.java
new file mode 100644
index 0000000..2c1d943
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/functor/Translator.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.functor;
+
+/**
+ * Generic <a href="http://en.wikipedia.org/wiki/Functor">functor</a> interface representing a data translation (or
+ * conversion) operation.  For a given input, a Translator can translate an input argument into a potentially different
+ * output value.
+ * <p/>
+ * One example of where this is particularly convenient is API translation: translating a 3rd-party framework Exception
+ * to a Shiro Exception or vice-versa.
+ *
+ * @param <I> The input type
+ * @param <O> The output type
+ *
+ * @since 1.2
+ */
+public interface Translator<I,O> {
+
+    /**
+     * Translates the input argument into the required output instance.
+     *
+     * @param input the input data to translate
+     * @return the translated output data
+     * @since 1.2
+     */
+    O translate(I input);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/functor/package-info.java b/core/src/main/java/org/apache/shiro/functor/package-info.java
new file mode 100644
index 0000000..9eb9915
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/functor/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Package containing <a href="http://en.wikipedia.org/wiki/Functor">functor</a> components used for data translation
+ * or conversion.  Of particular note is the {@link Translator} interface and its implementations.
+ * <p/>
+ * Translators are useful in a framework like Shiro which can integrate with many other 3rd-party APIs and
+ * frameworks.  A {@code Translator} is convenient when translating from one API concept into another, for
+ * example, translating a 3rd-party framework Exception to a Shiro Exception or vice-versa.
+ *
+ * @since 1.2
+ * @see Translator
+ */
+package org.apache.shiro.functor;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/io/ClassResolvingObjectInputStream.java b/core/src/main/java/org/apache/shiro/io/ClassResolvingObjectInputStream.java
new file mode 100644
index 0000000..1804b22
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/ClassResolvingObjectInputStream.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.UnknownClassException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+/**
+ * Enables correct ClassLoader lookup in various environments (e.g. JEE Servers, etc).
+ *
+ * @since 1.2
+ * @see <a href="https://issues.apache.org/jira/browse/SHIRO-334">SHIRO-334</a>
+ */
+public class ClassResolvingObjectInputStream extends ObjectInputStream {
+
+    public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {
+        super(inputStream);
+    }
+
+    /**
+     * Resolves an {@link ObjectStreamClass} by delegating to Shiro's 
+     * {@link ClassUtils#forName(String)} utility method, which is known to work in all ClassLoader environments.
+     * 
+     * @param osc the ObjectStreamClass to resolve the class name.
+     * @return the discovered class
+     * @throws IOException never - declaration retained for subclass consistency
+     * @throws ClassNotFoundException if the class could not be found in any known ClassLoader
+     */
+    @Override
+    protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
+        try {
+            return ClassUtils.forName(osc.getName());
+        } catch (UnknownClassException e) {
+            throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/io/DefaultSerializer.java b/core/src/main/java/org/apache/shiro/io/DefaultSerializer.java
new file mode 100644
index 0000000..6fa86b8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/DefaultSerializer.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import java.io.*;
+
+/**
+ * Serializer implementation that uses the default JVM serialization mechanism (Object Input/Output Streams).
+ *
+ * @since 0.9
+ */
+public class DefaultSerializer<T> implements Serializer<T> {
+
+    /**
+     * This implementation serializes the Object by using an {@link ObjectOutputStream} backed by a
+     * {@link ByteArrayOutputStream}.  The {@code ByteArrayOutputStream}'s backing byte array is returned.
+     *
+     * @param o the Object to convert into a byte[] array.
+     * @return the bytes representing the serialized object using standard JVM serialization.
+     * @throws SerializationException wrapping a {@link IOException} if something goes wrong with the streams.
+     */
+    public byte[] serialize(T o) throws SerializationException {
+        if (o == null) {
+            String msg = "argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        BufferedOutputStream bos = new BufferedOutputStream(baos);
+
+        try {
+            ObjectOutputStream oos = new ObjectOutputStream(bos);
+            oos.writeObject(o);
+            oos.close();
+            return baos.toByteArray();
+        } catch (IOException e) {
+            String msg = "Unable to serialize object [" + o + "].  " +
+                    "In order for the DefaultSerializer to serialize this object, the [" + o.getClass().getName() + "] " +
+                    "class must implement java.io.Serializable.";
+            throw new SerializationException(msg, e);
+        }
+    }
+
+    /**
+     * This implementation deserializes the byte array using a {@link ObjectInputStream} using a source
+     * {@link ByteArrayInputStream} constructed with the argument byte array.
+     *
+     * @param serialized the raw data resulting from a previous {@link #serialize(Object) serialize} call.
+     * @return the deserialized/reconstituted object based on the given byte array
+     * @throws SerializationException if anything goes wrong using the streams.
+     */
+    public T deserialize(byte[] serialized) throws SerializationException {
+        if (serialized == null) {
+            String msg = "argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
+        BufferedInputStream bis = new BufferedInputStream(bais);
+        try {
+            ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
+            @SuppressWarnings({"unchecked"})
+            T deserialized = (T) ois.readObject();
+            ois.close();
+            return deserialized;
+        } catch (Exception e) {
+            String msg = "Unable to deserialze argument byte array.";
+            throw new SerializationException(msg, e);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/io/ResourceUtils.java b/core/src/main/java/org/apache/shiro/io/ResourceUtils.java
new file mode 100644
index 0000000..ae44d70
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/ResourceUtils.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import org.apache.shiro.util.ClassUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Static helper methods for loading {@code Stream}-backed resources.
+ *
+ * @see #getInputStreamForPath(String)
+ * @since 0.2
+ */
+public class ResourceUtils {
+
+    /**
+     * Resource path prefix that specifies to load from a classpath location, value is <b>{@code classpath:}</b>
+     */
+    public static final String CLASSPATH_PREFIX = "classpath:";
+    /**
+     * Resource path prefix that specifies to load from a url location, value is <b>{@code url:}</b>
+     */
+    public static final String URL_PREFIX = "url:";
+    /**
+     * Resource path prefix that specifies to load from a file location, value is <b>{@code file:}</b>
+     */
+    public static final String FILE_PREFIX = "file:";
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(ResourceUtils.class);
+
+    /**
+     * Prevent instantiation.
+     */
+    private ResourceUtils() {
+    }
+
+    /**
+     * Returns {@code true} if the resource path is not null and starts with one of the recognized
+     * resource prefixes ({@link #CLASSPATH_PREFIX CLASSPATH_PREFIX},
+     * {@link #URL_PREFIX URL_PREFIX}, or {@link #FILE_PREFIX FILE_PREFIX}), {@code false} otherwise.
+     *
+     * @param resourcePath the resource path to check
+     * @return {@code true} if the resource path is not null and starts with one of the recognized
+     *         resource prefixes, {@code false} otherwise.
+     * @since 0.9
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static boolean hasResourcePrefix(String resourcePath) {
+        return resourcePath != null &&
+                (resourcePath.startsWith(CLASSPATH_PREFIX) ||
+                        resourcePath.startsWith(URL_PREFIX) ||
+                        resourcePath.startsWith(FILE_PREFIX));
+    }
+
+    /**
+     * Returns {@code true} if the resource at the specified path exists, {@code false} otherwise.  This
+     * method supports scheme prefixes on the path as defined in {@link #getInputStreamForPath(String)}.
+     *
+     * @param resourcePath the path of the resource to check.
+     * @return {@code true} if the resource at the specified path exists, {@code false} otherwise.
+     * @since 0.9
+     */
+    public static boolean resourceExists(String resourcePath) {
+        InputStream stream = null;
+        boolean exists = false;
+
+        try {
+            stream = getInputStreamForPath(resourcePath);
+            exists = true;
+        } catch (IOException e) {
+            stream = null;
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
+        return exists;
+    }
+
+
+    /**
+     * Returns the InputStream for the resource represented by the specified path, supporting scheme
+     * prefixes that direct how to acquire the input stream
+     * ({@link #CLASSPATH_PREFIX CLASSPATH_PREFIX},
+     * {@link #URL_PREFIX URL_PREFIX}, or {@link #FILE_PREFIX FILE_PREFIX}).  If the path is not prefixed by one
+     * of these schemes, the path is assumed to be a file-based path that can be loaded with a
+     * {@link FileInputStream FileInputStream}.
+     *
+     * @param resourcePath the String path representing the resource to obtain.
+     * @return the InputStraem for the specified resource.
+     * @throws IOException if there is a problem acquiring the resource at the specified path.
+     */
+    public static InputStream getInputStreamForPath(String resourcePath) throws IOException {
+
+        InputStream is;
+        if (resourcePath.startsWith(CLASSPATH_PREFIX)) {
+            is = loadFromClassPath(stripPrefix(resourcePath));
+
+        } else if (resourcePath.startsWith(URL_PREFIX)) {
+            is = loadFromUrl(stripPrefix(resourcePath));
+
+        } else if (resourcePath.startsWith(FILE_PREFIX)) {
+            is = loadFromFile(stripPrefix(resourcePath));
+
+        } else {
+            is = loadFromFile(resourcePath);
+        }
+
+        if (is == null) {
+            throw new IOException("Resource [" + resourcePath + "] could not be found.");
+        }
+
+        return is;
+    }
+
+    private static InputStream loadFromFile(String path) throws IOException {
+        if (log.isDebugEnabled()) {
+            log.debug("Opening file [" + path + "]...");
+        }
+        return new FileInputStream(path);
+    }
+
+    private static InputStream loadFromUrl(String urlPath) throws IOException {
+        log.debug("Opening url {}", urlPath);
+        URL url = new URL(urlPath);
+        return url.openStream();
+    }
+
+    private static InputStream loadFromClassPath(String path) {
+        log.debug("Opening resource from class path [{}]", path);
+        return ClassUtils.getResourceAsStream(path);
+    }
+
+    private static String stripPrefix(String resourcePath) {
+        return resourcePath.substring(resourcePath.indexOf(":") + 1);
+    }
+
+    /**
+     * Convenience method that closes the specified {@link InputStream InputStream}, logging any
+     * {@link IOException IOException} that might occur. If the {@code InputStream}
+     * argument is {@code null}, this method does nothing.  It returns quietly in all cases.
+     *
+     * @param is the {@code InputStream} to close, logging any {@code IOException} that might occur.
+     */
+    public static void close(InputStream is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException e) {
+                log.warn("Error closing input stream.", e);
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/io/SerializationException.java b/core/src/main/java/org/apache/shiro/io/SerializationException.java
new file mode 100644
index 0000000..8f90ba0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/SerializationException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Root exception for problems either serializing or de-serializing data.
+ *
+ * @since Apr 23, 2008 8:58:22 AM
+ */
+public class SerializationException extends ShiroException
+{
+
+    /**
+     * Creates a new SerializationException.
+     */
+    public SerializationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new SerializationException.
+     *
+     * @param message the reason for the exception
+     */
+    public SerializationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new SerializationException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public SerializationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new SerializationException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public SerializationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/io/Serializer.java b/core/src/main/java/org/apache/shiro/io/Serializer.java
new file mode 100644
index 0000000..10a5cfa
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/Serializer.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+/**
+ * A <code>Serializer</code> converts objects to raw binary data and vice versa, enabling persistent storage
+ * of objects to files, HTTP cookies, or other mechanism.
+ * <p/>
+ * A <code>Serializer</code> should only do conversion, never change the data, such as encoding/decoding or
+ * encryption.  These orthogonal concerns are handled elsewhere by Shiro, for example, via
+ * {@link org.apache.shiro.codec.CodecSupport CodecSupport} and {@link org.apache.shiro.crypto.CipherService CipherService}s.
+ *
+ * @param <T> The type of the object being serialized and deserialized.
+ * @since 0.9
+ */
+public interface Serializer<T> {
+
+    /**
+     * Converts the specified Object into a byte[] array.  This byte[] array must be able to be reconstructed
+     * back into the original Object form via the {@link #deserialize(byte[]) deserialize} method.
+     *
+     * @param o the Object to convert into a byte[] array.
+     * @return a byte[] array representing the Object's state that can be restored later.
+     * @throws SerializationException if an error occurrs converting the Object into a byte[] array.
+     */
+    byte[] serialize(T o) throws SerializationException;
+
+    /**
+     * Converts the specified raw byte[] array back into an original Object form.  This byte[] array is expected to
+     * be the output of a previous {@link #serialize(Object) serialize} method call.
+     *
+     * @param serialized the raw data resulting from a previous {@link #serialize(Object) serialize} call.
+     * @return the Object that was previously serialized into the raw byte[] array.
+     * @throws SerializationException if an error occurrs converting the raw byte[] array back into an Object.
+     */
+    T deserialize(byte[] serialized) throws SerializationException;
+}
diff --git a/core/src/main/java/org/apache/shiro/io/XmlSerializer.java b/core/src/main/java/org/apache/shiro/io/XmlSerializer.java
new file mode 100644
index 0000000..12c2a39
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/XmlSerializer.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import java.beans.XMLDecoder;
+import java.beans.XMLEncoder;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Serializer implementation that uses the JavaBeans
+ * {@link java.beans.XMLEncoder XMLEncoder} and {@link java.beans.XMLDecoder XMLDecoder} to serialize
+ * and deserialize, respectively.
+ * <p/>
+ * <b>NOTE:</b> The JavaBeans XMLEncoder/XMLDecoder only successfully encode/decode objects when they are
+ * JavaBeans compatible!
+ * 
+ * @since 0.9
+ */
+public class XmlSerializer implements Serializer {
+
+    /**
+     * Serializes the specified <code>source</code> into a byte[] array by using the
+     * {@link java.beans.XMLEncoder XMLEncoder} to encode the object out to a
+     * {@link java.io.ByteArrayOutputStream ByteArrayOutputStream}, where the resulting byte[] array is returned.
+     * @param source the Object to convert into a byte[] array.
+     * @return the byte[] array representation of the XML encoded output.
+     */
+    public byte[] serialize(Object source) {
+        if (source == null) {
+            String msg = "argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(bos));
+        encoder.writeObject(source);
+        encoder.close();
+
+        return bos.toByteArray();
+    }
+
+    /**
+     * Deserializes the specified <code>serialized</code> source back into an Object by using a
+     * {@link java.io.ByteArrayInputStream ByteArrayInputStream} to wrap the argument and then decode this
+     * stream via an {@link java.beans.XMLDecoder XMLDecoder}, where the
+     * {@link java.beans.XMLDecoder#readObject() readObject} call results in the original Object to return.
+     * @param serialized the byte[] array representation of the XML encoded output.
+     * @return the original source Object in reconstituted form.
+     */
+    public Object deserialize(byte[] serialized) {
+        if (serialized == null) {
+            throw new IllegalArgumentException("Argument cannot be null.");
+        }
+        ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
+        XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(bis));
+        Object o = decoder.readObject();
+        decoder.close();
+        return o;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/io/package-info.java b/core/src/main/java/org/apache/shiro/io/package-info.java
new file mode 100644
index 0000000..043c3b1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/io/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support for reading and writing (input/output) raw data from multiple resource locations.
+ */
+package org.apache.shiro.io;
diff --git a/core/src/main/java/org/apache/shiro/jndi/JndiCallback.java b/core/src/main/java/org/apache/shiro/jndi/JndiCallback.java
new file mode 100644
index 0000000..deb9cc9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/jndi/JndiCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.jndi;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+
+/**
+ * Callback interface to be implemented by classes that need to perform an
+ * operation (such as a lookup) in a JNDI context. This callback approach
+ * is valuable in simplifying error handling, which is performed by the
+ * JndiTemplate class. This is a similar to JdbcTemplate's approach.
+ *
+ * <p>Note that there is hardly any need to implement this callback
+ * interface, as JndiTemplate provides all usual JNDI operations via
+ * convenience methods.
+ *
+ * <p>Note that this interface is an exact copy of the Spring Framework's identically named interface from
+ * their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require a full dependency on the
+ * Spring framework, nor does Spring make available only its JNDI classes in a small jar, or we would have used that.
+ * Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and authors have remained in tact.
+ *
+ * @see JndiTemplate
+ */
+public interface JndiCallback {
+
+    /**
+     * Do something with the given JNDI context.
+     * Implementations don't need to worry about error handling
+     * or cleanup, as the JndiTemplate class will handle this.
+     *
+     * @param ctx the current JNDI context
+     * @return a result object, or <code>null</code>
+     * @throws NamingException if thrown by JNDI methods
+     */
+    Object doInContext(Context ctx) throws NamingException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/jndi/JndiLocator.java b/core/src/main/java/org/apache/shiro/jndi/JndiLocator.java
new file mode 100644
index 0000000..1701e73
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/jndi/JndiLocator.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.jndi;
+
+import java.util.Properties;
+import javax.naming.NamingException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Convenient superclass for JNDI accessors, providing "jndiTemplate"
+ * and "jndiEnvironment" bean properties.
+ *
+ * <p>Note that this implementation is an almost exact combined copy of the Spring Framework's 'JndiAccessor' and
+ * 'JndiLocatorSupport' classes from their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require
+ * a full dependency on the Spring framework, nor does Spring make available only its JNDI classes in a small jar, or
+ * we would have used that. Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and
+ * authors have remained in tact.
+ *
+ * @see #setJndiTemplate
+ * @see #setJndiEnvironment
+ * @see #setResourceRef
+ * @since 1.1
+ */
+public class JndiLocator {
+
+    /**
+     * Private class log.
+     */
+    private static final Logger log = LoggerFactory.getLogger(JndiLocator.class);
+
+    /**
+     * JNDI prefix used in a J2EE container
+     */
+    public static final String CONTAINER_PREFIX = "java:comp/env/";
+
+    private boolean resourceRef = false;
+
+    private JndiTemplate jndiTemplate = new JndiTemplate();
+
+
+    /**
+     * Set the JNDI template to use for JNDI lookups.
+     * <p>You can also specify JNDI environment settings via "jndiEnvironment".
+     *
+     * @see #setJndiEnvironment
+     */
+    public void setJndiTemplate(JndiTemplate jndiTemplate) {
+        this.jndiTemplate = (jndiTemplate != null ? jndiTemplate : new JndiTemplate());
+    }
+
+    /**
+     * Return the JNDI template to use for JNDI lookups.
+     */
+    public JndiTemplate getJndiTemplate() {
+        return this.jndiTemplate;
+    }
+
+    /**
+     * Set the JNDI environment to use for JNDI lookups.
+     * <p>Creates a JndiTemplate with the given environment settings.
+     *
+     * @see #setJndiTemplate
+     */
+    public void setJndiEnvironment(Properties jndiEnvironment) {
+        this.jndiTemplate = new JndiTemplate(jndiEnvironment);
+    }
+
+    /**
+     * Return the JNDI environment to use for JNDI lookups.
+     */
+    public Properties getJndiEnvironment() {
+        return this.jndiTemplate.getEnvironment();
+    }
+
+    /**
+     * Set whether the lookup occurs in a J2EE container, i.e. if the prefix
+     * "java:comp/env/" needs to be added if the JNDI name doesn't already
+     * contain it. Default is "false".
+     * <p>Note: Will only get applied if no other scheme (e.g. "java:") is given.
+     */
+    public void setResourceRef(boolean resourceRef) {
+        this.resourceRef = resourceRef;
+    }
+
+    /**
+     * Return whether the lookup occurs in a J2EE container.
+     */
+    public boolean isResourceRef() {
+        return this.resourceRef;
+    }
+
+
+    /**
+     * Perform an actual JNDI lookup for the given name via the JndiTemplate.
+     * <p>If the name doesn't begin with "java:comp/env/", this prefix is added
+     * if "resourceRef" is set to "true".
+     *
+     * @param jndiName the JNDI name to look up
+     * @return the obtained object
+     * @throws javax.naming.NamingException if the JNDI lookup failed
+     * @see #setResourceRef
+     */
+    protected Object lookup(String jndiName) throws NamingException {
+        return lookup(jndiName, null);
+    }
+
+    /**
+     * Perform an actual JNDI lookup for the given name via the JndiTemplate.
+     * <p>If the name doesn't begin with "java:comp/env/", this prefix is added
+     * if "resourceRef" is set to "true".
+     *
+     * @param jndiName     the JNDI name to look up
+     * @param requiredType the required type of the object
+     * @return the obtained object
+     * @throws NamingException if the JNDI lookup failed
+     * @see #setResourceRef
+     */
+    protected Object lookup(String jndiName, Class requiredType) throws NamingException {
+        if (jndiName == null) {
+            throw new IllegalArgumentException("jndiName argument must not be null");
+        }
+        String convertedName = convertJndiName(jndiName);
+        Object jndiObject;
+        try {
+            jndiObject = getJndiTemplate().lookup(convertedName, requiredType);
+        }
+        catch (NamingException ex) {
+            if (!convertedName.equals(jndiName)) {
+                // Try fallback to originally specified name...
+                if (log.isDebugEnabled()) {
+                    log.debug("Converted JNDI name [" + convertedName +
+                            "] not found - trying original name [" + jndiName + "]. " + ex);
+                }
+                jndiObject = getJndiTemplate().lookup(jndiName, requiredType);
+            } else {
+                throw ex;
+            }
+        }
+        log.debug("Located object with JNDI name '{}'", convertedName);
+        return jndiObject;
+    }
+
+    /**
+     * Convert the given JNDI name into the actual JNDI name to use.
+     * <p>The default implementation applies the "java:comp/env/" prefix if
+     * "resourceRef" is "true" and no other scheme (e.g. "java:") is given.
+     *
+     * @param jndiName the original JNDI name
+     * @return the JNDI name to use
+     * @see #CONTAINER_PREFIX
+     * @see #setResourceRef
+     */
+    protected String convertJndiName(String jndiName) {
+        // Prepend container prefix if not already specified and no other scheme given.
+        if (isResourceRef() && !jndiName.startsWith(CONTAINER_PREFIX) && jndiName.indexOf(':') == -1) {
+            jndiName = CONTAINER_PREFIX + jndiName;
+        }
+        return jndiName;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/jndi/JndiObjectFactory.java b/core/src/main/java/org/apache/shiro/jndi/JndiObjectFactory.java
new file mode 100644
index 0000000..5bc9d63
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/jndi/JndiObjectFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.jndi;
+
+import org.apache.shiro.util.Factory;
+
+import javax.naming.NamingException;
+
+/**
+ * A factory implementation intended to be used to look up objects in jndi.
+ * @param <T>
+ * @since 1.2
+ */
+public class JndiObjectFactory<T> extends JndiLocator implements Factory<T> {
+
+    private String resourceName;
+    private Class<? extends T> requiredType;
+
+    public T getInstance() {
+        try {
+            if(requiredType != null) {
+                return requiredType.cast(this.lookup(resourceName, requiredType));
+            } else {
+                return (T) this.lookup(resourceName);
+            }
+        } catch (NamingException e) {
+            final String typeName = requiredType != null ? requiredType.getName() : "object";
+            throw new IllegalStateException("Unable to look up " + typeName + " with jndi name '" + resourceName + "'.", e);
+        }
+    }
+
+    public String getResourceName() {
+        return resourceName;
+    }
+
+    public void setResourceName(String resourceName) {
+        this.resourceName = resourceName;
+    }
+
+    public Class<? extends T> getRequiredType() {
+        return requiredType;
+    }
+
+    public void setRequiredType(Class<? extends T> requiredType) {
+        this.requiredType = requiredType;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/jndi/JndiTemplate.java b/core/src/main/java/org/apache/shiro/jndi/JndiTemplate.java
new file mode 100644
index 0000000..a987051
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/jndi/JndiTemplate.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.jndi;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class that simplifies JNDI operations. It provides methods to lookup and
+ * bind objects, and allows implementations of the {@link JndiCallback} interface
+ * to perform any operation they like with a JNDI naming context provided.
+ * <p/>
+ * <p>Note that this implementation is an almost exact copy of the Spring Framework's identically named class from
+ * their 2.5.4 distribution - we didn't want to re-invent the wheel, but not require a full dependency on the
+ * Spring framework, nor does Spring make available only its JNDI classes in a small jar, or we would have used that.
+ * Since Shiro is also Apache 2.0 licensed, all regular licenses and conditions and authors have remained in tact.
+ *
+ * @see JndiCallback
+ * @see #execute
+ */
+public class JndiTemplate {
+
+    private static final Logger log = LoggerFactory.getLogger(JndiTemplate.class);
+
+    private Properties environment;
+
+    /** Create a new JndiTemplate instance. */
+    public JndiTemplate() {
+    }
+
+    /**
+     * Create a new JndiTemplate instance, using the given environment.
+     *
+     * @param environment the Properties to initialize with
+     */
+    public JndiTemplate(Properties environment) {
+        this.environment = environment;
+    }
+
+    /**
+     * Set the environment for the JNDI InitialContext.
+     *
+     * @param environment the Properties to initialize with
+     */
+    public void setEnvironment(Properties environment) {
+        this.environment = environment;
+    }
+
+    /**
+     * Return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
+     *
+     * @return the environment for the JNDI InitialContext, or <code>null</code> if none should be used.
+     */
+    public Properties getEnvironment() {
+        return this.environment;
+    }
+
+    /**
+     * Execute the given JNDI context callback implementation.
+     *
+     * @param contextCallback JndiCallback implementation
+     * @return a result object returned by the callback, or <code>null</code>
+     * @throws NamingException thrown by the callback implementation
+     * @see #createInitialContext
+     */
+    public Object execute(JndiCallback contextCallback) throws NamingException {
+        Context ctx = createInitialContext();
+        try {
+            return contextCallback.doInContext(ctx);
+        }
+        finally {
+            try {
+                ctx.close();
+            } catch (NamingException ex) {
+                log.debug("Could not close JNDI InitialContext", ex);
+            }
+        }
+    }
+
+    /**
+     * Create a new JNDI initial context. Invoked by {@link #execute}.
+     * <p>The default implementation use this template's environment settings.
+     * Can be subclassed for custom contexts, e.g. for testing.
+     *
+     * @return the initial Context instance
+     * @throws NamingException in case of initialization errors
+     */
+    @SuppressWarnings({"unchecked"})
+    protected Context createInitialContext() throws NamingException {
+        Properties env = getEnvironment();
+        Hashtable icEnv = null;
+        if (env != null) {
+            icEnv = new Hashtable(env.size());
+            for (Enumeration en = env.propertyNames(); en.hasMoreElements();) {
+                String key = (String) en.nextElement();
+                icEnv.put(key, env.getProperty(key));
+            }
+        }
+        return new InitialContext(icEnv);
+    }
+
+    /**
+     * Look up the object with the given name in the current JNDI context.
+     *
+     * @param name the JNDI name of the object
+     * @return object found (cannot be <code>null</code>; if a not so well-behaved
+     *         JNDI implementations returns null, a NamingException gets thrown)
+     * @throws NamingException if there is no object with the given
+     *                         name bound to JNDI
+     */
+    public Object lookup(final String name) throws NamingException {
+        log.debug("Looking up JNDI object with name '{}'", name);
+        return execute(new JndiCallback() {
+            public Object doInContext(Context ctx) throws NamingException {
+                Object located = ctx.lookup(name);
+                if (located == null) {
+                    throw new NameNotFoundException(
+                            "JNDI object with [" + name + "] not found: JNDI implementation returned null");
+                }
+                return located;
+            }
+        });
+    }
+
+    /**
+     * Look up the object with the given name in the current JNDI context.
+     *
+     * @param name         the JNDI name of the object
+     * @param requiredType type the JNDI object must match. Can be an interface or
+     *                     superclass of the actual class, or <code>null</code> for any match. For example,
+     *                     if the value is <code>Object.class</code>, this method will succeed whatever
+     *                     the class of the returned instance.
+     * @return object found (cannot be <code>null</code>; if a not so well-behaved
+     *         JNDI implementations returns null, a NamingException gets thrown)
+     * @throws NamingException if there is no object with the given
+     *                         name bound to JNDI
+     */
+    public Object lookup(String name, Class requiredType) throws NamingException {
+        Object jndiObject = lookup(name);
+        if (requiredType != null && !requiredType.isInstance(jndiObject)) {
+            String msg = "Jndi object acquired under name '" + name + "' is of type [" +
+                    jndiObject.getClass().getName() + "] and not assignable to the required type [" +
+                    requiredType.getName() + "].";
+            throw new NamingException(msg);
+        }
+        return jndiObject;
+    }
+
+    /**
+     * Bind the given object to the current JNDI context, using the given name.
+     *
+     * @param name   the JNDI name of the object
+     * @param object the object to bind
+     * @throws NamingException thrown by JNDI, mostly name already bound
+     */
+    public void bind(final String name, final Object object) throws NamingException {
+        log.debug("Binding JNDI object with name '{}'", name);
+        execute(new JndiCallback() {
+            public Object doInContext(Context ctx) throws NamingException {
+                ctx.bind(name, object);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Rebind the given object to the current JNDI context, using the given name.
+     * Overwrites any existing binding.
+     *
+     * @param name   the JNDI name of the object
+     * @param object the object to rebind
+     * @throws NamingException thrown by JNDI
+     */
+    public void rebind(final String name, final Object object) throws NamingException {
+        log.debug("Rebinding JNDI object with name '{}'", name);
+        execute(new JndiCallback() {
+            public Object doInContext(Context ctx) throws NamingException {
+                ctx.rebind(name, object);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Remove the binding for the given name from the current JNDI context.
+     *
+     * @param name the JNDI name of the object
+     * @throws NamingException thrown by JNDI, mostly name not found
+     */
+    public void unbind(final String name) throws NamingException {
+        log.debug("Unbinding JNDI object with name '{}'", name);
+        execute(new JndiCallback() {
+            public Object doInContext(Context ctx) throws NamingException {
+                ctx.unbind(name);
+                return null;
+            }
+        });
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/jndi/package-info.java b/core/src/main/java/org/apache/shiro/jndi/package-info.java
new file mode 100644
index 0000000..2237426
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/jndi/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Enables accessing objects located in JNDI that might be useful when configuring a Shiro-enabled application.
+ */
+package org.apache.shiro.jndi;
diff --git a/core/src/main/java/org/apache/shiro/ldap/UnsupportedAuthenticationMechanismException.java b/core/src/main/java/org/apache/shiro/ldap/UnsupportedAuthenticationMechanismException.java
new file mode 100644
index 0000000..f731cba
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/ldap/UnsupportedAuthenticationMechanismException.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.ldap;
+
+import org.apache.shiro.dao.InvalidResourceUsageException;
+
+/**
+ * Exception thrown when a configured LDAP
+ * <a href="http://download.oracle.com/javase/jndi/tutorial/ldap/security/auth.html">
+ * Authentication Mechanism</a> is unsupported by the target LDAP server. (e.g. DIGEST-MD5, simple, etc)
+ *
+ * @since 1.2
+ */
+public class UnsupportedAuthenticationMechanismException extends InvalidResourceUsageException {
+
+    public UnsupportedAuthenticationMechanismException(String message) {
+        super(message);
+    }
+
+    public UnsupportedAuthenticationMechanismException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/ldap/package-info.java b/core/src/main/java/org/apache/shiro/ldap/package-info.java
new file mode 100644
index 0000000..48ccc3b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/ldap/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.shiro.ldap;
+
+/**
+ * Support for accessing <a href="http://en.wikipedia.org/wiki/LDAP">LDAP</a> data sources.
+ *
+ * @since 1.2
+ */
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
new file mode 100644
index 0000000..0b1df0a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.RememberMeAuthenticationToken;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.crypto.AesCipherService;
+import org.apache.shiro.crypto.CipherService;
+import org.apache.shiro.io.DefaultSerializer;
+import org.apache.shiro.io.Serializer;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.util.ByteSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation of the {@code RememberMeManager} interface that handles
+ * {@link #setSerializer(org.apache.shiro.io.Serializer) serialization} and
+ * {@link #setCipherService encryption} of the remembered user identity.
+ * <p/>
+ * The remembered identity storage location and details are left to subclasses.
+ * <h2>Default encryption key</h2>
+ * This implementation uses an {@link AesCipherService AesCipherService} for strong encryption by default.  It also
+ * uses a default generated symmetric key to both encrypt and decrypt data.  As AES is a symmetric cipher, the same
+ * {@code key} is used to both encrypt and decrypt data, BUT NOTE:
+ * <p/>
+ * Because Shiro is an open-source project, if anyone knew that you were using Shiro's default
+ * {@code key}, they could download/view the source, and with enough effort, reconstruct the {@code key}
+ * and decode encrypted data at will.
+ * <p/>
+ * Of course, this key is only really used to encrypt the remembered {@code PrincipalCollection} which is typically
+ * a user id or username.  So if you do not consider that sensitive information, and you think the default key still
+ * makes things 'sufficiently difficult', then you can ignore this issue.
+ * <p/>
+ * However, if you do feel this constitutes sensitive information, it is recommended that you provide your own
+ * {@code key} via the {@link #setCipherKey setCipherKey} method to a key known only to your application,
+ * guaranteeing that no third party can decrypt your data.  You can generate your own key by calling the
+ * {@code CipherService}'s {@link org.apache.shiro.crypto.AesCipherService#generateNewKey() generateNewKey} method
+ * and using that result as the {@link #setCipherKey cipherKey} configuration attribute.
+ *
+ * @since 0.9
+ */
+public abstract class AbstractRememberMeManager implements RememberMeManager {
+
+    /**
+     * private inner log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);
+
+    /**
+     * The following Base64 string was generated by auto-generating an AES Key:
+     * <pre>
+     * AesCipherService aes = new AesCipherService();
+     * byte[] key = aes.generateNewKey().getEncoded();
+     * String base64 = Base64.encodeToString(key);
+     * </pre>
+     * The value of 'base64' was copied-n-pasted here:
+     */
+    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
+
+    /**
+     * Serializer to use for converting PrincipalCollection instances to/from byte arrays
+     */
+    private Serializer<PrincipalCollection> serializer;
+
+    /**
+     * Cipher to use for encrypting/decrypting serialized byte arrays for added security
+     */
+    private CipherService cipherService;
+
+    /**
+     * Cipher encryption key to use with the Cipher when encrypting data
+     */
+    private byte[] encryptionCipherKey;
+
+    /**
+     * Cipher decryption key to use with the Cipher when decrypting data
+     */
+    private byte[] decryptionCipherKey;
+
+    /**
+     * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
+     * an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
+     */
+    public AbstractRememberMeManager() {
+        this.serializer = new DefaultSerializer<PrincipalCollection>();
+        this.cipherService = new AesCipherService();
+        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
+    }
+
+    /**
+     * Returns the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     * persistent remember me storage.
+     * <p/>
+     * Unless overridden by the {@link #setSerializer} method, the default instance is a
+     * {@link org.apache.shiro.io.DefaultSerializer}.
+     *
+     * @return the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     *         persistent remember me storage.
+     */
+    public Serializer<PrincipalCollection> getSerializer() {
+        return serializer;
+    }
+
+    /**
+     * Sets the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances for
+     * persistent remember me storage.
+     * <p/>
+     * Unless overridden by this method, the default instance is a {@link DefaultSerializer}.
+     *
+     * @param serializer the {@code Serializer} used to serialize and deserialize {@link PrincipalCollection} instances
+     *                   for persistent remember me storage.
+     */
+    public void setSerializer(Serializer<PrincipalCollection> serializer) {
+        this.serializer = serializer;
+    }
+
+    /**
+     * Returns the {@code CipherService} to use for encrypting and decrypting serialized identity data to prevent easy
+     * inspection of Subject identity data.
+     * <p/>
+     * Unless overridden by the {@link #setCipherService} method, the default instance is an {@link AesCipherService}.
+     *
+     * @return the {@code Cipher} to use for encrypting and decrypting serialized identity data to prevent easy
+     *         inspection of Subject identity data
+     */
+    public CipherService getCipherService() {
+        return cipherService;
+    }
+
+    /**
+     * Sets the {@code CipherService} to use for encrypting and decrypting serialized identity data to prevent easy
+     * inspection of Subject identity data.
+     * <p/>
+     * If the CipherService is a symmetric CipherService (using the same key for both encryption and decryption), you
+     * should set your key via the {@link #setCipherKey(byte[])} method.
+     * <p/>
+     * If the CipherService is an asymmetric CipherService (different keys for encryption and decryption, such as
+     * public/private key pairs), you should set your encryption and decryption key via the respective
+     * {@link #setEncryptionCipherKey(byte[])} and {@link #setDecryptionCipherKey(byte[])} methods.
+     * <p/>
+     * <b>N.B.</b> Unless overridden by this method, the default CipherService instance is an
+     * {@link AesCipherService}.  This {@code RememberMeManager} implementation already has a configured symmetric key
+     * to use for encryption and decryption, but it is recommended to provide your own for added security.  See the
+     * class-level JavaDoc for more information and why it might be good to provide your own.
+     *
+     * @param cipherService the {@code CipherService} to use for encrypting and decrypting serialized identity data to
+     *                      prevent easy inspection of Subject identity data.
+     */
+    public void setCipherService(CipherService cipherService) {
+        this.cipherService = cipherService;
+    }
+
+    /**
+     * Returns the cipher key to use for encryption operations.
+     *
+     * @return the cipher key to use for encryption operations.
+     * @see #setCipherService for a description of the various {@code get/set*Key} methods.
+     */
+    public byte[] getEncryptionCipherKey() {
+        return encryptionCipherKey;
+    }
+
+    /**
+     * Sets the encryption key to use for encryption operations.
+     *
+     * @param encryptionCipherKey the encryption key to use for encryption operations.
+     * @see #setCipherService for a description of the various {@code get/set*Key} methods.
+     */
+    public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
+        this.encryptionCipherKey = encryptionCipherKey;
+    }
+
+    /**
+     * Returns the decryption cipher key to use for decryption operations.
+     *
+     * @return the cipher key to use for decryption operations.
+     * @see #setCipherService for a description of the various {@code get/set*Key} methods.
+     */
+    public byte[] getDecryptionCipherKey() {
+        return decryptionCipherKey;
+    }
+
+    /**
+     * Sets the decryption key to use for decryption operations.
+     *
+     * @param decryptionCipherKey the decryption key to use for decryption operations.
+     * @see #setCipherService for a description of the various {@code get/set*Key} methods.
+     */
+    public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
+        this.decryptionCipherKey = decryptionCipherKey;
+    }
+
+    /**
+     * Convenience method that returns the cipher key to use for <em>both</em> encryption and decryption.
+     * <p/>
+     * <b>N.B.</b> This method can only be called if the underlying {@link #getCipherService() cipherService} is a symmetric
+     * CipherService which by definition uses the same key for both encryption and decryption.  If using an asymmetric
+     * CipherService public/private key pair, you cannot use this method, and should instead use the
+     * {@link #getEncryptionCipherKey()} and {@link #getDecryptionCipherKey()} methods individually.
+     * <p/>
+     * The default {@link AesCipherService} instance is a symmetric cipher service, so this method can be used if you are
+     * using the default.
+     *
+     * @return the symmetric cipher key used for both encryption and decryption.
+     */
+    public byte[] getCipherKey() {
+        //Since this method should only be used with symmetric ciphers
+        //(where the enc and dec keys are the same), either is fine, just return one of them:
+        return getEncryptionCipherKey();
+    }
+
+    /**
+     * Convenience method that sets the cipher key to use for <em>both</em> encryption and decryption.
+     * <p/>
+     * <b>N.B.</b> This method can only be called if the underlying {@link #getCipherService() cipherService} is a
+     * symmetric CipherService?which by definition uses the same key for both encryption and decryption.  If using an
+     * asymmetric CipherService?(such as a public/private key pair), you cannot use this method, and should instead use
+     * the {@link #setEncryptionCipherKey(byte[])} and {@link #setDecryptionCipherKey(byte[])} methods individually.
+     * <p/>
+     * The default {@link AesCipherService} instance is a symmetric CipherService, so this method can be used if you
+     * are using the default.
+     *
+     * @param cipherKey the symmetric cipher key to use for both encryption and decryption.
+     */
+    public void setCipherKey(byte[] cipherKey) {
+        //Since this method should only be used in symmetric ciphers
+        //(where the enc and dec keys are the same), set it on both:
+        setEncryptionCipherKey(cipherKey);
+        setDecryptionCipherKey(cipherKey);
+    }
+
+    /**
+     * Forgets (removes) any remembered identity data for the specified {@link Subject} instance.
+     *
+     * @param subject the subject instance for which identity data should be forgotten from the underlying persistence
+     *                mechanism.
+     */
+    protected abstract void forgetIdentity(Subject subject);
+
+    /**
+     * Determines whether or not remember me services should be performed for the specified token.  This method returns
+     * {@code true} iff:
+     * <ol>
+     * <li>The token is not {@code null} and</li>
+     * <li>The token is an {@code instanceof} {@link RememberMeAuthenticationToken} and</li>
+     * <li>{@code token}.{@link org.apache.shiro.authc.RememberMeAuthenticationToken#isRememberMe() isRememberMe()} is
+     * {@code true}</li>
+     * </ol>
+     *
+     * @param token the authentication token submitted during the successful authentication attempt.
+     * @return true if remember me services should be performed as a result of the successful authentication attempt.
+     */
+    protected boolean isRememberMe(AuthenticationToken token) {
+        return token != null && (token instanceof RememberMeAuthenticationToken) &&
+                ((RememberMeAuthenticationToken) token).isRememberMe();
+    }
+
+    /**
+     * Reacts to the successful login attempt by first always {@link #forgetIdentity(Subject) forgetting} any previously
+     * stored identity.  Then if the {@code token}
+     * {@link #isRememberMe(org.apache.shiro.authc.AuthenticationToken) is a RememberMe} token, the associated identity
+     * will be {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) remembered}
+     * for later retrieval during a new user session.
+     *
+     * @param subject the subject for which the principals are being remembered.
+     * @param token   the token that resulted in a successful authentication attempt.
+     * @param info    the authentication info resulting from the successful authentication attempt.
+     */
+    public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
+        //always clear any previous identity:
+        forgetIdentity(subject);
+
+        //now save the new identity:
+        if (isRememberMe(token)) {
+            rememberIdentity(subject, token, info);
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("AuthenticationToken did not indicate RememberMe is requested.  " +
+                        "RememberMe functionality will not be executed for corresponding account.");
+            }
+        }
+    }
+
+    /**
+     * Remembers a subject-unique identity for retrieval later.  This implementation first
+     * {@link #getIdentityToRemember resolves} the exact
+     * {@link PrincipalCollection principals} to remember.  It then remembers the principals by calling
+     * {@link #rememberIdentity(org.apache.shiro.subject.Subject, org.apache.shiro.subject.PrincipalCollection)}.
+     * <p/>
+     * This implementation ignores the {@link AuthenticationToken} argument, but it is available to subclasses if
+     * necessary for custom logic.
+     *
+     * @param subject   the subject for which the principals are being remembered.
+     * @param token     the token that resulted in a successful authentication attempt.
+     * @param authcInfo the authentication info resulting from the successful authentication attempt.
+     */
+    public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
+        PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
+        rememberIdentity(subject, principals);
+    }
+
+    /**
+     * Returns {@code info}.{@link org.apache.shiro.authc.AuthenticationInfo#getPrincipals() getPrincipals()} and
+     * ignores the {@link Subject} argument.
+     *
+     * @param subject the subject for which the principals are being remembered.
+     * @param info    the authentication info resulting from the successful authentication attempt.
+     * @return the {@code PrincipalCollection} to remember.
+     */
+    protected PrincipalCollection getIdentityToRemember(Subject subject, AuthenticationInfo info) {
+        return info.getPrincipals();
+    }
+
+    /**
+     * Remembers the specified account principals by first
+     * {@link #convertPrincipalsToBytes(org.apache.shiro.subject.PrincipalCollection) converting} them to a byte
+     * array and then {@link #rememberSerializedIdentity(org.apache.shiro.subject.Subject, byte[]) remembers} that
+     * byte array.
+     *
+     * @param subject           the subject for which the principals are being remembered.
+     * @param accountPrincipals the principals to remember for retrieval later.
+     */
+    protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
+        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
+        rememberSerializedIdentity(subject, bytes);
+    }
+
+    /**
+     * Converts the given principal collection the byte array that will be persisted to be 'remembered' later.
+     * <p/>
+     * This implementation first {@link #serialize(org.apache.shiro.subject.PrincipalCollection) serializes} the
+     * principals to a byte array and then {@link #encrypt(byte[]) encrypts} that byte array.
+     *
+     * @param principals the {@code PrincipalCollection} to convert to a byte array
+     * @return the representative byte array to be persisted for remember me functionality.
+     */
+    protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
+        byte[] bytes = serialize(principals);
+        if (getCipherService() != null) {
+            bytes = encrypt(bytes);
+        }
+        return bytes;
+    }
+
+    /**
+     * Persists the identity bytes to a persistent store for retrieval later via the
+     * {@link #getRememberedSerializedIdentity(SubjectContext)} method.
+     *
+     * @param subject    the Subject for which the identity is being serialized.
+     * @param serialized the serialized bytes to be persisted.
+     */
+    protected abstract void rememberSerializedIdentity(Subject subject, byte[] serialized);
+
+    /**
+     * Implements the interface method by first {@link #getRememberedSerializedIdentity(SubjectContext) acquiring}
+     * the remembered serialized byte array.  Then it {@link #convertBytesToPrincipals(byte[], SubjectContext) converts}
+     * them and returns the re-constituted {@link PrincipalCollection}.  If no remembered principals could be
+     * obtained, {@code null} is returned.
+     * <p/>
+     * If any exceptions are thrown, the {@link #onRememberedPrincipalFailure(RuntimeException, SubjectContext)} method
+     * is called to allow any necessary post-processing (such as immediately removing any previously remembered
+     * values for safety).
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return the remembered principals or {@code null} if none could be acquired.
+     */
+    public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
+        PrincipalCollection principals = null;
+        try {
+            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
+            //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
+            if (bytes != null && bytes.length > 0) {
+                principals = convertBytesToPrincipals(bytes, subjectContext);
+            }
+        } catch (RuntimeException re) {
+            principals = onRememberedPrincipalFailure(re, subjectContext);
+        }
+
+        return principals;
+    }
+
+    /**
+     * Based on the given subject context data, retrieves the previously persisted serialized identity, or
+     * {@code null} if there is no available data.  The context map is usually populated by a {@link Subject.Builder}
+     * implementation.  See the {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.  To be used to assist with data
+     *                       lookup.
+     * @return the previously persisted serialized identity, or {@code null} if there is no available data for the
+     *         Subject.
+     */
+    protected abstract byte[] getRememberedSerializedIdentity(SubjectContext subjectContext);
+
+    /**
+     * If a {@link #getCipherService() cipherService} is available, it will be used to first decrypt the byte array.
+     * Then the bytes are then {@link #deserialize(byte[]) deserialized} and then returned.
+     *
+     * @param bytes          the bytes to decrypt if necessary and then deserialize.
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return the de-serialized and possibly decrypted principals
+     */
+    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
+        if (getCipherService() != null) {
+            bytes = decrypt(bytes);
+        }
+        return deserialize(bytes);
+    }
+
+    /**
+     * Called when an exception is thrown while trying to retrieve principals.  The default implementation logs a
+     * debug message and forgets ('unremembers') the problem identity by calling
+     * {@link #forgetIdentity(SubjectContext) forgetIdentity(context)} and then immediately re-throws the
+     * exception to allow the calling component to react accordingly.
+     * <p/>
+     * This method implementation never returns an
+     * object - it always rethrows, but can be overridden by subclasses for custom handling behavior.
+     * <p/>
+     * This most commonly would be called when an encryption key is updated and old principals are retrieved that have
+     * been encrypted with the previous key.
+     *
+     * @param e       the exception that was thrown.
+     * @param context the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                is being used to construct a {@link Subject} instance.
+     * @return nothing - the original {@code RuntimeException} is propagated in all cases.
+     */
+    protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context) {
+        if (log.isDebugEnabled()) {
+            log.debug("There was a failure while trying to retrieve remembered principals.  This could be due to a " +
+                    "configuration problem or corrupted principals.  This could also be due to a recently " +
+                    "changed encryption key.  The remembered identity will be forgotten and not used for this " +
+                    "request.", e);
+        }
+        forgetIdentity(context);
+        //propagate - security manager implementation will handle and warn appropriately
+        throw e;
+    }
+
+    /**
+     * Encrypts the byte array by using the configured {@link #getCipherService() cipherService}.
+     *
+     * @param serialized the serialized object byte array to be encrypted
+     * @return an encrypted byte array returned by the configured {@link #getCipherService () cipher}.
+     */
+    protected byte[] encrypt(byte[] serialized) {
+        byte[] value = serialized;
+        CipherService cipherService = getCipherService();
+        if (cipherService != null) {
+            ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
+            value = byteSource.getBytes();
+        }
+        return value;
+    }
+
+    /**
+     * Decrypts the byte array using the configured {@link #getCipherService() cipherService}.
+     *
+     * @param encrypted the encrypted byte array to decrypt
+     * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}.
+     */
+    protected byte[] decrypt(byte[] encrypted) {
+        byte[] serialized = encrypted;
+        CipherService cipherService = getCipherService();
+        if (cipherService != null) {
+            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
+            serialized = byteSource.getBytes();
+        }
+        return serialized;
+    }
+
+    /**
+     * Serializes the given {@code principals} by serializing them to a byte array by using the
+     * {@link #getSerializer() serializer}'s {@link Serializer#serialize(Object) serialize} method.
+     *
+     * @param principals the principal collection to serialize to a byte array
+     * @return the serialized principal collection in the form of a byte array
+     */
+    protected byte[] serialize(PrincipalCollection principals) {
+        return getSerializer().serialize(principals);
+    }
+
+    /**
+     * De-serializes the given byte array by using the {@link #getSerializer() serializer}'s
+     * {@link Serializer#deserialize deserialize} method.
+     *
+     * @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
+     * @return the de-serialized (reconstituted) {@code PrincipalCollection}
+     */
+    protected PrincipalCollection deserialize(byte[] serializedIdentity) {
+        return getSerializer().deserialize(serializedIdentity);
+    }
+
+    /**
+     * Reacts to a failed login by immediately {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgetting} any
+     * previously remembered identity.  This is an additional security feature to prevent any remenant identity data
+     * from being retained in case the authentication attempt is not being executed by the expected user.
+     *
+     * @param subject the subject which executed the failed login attempt
+     * @param token   the authentication token resulting in a failed login attempt - ignored by this implementation
+     * @param ae      the exception thrown as a result of the failed login attempt - ignored by this implementation
+     */
+    public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) {
+        forgetIdentity(subject);
+    }
+
+    /**
+     * Reacts to a subject logging out of the application and immediately
+     * {@link #forgetIdentity(org.apache.shiro.subject.Subject) forgets} any previously stored identity and returns.
+     *
+     * @param subject the subject logging out.
+     */
+    public void onLogout(Subject subject) {
+        forgetIdentity(subject);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/AuthenticatingSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/AuthenticatingSecurityManager.java
new file mode 100644
index 0000000..989d031
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/AuthenticatingSecurityManager.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.Authenticator;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.util.LifecycleUtils;
+
+
+/**
+ * Shiro support of a {@link SecurityManager} class hierarchy that delegates all
+ * authentication operations to a wrapped {@link Authenticator Authenticator} instance.  That is, this class
+ * implements all the <tt>Authenticator</tt> methods in the {@link SecurityManager SecurityManager}
+ * interface, but in reality, those methods are merely passthrough calls to the underlying 'real'
+ * <tt>Authenticator</tt> instance.
+ *
+ * <p>All other <tt>SecurityManager</tt> (authorization, session, etc) methods are left to be implemented by subclasses.
+ *
+ * <p>In keeping with the other classes in this hierarchy and Shiro's desire to minimize configuration whenever
+ * possible, suitable default instances for all dependencies are created upon instantiation.
+ *
+ * @since 0.9
+ */
+public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
+
+    /**
+     * The internal <code>Authenticator</code> delegate instance that this SecurityManager instance will use
+     * to perform all authentication operations.
+     */
+    private Authenticator authenticator;
+
+    /**
+     * Default no-arg constructor that initializes its internal
+     * <code>authenticator</code> instance to a
+     * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
+     */
+    public AuthenticatingSecurityManager() {
+        super();
+        this.authenticator = new ModularRealmAuthenticator();
+    }
+
+    /**
+     * Returns the delegate <code>Authenticator</code> instance that this SecurityManager uses to perform all
+     * authentication operations.  Unless overridden by the
+     * {@link #setAuthenticator(org.apache.shiro.authc.Authenticator) setAuthenticator}, the default instance is a
+     * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
+     *
+     * @return the delegate <code>Authenticator</code> instance that this SecurityManager uses to perform all
+     *         authentication operations.
+     */
+    public Authenticator getAuthenticator() {
+        return authenticator;
+    }
+
+    /**
+     * Sets the delegate <code>Authenticator</code> instance that this SecurityManager uses to perform all
+     * authentication operations.  Unless overridden by this method, the default instance is a
+     * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
+     *
+     * @param authenticator the delegate <code>Authenticator</code> instance that this SecurityManager will use to
+     *                      perform all authentication operations.
+     * @throws IllegalArgumentException if the argument is <code>null</code>.
+     */
+    public void setAuthenticator(Authenticator authenticator) throws IllegalArgumentException {
+        if (authenticator == null) {
+            String msg = "Authenticator argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.authenticator = authenticator;
+    }
+
+    /**
+     * Passes on the {@link #getRealms() realms} to the internal delegate <code>Authenticator</code> instance so
+     * that it may use them during authentication attempts.
+     */
+    protected void afterRealmsSet() {
+        super.afterRealmsSet();
+        if (this.authenticator instanceof ModularRealmAuthenticator) {
+            ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
+        }
+    }
+
+    /**
+     * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.
+     */
+    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
+        return this.authenticator.authenticate(token);
+    }
+
+    public void destroy() {
+        LifecycleUtils.destroy(getAuthenticator());
+        this.authenticator = null;
+        super.destroy();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/AuthorizingSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/AuthorizingSecurityManager.java
new file mode 100644
index 0000000..c156ff8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/AuthorizingSecurityManager.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.Authorizer;
+import org.apache.shiro.authz.ModularRealmAuthorizer;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.LifecycleUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * Shiro support of a {@link SecurityManager} class hierarchy that delegates all
+ * authorization (access control) operations to a wrapped {@link Authorizer Authorizer} instance.  That is,
+ * this class implements all the <tt>Authorizer</tt> methods in the {@link SecurityManager SecurityManager}
+ * interface, but in reality, those methods are merely passthrough calls to the underlying 'real'
+ * <tt>Authorizer</tt> instance.
+ *
+ * <p>All remaining <tt>SecurityManager</tt> methods not covered by this class or its parents (mostly Session support)
+ * are left to be implemented by subclasses.
+ *
+ * <p>In keeping with the other classes in this hierarchy and Shiro's desire to minimize configuration whenever
+ * possible, suitable default instances for all dependencies will be created upon instantiation.
+ *
+ * @since 0.9
+ */
+public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
+
+    /**
+     * The wrapped instance to which all of this <tt>SecurityManager</tt> authorization calls are delegated.
+     */
+    private Authorizer authorizer;
+
+    /**
+     * Default no-arg constructor that initializes an internal default
+     * {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}.
+     */
+    public AuthorizingSecurityManager() {
+        super();
+        this.authorizer = new ModularRealmAuthorizer();
+    }
+
+    /**
+     * Returns the underlying wrapped <tt>Authorizer</tt> instance to which this <tt>SecurityManager</tt>
+     * implementation delegates all of its authorization calls.
+     *
+     * @return the wrapped <tt>Authorizer</tt> used by this <tt>SecurityManager</tt> implementation.
+     */
+    public Authorizer getAuthorizer() {
+        return authorizer;
+    }
+
+    /**
+     * Sets the underlying <tt>Authorizer</tt> instance to which this <tt>SecurityManager</tt> implementation will
+     * delegate all of its authorization calls.
+     *
+     * @param authorizer the <tt>Authorizer</tt> this <tt>SecurityManager</tt> should wrap and delegate all of its
+     *                   authorization calls to.
+     */
+    public void setAuthorizer(Authorizer authorizer) {
+        if (authorizer == null) {
+            String msg = "Authorizer argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.authorizer = authorizer;
+    }
+
+    /**
+     * First calls <code>super.afterRealmsSet()</code> and then sets these same <code>Realm</code> objects on this
+     * instance's wrapped {@link Authorizer Authorizer}.
+     * <p/>
+     * The setting of realms the Authorizer will only occur if it is an instance of
+     * {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}, that is:
+     * <pre>
+     * if ( this.authorizer instanceof ModularRealmAuthorizer ) {
+     *     ((ModularRealmAuthorizer)this.authorizer).setRealms(realms);
+     * }</pre>
+     */
+    protected void afterRealmsSet() {
+        super.afterRealmsSet();
+        if (this.authorizer instanceof ModularRealmAuthorizer) {
+            ((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
+        }
+    }
+
+    public void destroy() {
+        LifecycleUtils.destroy(getAuthorizer());
+        this.authorizer = null;
+        super.destroy();
+    }
+
+    public boolean isPermitted(PrincipalCollection principals, String permissionString) {
+        return this.authorizer.isPermitted(principals, permissionString);
+    }
+
+    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
+        return this.authorizer.isPermitted(principals, permission);
+    }
+
+    public boolean[] isPermitted(PrincipalCollection principals, String... permissions) {
+        return this.authorizer.isPermitted(principals, permissions);
+    }
+
+    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
+        return this.authorizer.isPermitted(principals, permissions);
+    }
+
+    public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
+        return this.authorizer.isPermittedAll(principals, permissions);
+    }
+
+    public boolean isPermittedAll(PrincipalCollection principals, Collection<Permission> permissions) {
+        return this.authorizer.isPermittedAll(principals, permissions);
+    }
+
+    public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
+        this.authorizer.checkPermission(principals, permission);
+    }
+
+    public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
+        this.authorizer.checkPermission(principals, permission);
+    }
+
+    public void checkPermissions(PrincipalCollection principals, String... permissions) throws AuthorizationException {
+        this.authorizer.checkPermissions(principals, permissions);
+    }
+
+    public void checkPermissions(PrincipalCollection principals, Collection<Permission> permissions) throws AuthorizationException {
+        this.authorizer.checkPermissions(principals, permissions);
+    }
+
+    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
+        return this.authorizer.hasRole(principals, roleIdentifier);
+    }
+
+    public boolean[] hasRoles(PrincipalCollection principals, List<String> roleIdentifiers) {
+        return this.authorizer.hasRoles(principals, roleIdentifiers);
+    }
+
+    public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
+        return this.authorizer.hasAllRoles(principals, roleIdentifiers);
+    }
+
+    public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
+        this.authorizer.checkRole(principals, role);
+    }
+
+    public void checkRoles(PrincipalCollection principals, Collection<String> roles) throws AuthorizationException {
+        this.authorizer.checkRoles(principals, roles);
+    }
+    
+    public void checkRoles(PrincipalCollection principals, String... roles) throws AuthorizationException {
+        this.authorizer.checkRoles(principals, roles);
+    }    
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
new file mode 100644
index 0000000..c2c38ad
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/CachingSecurityManager.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+
+
+/**
+ * A very basic starting point for the SecurityManager interface that merely provides logging and caching
+ * support.  All actual {@code SecurityManager} method implementations are left to subclasses.
+ * <p/>
+ * <b>Change in 1.0</b> - a default {@code CacheManager} instance is <em>not</em> created by default during
+ * instantiation.  As caching strategies can vary greatly depending on an application's needs, a {@code CacheManager}
+ * instance must be explicitly configured if caching across the framework is to be enabled.
+ *
+ * @since 0.9
+ */
+public abstract class CachingSecurityManager implements SecurityManager, Destroyable, CacheManagerAware {
+
+    /**
+     * The CacheManager to use to perform caching operations to enhance performance.  Can be null.
+     */
+    private CacheManager cacheManager;
+
+    /**
+     * Default no-arg constructor that will automatically attempt to initialize a default cacheManager
+     */
+    public CachingSecurityManager() {
+    }
+
+    /**
+     * Returns the CacheManager used by this SecurityManager.
+     *
+     * @return the cacheManager used by this SecurityManager
+     */
+    public CacheManager getCacheManager() {
+        return cacheManager;
+    }
+
+    /**
+     * Sets the CacheManager used by this {@code SecurityManager} and potentially any of its
+     * children components.
+     * <p/>
+     * After the cacheManager attribute has been set, the template method
+     * {@link #afterCacheManagerSet afterCacheManagerSet()} is executed to allow subclasses to adjust when a
+     * cacheManager is available.
+     *
+     * @param cacheManager the CacheManager used by this {@code SecurityManager} and potentially any of its
+     *                     children components.
+     */
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+        afterCacheManagerSet();
+    }
+
+    /**
+     * Template callback to notify subclasses that a
+     * {@link org.apache.shiro.cache.CacheManager CacheManager} has been set and is available for use via the
+     * {@link #getCacheManager getCacheManager()} method.
+     */
+    protected void afterCacheManagerSet() {
+    }
+
+    /**
+     * Destroys the {@link #getCacheManager() cacheManager} via {@link LifecycleUtils#destroy LifecycleUtils.destroy}.
+     */
+    public void destroy() {
+        LifecycleUtils.destroy(getCacheManager());
+        this.cacheManager = null;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
new file mode 100644
index 0000000..9b0691c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
@@ -0,0 +1,615 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.Authorizer;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.DefaultSessionContext;
+import org.apache.shiro.session.mgt.DefaultSessionKey;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * The Shiro framework's default concrete implementation of the {@link SecurityManager} interface,
+ * based around a collection of {@link org.apache.shiro.realm.Realm}s.  This implementation delegates its
+ * authentication, authorization, and session operations to wrapped {@link Authenticator}, {@link Authorizer}, and
+ * {@link org.apache.shiro.session.mgt.SessionManager SessionManager} instances respectively via superclass
+ * implementation.
+ * <p/>
+ * To greatly reduce and simplify configuration, this implementation (and its superclasses) will
+ * create suitable defaults for all of its required dependencies, <em>except</em> the required one or more
+ * {@link Realm Realm}s.  Because {@code Realm} implementations usually interact with an application's data model,
+ * they are almost always application specific;  you will want to specify at least one custom
+ * {@code Realm} implementation that 'knows' about your application's data/security model
+ * (via {@link #setRealm} or one of the overloaded constructors).  All other attributes in this class hierarchy
+ * will have suitable defaults for most enterprise applications.
+ * <p/>
+ * <b>RememberMe notice</b>: This class supports the ability to configure a
+ * {@link #setRememberMeManager RememberMeManager}
+ * for {@code RememberMe} identity services for login/logout, BUT, a default instance <em>will not</em> be created
+ * for this attribute at startup.
+ * <p/>
+ * Because RememberMe services are inherently client tier-specific and
+ * therefore aplication-dependent, if you want {@code RememberMe} services enabled, you will have to specify an
+ * instance yourself via the {@link #setRememberMeManager(RememberMeManager) setRememberMeManager}
+ * mutator.  However if you're reading this JavaDoc with the
+ * expectation of operating in a Web environment, take a look at the
+ * {@code org.apache.shiro.web.DefaultWebSecurityManager} implementation, which
+ * <em>does</em> support {@code RememberMe} services by default at startup.
+ *
+ * @since 0.2
+ */
+public class DefaultSecurityManager extends SessionsSecurityManager {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class);
+
+    protected RememberMeManager rememberMeManager;
+    protected SubjectDAO subjectDAO;
+    protected SubjectFactory subjectFactory;
+
+    /**
+     * Default no-arg constructor.
+     */
+    public DefaultSecurityManager() {
+        super();
+        this.subjectFactory = new DefaultSubjectFactory();
+        this.subjectDAO = new DefaultSubjectDAO();
+    }
+
+    /**
+     * Supporting constructor for a single-realm application.
+     *
+     * @param singleRealm the single realm used by this SecurityManager.
+     */
+    public DefaultSecurityManager(Realm singleRealm) {
+        this();
+        setRealm(singleRealm);
+    }
+
+    /**
+     * Supporting constructor for multiple {@link #setRealms realms}.
+     *
+     * @param realms the realm instances backing this SecurityManager.
+     */
+    public DefaultSecurityManager(Collection<Realm> realms) {
+        this();
+        setRealms(realms);
+    }
+
+    /**
+     * Returns the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
+     *
+     * @return the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
+     */
+    public SubjectFactory getSubjectFactory() {
+        return subjectFactory;
+    }
+
+    /**
+     * Sets the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
+     *
+     * @param subjectFactory the {@code SubjectFactory} responsible for creating {@link Subject} instances exposed to the application.
+     */
+    public void setSubjectFactory(SubjectFactory subjectFactory) {
+        this.subjectFactory = subjectFactory;
+    }
+
+    /**
+     * Returns the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
+     * Subject identity is discovered (eg after RememberMe services).  Unless configured otherwise, the default
+     * implementation is a {@link DefaultSubjectDAO}.
+     *
+     * @return the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
+     *         Subject identity is discovered (eg after RememberMe services).
+     * @see DefaultSubjectDAO
+     * @since 1.2
+     */
+    public SubjectDAO getSubjectDAO() {
+        return subjectDAO;
+    }
+
+    /**
+     * Sets the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
+     * Subject identity is discovered (eg after RememberMe services). Unless configured otherwise, the default
+     * implementation is a {@link DefaultSubjectDAO}.
+     *
+     * @param subjectDAO the {@code SubjectDAO} responsible for persisting Subject state, typically used after login or when an
+     *                   Subject identity is discovered (eg after RememberMe services).
+     * @see DefaultSubjectDAO
+     * @since 1.2
+     */
+    public void setSubjectDAO(SubjectDAO subjectDAO) {
+        this.subjectDAO = subjectDAO;
+    }
+
+    public RememberMeManager getRememberMeManager() {
+        return rememberMeManager;
+    }
+
+    public void setRememberMeManager(RememberMeManager rememberMeManager) {
+        this.rememberMeManager = rememberMeManager;
+    }
+
+    protected SubjectContext createSubjectContext() {
+        return new DefaultSubjectContext();
+    }
+
+    /**
+     * Creates a {@code Subject} instance for the user represented by the given method arguments.
+     *
+     * @param token    the {@code AuthenticationToken} submitted for the successful authentication.
+     * @param info     the {@code AuthenticationInfo} of a newly authenticated user.
+     * @param existing the existing {@code Subject} instance that initiated the authentication attempt
+     * @return the {@code Subject} instance that represents the context and session data for the newly
+     *         authenticated subject.
+     */
+    protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
+        SubjectContext context = createSubjectContext();
+        context.setAuthenticated(true);
+        context.setAuthenticationToken(token);
+        context.setAuthenticationInfo(info);
+        if (existing != null) {
+            context.setSubject(existing);
+        }
+        return createSubject(context);
+    }
+
+    /**
+     * Binds a {@code Subject} instance created after authentication to the application for later use.
+     * <p/>
+     * As of Shiro 1.2, this method has been deprecated in favor of {@link #save(org.apache.shiro.subject.Subject)},
+     * which this implementation now calls.
+     *
+     * @param subject the {@code Subject} instance created after authentication to be bound to the application
+     *                for later use.
+     * @see #save(org.apache.shiro.subject.Subject)
+     * @deprecated in favor of {@link #save(org.apache.shiro.subject.Subject) save(subject)}.
+     */
+    @Deprecated
+    protected void bind(Subject subject) {
+        save(subject);
+    }
+
+    protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
+        RememberMeManager rmm = getRememberMeManager();
+        if (rmm != null) {
+            try {
+                rmm.onSuccessfulLogin(subject, token, info);
+            } catch (Exception e) {
+                if (log.isWarnEnabled()) {
+                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
+                            "] threw an exception during onSuccessfulLogin.  RememberMe services will not be " +
+                            "performed for account [" + info + "].";
+                    log.warn(msg, e);
+                }
+            }
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("This " + getClass().getName() + " instance does not have a " +
+                        "[" + RememberMeManager.class.getName() + "] instance configured.  RememberMe services " +
+                        "will not be performed for account [" + info + "].");
+            }
+        }
+    }
+
+    protected void rememberMeFailedLogin(AuthenticationToken token, AuthenticationException ex, Subject subject) {
+        RememberMeManager rmm = getRememberMeManager();
+        if (rmm != null) {
+            try {
+                rmm.onFailedLogin(subject, token, ex);
+            } catch (Exception e) {
+                if (log.isWarnEnabled()) {
+                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
+                            "] threw an exception during onFailedLogin for AuthenticationToken [" +
+                            token + "].";
+                    log.warn(msg, e);
+                }
+            }
+        }
+    }
+
+    protected void rememberMeLogout(Subject subject) {
+        RememberMeManager rmm = getRememberMeManager();
+        if (rmm != null) {
+            try {
+                rmm.onLogout(subject);
+            } catch (Exception e) {
+                if (log.isWarnEnabled()) {
+                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
+                            "] threw an exception during onLogout for subject with principals [" +
+                            (subject != null ? subject.getPrincipals() : null) + "]";
+                    log.warn(msg, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
+     * {@code Subject} instance representing the authenticated account's identity.
+     * <p/>
+     * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
+     * subsequent access before being returned to the caller.
+     *
+     * @param token the authenticationToken to process for the login attempt.
+     * @return a Subject representing the authenticated user.
+     * @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
+     */
+    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
+        AuthenticationInfo info;
+        try {
+            info = authenticate(token);
+        } catch (AuthenticationException ae) {
+            try {
+                onFailedLogin(token, ae, subject);
+            } catch (Exception e) {
+                if (log.isInfoEnabled()) {
+                    log.info("onFailedLogin method threw an " +
+                            "exception.  Logging and propagating original AuthenticationException.", e);
+                }
+            }
+            throw ae; //propagate
+        }
+
+        Subject loggedIn = createSubject(token, info, subject);
+
+        onSuccessfulLogin(token, info, loggedIn);
+
+        return loggedIn;
+    }
+
+    protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
+        rememberMeSuccessfulLogin(token, info, subject);
+    }
+
+    protected void onFailedLogin(AuthenticationToken token, AuthenticationException ae, Subject subject) {
+        rememberMeFailedLogin(token, ae, subject);
+    }
+
+    protected void beforeLogout(Subject subject) {
+        rememberMeLogout(subject);
+    }
+
+    protected SubjectContext copy(SubjectContext subjectContext) {
+        return new DefaultSubjectContext(subjectContext);
+    }
+
+    /**
+     * This implementation functions as follows:
+     * <p/>
+     * <ol>
+     * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire
+     * data that may not have already been available to it (such as a referenced session or remembered principals).</li>
+     * <li>Calls {@link #doCreateSubject(org.apache.shiro.subject.SubjectContext)} to actually perform the
+     * {@code Subject} instance creation.</li>
+     * <li>calls {@link #save(org.apache.shiro.subject.Subject) save(subject)} to ensure the constructed
+     * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li>
+     * <li>returns the constructed {@code Subject} instance.</li>
+     * </ol>
+     *
+     * @param subjectContext any data needed to direct how the Subject should be constructed.
+     * @return the {@code Subject} instance reflecting the specified contextual data.
+     * @see #ensureSecurityManager(org.apache.shiro.subject.SubjectContext)
+     * @see #resolveSession(org.apache.shiro.subject.SubjectContext)
+     * @see #resolvePrincipals(org.apache.shiro.subject.SubjectContext)
+     * @see #doCreateSubject(org.apache.shiro.subject.SubjectContext)
+     * @see #save(org.apache.shiro.subject.Subject)
+     * @since 1.0
+     */
+    public Subject createSubject(SubjectContext subjectContext) {
+        //create a copy so we don't modify the argument's backing map:
+        SubjectContext context = copy(subjectContext);
+
+        //ensure that the context has a SecurityManager instance, and if not, add one:
+        context = ensureSecurityManager(context);
+
+        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
+        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
+        //process is often environment specific - better to shield the SF from these details:
+        context = resolveSession(context);
+
+        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
+        //if possible before handing off to the SubjectFactory:
+        context = resolvePrincipals(context);
+
+        Subject subject = doCreateSubject(context);
+
+        //save this subject for future reference if necessary:
+        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
+        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
+        //Added in 1.2:
+        save(subject);
+
+        return subject;
+    }
+
+    /**
+     * Actually creates a {@code Subject} instance by delegating to the internal
+     * {@link #getSubjectFactory() subjectFactory}.  By the time this method is invoked, all possible
+     * {@code SubjectContext} data (session, principals, et. al.) has been made accessible using all known heuristics
+     * and will be accessible to the {@code subjectFactory} via the {@code subjectContext.resolve*} methods.
+     *
+     * @param context the populated context (data map) to be used by the {@code SubjectFactory} when creating a
+     *                {@code Subject} instance.
+     * @return a {@code Subject} instance reflecting the data in the specified {@code SubjectContext} data map.
+     * @see #getSubjectFactory()
+     * @see SubjectFactory#createSubject(org.apache.shiro.subject.SubjectContext)
+     * @since 1.2
+     */
+    protected Subject doCreateSubject(SubjectContext context) {
+        return getSubjectFactory().createSubject(context);
+    }
+
+    /**
+     * Saves the subject's state to a persistent location for future reference if necessary.
+     * <p/>
+     * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
+     * {@link SubjectDAO#save(org.apache.shiro.subject.Subject) subjectDAO.save(subject)}.
+     *
+     * @param subject the subject for which state will potentially be persisted
+     * @see SubjectDAO#save(org.apache.shiro.subject.Subject)
+     * @since 1.2
+     */
+    protected void save(Subject subject) {
+        this.subjectDAO.save(subject);
+    }
+
+    /**
+     * Removes (or 'unbinds') the Subject's state from the application, typically called during {@link #logout}..
+     * <p/>
+     * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
+     * {@link SubjectDAO#delete(org.apache.shiro.subject.Subject) delete(subject)}.
+     *
+     * @param subject the subject for which state will be removed
+     * @see SubjectDAO#delete(org.apache.shiro.subject.Subject)
+     * @since 1.2
+     */
+    protected void delete(Subject subject) {
+        this.subjectDAO.delete(subject);
+    }
+
+    /**
+     * Determines if there is a {@code SecurityManager} instance in the context, and if not, adds 'this' to the
+     * context.  This ensures the SubjectFactory instance will have access to a SecurityManager during Subject
+     * construction if necessary.
+     *
+     * @param context the subject context data that may contain a SecurityManager instance.
+     * @return The SubjectContext to use to pass to a {@link SubjectFactory} for subject creation.
+     * @since 1.0
+     */
+    @SuppressWarnings({"unchecked"})
+    protected SubjectContext ensureSecurityManager(SubjectContext context) {
+        if (context.resolveSecurityManager() != null) {
+            log.trace("Context already contains a SecurityManager instance.  Returning.");
+            return context;
+        }
+        log.trace("No SecurityManager found in context.  Adding self reference.");
+        context.setSecurityManager(this);
+        return context;
+    }
+
+    /**
+     * Attempts to resolve any associated session based on the context and returns a
+     * context that represents this resolved {@code Session} to ensure it may be referenced if necessary by the
+     * invoked {@link SubjectFactory} that performs actual {@link Subject} construction.
+     * <p/>
+     * If there is a {@code Session} already in the context because that is what the caller wants to be used for
+     * {@code Subject} construction, or if no session is resolved, this method effectively does nothing
+     * returns the context method argument unaltered.
+     *
+     * @param context the subject context data that may resolve a Session instance.
+     * @return The context to use to pass to a {@link SubjectFactory} for subject creation.
+     * @since 1.0
+     */
+    @SuppressWarnings({"unchecked"})
+    protected SubjectContext resolveSession(SubjectContext context) {
+        if (context.resolveSession() != null) {
+            log.debug("Context already contains a session.  Returning.");
+            return context;
+        }
+        try {
+            //Context couldn't resolve it directly, let's see if we can since we have direct access to 
+            //the session manager:
+            Session session = resolveContextSession(context);
+            if (session != null) {
+                context.setSession(session);
+            }
+        } catch (InvalidSessionException e) {
+            log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +
+                    "(session-less) Subject instance.", e);
+        }
+        return context;
+    }
+
+    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
+        SessionKey key = getSessionKey(context);
+        if (key != null) {
+            return getSession(key);
+        }
+        return null;
+    }
+
+    protected SessionKey getSessionKey(SubjectContext context) {
+        Serializable sessionId = context.getSessionId();
+        if (sessionId != null) {
+            return new DefaultSessionKey(sessionId);
+        }
+        return null;
+    }
+
+    /**
+     * Attempts to resolve an identity (a {@link PrincipalCollection}) for the context using heuristics.  This
+     * implementation functions as follows:
+     * <ol>
+     * <li>Check the context to see if it can already {@link SubjectContext#resolvePrincipals resolve an identity}.  If
+     * so, this method does nothing and returns the method argument unaltered.</li>
+     * <li>Check for a RememberMe identity by calling {@link #getRememberedIdentity}.  If that method returns a
+     * non-null value, place the remembered {@link PrincipalCollection} in the context.</li>
+     * </ol>
+     *
+     * @param context the subject context data that may provide (directly or indirectly through one of its values) a
+     *                {@link PrincipalCollection} identity.
+     * @return The Subject context to use to pass to a {@link SubjectFactory} for subject creation.
+     * @since 1.0
+     */
+    @SuppressWarnings({"unchecked"})
+    protected SubjectContext resolvePrincipals(SubjectContext context) {
+
+        PrincipalCollection principals = context.resolvePrincipals();
+
+        if (CollectionUtils.isEmpty(principals)) {
+            log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");
+
+            principals = getRememberedIdentity(context);
+
+            if (!CollectionUtils.isEmpty(principals)) {
+                log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
+                        "for subject construction by the SubjectFactory.");
+
+                context.setPrincipals(principals);
+
+                // The following call was removed (commented out) in Shiro 1.2 because it uses the session as an
+                // implementation strategy.  Session use for Shiro's own needs should be controlled in a single place
+                // to be more manageable for end-users: there are a number of stateless (e.g. REST) applications that
+                // use Shiro that need to ensure that sessions are only used when desirable.  If Shiro's internal
+                // implementations used Subject sessions (setting attributes) whenever we wanted, it would be much
+                // harder for end-users to control when/where that occurs.
+                //
+                // Because of this, the SubjectDAO was created as the single point of control, and session state logic
+                // has been moved to the DefaultSubjectDAO implementation.
+
+                // Removed in Shiro 1.2.  SHIRO-157 is still satisfied by the new DefaultSubjectDAO implementation
+                // introduced in 1.2
+                // Satisfies SHIRO-157:
+                // bindPrincipalsToSession(principals, context);
+
+            } else {
+                log.trace("No remembered identity found.  Returning original context.");
+            }
+        }
+
+        return context;
+    }
+
+    protected SessionContext createSessionContext(SubjectContext subjectContext) {
+        DefaultSessionContext sessionContext = new DefaultSessionContext();
+        if (!CollectionUtils.isEmpty(subjectContext)) {
+            sessionContext.putAll(subjectContext);
+        }
+        Serializable sessionId = subjectContext.getSessionId();
+        if (sessionId != null) {
+            sessionContext.setSessionId(sessionId);
+        }
+        String host = subjectContext.resolveHost();
+        if (host != null) {
+            sessionContext.setHost(host);
+        }
+        return sessionContext;
+    }
+
+    public void logout(Subject subject) {
+
+        if (subject == null) {
+            throw new IllegalArgumentException("Subject method argument cannot be null.");
+        }
+
+        beforeLogout(subject);
+
+        PrincipalCollection principals = subject.getPrincipals();
+        if (principals != null && !principals.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("Logging out subject with primary principal {}", principals.getPrimaryPrincipal());
+            }
+            Authenticator authc = getAuthenticator();
+            if (authc instanceof LogoutAware) {
+                ((LogoutAware) authc).onLogout(principals);
+            }
+        }
+
+        try {
+            delete(subject);
+        } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                String msg = "Unable to cleanly unbind Subject.  Ignoring (logging out).";
+                log.debug(msg, e);
+            }
+        } finally {
+            try {
+                stopSession(subject);
+            } catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    String msg = "Unable to cleanly stop Session for Subject [" + subject.getPrincipal() + "] " +
+                            "Ignoring (logging out).";
+                    log.debug(msg, e);
+                }
+            }
+        }
+    }
+
+    protected void stopSession(Subject subject) {
+        Session s = subject.getSession(false);
+        if (s != null) {
+            s.stop();
+        }
+    }
+
+    /**
+     * Unbinds or removes the Subject's state from the application, typically called during {@link #logout}.
+     * <p/>
+     * This has been deprecated in Shiro 1.2 in favor of the {@link #delete(org.apache.shiro.subject.Subject) delete}
+     * method.  The implementation has been updated to invoke that method.
+     *
+     * @param subject the subject to unbind from the application as it will no longer be used.
+     * @deprecated in Shiro 1.2 in favor of {@link #delete(org.apache.shiro.subject.Subject)}
+     */
+    @Deprecated
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void unbind(Subject subject) {
+        delete(subject);
+    }
+
+    protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
+        RememberMeManager rmm = getRememberMeManager();
+        if (rmm != null) {
+            try {
+                return rmm.getRememberedPrincipals(subjectContext);
+            } catch (Exception e) {
+                if (log.isWarnEnabled()) {
+                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
+                            "] threw an exception during getRememberedPrincipals().";
+                    log.warn(msg, e);
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/DefaultSessionStorageEvaluator.java b/core/src/main/java/org/apache/shiro/mgt/DefaultSessionStorageEvaluator.java
new file mode 100644
index 0000000..b720ac6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/DefaultSessionStorageEvaluator.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A Default {@code SessionStorageEvaluator} that provides reasonable control over if and how Sessions may be used for
+ * storing Subject state.  See the {@link #isSessionStorageEnabled(org.apache.shiro.subject.Subject)}
+ * method for exact behavior.
+ *
+ * @since 1.2
+ */
+public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator {
+
+    /**
+     * Global policy determining if Subject sessions may be used to persist Subject state if the Subject's Session
+     * does not yet exist.
+     */
+    private boolean sessionStorageEnabled = true;
+
+    /**
+     * This implementation functions as follows:
+     * <ul>
+     * <li>If the specified Subject already has an existing {@code Session} (typically because an application developer
+     * has called {@code subject.getSession()} already), Shiro will use that existing session to store subject state.</li>
+     * <li>If a Subject does not yet have a Session, this implementation checks the
+     * {@link #isSessionStorageEnabled() sessionStorageEnabled} property:
+     * <ul>
+     * <li>If {@code sessionStorageEnabled} is true (the default setting), a new session may be created to persist
+     * Subject state if necessary.</li>
+     * <li>If {@code sessionStorageEnabled} is {@code false}, a new session will <em>not</em> be created to persist
+     * session state.</li>
+     * </ul></li>
+     * </ul>
+     * Most applications use Sessions and are OK with the default {@code true} setting for {@code sessionStorageEnabled}.
+     * <p/>
+     * However, if your application is a purely 100% stateless application that never uses sessions,
+     * you will want to set {@code sessionStorageEnabled} to {@code false}.  Realize that a {@code false} value will
+     * ensure that any subject login only retains the authenticated identity for the duration of a request.  Any other
+     * requests, invocations or messages will not be authenticated.
+     *
+     * @param subject the {@code Subject} for which session state persistence may be enabled
+     * @return the value of {@link #isSessionStorageEnabled()} and ignores the {@code Subject} argument.
+     */
+    public boolean isSessionStorageEnabled(Subject subject) {
+        return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();
+    }
+
+    /**
+     * Returns {@code true} if any Subject's {@code Session} may be used to persist that {@code Subject}'s state,
+     * {@code false} otherwise.  The default value is {@code true}.
+     * <p/>
+     * <b>N.B.</b> This is a global configuration setting; setting this value to {@code false} will disable sessions
+     * to persist Subject state for all Subjects that do not already have a Session.  It should typically only be set
+     * to {@code false} for 100% stateless applications (e.g. when sessions aren't used or when remote clients
+     * authenticate on every request).
+     *
+     * @return {@code true} if any Subject's {@code Session} may be used to persist that {@code Subject}'s state,
+     *         {@code false} otherwise.
+     */
+    public boolean isSessionStorageEnabled() {
+        return sessionStorageEnabled;
+    }
+
+    /**
+     * Sets if any Subject's {@code Session} may be used to persist that {@code Subject}'s state.  The
+     * default value is {@code true}.
+     * <p/>
+     * <b>N.B.</b> This is a global configuration setting; setting this value to {@code false} will disable sessions
+     * to persist Subject state for all Subjects that do not already have a Session.  It should typically only be set
+     * to {@code false} for 100% stateless applications (e.g. when sessions aren't used or when remote clients
+     * authenticate on every request).
+     *
+     * @param sessionStorageEnabled if any Subject's {@code Session} may be used to persist that {@code Subject}'s state.
+     */
+    public void setSessionStorageEnabled(boolean sessionStorageEnabled) {
+        this.sessionStorageEnabled = sessionStorageEnabled;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectDAO.java b/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectDAO.java
new file mode 100644
index 0000000..c6b5aea
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectDAO.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+
+/**
+ * Default {@code SubjectDAO} implementation that stores Subject state in the Subject's Session by default (but this
+ * can be disabled - see below).  The Subject instance
+ * can be re-created at a later time by first acquiring the associated Session (typically from a
+ * {@link org.apache.shiro.session.mgt.SessionManager SessionManager}) via a session ID or session key and then
+ * building a {@code Subject} instance from {@code Session} attributes.
+ * <h2>Controlling how Sessions are used</h2>
+ * Whether or not a {@code Subject}'s {@code Session} is used or not to persist its own state is controlled on a
+ * <em>per-Subject</em> basis as determined by the configured
+ * {@link #setSessionStorageEvaluator(SessionStorageEvaluator) sessionStorageEvaluator}.
+ * The default {@code Evaluator} is a {@link DefaultSessionStorageEvaluator}, which supports enabling or disabling
+ * session usage for Subject persistence at a global level for all subjects (and defaults to allowing sessions to be
+ * used).
+ * <h3>Disabling Session Persistence Entirely</h3>
+ * Because the default {@code SessionStorageEvaluator} instance is a {@link DefaultSessionStorageEvaluator}, you
+ * can disable Session usage for Subject state entirely by configuring that instance directly, e.g.:
+ * <pre>
+ *     ((DefaultSessionStorageEvaluator)sessionDAO.getSessionStorageEvaluator()).setSessionStorageEnabled(false);
+ * </pre>
+ * or, for example, in {@code shiro.ini}:
+ * <pre>
+ *     securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
+ * </pre>
+ * but <b>note:</b> ONLY do this your
+ * application is 100% stateless and you <em>DO NOT</em> need subjects to be remembered across remote
+ * invocations, or in a web environment across HTTP requests.
+ * <h3>Supporting Both Stateful and Stateless Subject paradigms</h3>
+ * Perhaps your application needs to support a hybrid approach of both stateful and stateless Subjects:
+ * <ul>
+ * <li>Stateful: Stateful subjects might represent web end-users that need their identity and authentication
+ * state to be remembered from page to page.</li>
+ * <li>Stateless: Stateless subjects might represent API clients (e.g. REST clients) that authenticate on every
+ * request, and therefore don't need authentication state to be stored across requests in a session.</li>
+ * </ul>
+ * To support the hybrid <em>per-Subject</em> approach, you will need to create your own implementation of the
+ * {@link SessionStorageEvaluator} interface and configure it via the
+ * {@link #setSessionStorageEvaluator(SessionStorageEvaluator)} method, or, with {@code shiro.ini}:
+ * <pre>
+ *     myEvaluator = com.my.CustomSessionStorageEvaluator
+ *     securityManager.subjectDAO.sessionStorageEvaluator = $myEvaluator
+ * </pre>
+ * <p/>
+ * Unless overridden, the default evaluator is a {@link DefaultSessionStorageEvaluator}, which enables session usage for
+ * Subject state by default.
+ *
+ * @see #isSessionStorageEnabled(org.apache.shiro.subject.Subject)
+ * @see SessionStorageEvaluator
+ * @see DefaultSessionStorageEvaluator
+ * @since 1.2
+ */
+public class DefaultSubjectDAO implements SubjectDAO {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultSubjectDAO.class);
+
+    /**
+     * Evaluator that determines if a Subject's session may be used to store the Subject's own state.
+     */
+    private SessionStorageEvaluator sessionStorageEvaluator;
+
+    public DefaultSubjectDAO() {
+        //default implementation allows enabling/disabling session usages at a global level for all subjects:
+        this.sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
+    }
+
+    /**
+     * Determines if the subject's session will be used to persist subject state or not.  This implementation
+     * merely delegates to the internal {@link SessionStorageEvaluator} (a
+     * {@code DefaultSessionStorageEvaluator} by default).
+     *
+     * @param subject the subject to inspect to determine if the subject's session will be used to persist subject
+     *                state or not.
+     * @return {@code true} if the subject's session will be used to persist subject state, {@code false} otherwise.
+     * @see #setSessionStorageEvaluator(SessionStorageEvaluator)
+     * @see DefaultSessionStorageEvaluator
+     */
+    protected boolean isSessionStorageEnabled(Subject subject) {
+        return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
+    }
+
+    /**
+     * Returns the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
+     * the Subject's session.  The default instance is a {@link DefaultSessionStorageEvaluator}.
+     *
+     * @return the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
+     *         the Subject's session.
+     * @see DefaultSessionStorageEvaluator
+     */
+    public SessionStorageEvaluator getSessionStorageEvaluator() {
+        return sessionStorageEvaluator;
+    }
+
+    /**
+     * Sets the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
+     * the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}.
+     *
+     * @param sessionStorageEvaluator the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s
+     *                                state may be persisted in the Subject's session.
+     * @see DefaultSessionStorageEvaluator
+     */
+    public void setSessionStorageEvaluator(SessionStorageEvaluator sessionStorageEvaluator) {
+        this.sessionStorageEvaluator = sessionStorageEvaluator;
+    }
+
+    /**
+     * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only
+     * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}.  If session storage is not enabled
+     * for the specific {@code Subject}, this method does nothing.
+     * <p/>
+     * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created).
+     *
+     * @param subject the Subject instance for which its state will be created or updated.
+     * @return the same {@code Subject} passed in (a new Subject instance is not created).
+     */
+    public Subject save(Subject subject) {
+        if (isSessionStorageEnabled(subject)) {
+            saveToSession(subject);
+        } else {
+            log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
+                    "authentication state are expected to be initialized on every request or invocation.", subject);
+        }
+
+        return subject;
+    }
+
+    /**
+     * Saves the subject's state (it's principals and authentication state) to its
+     * {@link org.apache.shiro.subject.Subject#getSession() session}.  The session can be retrieved at a later time
+     * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
+     * the {@code Subject} instance.
+     *
+     * @param subject the subject for which state will be persisted to its session.
+     */
+    protected void saveToSession(Subject subject) {
+        //performs merge logic, only updating the Subject's session if it does not match the current state:
+        mergePrincipals(subject);
+        mergeAuthenticationState(subject);
+    }
+
+    /**
+     * Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in
+     * any available session.  Only updates the Subject's session if the session does not match the current principals
+     * state.
+     *
+     * @param subject the Subject for which principals will potentially be merged into the Subject's session.
+     */
+    protected void mergePrincipals(Subject subject) {
+        //merge PrincipalCollection state:
+
+        PrincipalCollection currentPrincipals = null;
+
+        //SHIRO-380: added if/else block - need to retain original (source) principals
+        //This technique (reflection) is only temporary - a proper long term solution needs to be found,
+        //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
+        //
+        //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
+        if (subject.isRunAs() && subject instanceof DelegatingSubject) {
+            try {
+                Field field = DelegatingSubject.class.getDeclaredField("principals");
+                field.setAccessible(true);
+                currentPrincipals = (PrincipalCollection)field.get(subject);
+            } catch (Exception e) {
+                throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
+            }
+        }
+        if (currentPrincipals == null || currentPrincipals.isEmpty()) {
+            currentPrincipals = subject.getPrincipals();
+        }
+
+        Session session = subject.getSession(false);
+
+        if (session == null) {
+            if (!CollectionUtils.isEmpty(currentPrincipals)) {
+                session = subject.getSession();
+                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
+            }
+            //otherwise no session and no principals - nothing to save
+        } else {
+            PrincipalCollection existingPrincipals =
+                    (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+
+            if (CollectionUtils.isEmpty(currentPrincipals)) {
+                if (!CollectionUtils.isEmpty(existingPrincipals)) {
+                    session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+                }
+                //otherwise both are null or empty - no need to update the session
+            } else {
+                if (!currentPrincipals.equals(existingPrincipals)) {
+                    session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
+                }
+                //otherwise they're the same - no need to update the session
+            }
+        }
+    }
+
+    /**
+     * Merges the Subject's current authentication state with whatever may be in
+     * any available session.  Only updates the Subject's session if the session does not match the current
+     * authentication state.
+     *
+     * @param subject the Subject for which principals will potentially be merged into the Subject's session.
+     */
+    protected void mergeAuthenticationState(Subject subject) {
+
+        Session session = subject.getSession(false);
+
+        if (session == null) {
+            if (subject.isAuthenticated()) {
+                session = subject.getSession();
+                session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
+            }
+            //otherwise no session and not authenticated - nothing to save
+        } else {
+            Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
+
+            if (subject.isAuthenticated()) {
+                if (existingAuthc == null || !existingAuthc) {
+                    session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
+                }
+                //otherwise authc state matches - no need to update the session
+            } else {
+                if (existingAuthc != null) {
+                    //existing doesn't match the current state - remove it:
+                    session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
+                }
+                //otherwise not in the session and not authenticated - no need to update the session
+            }
+        }
+    }
+
+    /**
+     * Removes any existing subject state from the Subject's session (if the session exists).  If the session
+     * does not exist, this method does not do anything.
+     *
+     * @param subject the subject for which any existing subject state will be removed from its session.
+     */
+    protected void removeFromSession(Subject subject) {
+        Session session = subject.getSession(false);
+        if (session != null) {
+            session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
+            session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
+        }
+    }
+
+    /**
+     * Removes any existing subject state from the subject's session (if the session exists).
+     *
+     * @param subject the Subject instance for which any persistent state should be deleted.
+     */
+    public void delete(Subject subject) {
+        removeFromSession(subject);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectFactory.java b/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectFactory.java
new file mode 100644
index 0000000..7e41abb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/DefaultSubjectFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.subject.support.DelegatingSubject;
+
+
+/**
+ * Default {@link SubjectFactory SubjectFactory} implementation that creates {@link org.apache.shiro.subject.support.DelegatingSubject DelegatingSubject}
+ * instances.
+ *
+ * @since 1.0
+ */
+public class DefaultSubjectFactory implements SubjectFactory {
+
+    public DefaultSubjectFactory() {
+    }
+
+    public Subject createSubject(SubjectContext context) {
+        SecurityManager securityManager = context.resolveSecurityManager();
+        Session session = context.resolveSession();
+        boolean sessionCreationEnabled = context.isSessionCreationEnabled();
+        PrincipalCollection principals = context.resolvePrincipals();
+        boolean authenticated = context.resolveAuthenticated();
+        String host = context.resolveHost();
+
+        return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
+    }
+
+    /**
+     * @deprecated since 1.2 - override {@link #createSubject(org.apache.shiro.subject.SubjectContext)} directly if you
+     *             need to instantiate a custom {@link Subject} class.
+     */
+    @Deprecated
+    protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated, String host,
+                                         Session session, SecurityManager securityManager) {
+        return new DelegatingSubject(principals, authenticated, host, session, true, securityManager);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
new file mode 100644
index 0000000..b63672b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/RealmSecurityManager.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.util.LifecycleUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+/**
+ * Shiro support of a {@link SecurityManager} class hierarchy based around a collection of
+ * {@link org.apache.shiro.realm.Realm}s.  All actual {@code SecurityManager} method implementations are left to
+ * subclasses.
+ *
+ * @since 0.9
+ */
+public abstract class RealmSecurityManager extends CachingSecurityManager {
+
+    /**
+     * Internal collection of <code>Realm</code>s used for all authentication and authorization operations.
+     */
+    private Collection<Realm> realms;
+
+    /**
+     * Default no-arg constructor.
+     */
+    public RealmSecurityManager() {
+        super();
+    }
+
+    /**
+     * Convenience method for applications using a single realm that merely wraps the realm in a list and then invokes
+     * the {@link #setRealms} method.
+     *
+     * @param realm the realm to set for a single-realm application.
+     * @since 0.2
+     */
+    public void setRealm(Realm realm) {
+        if (realm == null) {
+            throw new IllegalArgumentException("Realm argument cannot be null");
+        }
+        Collection<Realm> realms = new ArrayList<Realm>(1);
+        realms.add(realm);
+        setRealms(realms);
+    }
+
+    /**
+     * Sets the realms managed by this <tt>SecurityManager</tt> instance.
+     *
+     * @param realms the realms managed by this <tt>SecurityManager</tt> instance.
+     * @throws IllegalArgumentException if the realms collection is null or empty.
+     */
+    public void setRealms(Collection<Realm> realms) {
+        if (realms == null) {
+            throw new IllegalArgumentException("Realms collection argument cannot be null.");
+        }
+        if (realms.isEmpty()) {
+            throw new IllegalArgumentException("Realms collection argument cannot be empty.");
+        }
+        this.realms = realms;
+        afterRealmsSet();
+    }
+
+    protected void afterRealmsSet() {
+        applyCacheManagerToRealms();
+    }
+
+    /**
+     * Returns the {@link Realm Realm}s managed by this SecurityManager instance.
+     *
+     * @return the {@link Realm Realm}s managed by this SecurityManager instance.
+     */
+    public Collection<Realm> getRealms() {
+        return realms;
+    }
+
+    /**
+     * Sets the internal {@link #getCacheManager CacheManager} on any internal configured
+     * {@link #getRealms Realms} that implement the {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
+     * <p/>
+     * This method is called after setting a cacheManager on this securityManager via the
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method to allow it to be propagated
+     * down to all the internal Realms that would need to use it.
+     * <p/>
+     * It is also called after setting one or more realms via the {@link #setRealm setRealm} or
+     * {@link #setRealms setRealms} methods to allow these newly available realms to be given the cache manager
+     * already in use.
+     */
+    protected void applyCacheManagerToRealms() {
+        CacheManager cacheManager = getCacheManager();
+        Collection<Realm> realms = getRealms();
+        if (cacheManager != null && realms != null && !realms.isEmpty()) {
+            for (Realm realm : realms) {
+                if (realm instanceof CacheManagerAware) {
+                    ((CacheManagerAware) realm).setCacheManager(cacheManager);
+                }
+            }
+        }
+    }
+
+    /**
+     * Simply calls {@link #applyCacheManagerToRealms() applyCacheManagerToRealms()} to allow the
+     * newly set {@link org.apache.shiro.cache.CacheManager CacheManager} to be propagated to the internal collection of <code>Realm</code>
+     * that would need to use it.
+     */
+    protected void afterCacheManagerSet() {
+        applyCacheManagerToRealms();
+    }
+
+    public void destroy() {
+        LifecycleUtils.destroy(getRealms());
+        this.realms = null;
+        super.destroy();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java b/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java
new file mode 100644
index 0000000..4f03076
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/RememberMeManager.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+
+/**
+ * A RememberMeManager is responsible for remembering a Subject's identity across that Subject's sessions with
+ * the application.
+ *
+ * @since 0.9
+ */
+public interface RememberMeManager {
+
+    /**
+     * Based on the specified subject context map being used to build a Subject instance, returns any previously
+     * remembered principals for the subject for automatic identity association (aka 'Remember Me').
+     * <p/>
+     * The context map is usually populated by a {@link Subject.Builder} implementation.
+     * See the {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @return he remembered principals or {@code null} if none could be acquired.
+     * @since 1.0
+     */
+    PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext);
+
+    /**
+     * Forgets any remembered identity corresponding to the subject context map being used to build a subject instance.
+     * <p/>
+     * The context map is usually populated by a {@link Subject.Builder} implementation.
+     * See the {@link SubjectFactory} class constants for Shiro's known map keys.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.
+     * @since 1.0
+     */
+    void forgetIdentity(SubjectContext subjectContext);
+
+    /**
+     * Reacts to a successful authentication attempt, typically saving the principals to be retrieved ('remembered')
+     * for future system access.
+     *
+     * @param subject the subject that executed a successful authentication attempt
+     * @param token   the authentication token submitted resulting in a successful authentication attempt
+     * @param info    the authenticationInfo returned as a result of the successful authentication attempt
+     * @since 1.0
+     */
+    void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info);
+
+    /**
+     * Reacts to a failed authentication attempt, typically by forgetting any previously remembered principals for the
+     * Subject.
+     *
+     * @param subject the subject that executed the failed authentication attempt
+     * @param token   the authentication token submitted resulting in the failed authentication attempt
+     * @param ae      the authentication exception thrown as a result of the failed authentication attempt
+     * @since 1.0
+     */
+    void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae);
+
+    /**
+     * Reacts to a Subject logging out of the application, typically by forgetting any previously remembered
+     * principals for the Subject.
+     *
+     * @param subject the subject logging out.
+     * @since 1.0
+     */
+    void onLogout(Subject subject);
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
new file mode 100644
index 0000000..ac6aaf5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/SecurityManager.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.Authenticator;
+import org.apache.shiro.authz.Authorizer;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+
+
+/**
+ * A {@code SecurityManager} executes all security operations for <em>all</em> Subjects (aka users) across a
+ * single application.
+ * <p/>
+ * The interface itself primarily exists as a convenience - it extends the {@link org.apache.shiro.authc.Authenticator},
+ * {@link Authorizer}, and {@link SessionManager} interfaces, thereby consolidating
+ * these behaviors into a single point of reference.  For most Shiro usages, this simplifies configuration and
+ * tends to be a more convenient approach than referencing {@code Authenticator}, {@code Authorizer}, and
+ * {@code SessionManager} instances separately;  instead one only needs to interact with a single
+ * {@code SecurityManager} instance.
+ * <p/>
+ * In addition to the above three interfaces, this interface provides a number of methods supporting
+ * {@link Subject} behavior. A {@link org.apache.shiro.subject.Subject Subject} executes
+ * authentication, authorization, and session operations for a <em>single</em> user, and as such can only be
+ * managed by {@code A SecurityManager} which is aware of all three functions.  The three parent interfaces on the
+ * other hand do not 'know' about {@code Subject}s to ensure a clean separation of concerns.
+ * <p/>
+ * <b>Usage Note</b>: In actuality the large majority of application programmers won't interact with a SecurityManager
+ * very often, if at all.  <em>Most</em> application programmers only care about security operations for the currently
+ * executing user, usually attained by calling
+ * {@link org.apache.shiro.SecurityUtils#getSubject() SecurityUtils.getSubject()}.
+ * <p/>
+ * Framework developers on the other hand might find working with an actual SecurityManager useful.
+ *
+ * @see org.apache.shiro.mgt.DefaultSecurityManager
+ * @since 0.2
+ */
+public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
+
+    /**
+     * Logs in the specified Subject using the given {@code authenticationToken}, returning an updated Subject
+     * instance reflecting the authenticated state if successful or throwing {@code AuthenticationException} if it is
+     * not.
+     * <p/>
+     * Note that most application developers should probably not call this method directly unless they have a good
+     * reason for doing so.  The preferred way to log in a Subject is to call
+     * <code>subject.{@link org.apache.shiro.subject.Subject#login login(authenticationToken)}</code> (usually after
+     * acquiring the Subject by calling {@link org.apache.shiro.SecurityUtils#getSubject() SecurityUtils.getSubject()}).
+     * <p/>
+     * Framework developers on the other hand might find calling this method directly useful in certain cases.
+     *
+     * @param subject             the subject against which the authentication attempt will occur
+     * @param authenticationToken the token representing the Subject's principal(s) and credential(s)
+     * @return the subject instance reflecting the authenticated state after a successful attempt
+     * @throws AuthenticationException if the login attempt failed.
+     * @since 1.0
+     */
+    Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
+
+    /**
+     * Logs out the specified Subject from the system.
+     * <p/>
+     * Note that most application developers should not call this method unless they have a good reason for doing
+     * so.  The preferred way to logout a Subject is to call
+     * <code>{@link org.apache.shiro.subject.Subject#logout Subject.logout()}</code>, not the
+     * {@code SecurityManager} directly.
+     * <p/>
+     * Framework developers on the other hand might find calling this method directly useful in certain cases.
+     *
+     * @param subject the subject to log out.
+     * @since 1.0
+     */
+    void logout(Subject subject);
+
+    /**
+     * Creates a {@code Subject} instance reflecting the specified contextual data.
+     * <p/>
+     * The context can be anything needed by this {@code SecurityManager} to construct a {@code Subject} instance.
+     * Most Shiro end-users will never call this method - it exists primarily for
+     * framework development and to support any underlying custom {@link SubjectFactory SubjectFactory} implementations
+     * that may be used by the {@code SecurityManager}.
+     * <h4>Usage</h4>
+     * After calling this method, the returned instance is <em>not</em> bound to the application for further use.
+     * Callers are expected to know that {@code Subject} instances have local scope only and any
+     * other further use beyond the calling method must be managed explicitly.
+     *
+     * @param context any data needed to direct how the Subject should be constructed.
+     * @return the {@code Subject} instance reflecting the specified initialization data.
+     * @see SubjectFactory#createSubject(SubjectContext)
+     * @see Subject.Builder
+     * @since 1.0
+     */
+    Subject createSubject(SubjectContext context);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/SessionStorageEvaluator.java b/core/src/main/java/org/apache/shiro/mgt/SessionStorageEvaluator.java
new file mode 100644
index 0000000..ebdd9df
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/SessionStorageEvaluator.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * Evaluates whether or not Shiro may use a {@code Subject}'s {@link org.apache.shiro.session.Session Session}
+ * to persist that {@code Subject}'s internal state.
+ * <p/>
+ * It is a common Shiro implementation strategy to use a Subject's session to persist the Subject's identity and
+ * authentication state (e.g. after login) so that information does not need to be passed around for any further
+ * requests/invocations.  This effectively allows a session id to be used for any request or invocation as the only
+ * 'pointer' that Shiro needs, and from that, Shiro can re-create the Subject instance based on the referenced Session.
+ * <p/>
+ * However, in purely stateless applications, such as some REST applications or those where every request is
+ * authenticated, it is usually not needed or desirable to use Sessions to store this state (since it is in
+ * fact re-created on every request).  In these applications, sessions would never be used.
+ * <p/>
+ * This interface allows implementations to determine exactly when a Session might be used or not to store
+ * {@code Subject} state on a <em>per-Subject</em> basis.
+ * <p/>
+ * If you simply wish to enable or disable session usage at a global level for all {@code Subject}s, the
+ * {@link DefaultSessionStorageEvaluator} should be sufficient.  Per-subject behavior should be performed in custom
+ * implementations of this interface.
+ *
+ * @see Subject#getSession()
+ * @see Subject#getSession(boolean)
+ * @see DefaultSessionStorageEvaluator
+ * @since 1.2
+ */
+public interface SessionStorageEvaluator {
+
+    /**
+     * Returns {@code true} if the specified {@code Subject}'s
+     * {@link org.apache.shiro.subject.Subject#getSession() session} may be used to persist that Subject's
+     * state, {@code false} otherwise.
+     *
+     * @param subject the {@code Subject} for which session state persistence may be enabled
+     * @return {@code true} if the specified {@code Subject}'s
+     *         {@link org.apache.shiro.subject.Subject#getSession() session} may be used to persist that Subject's
+     *         state, {@code false} otherwise.
+     * @see Subject#getSession()
+     * @see Subject#getSession(boolean)
+     */
+    boolean isSessionStorageEnabled(Subject subject);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
new file mode 100644
index 0000000..05a7dec
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.util.LifecycleUtils;
+
+
+/**
+ * Shiro support of a {@link SecurityManager} class hierarchy that delegates all
+ * {@link org.apache.shiro.session.Session session} operations to a wrapped
+ * {@link org.apache.shiro.session.mgt.SessionManager SessionManager} instance.  That is, this class implements the
+ * methods in the {@link SessionManager SessionManager} interface, but in reality, those methods are merely
+ * passthrough calls to the underlying 'real' {@code SessionManager} instance.
+ * <p/>
+ * The remaining {@code SecurityManager} methods not implemented by this class or its parents are left to be
+ * implemented by subclasses.
+ * <p/>
+ * In keeping with the other classes in this hierarchy and Shiro's desire to minimize configuration whenever
+ * possible, suitable default instances for all dependencies will be created upon instantiation.
+ *
+ * @since 0.9
+ */
+public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
+
+    /**
+     * The internal delegate <code>SessionManager</code> used by this security manager that manages all the
+     * application's {@link Session Session}s.
+     */
+    private SessionManager sessionManager;
+
+    /**
+     * Default no-arg constructor, internally creates a suitable default {@link SessionManager SessionManager} delegate
+     * instance.
+     */
+    public SessionsSecurityManager() {
+        super();
+        this.sessionManager = new DefaultSessionManager();
+        applyCacheManagerToSessionManager();
+    }
+
+    /**
+     * Sets the underlying delegate {@link SessionManager} instance that will be used to support this implementation's
+     * <tt>SessionManager</tt> method calls.
+     * <p/>
+     * This <tt>SecurityManager</tt> implementation does not provide logic to support the inherited
+     * <tt>SessionManager</tt> interface, but instead delegates these calls to an internal
+     * <tt>SessionManager</tt> instance.
+     * <p/>
+     * If a <tt>SessionManager</tt> instance is not set, a default one will be automatically created and
+     * initialized appropriately for the the existing runtime environment.
+     *
+     * @param sessionManager delegate instance to use to support this manager's <tt>SessionManager</tt> method calls.
+     */
+    public void setSessionManager(SessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+        afterSessionManagerSet();
+    }
+
+    protected void afterSessionManagerSet() {
+        applyCacheManagerToSessionManager();
+    }
+
+    /**
+     * Returns this security manager's internal delegate {@link SessionManager SessionManager}.
+     *
+     * @return this security manager's internal delegate {@link SessionManager SessionManager}.
+     * @see #setSessionManager(org.apache.shiro.session.mgt.SessionManager) setSessionManager
+     */
+    public SessionManager getSessionManager() {
+        return this.sessionManager;
+    }
+
+    /**
+     * Calls {@link org.apache.shiro.mgt.AuthorizingSecurityManager#afterCacheManagerSet() super.afterCacheManagerSet()} and then immediately calls
+     * {@link #applyCacheManagerToSessionManager() applyCacheManagerToSessionManager()} to ensure the
+     * <code>CacheManager</code> is applied to the SessionManager as necessary.
+     */
+    protected void afterCacheManagerSet() {
+        super.afterCacheManagerSet();
+        applyCacheManagerToSessionManager();
+    }
+
+    /**
+     * Ensures the internal delegate <code>SessionManager</code> is injected with the newly set
+     * {@link #setCacheManager CacheManager} so it may use it for its internal caching needs.
+     * <p/>
+     * Note:  This implementation only injects the CacheManager into the SessionManager if the SessionManager
+     * instance implements the {@link CacheManagerAware CacheManagerAware} interface.
+     */
+    protected void applyCacheManagerToSessionManager() {
+        if (this.sessionManager instanceof CacheManagerAware) {
+            ((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
+        }
+    }
+
+    public Session start(SessionContext context) throws AuthorizationException {
+        return this.sessionManager.start(context);
+    }
+
+    public Session getSession(SessionKey key) throws SessionException {
+        return this.sessionManager.getSession(key);
+    }
+
+    public void destroy() {
+        LifecycleUtils.destroy(getSessionManager());
+        this.sessionManager = null;
+        super.destroy();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/SubjectDAO.java b/core/src/main/java/org/apache/shiro/mgt/SubjectDAO.java
new file mode 100644
index 0000000..788b9c9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/SubjectDAO.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A {@code SubjectDAO} is responsible for persisting a Subject instance's internal state such that the Subject instance
+ * can be recreated at a later time if necessary.
+ * <p/>
+ * Shiro's default {@code SecurityManager} implementations typically use a {@code SubjectDAO} in conjunction
+ * with a {@link SubjectFactory}: after the {@code SubjectFactory} creates a {@code Subject} instance, the
+ * {@code SubjectDAO} is used to persist that subject's state such that it can be accessed later if necessary.
+ * <h3>Usage</h3>
+ * It should be noted that this component is used by {@code SecurityManager} implementations to manage Subject
+ * state persistence.  It does <em>not</em> make Subject instances accessible to the
+ * application (e.g. via {@link org.apache.shiro.SecurityUtils#getSubject() SecurityUtils.getSubject()}).
+ *
+ * @see DefaultSubjectDAO
+ * @since 1.2
+ */
+public interface SubjectDAO {
+
+    /**
+     * Persists the specified Subject's state for later access.  If there is a no existing state persisted, this
+     * persists it if possible (i.e. a create operation).  If there is existing state for the specified {@code Subject},
+     * this method updates the existing state to reflect the current state (i.e. an update operation).
+     *
+     * @param subject the Subject instance for which its state will be created or updated.
+     * @return the Subject instance to use after persistence is complete.  This can be the same as the method argument
+     * if the underlying implementation does not need to make any Subject changes.
+     */
+    Subject save(Subject subject);
+
+    /**
+     * Removes any persisted state for the specified {@code Subject} instance.  This is a delete operation such that
+     * the Subject's state will not be accessible at a later time.
+     *
+     * @param subject the Subject instance for which any persistent state should be deleted.
+     */
+    void delete(Subject subject);
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/SubjectFactory.java b/core/src/main/java/org/apache/shiro/mgt/SubjectFactory.java
new file mode 100644
index 0000000..193b8ee
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/SubjectFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+
+import java.util.Map;
+
+/**
+ * A {@code SubjectFactory} is responsible for constructing {@link Subject Subject} instances as needed.
+ *
+ * @since 1.0
+ */
+public interface SubjectFactory {
+
+    /**
+     * Creates a new Subject instance reflecting the state of the specified contextual data.  The data would be
+     * anything required to required to construct a {@code Subject} instance and its contents can vary based on
+     * environment.
+     * <p/>
+     * Any data supported by Shiro core will be accessible by one of the {@code SubjectContext}'s {@code get*}
+     * or {@code resolve*} methods.  All other data is available as map {@link Map#get attribute}s.
+     *
+     * @param context the contextual data to be used by the implementation to construct an appropriate {@code Subject}
+     *                instance.
+     * @return a {@code Subject} instance created based on the specified context.
+     * @see SubjectContext
+     */
+    Subject createSubject(SubjectContext context);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/mgt/package-info.java b/core/src/main/java/org/apache/shiro/mgt/package-info.java
new file mode 100644
index 0000000..1d9b7f2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/mgt/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Provides the master {@link org.apache.shiro.mgt.SecurityManager SecurityManager} interface and a default implementation
+ * hierarchy for managing all aspects of Shiro's functionality in an application.
+ */
+package org.apache.shiro.mgt;
diff --git a/core/src/main/java/org/apache/shiro/package-info.java b/core/src/main/java/org/apache/shiro/package-info.java
new file mode 100644
index 0000000..a380c7a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * This package primarily exists as a root classpath distinction, but it does contain two core classes widely used
+ * by applications, {@link org.apache.shiro.SecurityUtils SecurityUtils} and
+ * {@link org.apache.shiro.ShiroException ShiroException}.
+ */
+package org.apache.shiro;
diff --git a/core/src/main/java/org/apache/shiro/realm/AuthenticatingRealm.java b/core/src/main/java/org/apache/shiro/realm/AuthenticatingRealm.java
new file mode 100644
index 0000000..0b3e09b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/AuthenticatingRealm.java
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Initializable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A top-level abstract implementation of the <tt>Realm</tt> interface that only implements authentication support
+ * (log-in) operations and leaves authorization (access control) behavior to subclasses.
+ * <h2>Authentication Caching</h2>
+ * For applications that perform frequent repeated authentication of the same accounts (e.g. as is often done in
+ * REST or Soap applications that authenticate on every request), it might be prudent to enable authentication
+ * caching to alleviate constant load on any back-end data sources.
+ * <p/>
+ * This feature is disabled by default to retain backwards-compatibility with Shiro 1.1 and earlier.  It may be
+ * enabled by setting {@link #setAuthenticationCachingEnabled(boolean) authenticationCachingEnabled} = {@code true}
+ * (and configuring Shiro with a {@link CacheManager} of course), but <b>NOTE:</b>
+ * <p/>
+ * <b>ONLY enable authentication caching if either of the following is true for your realm implementation:</b>
+ * <ul>
+ * <li>The {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) doGetAuthenticationInfo}
+ * implementation returns {@code AuthenticationInfo} instances where the
+ * {@link org.apache.shiro.authc.AuthenticationInfo#getCredentials() credentials} are securely obfuscated and NOT
+ * plaintext (raw) credentials. For example,
+ * if your realm references accounts with passwords, that the {@code AuthenticationInfo}'s
+ * {@link org.apache.shiro.authc.AuthenticationInfo#getCredentials() credentials} are safely hashed and salted or otherwise
+ * fully encrypted.<br/><br/></li>
+ * <li>The {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) doGetAuthenticationInfo}
+ * implementation returns {@code AuthenticationInfo} instances where the
+ * {@link org.apache.shiro.authc.AuthenticationInfo#getCredentials() credentials} are plaintext (raw) <b>AND</b> the
+ * cache region storing the {@code AuthenticationInfo} instances WILL NOT overflow to disk and WILL NOT transmit cache
+ * entries over an unprotected (non TLS/SSL) network (as might be the case with a networked/distributed enterprise cache).
+ * This should be the case even in private/trusted/corporate networks.</li>
+ * </ul>
+ * <p/>
+ * These points are very important because if authentication caching is enabled, this abstract class implementation
+ * will place AuthenticationInfo instances returned from the subclass implementations directly into the cache, for
+ * example:
+ * <pre>
+ * cache.put(cacheKey, subclassAuthenticationInfoInstance);
+ * </pre>
+ * <p/>
+ * Enabling authentication caching is ONLY safe to do if the above two scenarios apply.  It is NOT safe to enable under
+ * any other scenario.
+ * <p/>
+ * When possible, always represent and store credentials in a safe form (hash+salt or encrypted) to eliminate plaintext
+ * visibility.
+ * <h3>Authentication Cache Invalidation on Logout</h3>
+ * If authentication caching is enabled, this implementation will attempt to evict (remove) cached authentication data
+ * for an account during logout.  This can only occur if the
+ * {@link #getAuthenticationCacheKey(org.apache.shiro.authc.AuthenticationToken)} and
+ * {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection)} methods return the exact same value.
+ * <p/>
+ * The default implementations of these methods expect that the
+ * {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal()} (what the user submits during login) and
+ * {@link #getAvailablePrincipal(org.apache.shiro.subject.PrincipalCollection) getAvailablePrincipal} (what is returned
+ * by the realm after account lookup) return
+ * the same exact value.  For example, the user submitted username is also the primary account identifier.
+ * <p/>
+ * However, if your application uses, say, a username for end-user login, but returns a primary key ID as the
+ * primary principal after authentication, then you will need to override either
+ * {@link #getAuthenticationCacheKey(org.apache.shiro.authc.AuthenticationToken) getAuthenticationCacheKey(token)} or
+ * {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection) getAuthenticationCacheKey(principals)}
+ * (or both) to ensure that the same cache key can be used for either object.
+ * <p/>
+ * This guarantees that the same cache key used to cache the data during authentication (derived from the
+ * {@code AuthenticationToken}) will be used to remove the cached data during logout (derived from the
+ * {@code PrincipalCollection}).
+ * <h4>Unmatching Cache Key Values</h4>
+ * If the return values from {@link #getAuthenticationCacheKey(org.apache.shiro.authc.AuthenticationToken)} and
+ * {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection)} are not identical, cached
+ * authentication data removal is at the mercy of your cache provider settings.  For example, often cache
+ * implementations will evict cache entries based on a timeToIdle or timeToLive (TTL) value.
+ * <p/>
+ * If this lazy eviction capability of the cache product is not sufficient and you want discrete behavior
+ * (highly recommended for authentication data), ensure that the return values from those two methods are identical in
+ * the subclass implementation.
+ *
+ * @since 0.2
+ */
+public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class);
+
+    private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
+
+    /**
+     * The default suffix appended to the realm name used for caching authentication data.
+     *
+     * @since 1.2
+     */
+    private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authenticationCache";
+
+    /**
+     * Credentials matcher used to determine if the provided credentials match the credentials stored in the data store.
+     */
+    private CredentialsMatcher credentialsMatcher;
+
+
+    private Cache<Object, AuthenticationInfo> authenticationCache;
+
+    private boolean authenticationCachingEnabled;
+    private String authenticationCacheName;
+
+    /**
+     * The class that this realm supports for authentication tokens.  This is used by the
+     * default implementation of the {@link Realm#supports(org.apache.shiro.authc.AuthenticationToken)} method to
+     * determine whether or not the given authentication token is supported by this realm.
+     */
+    private Class<? extends AuthenticationToken> authenticationTokenClass;
+
+    /*-------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+    public AuthenticatingRealm() {
+        this(null, new SimpleCredentialsMatcher());
+    }
+
+    public AuthenticatingRealm(CacheManager cacheManager) {
+        this(cacheManager, new SimpleCredentialsMatcher());
+    }
+
+    public AuthenticatingRealm(CredentialsMatcher matcher) {
+        this(null, matcher);
+    }
+
+    public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
+        authenticationTokenClass = UsernamePasswordToken.class;
+
+        //retain backwards compatibility for Shiro 1.1 and earlier.  Setting to true by default will probably cause
+        //unexpected results for existing applications:
+        this.authenticationCachingEnabled = false;
+
+        int instanceNumber = INSTANCE_COUNT.getAndIncrement();
+        this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
+        if (instanceNumber > 0) {
+            this.authenticationCacheName = this.authenticationCacheName + "." + instanceNumber;
+        }
+
+        if (cacheManager != null) {
+            setCacheManager(cacheManager);
+        }
+        if (matcher != null) {
+            setCredentialsMatcher(matcher);
+        }
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Returns the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
+     * credentials with those stored in the system.
+     * <p/>
+     * <p>Unless overridden by the {@link #setCredentialsMatcher setCredentialsMatcher} method, the default
+     * value is a {@link org.apache.shiro.authc.credential.SimpleCredentialsMatcher SimpleCredentialsMatcher} instance.
+     *
+     * @return the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
+     *         credentials with those stored in the system.
+     */
+    public CredentialsMatcher getCredentialsMatcher() {
+        return credentialsMatcher;
+    }
+
+    /**
+     * Sets the CrendialsMatcher used during an authentication attempt to verify submitted credentials with those
+     * stored in the system.  The implementation of this matcher can be switched via configuration to
+     * support any number of schemes, including plain text comparisons, hashing comparisons, and others.
+     * <p/>
+     * <p>Unless overridden by this method, the default value is a
+     * {@link org.apache.shiro.authc.credential.SimpleCredentialsMatcher} instance.
+     *
+     * @param credentialsMatcher the matcher to use.
+     */
+    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
+        this.credentialsMatcher = credentialsMatcher;
+    }
+
+    /**
+     * Returns the authenticationToken class supported by this realm.
+     * <p/>
+     * <p>The default value is <tt>{@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class}</tt>, since
+     * about 90% of realms use username/password authentication, regardless of their protocol (e.g. over jdbc, ldap,
+     * kerberos, http, etc).
+     * <p/>
+     * <p>If subclasses haven't already overridden the {@link Realm#supports Realm.supports(AuthenticationToken)} method,
+     * they must {@link #setAuthenticationTokenClass(Class) set a new class} if they won't support
+     * <tt>UsernamePasswordToken</tt> authentication token submissions.
+     *
+     * @return the authenticationToken class supported by this realm.
+     * @see #setAuthenticationTokenClass
+     */
+    public Class getAuthenticationTokenClass() {
+        return authenticationTokenClass;
+    }
+
+    /**
+     * Sets the authenticationToken class supported by this realm.
+     * <p/>
+     * <p>Unless overridden by this method, the default value is
+     * {@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken.class} to support the majority of applications.
+     *
+     * @param authenticationTokenClass the class of authentication token instances supported by this realm.
+     * @see #getAuthenticationTokenClass getAuthenticationTokenClass() for more explanation.
+     */
+    public void setAuthenticationTokenClass(Class<? extends AuthenticationToken> authenticationTokenClass) {
+        this.authenticationTokenClass = authenticationTokenClass;
+    }
+
+    /**
+     * Sets an explicit {@link Cache} instance to use for authentication caching.  If not set and authentication
+     * caching is {@link #isAuthenticationCachingEnabled() enabled}, any available
+     * {@link #getCacheManager() cacheManager} will be used to acquire the cache instance if available.
+     * <p/>
+     * <b>WARNING:</b> Only set this property if safe caching conditions apply, as documented at the top
+     * of this page in the class-level JavaDoc.
+     *
+     * @param authenticationCache an explicit {@link Cache} instance to use for authentication caching or
+     *                            {@code null} if the cache should possibly be obtained another way.
+     * @see #isAuthenticationCachingEnabled()
+     * @since 1.2
+     */
+    public void setAuthenticationCache(Cache<Object, AuthenticationInfo> authenticationCache) {
+        this.authenticationCache = authenticationCache;
+    }
+
+    /**
+     * Returns a {@link Cache} instance to use for authentication caching, or {@code null} if no cache has been
+     * set.
+     *
+     * @return a {@link Cache} instance to use for authentication caching, or {@code null} if no cache has been
+     *         set.
+     * @see #setAuthenticationCache(org.apache.shiro.cache.Cache)
+     * @see #isAuthenticationCachingEnabled()
+     * @since 1.2
+     */
+    public Cache<Object, AuthenticationInfo> getAuthenticationCache() {
+        return this.authenticationCache;
+    }
+
+    /**
+     * Returns the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if
+     * a cache is not explicitly configured via {@link #setAuthenticationCache(org.apache.shiro.cache.Cache)}.
+     * <p/>
+     * This name will only be used to look up a cache if authentication caching is
+     * {@link #isAuthenticationCachingEnabled() enabled}.
+     * <p/>
+     * <b>WARNING:</b> Only set this property if safe caching conditions apply, as documented at the top
+     * of this page in the class-level JavaDoc.
+     *
+     * @return the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if
+     *         a cache is not explicitly configured via {@link #setAuthenticationCache(org.apache.shiro.cache.Cache)}.
+     * @see #isAuthenticationCachingEnabled()
+     * @since 1.2
+     */
+    public String getAuthenticationCacheName() {
+        return this.authenticationCacheName;
+    }
+
+    /**
+     * Sets the name of a {@link Cache} to lookup from any available {@link #getCacheManager() cacheManager} if
+     * a cache is not explicitly configured via {@link #setAuthenticationCache(org.apache.shiro.cache.Cache)}.
+     * <p/>
+     * This name will only be used to look up a cache if authentication caching is
+     * {@link #isAuthenticationCachingEnabled() enabled}.
+     *
+     * @param authenticationCacheName the name of a {@link Cache} to lookup from any available
+     *                                {@link #getCacheManager() cacheManager} if a cache is not explicitly configured
+     *                                via {@link #setAuthenticationCache(org.apache.shiro.cache.Cache)}.
+     * @see #isAuthenticationCachingEnabled()
+     * @since 1.2
+     */
+    public void setAuthenticationCacheName(String authenticationCacheName) {
+        this.authenticationCacheName = authenticationCacheName;
+    }
+
+    /**
+     * Returns {@code true} if authentication caching should be utilized if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code true}.
+     *
+     * @return {@code true} if authentication caching should be utilized, {@code false} otherwise.
+     */
+    public boolean isAuthenticationCachingEnabled() {
+        return this.authenticationCachingEnabled && isCachingEnabled();
+    }
+
+    /**
+     * Sets whether or not authentication caching should be utilized if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code false} to retain backwards compatibility with Shiro 1.1 and earlier.
+     * <p/>
+     * <b>WARNING:</b> Only set this property to {@code true} if safe caching conditions apply, as documented at the top
+     * of this page in the class-level JavaDoc.
+     *
+     * @param authenticationCachingEnabled the value to set
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setAuthenticationCachingEnabled(boolean authenticationCachingEnabled) {
+        this.authenticationCachingEnabled = authenticationCachingEnabled;
+        if (authenticationCachingEnabled) {
+            setCachingEnabled(true);
+        }
+    }
+
+    public void setName(String name) {
+        super.setName(name);
+        String authcCacheName = this.authenticationCacheName;
+        if (authcCacheName != null && authcCacheName.startsWith(getClass().getName())) {
+            //get rid of the default heuristically-created cache name.  Create a more meaningful one
+            //based on the application-unique Realm name:
+            this.authenticationCacheName = name + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
+        }
+    }
+
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Convenience implementation that returns
+     * <tt>getAuthenticationTokenClass().isAssignableFrom( token.getClass() );</tt>.  Can be overridden
+     * by subclasses for more complex token checking.
+     * <p>Most configurations will only need to set a different class via
+     * {@link #setAuthenticationTokenClass}, as opposed to overriding this method.
+     *
+     * @param token the token being submitted for authentication.
+     * @return true if this authentication realm can process the submitted token instance of the class, false otherwise.
+     */
+    public boolean supports(AuthenticationToken token) {
+        return token != null && getAuthenticationTokenClass().isAssignableFrom(token.getClass());
+    }
+
+    /**
+     * Initializes this realm and potentially enables an authentication cache, depending on configuration.  Based on
+     * the availability of an authentication cache, this class functions as follows:
+     * <ol>
+     * <li>If the {@link #setAuthenticationCache cache} property has been set, it will be
+     * used to cache the AuthenticationInfo objects returned from {@link #getAuthenticationInfo}
+     * method invocations.
+     * All future calls to {@link #getAuthenticationInfo} will attempt to use this cache first
+     * to alleviate any potentially unnecessary calls to an underlying data store.</li>
+     * <li>If the {@link #setAuthenticationCache cache} property has <b>not</b> been set,
+     * the {@link #setCacheManager cacheManager} property will be checked.
+     * If a {@code cacheManager} has been set, it will be used to eagerly acquire an authentication
+     * {@code cache}, and this cache which will be used as specified in #1.</li>
+     * <li>If neither the {@link #setAuthenticationCache (org.apache.shiro.cache.Cache) authenticationCache}
+     * or {@link #setCacheManager(org.apache.shiro.cache.CacheManager) cacheManager}
+     * properties are set, caching will not be utilized and authentication look-ups will be delegated to
+     * subclass implementations for each authentication attempt.</li>
+     * </ol>
+     * <p/>
+     * This method finishes by calling {@link #onInit()} is to allow subclasses to perform any init behavior desired.
+     *
+     * @since 1.2
+     */
+    public final void init() {
+        //trigger obtaining the authorization cache if possible
+        getAvailableAuthenticationCache();
+        onInit();
+    }
+
+    /**
+     * Template method for subclasses to implement any initialization logic.  Called from
+     * {@link #init()}.
+     *
+     * @since 1.2
+     */
+    protected void onInit() {
+    }
+
+    /**
+     * This implementation attempts to acquire an authentication cache if one is not already configured.
+     *
+     * @since 1.2
+     */
+    protected void afterCacheManagerSet() {
+        //trigger obtaining the authorization cache if possible
+        getAvailableAuthenticationCache();
+    }
+
+    /**
+     * Returns any available {@link Cache} instance to use for authentication caching.  This functions as follows:
+     * <ol>
+     * <li>If an {@link #setAuthenticationCache(org.apache.shiro.cache.Cache) authenticationCache} has been explicitly
+     * configured (it is not null), it is returned.</li>
+     * <li>If there is no {@link #getAuthenticationCache() authenticationCache} configured:
+     * <ol>
+     * <li>If authentication caching is {@link #isAuthenticationCachingEnabled() enabled}, any available
+     * {@link #getCacheManager() cacheManager} will be consulted to obtain an available authentication cache.
+     * </li>
+     * <li>If authentication caching is disabled, this implementation does nothing.</li>
+     * </ol>
+     * </li>
+     * </ol>
+     *
+     * @return any available {@link Cache} instance to use for authentication caching.
+     */
+    private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
+        Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
+        boolean authcCachingEnabled = isAuthenticationCachingEnabled();
+        if (cache == null && authcCachingEnabled) {
+            cache = getAuthenticationCacheLazy();
+        }
+        return cache;
+    }
+
+    /**
+     * Checks to see if the authenticationCache class attribute is null, and if so, attempts to acquire one from
+     * any configured {@link #getCacheManager() cacheManager}.  If one is acquired, it is set as the class attribute.
+     * The class attribute is then returned.
+     *
+     * @return an available cache instance to be used for authentication caching or {@code null} if one is not available.
+     * @since 1.2
+     */
+    private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {
+
+        if (this.authenticationCache == null) {
+
+            log.trace("No authenticationCache instance set.  Checking for a cacheManager...");
+
+            CacheManager cacheManager = getCacheManager();
+
+            if (cacheManager != null) {
+                String cacheName = getAuthenticationCacheName();
+                log.debug("CacheManager [{}] configured.  Building authentication cache '{}'", cacheManager, cacheName);
+                this.authenticationCache = cacheManager.getCache(cacheName);
+            }
+        }
+
+        return this.authenticationCache;
+    }
+
+    /**
+     * Returns any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
+     * isn't any cached data.
+     *
+     * @param token the token submitted during the authentication attempt.
+     * @return any cached AuthenticationInfo corresponding to the specified token or {@code null} if there currently
+     *         isn't any cached data.
+     * @since 1.2
+     */
+    private AuthenticationInfo getCachedAuthenticationInfo(AuthenticationToken token) {
+        AuthenticationInfo info = null;
+
+        Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
+        if (cache != null && token != null) {
+            log.trace("Attempting to retrieve the AuthenticationInfo from cache.");
+            Object key = getAuthenticationCacheKey(token);
+            info = cache.get(key);
+            if (info == null) {
+                log.trace("No AuthorizationInfo found in cache for key [{}]", key);
+            } else {
+                log.trace("Found cached AuthorizationInfo for key [{}]", key);
+            }
+        }
+
+        return info;
+    }
+
+    /**
+     * Caches the specified info if authentication caching
+     * {@link #isAuthenticationCachingEnabled(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) isEnabled}
+     * for the specific token/info pair and a cache instance is available to be used.
+     *
+     * @param token the authentication token submitted which resulted in a successful authentication attempt.
+     * @param info  the AuthenticationInfo to cache as a result of the successful authentication attempt.
+     * @since 1.2
+     */
+    private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) {
+        if (!isAuthenticationCachingEnabled(token, info)) {
+            log.debug("AuthenticationInfo caching is disabled for info [{}].  Submitted token: [{}].", info, token);
+            //return quietly, caching is disabled for this token/info pair:
+            return;
+        }
+
+        Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
+        if (cache != null) {
+            Object key = getAuthenticationCacheKey(token);
+            cache.put(key, info);
+            log.trace("Cached AuthenticationInfo for continued authentication.  key=[{}], value=[{}].", key, info);
+        }
+    }
+
+    /**
+     * Returns {@code true} if authentication caching should be utilized based on the specified
+     * {@link AuthenticationToken} and/or {@link AuthenticationInfo}, {@code false} otherwise.
+     * <p/>
+     * The default implementation simply delegates to {@link #isAuthenticationCachingEnabled()}, the general-case
+     * authentication caching setting.  Subclasses can override this to turn on or off caching at runtime
+     * based on the specific submitted runtime values.
+     *
+     * @param token the submitted authentication token
+     * @param info  the {@code AuthenticationInfo} acquired from data source lookup via
+     *              {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)}
+     * @return {@code true} if authentication caching should be utilized based on the specified
+     *         {@link AuthenticationToken} and/or {@link AuthenticationInfo}, {@code false} otherwise.
+     * @since 1.2
+     */
+    protected boolean isAuthenticationCachingEnabled(AuthenticationToken token, AuthenticationInfo info) {
+        return isAuthenticationCachingEnabled();
+    }
+
+    /**
+     * This implementation functions as follows:
+     * <ol>
+     * <li>It attempts to acquire any cached {@link AuthenticationInfo} corresponding to the specified
+     * {@link AuthenticationToken} argument.  If a cached value is found, it will be used for credentials matching,
+     * alleviating the need to perform any lookups with a data source.</li>
+     * <li>If there is no cached {@link AuthenticationInfo} found, delegate to the
+     * {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} method to perform the actual
+     * lookup.  If authentication caching is enabled and possible, any returned info object will be
+     * {@link #cacheAuthenticationInfoIfPossible(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) cached}
+     * to be used in future authentication attempts.</li>
+     * <li>If an AuthenticationInfo instance is not found in the cache or by lookup, {@code null} is returned to
+     * indicate an account cannot be found.</li>
+     * <li>If an AuthenticationInfo instance is found (either cached or via lookup), ensure the submitted
+     * AuthenticationToken's credentials match the expected {@code AuthenticationInfo}'s credentials using the
+     * {@link #getCredentialsMatcher() credentialsMatcher}.  This means that credentials are always verified
+     * for an authentication attempt.</li>
+     * </ol>
+     *
+     * @param token the submitted account principal and credentials.
+     * @return the AuthenticationInfo corresponding to the given {@code token}, or {@code null} if no
+     *         AuthenticationInfo could be found.
+     * @throws AuthenticationException if authentication failed.
+     */
+    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+
+        AuthenticationInfo info = getCachedAuthenticationInfo(token);
+        if (info == null) {
+            //otherwise not cached, perform the lookup:
+            info = doGetAuthenticationInfo(token);
+            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
+            if (token != null && info != null) {
+                cacheAuthenticationInfoIfPossible(token, info);
+            }
+        } else {
+            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
+        }
+
+        if (info != null) {
+            assertCredentialsMatch(token, info);
+        } else {
+            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
+        }
+
+        return info;
+    }
+
+    /**
+     * Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account
+     * {@code AuthenticationInfo}'s credentials, and if not, throws an {@link AuthenticationException}.
+     *
+     * @param token the submitted authentication token
+     * @param info  the AuthenticationInfo corresponding to the given {@code token}
+     * @throws AuthenticationException if the token's credentials do not match the stored account credentials.
+     */
+    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
+        CredentialsMatcher cm = getCredentialsMatcher();
+        if (cm != null) {
+            if (!cm.doCredentialsMatch(token, info)) {
+                //not successful - throw an exception to indicate this:
+                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
+                throw new IncorrectCredentialsException(msg);
+            }
+        } else {
+            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
+                    "credentials during authentication.  If you do not wish for credentials to be examined, you " +
+                    "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
+        }
+    }
+
+    /**
+     * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled.
+     * This implementation defaults to returning the token's
+     * {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal() principal}, which is usually a username in
+     * most applications.
+     * <h3>Cache Invalidation on Logout</h3>
+     * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you
+     * must ensure the {@link #getAuthenticationCacheKey(org.apache.shiro.subject.PrincipalCollection)} method returns
+     * the same value as this method.
+     *
+     * @param token the authentication token for which any successful authentication will be cached.
+     * @return the cache key to use to cache the associated {@link AuthenticationInfo} after a successful authentication.
+     * @since 1.2
+     */
+    protected Object getAuthenticationCacheKey(AuthenticationToken token) {
+        return token != null ? token.getPrincipal() : null;
+    }
+
+    /**
+     * Returns the key under which {@link AuthenticationInfo} instances are cached if authentication caching is enabled.
+     * This implementation delegates to
+     * {@link #getAvailablePrincipal(org.apache.shiro.subject.PrincipalCollection)}, which returns the primary principal
+     * associated with this particular Realm.
+     * <h3>Cache Invalidation on Logout</h3>
+     * <b>NOTE:</b> If you want to be able to invalidate an account's cached {@code AuthenticationInfo} on logout, you
+     * must ensure that this method returns the same value as the
+     * {@link #getAuthenticationCacheKey(org.apache.shiro.authc.AuthenticationToken)} method!
+     *
+     * @param principals the principals of the account for which to set or remove cached {@code AuthenticationInfo}.
+     * @return the cache key to use when looking up cached {@link AuthenticationInfo} instances.
+     * @since 1.2
+     */
+    protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
+        return getAvailablePrincipal(principals);
+    }
+
+    /**
+     * This implementation clears out any cached authentication data by calling
+     * {@link #clearCachedAuthenticationInfo(org.apache.shiro.subject.PrincipalCollection)}.
+     * If overriding in a subclass, be sure to call {@code super.doClearCache} to ensure this behavior is maintained.
+     *
+     * @param principals principals the principals of the account for which to clear any cached data.
+     * @since 1.2
+     */
+    @Override
+    protected void doClearCache(PrincipalCollection principals) {
+        super.doClearCache(principals);
+        clearCachedAuthenticationInfo(principals);
+    }
+
+    /**
+     * Clears out the AuthenticationInfo cache entry for the specified account.
+     * <p/>
+     * This method is provided as a convenience to subclasses so they can invalidate a cache entry when they
+     * change an account's authentication data (e.g. reset password) during runtime.  Because an account's
+     * AuthenticationInfo can be cached, there needs to be a way to invalidate the cache for only that account so that
+     * subsequent authentication operations don't used the (old) cached value if account data changes.
+     * <p/>
+     * After this method is called, the next authentication for that same account will result in a call to
+     * {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) doGetAuthenticationInfo}, and the
+     * resulting return value will be cached before being returned so it can be reused for later authentications.
+     * <p/>
+     * If you wish to clear out all associated cached data (and not just authentication data), use the
+     * {@link #clearCache(org.apache.shiro.subject.PrincipalCollection)} method instead (which will in turn call this
+     * method by default).
+     *
+     * @param principals the principals of the account for which to clear the cached AuthorizationInfo.
+     * @see #clearCache(org.apache.shiro.subject.PrincipalCollection)
+     * @since 1.2
+     */
+    protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
+        if (!CollectionUtils.isEmpty(principals)) {
+            Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
+            //cache instance will be non-null if caching is enabled:
+            if (cache != null) {
+                Object key = getAuthenticationCacheKey(principals);
+                cache.remove(key);
+            }
+        }
+    }
+
+    /**
+     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
+     * authentication token.
+     * <p/>
+     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
+     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
+     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.
+     * <p/>
+     * A {@code null} return value means that no account could be associated with the specified token.
+     *
+     * @param token the authentication token containing the user's principal and credentials.
+     * @return an {@link AuthenticationInfo} object containing account data resulting from the
+     *         authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
+     * @throws AuthenticationException if there is an error acquiring data or performing
+     *                                 realm-specific authentication logic for the specified <tt>token</tt>
+     */
+    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/AuthorizingRealm.java b/core/src/main/java/org/apache/shiro/realm/AuthorizingRealm.java
new file mode 100644
index 0000000..a811df7
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/AuthorizingRealm.java
@@ -0,0 +1,663 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authz.*;
+import org.apache.shiro.authz.permission.*;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Initializable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * An {@code AuthorizingRealm} extends the {@code AuthenticatingRealm}'s capabilities by adding Authorization
+ * (access control) support.
+ * <p/>
+ * This implementation will perform all role and permission checks automatically (and subclasses do not have to
+ * write this logic) as long as the
+ * {@link #getAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} method returns an
+ * {@link AuthorizationInfo}.  Please see that method's JavaDoc for an in-depth explanation.
+ * <p/>
+ * If you find that you do not want to utilize the {@link AuthorizationInfo AuthorizationInfo} construct,
+ * you are of course free to subclass the {@link AuthenticatingRealm AuthenticatingRealm} directly instead and
+ * implement the remaining Realm interface methods directly.  You might do this if you want have better control
+ * over how the Role and Permission checks occur for your specific data source.  However, using AuthorizationInfo
+ * (and its default implementation {@link org.apache.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}) is sufficient in the large
+ * majority of Realm cases.
+ *
+ * @see org.apache.shiro.authz.SimpleAuthorizationInfo
+ * @since 0.2
+ */
+public abstract class AuthorizingRealm extends AuthenticatingRealm
+        implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
+
+    //TODO - complete JavaDoc
+
+    /*-------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
+
+    /**
+     * The default suffix appended to the realm name for caching AuthorizationInfo instances.
+     */
+    private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache";
+
+    private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
+
+    /*-------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * The cache used by this realm to store AuthorizationInfo instances associated with individual Subject principals.
+     */
+    private boolean authorizationCachingEnabled;
+    private Cache<Object, AuthorizationInfo> authorizationCache;
+    private String authorizationCacheName;
+
+    private PermissionResolver permissionResolver;
+
+    private RolePermissionResolver permissionRoleResolver;
+
+    /*-------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    public AuthorizingRealm() {
+        this(null, null);
+    }
+
+    public AuthorizingRealm(CacheManager cacheManager) {
+        this(cacheManager, null);
+    }
+
+    public AuthorizingRealm(CredentialsMatcher matcher) {
+        this(null, matcher);
+    }
+
+    public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
+        super();
+        if (cacheManager != null) setCacheManager(cacheManager);
+        if (matcher != null) setCredentialsMatcher(matcher);
+
+        this.authorizationCachingEnabled = true;
+        this.permissionResolver = new WildcardPermissionResolver();
+
+        int instanceNumber = INSTANCE_COUNT.getAndIncrement();
+        this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
+        if (instanceNumber > 0) {
+            this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
+        }
+    }
+
+    /*-------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public void setName(String name) {
+        super.setName(name);
+        String authzCacheName = this.authorizationCacheName;
+        if (authzCacheName != null && authzCacheName.startsWith(getClass().getName())) {
+            //get rid of the default class-name based cache name.  Create a more meaningful one
+            //based on the application-unique Realm name:
+            this.authorizationCacheName = name + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
+        }
+    }
+
+    public void setAuthorizationCache(Cache<Object, AuthorizationInfo> authorizationCache) {
+        this.authorizationCache = authorizationCache;
+    }
+
+    public Cache<Object, AuthorizationInfo> getAuthorizationCache() {
+        return this.authorizationCache;
+    }
+
+    public String getAuthorizationCacheName() {
+        return authorizationCacheName;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setAuthorizationCacheName(String authorizationCacheName) {
+        this.authorizationCacheName = authorizationCacheName;
+    }
+
+    /**
+     * Returns {@code true} if authorization caching should be utilized if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code true}.
+     *
+     * @return {@code true} if authorization caching should be utilized, {@code false} otherwise.
+     */
+    public boolean isAuthorizationCachingEnabled() {
+        return isCachingEnabled() && authorizationCachingEnabled;
+    }
+
+    /**
+     * Sets whether or not authorization caching should be utilized if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code true}.
+     *
+     * @param authenticationCachingEnabled the value to set
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setAuthorizationCachingEnabled(boolean authenticationCachingEnabled) {
+        this.authorizationCachingEnabled = authenticationCachingEnabled;
+        if (authenticationCachingEnabled) {
+            setCachingEnabled(true);
+        }
+    }
+
+    public PermissionResolver getPermissionResolver() {
+        return permissionResolver;
+    }
+
+    public void setPermissionResolver(PermissionResolver permissionResolver) {
+        if (permissionResolver == null) throw new IllegalArgumentException("Null PermissionResolver is not allowed");
+        this.permissionResolver = permissionResolver;
+    }
+
+    public RolePermissionResolver getRolePermissionResolver() {
+        return permissionRoleResolver;
+    }
+
+    public void setRolePermissionResolver(RolePermissionResolver permissionRoleResolver) {
+        this.permissionRoleResolver = permissionRoleResolver;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Initializes this realm and potentially enables a cache, depending on configuration.
+     * <p/>
+     * When this method is called, the following logic is executed:
+     * <ol>
+     * <li>If the {@link #setAuthorizationCache cache} property has been set, it will be
+     * used to cache the AuthorizationInfo objects returned from {@link #getAuthorizationInfo}
+     * method invocations.
+     * All future calls to {@code getAuthorizationInfo} will attempt to use this cache first
+     * to alleviate any potentially unnecessary calls to an underlying data store.</li>
+     * <li>If the {@link #setAuthorizationCache cache} property has <b>not</b> been set,
+     * the {@link #setCacheManager cacheManager} property will be checked.
+     * If a {@code cacheManager} has been set, it will be used to create an authorization
+     * {@code cache}, and this newly created cache which will be used as specified in #1.</li>
+     * <li>If neither the {@link #setAuthorizationCache (org.apache.shiro.cache.Cache) cache}
+     * or {@link #setCacheManager(org.apache.shiro.cache.CacheManager) cacheManager}
+     * properties are set, caching will be disabled and authorization look-ups will be delegated to
+     * subclass implementations for each authorization check.</li>
+     * </ol>
+     */
+    protected void onInit() {
+        super.onInit();
+        //trigger obtaining the authorization cache if possible
+        getAvailableAuthorizationCache();
+    }
+
+    protected void afterCacheManagerSet() {
+        super.afterCacheManagerSet();
+        //trigger obtaining the authorization cache if possible
+        getAvailableAuthorizationCache();
+    }
+
+    private Cache<Object, AuthorizationInfo> getAuthorizationCacheLazy() {
+
+        if (this.authorizationCache == null) {
+
+            if (log.isDebugEnabled()) {
+                log.debug("No authorizationCache instance set.  Checking for a cacheManager...");
+            }
+
+            CacheManager cacheManager = getCacheManager();
+
+            if (cacheManager != null) {
+                String cacheName = getAuthorizationCacheName();
+                if (log.isDebugEnabled()) {
+                    log.debug("CacheManager [" + cacheManager + "] has been configured.  Building " +
+                            "authorization cache named [" + cacheName + "]");
+                }
+                this.authorizationCache = cacheManager.getCache(cacheName);
+            } else {
+                if (log.isInfoEnabled()) {
+                    log.info("No cache or cacheManager properties have been set.  Authorization cache cannot " +
+                            "be obtained.");
+                }
+            }
+        }
+
+        return this.authorizationCache;
+    }
+
+    private Cache<Object, AuthorizationInfo> getAvailableAuthorizationCache() {
+        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
+        if (cache == null && isAuthorizationCachingEnabled()) {
+            cache = getAuthorizationCacheLazy();
+        }
+        return cache;
+    }
+
+    /**
+     * Returns an account's authorization-specific information for the specified {@code principals},
+     * or {@code null} if no account could be found.  The resulting {@code AuthorizationInfo} object is used
+     * by the other method implementations in this class to automatically perform access control checks for the
+     * corresponding {@code Subject}.
+     * <p/>
+     * This implementation obtains the actual {@code AuthorizationInfo} object from the subclass's
+     * implementation of
+     * {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) doGetAuthorizationInfo}, and then
+     * caches it for efficient reuse if caching is enabled (see below).
+     * <p/>
+     * Invocations of this method should be thought of as completely orthogonal to acquiring
+     * {@link #getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) authenticationInfo}, since either could
+     * occur in any order.
+     * <p/>
+     * For example, in "Remember Me" scenarios, the user identity is remembered (and
+     * assumed) for their current session and an authentication attempt during that session might never occur.
+     * But because their identity would be remembered, that is sufficient enough information to call this method to
+     * execute any necessary authorization checks.  For this reason, authentication and authorization should be
+     * loosely coupled and not depend on each other.
+     * <h3>Caching</h3>
+     * The {@code AuthorizationInfo} values returned from this method are cached for efficient reuse
+     * if caching is enabled.  Caching is enabled automatically when an {@link #setAuthorizationCache authorizationCache}
+     * instance has been explicitly configured, or if a {@link #setCacheManager cacheManager} has been configured, which
+     * will be used to lazily create the {@code authorizationCache} as needed.
+     * <p/>
+     * If caching is enabled, the authorization cache will be checked first and if found, will return the cached
+     * {@code AuthorizationInfo} immediately.  If caching is disabled, or there is a cache miss, the authorization
+     * info will be looked up from the underlying data store via the
+     * {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} method, which must be implemented
+     * by subclasses.
+     * <h4>Changed Data</h4>
+     * If caching is enabled and if any authorization data for an account is changed at
+     * runtime, such as adding or removing roles and/or permissions, the subclass implementation should clear the
+     * cached AuthorizationInfo for that account via the
+     * {@link #clearCachedAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) clearCachedAuthorizationInfo}
+     * method.  This ensures that the next call to {@code getAuthorizationInfo(PrincipalCollection)} will
+     * acquire the account's fresh authorization data, where it will then be cached for efficient reuse.  This
+     * ensures that stale authorization data will not be reused.
+     *
+     * @param principals the corresponding Subject's identifying principals with which to look up the Subject's
+     *                   {@code AuthorizationInfo}.
+     * @return the authorization information for the account associated with the specified {@code principals},
+     *         or {@code null} if no account could be found.
+     */
+    protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
+
+        if (principals == null) {
+            return null;
+        }
+
+        AuthorizationInfo info = null;
+
+        if (log.isTraceEnabled()) {
+            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
+        }
+
+        Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
+        if (cache != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
+            }
+            Object key = getAuthorizationCacheKey(principals);
+            info = cache.get(key);
+            if (log.isTraceEnabled()) {
+                if (info == null) {
+                    log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
+                } else {
+                    log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
+                }
+            }
+        }
+
+
+        if (info == null) {
+            // Call template method if the info was not found in a cache
+            info = doGetAuthorizationInfo(principals);
+            // If the info is not null and the cache has been created, then cache the authorization info.
+            if (info != null && cache != null) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Caching authorization info for principals: [" + principals + "].");
+                }
+                Object key = getAuthorizationCacheKey(principals);
+                cache.put(key, info);
+            }
+        }
+
+        return info;
+    }
+
+    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
+        return principals;
+    }
+
+    /**
+     * Clears out the AuthorizationInfo cache entry for the specified account.
+     * <p/>
+     * This method is provided as a convenience to subclasses so they can invalidate a cache entry when they
+     * change an account's authorization data (add/remove roles or permissions) during runtime.  Because an account's
+     * AuthorizationInfo can be cached, there needs to be a way to invalidate the cache for only that account so that
+     * subsequent authorization operations don't used the (old) cached value if account data changes.
+     * <p/>
+     * After this method is called, the next authorization check for that same account will result in a call to
+     * {@link #getAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) getAuthorizationInfo}, and the
+     * resulting return value will be cached before being returned so it can be reused for later authorization checks.
+     * <p/>
+     * If you wish to clear out all associated cached data (and not just authorization data), use the
+     * {@link #clearCache(org.apache.shiro.subject.PrincipalCollection)} method instead (which will in turn call this
+     * method by default).
+     *
+     * @param principals the principals of the account for which to clear the cached AuthorizationInfo.
+     */
+    protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
+        if (principals == null) {
+            return;
+        }
+
+        Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
+        //cache instance will be non-null if caching is enabled:
+        if (cache != null) {
+            Object key = getAuthorizationCacheKey(principals);
+            cache.remove(key);
+        }
+    }
+
+    /**
+     * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
+     * an instance from this method, you might want to consider using an instance of
+     * {@link org.apache.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
+     *
+     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
+     * @return the AuthorizationInfo associated with this principals.
+     * @see org.apache.shiro.authz.SimpleAuthorizationInfo
+     */
+    protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
+
+    private Collection<Permission> getPermissions(AuthorizationInfo info) {
+        Set<Permission> permissions = new HashSet<Permission>();
+
+        if (info != null) {
+            Collection<Permission> perms = info.getObjectPermissions();
+            if (!CollectionUtils.isEmpty(perms)) {
+                permissions.addAll(perms);
+            }
+            perms = resolvePermissions(info.getStringPermissions());
+            if (!CollectionUtils.isEmpty(perms)) {
+                permissions.addAll(perms);
+            }
+
+            perms = resolveRolePermissions(info.getRoles());
+            if (!CollectionUtils.isEmpty(perms)) {
+                permissions.addAll(perms);
+            }
+        }
+
+        if (permissions.isEmpty()) {
+            return Collections.emptySet();
+        } else {
+            return Collections.unmodifiableSet(permissions);
+        }
+    }
+
+    private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
+        Collection<Permission> perms = Collections.emptySet();
+        PermissionResolver resolver = getPermissionResolver();
+        if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
+            perms = new LinkedHashSet<Permission>(stringPerms.size());
+            for (String strPermission : stringPerms) {
+                Permission permission = getPermissionResolver().resolvePermission(strPermission);
+                perms.add(permission);
+            }
+        }
+        return perms;
+    }
+
+    private Collection<Permission> resolveRolePermissions(Collection<String> roleNames) {
+        Collection<Permission> perms = Collections.emptySet();
+        RolePermissionResolver resolver = getRolePermissionResolver();
+        if (resolver != null && !CollectionUtils.isEmpty(roleNames)) {
+            perms = new LinkedHashSet<Permission>(roleNames.size());
+            for (String roleName : roleNames) {
+                Collection<Permission> resolved = resolver.resolvePermissionsInRole(roleName);
+                if (!CollectionUtils.isEmpty(resolved)) {
+                    perms.addAll(resolved);
+                }
+            }
+        }
+        return perms;
+    }
+
+    public boolean isPermitted(PrincipalCollection principals, String permission) {
+        Permission p = getPermissionResolver().resolvePermission(permission);
+        return isPermitted(principals, p);
+    }
+
+    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
+        AuthorizationInfo info = getAuthorizationInfo(principals);
+        return isPermitted(permission, info);
+    }
+
+    private boolean isPermitted(Permission permission, AuthorizationInfo info) {
+        Collection<Permission> perms = getPermissions(info);
+        if (perms != null && !perms.isEmpty()) {
+            for (Permission perm : perms) {
+                if (perm.implies(permission)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean[] isPermitted(PrincipalCollection subjectIdentifier, String... permissions) {
+        List<Permission> perms = new ArrayList<Permission>(permissions.length);
+        for (String permString : permissions) {
+            perms.add(getPermissionResolver().resolvePermission(permString));
+        }
+        return isPermitted(subjectIdentifier, perms);
+    }
+
+    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
+        AuthorizationInfo info = getAuthorizationInfo(principals);
+        return isPermitted(permissions, info);
+    }
+
+    protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) {
+        boolean[] result;
+        if (permissions != null && !permissions.isEmpty()) {
+            int size = permissions.size();
+            result = new boolean[size];
+            int i = 0;
+            for (Permission p : permissions) {
+                result[i++] = isPermitted(p, info);
+            }
+        } else {
+            result = new boolean[0];
+        }
+        return result;
+    }
+
+    public boolean isPermittedAll(PrincipalCollection subjectIdentifier, String... permissions) {
+        if (permissions != null && permissions.length > 0) {
+            Collection<Permission> perms = new ArrayList<Permission>(permissions.length);
+            for (String permString : permissions) {
+                perms.add(getPermissionResolver().resolvePermission(permString));
+            }
+            return isPermittedAll(subjectIdentifier, perms);
+        }
+        return false;
+    }
+
+    public boolean isPermittedAll(PrincipalCollection principal, Collection<Permission> permissions) {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        return info != null && isPermittedAll(permissions, info);
+    }
+
+    protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) {
+        if (permissions != null && !permissions.isEmpty()) {
+            for (Permission p : permissions) {
+                if (!isPermitted(p, info)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public void checkPermission(PrincipalCollection subjectIdentifier, String permission) throws AuthorizationException {
+        Permission p = getPermissionResolver().resolvePermission(permission);
+        checkPermission(subjectIdentifier, p);
+    }
+
+    public void checkPermission(PrincipalCollection principal, Permission permission) throws AuthorizationException {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        checkPermission(permission, info);
+    }
+
+    protected void checkPermission(Permission permission, AuthorizationInfo info) {
+        if (!isPermitted(permission, info)) {
+            String msg = "User is not permitted [" + permission + "]";
+            throw new UnauthorizedException(msg);
+        }
+    }
+
+    public void checkPermissions(PrincipalCollection subjectIdentifier, String... permissions) throws AuthorizationException {
+        if (permissions != null) {
+            for (String permString : permissions) {
+                checkPermission(subjectIdentifier, permString);
+            }
+        }
+    }
+
+    public void checkPermissions(PrincipalCollection principal, Collection<Permission> permissions) throws AuthorizationException {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        checkPermissions(permissions, info);
+    }
+
+    protected void checkPermissions(Collection<Permission> permissions, AuthorizationInfo info) {
+        if (permissions != null && !permissions.isEmpty()) {
+            for (Permission p : permissions) {
+                checkPermission(p, info);
+            }
+        }
+    }
+
+    public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        return hasRole(roleIdentifier, info);
+    }
+
+    protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
+        return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
+    }
+
+    public boolean[] hasRoles(PrincipalCollection principal, List<String> roleIdentifiers) {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        boolean[] result = new boolean[roleIdentifiers != null ? roleIdentifiers.size() : 0];
+        if (info != null) {
+            result = hasRoles(roleIdentifiers, info);
+        }
+        return result;
+    }
+
+    protected boolean[] hasRoles(List<String> roleIdentifiers, AuthorizationInfo info) {
+        boolean[] result;
+        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
+            int size = roleIdentifiers.size();
+            result = new boolean[size];
+            int i = 0;
+            for (String roleName : roleIdentifiers) {
+                result[i++] = hasRole(roleName, info);
+            }
+        } else {
+            result = new boolean[0];
+        }
+        return result;
+    }
+
+    public boolean hasAllRoles(PrincipalCollection principal, Collection<String> roleIdentifiers) {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        return info != null && hasAllRoles(roleIdentifiers, info);
+    }
+
+    private boolean hasAllRoles(Collection<String> roleIdentifiers, AuthorizationInfo info) {
+        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
+            for (String roleName : roleIdentifiers) {
+                if (!hasRole(roleName, info)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public void checkRole(PrincipalCollection principal, String role) throws AuthorizationException {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        checkRole(role, info);
+    }
+
+    protected void checkRole(String role, AuthorizationInfo info) {
+        if (!hasRole(role, info)) {
+            String msg = "User does not have role [" + role + "]";
+            throw new UnauthorizedException(msg);
+        }
+    }
+
+    public void checkRoles(PrincipalCollection principal, Collection<String> roles) throws AuthorizationException {
+        AuthorizationInfo info = getAuthorizationInfo(principal);
+        checkRoles(roles, info);
+    }
+
+    public void checkRoles(PrincipalCollection principal, String... roles) throws AuthorizationException {
+        checkRoles(principal, Arrays.asList(roles));
+    }
+
+    protected void checkRoles(Collection<String> roles, AuthorizationInfo info) {
+        if (roles != null && !roles.isEmpty()) {
+            for (String roleName : roles) {
+                checkRole(roleName, info);
+            }
+        }
+    }
+
+    /**
+     * Calls {@code super.doClearCache} to ensure any cached authentication data is removed and then calls
+     * {@link #clearCachedAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} to remove any cached
+     * authorization data.
+     * <p/>
+     * If overriding in a subclass, be sure to call {@code super.doClearCache} to ensure this behavior is maintained.
+     *
+     * @param principals the principals of the account for which to clear any cached AuthorizationInfo
+     * @since 1.2
+     */
+    @Override
+    protected void doClearCache(PrincipalCollection principals) {
+        super.doClearCache(principals);
+        clearCachedAuthorizationInfo(principals);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/CachingRealm.java b/core/src/main/java/org/apache/shiro/realm/CachingRealm.java
new file mode 100644
index 0000000..f99d417
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/CachingRealm.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.LogoutAware;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Nameable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A very basic abstract extension point for the {@link Realm} interface that provides caching support for subclasses.
+ * <p/>
+ * It also provides a convenience method,
+ * {@link #getAvailablePrincipal(org.apache.shiro.subject.PrincipalCollection)}, which is useful across all
+ * realm subclasses for obtaining a realm-specific principal/identity.
+ * <p/>
+ * All actual Realm method implementations are left to subclasses.
+ *
+ * @see #clearCache(org.apache.shiro.subject.PrincipalCollection)
+ * @see #onLogout(org.apache.shiro.subject.PrincipalCollection)
+ * @see #getAvailablePrincipal(org.apache.shiro.subject.PrincipalCollection)
+ * @since 0.9
+ */
+public abstract class CachingRealm implements Realm, Nameable, CacheManagerAware, LogoutAware {
+
+    private static final Logger log = LoggerFactory.getLogger(CachingRealm.class);
+
+    //TODO - complete JavaDoc
+
+    private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private String name;
+    private boolean cachingEnabled;
+    private CacheManager cacheManager;
+
+    /**
+     * Default no-argument constructor that defaults
+     * {@link #isCachingEnabled() cachingEnabled} (for general caching) to {@code true} and sets a
+     * default {@link #getName() name} based on the class name.
+     * <p/>
+     * Note that while in general, caching may be enabled by default, subclasses have control over
+     * if specific caching is enabled.
+     */
+    public CachingRealm() {
+        this.cachingEnabled = true;
+        this.name = getClass().getName() + "_" + INSTANCE_COUNT.getAndIncrement();
+    }
+
+    /**
+     * Returns the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if
+     * caching is disabled.
+     *
+     * @return the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if
+     *         caching is disabled.
+     */
+    public CacheManager getCacheManager() {
+        return this.cacheManager;
+    }
+
+    /**
+     * Sets the <tt>CacheManager</tt> to be used for data caching to reduce EIS round trips.
+     * <p/>
+     * This property is <tt>null</tt> by default, indicating that caching is turned off.
+     *
+     * @param cacheManager the <tt>CacheManager</tt> to use for data caching, or <tt>null</tt> to disable caching.
+     */
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+        afterCacheManagerSet();
+    }
+
+    /**
+     * Returns {@code true} if caching should be used if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code true} since the large majority of Realms will benefit from caching if a CacheManager
+     * has been configured.  However, memory-only realms should set this value to {@code false} since they would
+     * manage account data in memory already lookups would already be as efficient as possible.
+     *
+     * @return {@code true} if caching will be globally enabled if a {@link CacheManager} has been
+     *         configured, {@code false} otherwise
+     */
+    public boolean isCachingEnabled() {
+        return cachingEnabled;
+    }
+
+    /**
+     * Sets whether or not caching should be used if a {@link CacheManager} has been
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) configured}.
+     *
+     * @param cachingEnabled whether or not to globally enable caching for this realm.
+     */
+    public void setCachingEnabled(boolean cachingEnabled) {
+        this.cachingEnabled = cachingEnabled;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Template method that may be implemented by subclasses should they wish to react to a
+     * {@link CacheManager} instance being set on the realm instance via the
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager)} mutator.
+     */
+    protected void afterCacheManagerSet() {
+    }
+
+    /**
+     * If caching is enabled, this will clear any cached data associated with the specified account identity.
+     * Subclasses are free to override for additional behavior, but be sure to call {@code super.onLogout} first.
+     * <p/>
+     * This default implementation merely calls {@link #clearCache(org.apache.shiro.subject.PrincipalCollection)}.
+     *
+     * @param principals the application-specific Subject/user identifier that is logging out.
+     * @see #clearCache(org.apache.shiro.subject.PrincipalCollection)
+     * @see #getAvailablePrincipal(org.apache.shiro.subject.PrincipalCollection)
+     * @since 1.2
+     */
+    public void onLogout(PrincipalCollection principals) {
+        clearCache(principals);
+    }
+
+    /**
+     * Clears out any cached data associated with the specified account identity/identities.
+     * <p/>
+     * This implementation will return quietly if the principals argument is null or empty.  Otherwise it delegates
+     * to {@link #doClearCache(org.apache.shiro.subject.PrincipalCollection)}.
+     *
+     * @param principals the principals of the account for which to clear any cached data.
+     * @since 1.2
+     */
+    protected void clearCache(PrincipalCollection principals) {
+        if (!CollectionUtils.isEmpty(principals)) {
+            doClearCache(principals);
+            log.trace("Cleared cache entries for account with principals [{}]", principals);
+        }
+    }
+
+    /**
+     * This implementation does nothing - it is a template to be overridden by subclasses if necessary.
+     *
+     * @param principals principals the principals of the account for which to clear any cached data.
+     * @since 1.2
+     */
+    protected void doClearCache(PrincipalCollection principals) {
+    }
+
+    /**
+     * A utility method for subclasses that returns the first available principal of interest to this particular realm.
+     * The heuristic used to acquire the principal is as follows:
+     * <ul>
+     * <li>Attempt to get <em>this particular Realm's</em> 'primary' principal in the {@code PrincipalCollection} via a
+     * <code>principals.{@link PrincipalCollection#fromRealm(String) fromRealm}({@link #getName() getName()})</code>
+     * call.</li>
+     * <li>If the previous call does not result in any principals, attempt to get the overall 'primary' principal
+     * from the PrincipalCollection via {@link org.apache.shiro.subject.PrincipalCollection#getPrimaryPrincipal()}.</li>
+     * <li>If there are no principals from that call (or the PrincipalCollection argument was null to begin with),
+     * return {@code null}</li>
+     * </ul>
+     *
+     * @param principals the PrincipalCollection holding all principals (from all realms) associated with a single Subject.
+     * @return the 'primary' principal attributed to this particular realm, or the fallback 'master' principal if it
+     *         exists, or if not {@code null}.
+     * @since 1.2
+     */
+    protected Object getAvailablePrincipal(PrincipalCollection principals) {
+        Object primary = null;
+        if (!CollectionUtils.isEmpty(principals)) {
+            Collection thisPrincipals = principals.fromRealm(getName());
+            if (!CollectionUtils.isEmpty(thisPrincipals)) {
+                primary = thisPrincipals.iterator().next();
+            } else {
+                //no principals attributed to this particular realm.  Fall back to the 'master' primary:
+                primary = principals.getPrimaryPrincipal();
+            }
+        }
+
+        return primary;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/Realm.java b/core/src/main/java/org/apache/shiro/realm/Realm.java
new file mode 100644
index 0000000..13f15e1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/Realm.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ * A <tt>Realm</tt> is a security component that can access application-specific security entities
+ * such as users, roles, and permissions to determine authentication and authorization operations.
+ *
+ * <p><tt>Realm</tt>s usually have a 1-to-1 correspondance with a datasource such as a relational database,
+ * file sysetem, or other similar resource.  As such, implementations of this interface use datasource-specific APIs to
+ * determine authorization data (roles, permissions, etc), such as JDBC, File IO, Hibernate or JPA, or any other
+ * Data Access API.  They are essentially security-specific
+ * <a href="http://en.wikipedia.org/wiki/Data_Access_Object" target="_blank">DAO</a>s.
+ *
+ * <p>Because most of these datasources usually contain Subject (a.k.a. User) information such as usernames and
+ * passwords, a Realm can act as a pluggable authentication module in a
+ * <a href="http://en.wikipedia.org/wiki/Pluggable_Authentication_Modules">PAM</a> configuration.  This allows a Realm to
+ * perform <i>both</i> authentication and authorization duties for a single datasource, which caters to the large
+ * majority of applications.  If for some reason you don't want your Realm implementation to perform authentication
+ * duties, you should override the {@link #supports(org.apache.shiro.authc.AuthenticationToken)} method to always
+ * return <tt>false</tt>.
+ *
+ * <p>Because every application is different, security data such as users and roles can be
+ * represented in any number of ways.  Shiro tries to maintain a non-intrusive development philosophy whenever
+ * possible - it does not require you to implement or extend any <tt>User</tt>, <tt>Group</tt> or <tt>Role</tt>
+ * interfaces or classes.
+ *
+ * <p>Instead, Shiro allows applications to implement this interface to access environment-specific datasources
+ * and data model objects.  The implementation can then be plugged in to the application's Shiro configuration.
+ * This modular technique abstracts away any environment/modeling details and allows Shiro to be deployed in
+ * practically any application environment.
+ *
+ * <p>Most users will not implement the <tt>Realm</tt> interface directly, but will extend one of the subclasses,
+ * {@link org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm} or {@link org.apache.shiro.realm.AuthorizingRealm}, greatly reducing the effort requird
+ * to implement a <tt>Realm</tt> from scratch.</p>
+ *
+ * @see org.apache.shiro.realm.CachingRealm CachingRealm
+ * @see org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm
+ * @see org.apache.shiro.realm.AuthorizingRealm AuthorizingRealm
+ * @see org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator
+ * @since 0.1
+ */
+public interface Realm {
+
+    /**
+     * Returns the (application-unique) name assigned to this <code>Realm</code>. All realms configured for a single
+     * application must have a unique name.
+     *
+     * @return the (application-unique) name assigned to this <code>Realm</code>.
+     */
+    String getName();
+
+    /**
+     * Returns <tt>true</tt> if this realm wishes to authenticate the Subject represented by the given
+     * {@link org.apache.shiro.authc.AuthenticationToken AuthenticationToken} instance, <tt>false</tt> otherwise.
+     *
+     * <p>If this method returns <tt>false</tt>, it will not be called to authenticate the Subject represented by
+     * the token - more specifically, a <tt>false</tt> return value means this Realm instance's
+     * {@link #getAuthenticationInfo} method will not be invoked for that token.
+     *
+     * @param token the AuthenticationToken submitted for the authentication attempt
+     * @return <tt>true</tt> if this realm can/will authenticate Subjects represented by specified token,
+     *         <tt>false</tt> otherwise.
+     */
+    boolean supports(AuthenticationToken token);
+
+    /**
+     * Returns an account's authentication-specific information for the specified <tt>token</tt>,
+     * or <tt>null</tt> if no account could be found based on the <tt>token</tt>.
+     *
+     * <p>This method effectively represents a login attempt for the corresponding user with the underlying EIS datasource.
+     * Most implementations merely just need to lookup and return the account data only (as the method name implies)
+     * and let Shiro do the rest, but implementations may of course perform eis specific login operations if so
+     * desired.
+     *
+     * @param token the application-specific representation of an account principal and credentials.
+     * @return the authentication information for the account associated with the specified <tt>token</tt>,
+     *         or <tt>null</tt> if no account could be found.
+     * @throws org.apache.shiro.authc.AuthenticationException
+     *          if there is an error obtaining or constructing an AuthenticationInfo object based on the
+     *          specified <tt>token</tt> or implementation-specifc login behavior fails.
+     */
+    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/RealmFactory.java b/core/src/main/java/org/apache/shiro/realm/RealmFactory.java
new file mode 100644
index 0000000..b360a5b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/RealmFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import java.util.Collection;
+
+/**
+ * Enables Shiro end-users to configure and initialize one or more {@link Realm Realm} instances
+ * in any manner desired.
+ * <p/>
+ * This interface exists to support environments where end-users may not wish to use Shiro's default
+ * text-based configuration to create and configure realms, and instead wish to retrieve a realm configured in a
+ * proprietary manner.  An implementation of this interface can access that proprietary mechanism to retrieve the
+ * already-created <tt>Realm</tt>s.
+ *
+ * <p>The <code>Realm</code> instances returned will used to construct the application's
+ * {@link org.apache.shiro.mgt.SecurityManager SecurityManager} instance.
+ *
+ * @since 0.9
+ */
+public interface RealmFactory {
+
+    /**
+     * Returns a collection of {@link Realm Realm} instances that will be used to construct
+     * the application's SecurityManager instance.
+     *
+     * <p>The order of the collection is important.  The {@link org.apache.shiro.mgt.SecurityManager SecurityManager}
+     * implementation will consult the Realms during authentication (log-in) and authorization (access control)
+     * operations in the collection's <b>iteration order</b>.  That is, the resulting collection's
+     * {@link java.util.Iterator Iterator} determines the order in which Realms are used.
+     *
+     * @return the <code>Collection</code> of Realms that the application's <code>SecurityManager</code> will use
+     *         for security data access.
+     */
+    Collection<Realm> getRealms();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/SimpleAccountRealm.java b/core/src/main/java/org/apache/shiro/realm/SimpleAccountRealm.java
new file mode 100644
index 0000000..3f77b98
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/SimpleAccountRealm.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleRole;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * A simple implementation of the {@link Realm Realm} interface that
+ * uses a set of configured user accounts and roles to support authentication and authorization.  Each account entry
+ * specifies the username, password, and roles for a user.  Roles can also be mapped
+ * to permissions and associated with users.
+ * <p/>
+ * User accounts and roles are stored in two {@code Map}s in memory, so it is expected that the total number of either
+ * is not sufficiently large.
+ *
+ * @since 0.1
+ */
+public class SimpleAccountRealm extends AuthorizingRealm {
+
+    //TODO - complete JavaDoc
+    protected final Map<String, SimpleAccount> users; //username-to-SimpleAccount
+    protected final Map<String, SimpleRole> roles; //roleName-to-SimpleRole
+    protected final ReadWriteLock USERS_LOCK;
+    protected final ReadWriteLock ROLES_LOCK;
+
+    public SimpleAccountRealm() {
+        this.users = new LinkedHashMap<String, SimpleAccount>();
+        this.roles = new LinkedHashMap<String, SimpleRole>();
+        USERS_LOCK = new ReentrantReadWriteLock();
+        ROLES_LOCK = new ReentrantReadWriteLock();
+        //SimpleAccountRealms are memory-only realms - no need for an additional cache mechanism since we're
+        //already as memory-efficient as we can be:
+        setCachingEnabled(false);
+    }
+
+    public SimpleAccountRealm(String name) {
+        this();
+        setName(name);
+    }
+
+    protected SimpleAccount getUser(String username) {
+        USERS_LOCK.readLock().lock();
+        try {
+            return this.users.get(username);
+        } finally {
+            USERS_LOCK.readLock().unlock();
+        }
+    }
+
+    public boolean accountExists(String username) {
+        return getUser(username) != null;
+    }
+
+    public void addAccount(String username, String password) {
+        addAccount(username, password, (String[]) null);
+    }
+
+    public void addAccount(String username, String password, String... roles) {
+        Set<String> roleNames = CollectionUtils.asSet(roles);
+        SimpleAccount account = new SimpleAccount(username, password, getName(), roleNames, null);
+        add(account);
+    }
+
+    protected String getUsername(SimpleAccount account) {
+        return getUsername(account.getPrincipals());
+    }
+
+    protected String getUsername(PrincipalCollection principals) {
+        return getAvailablePrincipal(principals).toString();
+    }
+
+    protected void add(SimpleAccount account) {
+        String username = getUsername(account);
+        USERS_LOCK.writeLock().lock();
+        try {
+            this.users.put(username, account);
+        } finally {
+            USERS_LOCK.writeLock().unlock();
+        }
+    }
+
+    protected SimpleRole getRole(String rolename) {
+        ROLES_LOCK.readLock().lock();
+        try {
+            return roles.get(rolename);
+        } finally {
+            ROLES_LOCK.readLock().unlock();
+        }
+    }
+
+    public boolean roleExists(String name) {
+        return getRole(name) != null;
+    }
+
+    public void addRole(String name) {
+        add(new SimpleRole(name));
+    }
+
+    protected void add(SimpleRole role) {
+        ROLES_LOCK.writeLock().lock();
+        try {
+            roles.put(role.getName(), role);
+        } finally {
+            ROLES_LOCK.writeLock().unlock();
+        }
+    }
+
+    protected static Set<String> toSet(String delimited, String delimiter) {
+        if (delimited == null || delimited.trim().equals("")) {
+            return null;
+        }
+
+        Set<String> values = new HashSet<String>();
+        String[] rolenamesArray = delimited.split(delimiter);
+        for (String s : rolenamesArray) {
+            String trimmed = s.trim();
+            if (trimmed.length() > 0) {
+                values.add(trimmed);
+            }
+        }
+
+        return values;
+    }
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        SimpleAccount account = getUser(upToken.getUsername());
+
+        if (account != null) {
+
+            if (account.isLocked()) {
+                throw new LockedAccountException("Account [" + account + "] is locked.");
+            }
+            if (account.isCredentialsExpired()) {
+                String msg = "The credentials for account [" + account + "] are expired";
+                throw new ExpiredCredentialsException(msg);
+            }
+
+        }
+
+        return account;
+    }
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        String username = getUsername(principals);
+        USERS_LOCK.readLock().lock();
+        try {
+            return this.users.get(username);
+        } finally {
+            USERS_LOCK.readLock().unlock();
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealm.java b/core/src/main/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealm.java
new file mode 100644
index 0000000..6926a99
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealm.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.activedirectory;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.ldap.AbstractLdapRealm;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.apache.shiro.realm.ldap.LdapUtils;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+import java.util.*;
+
+
+/**
+ * A {@link Realm} that authenticates with an active directory LDAP
+ * server to determine the roles for a particular user.  This implementation
+ * queries for the user's groups and then maps the group names to roles using the
+ * {@link #groupRolesMap}.
+ *
+ * @since 0.1
+ */
+public class ActiveDirectoryRealm extends AbstractLdapRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    private static final Logger log = LoggerFactory.getLogger(ActiveDirectoryRealm.class);
+
+    private static final String ROLE_NAMES_DELIMETER = ",";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+
+    /**
+     * Mapping from fully qualified active directory
+     * group names (e.g. CN=Group,OU=Company,DC=MyDomain,DC=local)
+     * as returned by the active directory LDAP server to role names.
+     */
+    private Map<String, String> groupRolesMap;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    public void setGroupRolesMap(Map<String, String> groupRolesMap) {
+        this.groupRolesMap = groupRolesMap;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+
+    /**
+     * Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for the
+     * specified username.  This method binds to the LDAP server using the provided username and password -
+     * which if successful, indicates that the password is correct.
+     * <p/>
+     * This method can be overridden by subclasses to query the LDAP server in a more complex way.
+     *
+     * @param token              the authentication token provided by the user.
+     * @param ldapContextFactory the factory used to build connections to the LDAP server.
+     * @return an {@link AuthenticationInfo} instance containing information retrieved from LDAP.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
+
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+
+        // Binds using the username and password provided by the user.
+        LdapContext ctx = null;
+        try {
+            ctx = ldapContextFactory.getLdapContext(upToken.getUsername(), String.valueOf(upToken.getPassword()));
+        } finally {
+            LdapUtils.closeContext(ctx);
+        }
+
+        return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword());
+    }
+
+    protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
+        return new SimpleAuthenticationInfo(username, password, getName());
+    }
+
+
+    /**
+     * Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by querying the active directory LDAP context for the
+     * groups that a user is a member of.  The groups are then translated to role names by using the
+     * configured {@link #groupRolesMap}.
+     * <p/>
+     * This implementation expects the <tt>principal</tt> argument to be a String username.
+     * <p/>
+     * Subclasses can override this method to determine authorization data (roles, permissions, etc) in a more
+     * complex way.  Note that this default implementation does not support permissions, only roles.
+     *
+     * @param principals         the principal of the Subject whose account is being retrieved.
+     * @param ldapContextFactory the factory used to create LDAP connections.
+     * @return the AuthorizationInfo for the given Subject principal.
+     * @throws NamingException if an error occurs when searching the LDAP server.
+     */
+    protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException {
+
+        String username = (String) getAvailablePrincipal(principals);
+
+        // Perform context search
+        LdapContext ldapContext = ldapContextFactory.getSystemLdapContext();
+
+        Set<String> roleNames;
+
+        try {
+            roleNames = getRoleNamesForUser(username, ldapContext);
+        } finally {
+            LdapUtils.closeContext(ldapContext);
+        }
+
+        return buildAuthorizationInfo(roleNames);
+    }
+
+    protected AuthorizationInfo buildAuthorizationInfo(Set<String> roleNames) {
+        return new SimpleAuthorizationInfo(roleNames);
+    }
+
+    private Set<String> getRoleNamesForUser(String username, LdapContext ldapContext) throws NamingException {
+        Set<String> roleNames;
+        roleNames = new LinkedHashSet<String>();
+
+        SearchControls searchCtls = new SearchControls();
+        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+        String userPrincipalName = username;
+        if (principalSuffix != null) {
+            userPrincipalName += principalSuffix;
+        }
+
+        //SHIRO-115 - prevent potential code injection:
+        String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))";
+        Object[] searchArguments = new Object[]{userPrincipalName};
+
+        NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
+
+        while (answer.hasMoreElements()) {
+            SearchResult sr = (SearchResult) answer.next();
+
+            if (log.isDebugEnabled()) {
+                log.debug("Retrieving group names for user [" + sr.getName() + "]");
+            }
+
+            Attributes attrs = sr.getAttributes();
+
+            if (attrs != null) {
+                NamingEnumeration ae = attrs.getAll();
+                while (ae.hasMore()) {
+                    Attribute attr = (Attribute) ae.next();
+
+                    if (attr.getID().equals("memberOf")) {
+
+                        Collection<String> groupNames = LdapUtils.getAllAttributeValues(attr);
+
+                        if (log.isDebugEnabled()) {
+                            log.debug("Groups found for user [" + username + "]: " + groupNames);
+                        }
+
+                        Collection<String> rolesForGroups = getRoleNamesForGroups(groupNames);
+                        roleNames.addAll(rolesForGroups);
+                    }
+                }
+            }
+        }
+        return roleNames;
+    }
+
+    /**
+     * This method is called by the default implementation to translate Active Directory group names
+     * to role names.  This implementation uses the {@link #groupRolesMap} to map group names to role names.
+     *
+     * @param groupNames the group names that apply to the current user.
+     * @return a collection of roles that are implied by the given role names.
+     */
+    protected Collection<String> getRoleNamesForGroups(Collection<String> groupNames) {
+        Set<String> roleNames = new HashSet<String>(groupNames.size());
+
+        if (groupRolesMap != null) {
+            for (String groupName : groupNames) {
+                String strRoleNames = groupRolesMap.get(groupName);
+                if (strRoleNames != null) {
+                    for (String roleName : strRoleNames.split(ROLE_NAMES_DELIMETER)) {
+
+                        if (log.isDebugEnabled()) {
+                            log.debug("User is member of group [" + groupName + "] so adding role [" + roleName + "]");
+                        }
+
+                        roleNames.add(roleName);
+
+                    }
+                }
+            }
+        }
+        return roleNames;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/activedirectory/package-info.java b/core/src/main/java/org/apache/shiro/realm/activedirectory/package-info.java
new file mode 100644
index 0000000..ebe8809
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/activedirectory/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Realms that acquire security data from a Microsoft Active Directory.
+ */
+package org.apache.shiro.realm.activedirectory;
diff --git a/core/src/main/java/org/apache/shiro/realm/jdbc/JdbcRealm.java b/core/src/main/java/org/apache/shiro/realm/jdbc/JdbcRealm.java
new file mode 100644
index 0000000..646875c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/jdbc/JdbcRealm.java
@@ -0,0 +1,427 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.jdbc;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.JdbcUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+
+/**
+ * Realm that allows authentication and authorization via JDBC calls.  The default queries suggest a potential schema
+ * for retrieving the user's password for authentication, and querying for a user's roles and permissions.  The
+ * default queries can be overridden by setting the query properties of the realm.
+ * <p/>
+ * If the default implementation
+ * of authentication and authorization cannot handle your schema, this class can be subclassed and the
+ * appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)},
+ * {@link #getRoleNamesForUser(java.sql.Connection,String)}, and/or {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}
+ * <p/>
+ * This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.
+ *
+ * @since 0.2
+ */
+public class JdbcRealm extends AuthorizingRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * The default query used to retrieve account data for the user.
+     */
+    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
+    
+    /**
+     * The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
+     */
+    protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
+
+    /**
+     * The default query used to retrieve the roles that apply to a user.
+     */
+    protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
+
+    /**
+     * The default query used to retrieve permissions that apply to a particular role.
+     */
+    protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
+
+    private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);
+    
+    /**
+     * Password hash salt configuration. <ul>
+     *   <li>NO_SALT - password hashes are not salted.</li>
+     *   <li>CRYPT - password hashes are stored in unix crypt format.</li>
+     *   <li>COLUMN - salt is in a separate column in the database.</li> 
+     *   <li>EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called
+     *       to get the salt</li></ul>
+     */
+    public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    protected DataSource dataSource;
+
+    protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
+
+    protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
+
+    protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
+
+    protected boolean permissionsLookupEnabled = false;
+    
+    protected SaltStyle saltStyle = SaltStyle.NO_SALT;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+    
+    /**
+     * Sets the datasource that should be used to retrieve connections used by this realm.
+     *
+     * @param dataSource the SQL data source.
+     */
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    /**
+     * Overrides the default query used to retrieve a user's password during authentication.  When using the default
+     * implementation, this query must take the user's username as a single parameter and return a single result
+     * with the user's password as the first column.  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)} or
+     * just {@link #getPasswordForUser(java.sql.Connection,String)}
+     *
+     * @param authenticationQuery the query to use for authentication.
+     * @see #DEFAULT_AUTHENTICATION_QUERY
+     */
+    public void setAuthenticationQuery(String authenticationQuery) {
+        this.authenticationQuery = authenticationQuery;
+    }
+
+    /**
+     * Overrides the default query used to retrieve a user's roles during authorization.  When using the default
+     * implementation, this query must take the user's username as a single parameter and return a row
+     * per role with a single column containing the role name.  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthorizationInfo(PrincipalCollection)} or just
+     * {@link #getRoleNamesForUser(java.sql.Connection,String)}
+     *
+     * @param userRolesQuery the query to use for retrieving a user's roles.
+     * @see #DEFAULT_USER_ROLES_QUERY
+     */
+    public void setUserRolesQuery(String userRolesQuery) {
+        this.userRolesQuery = userRolesQuery;
+    }
+
+    /**
+     * Overrides the default query used to retrieve a user's permissions during authorization.  When using the default
+     * implementation, this query must take a role name as the single parameter and return a row
+     * per permission with three columns containing the fully qualified name of the permission class, the permission
+     * name, and the permission actions (in that order).  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} or just
+     * {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}</p>
+     * <p/>
+     * <b>Permissions are only retrieved if you set {@link #permissionsLookupEnabled} to true.  Otherwise,
+     * this query is ignored.</b>
+     *
+     * @param permissionsQuery the query to use for retrieving permissions for a role.
+     * @see #DEFAULT_PERMISSIONS_QUERY
+     * @see #setPermissionsLookupEnabled(boolean)
+     */
+    public void setPermissionsQuery(String permissionsQuery) {
+        this.permissionsQuery = permissionsQuery;
+    }
+
+    /**
+     * Enables lookup of permissions during authorization.  The default is "false" - meaning that only roles
+     * are associated with a user.  Set this to true in order to lookup roles <b>and</b> permissions.
+     *
+     * @param permissionsLookupEnabled true if permissions should be looked up during authorization, or false if only
+     *                                 roles should be looked up.
+     */
+    public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
+        this.permissionsLookupEnabled = permissionsLookupEnabled;
+    }
+    
+    /**
+     * Sets the salt style.  See {@link #saltStyle}.
+     * 
+     * @param saltStyle new SaltStyle to set.
+     */
+    public void setSaltStyle(SaltStyle saltStyle) {
+        this.saltStyle = saltStyle;
+        if (saltStyle == SaltStyle.COLUMN && authenticationQuery.equals(DEFAULT_AUTHENTICATION_QUERY)) {
+            authenticationQuery = DEFAULT_SALTED_AUTHENTICATION_QUERY;
+        }
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        String username = upToken.getUsername();
+
+        // Null username is invalid
+        if (username == null) {
+            throw new AccountException("Null usernames are not allowed by this realm.");
+        }
+
+        Connection conn = null;
+        SimpleAuthenticationInfo info = null;
+        try {
+            conn = dataSource.getConnection();
+
+            String password = null;
+            String salt = null;
+            switch (saltStyle) {
+            case NO_SALT:
+                password = getPasswordForUser(conn, username)[0];
+                break;
+            case CRYPT:
+                // TODO: separate password and hash from getPasswordForUser[0]
+                throw new ConfigurationException("Not implemented yet");
+                //break;
+            case COLUMN:
+                String[] queryResults = getPasswordForUser(conn, username);
+                password = queryResults[0];
+                salt = queryResults[1];
+                break;
+            case EXTERNAL:
+                password = getPasswordForUser(conn, username)[0];
+                salt = getSaltForUser(username);
+            }
+
+            if (password == null) {
+                throw new UnknownAccountException("No account found for user [" + username + "]");
+            }
+
+            info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
+            
+            if (salt != null) {
+                info.setCredentialsSalt(ByteSource.Util.bytes(salt));
+            }
+
+        } catch (SQLException e) {
+            final String message = "There was a SQL error while authenticating user [" + username + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+
+            // Rethrow any SQL errors as an authentication exception
+            throw new AuthenticationException(message, e);
+        } finally {
+            JdbcUtils.closeConnection(conn);
+        }
+
+        return info;
+    }
+
+    private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
+
+        String[] result;
+        boolean returningSeparatedSalt = false;
+        switch (saltStyle) {
+        case NO_SALT:
+        case CRYPT:
+        case EXTERNAL:
+            result = new String[1];
+            break;
+        default:
+            result = new String[2];
+            returningSeparatedSalt = true;
+        }
+        
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        try {
+            ps = conn.prepareStatement(authenticationQuery);
+            ps.setString(1, username);
+
+            // Execute query
+            rs = ps.executeQuery();
+
+            // Loop over results - although we are only expecting one result, since usernames should be unique
+            boolean foundResult = false;
+            while (rs.next()) {
+
+                // Check to ensure only one row is processed
+                if (foundResult) {
+                    throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
+                }
+
+                result[0] = rs.getString(1);
+                if (returningSeparatedSalt) {
+                    result[1] = rs.getString(2);
+                }
+
+                foundResult = true;
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+
+        return result;
+    }
+
+    /**
+     * This implementation of the interface expects the principals collection to return a String username keyed off of
+     * this realm's {@link #getName() name}
+     *
+     * @see #getAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
+     */
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+
+        //null usernames are invalid
+        if (principals == null) {
+            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
+        }
+
+        String username = (String) getAvailablePrincipal(principals);
+
+        Connection conn = null;
+        Set<String> roleNames = null;
+        Set<String> permissions = null;
+        try {
+            conn = dataSource.getConnection();
+
+            // Retrieve roles and permissions from database
+            roleNames = getRoleNamesForUser(conn, username);
+            if (permissionsLookupEnabled) {
+                permissions = getPermissions(conn, username, roleNames);
+            }
+
+        } catch (SQLException e) {
+            final String message = "There was a SQL error while authorizing user [" + username + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+
+            // Rethrow any SQL errors as an authorization exception
+            throw new AuthorizationException(message, e);
+        } finally {
+            JdbcUtils.closeConnection(conn);
+        }
+
+        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
+        info.setStringPermissions(permissions);
+        return info;
+
+    }
+
+    protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        Set<String> roleNames = new LinkedHashSet<String>();
+        try {
+            ps = conn.prepareStatement(userRolesQuery);
+            ps.setString(1, username);
+
+            // Execute query
+            rs = ps.executeQuery();
+
+            // Loop over results and add each returned role to a set
+            while (rs.next()) {
+
+                String roleName = rs.getString(1);
+
+                // Add the role to the list of names if it isn't null
+                if (roleName != null) {
+                    roleNames.add(roleName);
+                } else {
+                    if (log.isWarnEnabled()) {
+                        log.warn("Null role name found while retrieving role names for user [" + username + "]");
+                    }
+                }
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+        return roleNames;
+    }
+
+    protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
+        PreparedStatement ps = null;
+        Set<String> permissions = new LinkedHashSet<String>();
+        try {
+            ps = conn.prepareStatement(permissionsQuery);
+            for (String roleName : roleNames) {
+
+                ps.setString(1, roleName);
+
+                ResultSet rs = null;
+
+                try {
+                    // Execute query
+                    rs = ps.executeQuery();
+
+                    // Loop over results and add each returned role to a set
+                    while (rs.next()) {
+
+                        String permissionString = rs.getString(1);
+
+                        // Add the permission to the set of permissions
+                        permissions.add(permissionString);
+                    }
+                } finally {
+                    JdbcUtils.closeResultSet(rs);
+                }
+
+            }
+        } finally {
+            JdbcUtils.closeStatement(ps);
+        }
+
+        return permissions;
+    }
+    
+    protected String getSaltForUser(String username) {
+        return username;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/jdbc/package-info.java b/core/src/main/java/org/apache/shiro/realm/jdbc/package-info.java
new file mode 100644
index 0000000..af7be66
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/jdbc/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Realms that acquire security data from an RDBMS (Relational Database Management System) using the 
+ * JDBC API.
+ */
+package org.apache.shiro.realm.jdbc;
diff --git a/core/src/main/java/org/apache/shiro/realm/jndi/JndiRealmFactory.java b/core/src/main/java/org/apache/shiro/realm/jndi/JndiRealmFactory.java
new file mode 100644
index 0000000..9657973
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/jndi/JndiRealmFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.jndi;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.shiro.jndi.JndiLocator;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.RealmFactory;
+import org.apache.shiro.util.StringUtils;
+
+
+/**
+ * Looks up one or more Realm instances from JNDI using specified {@link #setJndiNames jndiNames}.
+ *
+ * <p>This is primarily provided to support Realm instances configured in JEE and EJB environments, but will
+ * work in any environment where {@link Realm Realm} instances are bound in JNDI instead of using
+ * programmatic or text-based configuration.
+ *
+ * @since 0.9
+ */
+public class JndiRealmFactory extends JndiLocator implements RealmFactory {
+
+    Collection<String> jndiNames = null;
+
+    /**
+     * Returns the JNDI names that will be used to look up Realm(s) from JNDI.
+     *
+     * @return the JNDI names that will be used to look up Realm(s) from JNDI.
+     * @see #setJndiNames(String)
+     * @see #setJndiNames(Collection)
+     */
+    public Collection<String> getJndiNames() {
+        return jndiNames;
+    }
+
+    /**
+     * Sets the JNDI names that will be used to look up Realm(s) from JNDI.
+     * <p/>
+     * The order of the collection determines the order that the Realms will be returned to the SecurityManager.
+     * <p/>
+     * If you find it easier to specify these names as a comma-delmited string, you may use the
+     * {@link #setJndiNames(String)} method instead.
+     *
+     * @param jndiNames the JNDI names that will be used to look up Realm(s) from JNDI.
+     * @see #setJndiNames(String)
+     */
+    public void setJndiNames(Collection<String> jndiNames) {
+        this.jndiNames = jndiNames;
+    }
+
+    /**
+     * Specifies a comma-delimited list of JNDI names to lookup, each one corresponding to a jndi-bound
+     * {@link Realm Realm}.  The Realms will be made available to the SecurityManager in the order
+     * they are defined.
+     *
+     * @param commaDelimited a comma-delimited list of JNDI names, each representing the JNDI name used to
+     *                       look up a corresponding jndi-bound Realm.
+     * @throws IllegalStateException if the specified argument is null or the empty string.
+     */
+    public void setJndiNames(String commaDelimited) throws IllegalStateException {
+        String arg = StringUtils.clean(commaDelimited);
+        if (arg == null) {
+            String msg = "One or more comma-delimited jndi names must be specified for the " +
+                    getClass().getName() + " to locate Realms.";
+            throw new IllegalStateException(msg);
+        }
+        String[] names = StringUtils.tokenizeToStringArray(arg, ",");
+        setJndiNames(Arrays.asList(names));
+    }
+
+    /**
+     * Performs the JNDI lookups for each specified {@link #getJndiNames() JNDI name} and returns all
+     * discovered Realms in an ordered collection.
+     *
+     * <p>The returned Collection is in the same order as the specified
+     * {@link #setJndiNames(java.util.Collection) jndiNames}
+     *
+     * @return an ordered collection of the {@link #setJndiNames(java.util.Collection) specified Realms} found in JNDI.
+     * @throws IllegalStateException if any of the JNDI names fails to successfully look up a Realm instance.
+     */
+    public Collection<Realm> getRealms() throws IllegalStateException {
+        Collection<String> jndiNames = getJndiNames();
+        if (jndiNames == null || jndiNames.isEmpty()) {
+            String msg = "One or more jndi names must be specified for the " +
+                    getClass().getName() + " to locate Realms.";
+            throw new IllegalStateException(msg);
+        }
+        List<Realm> realms = new ArrayList<Realm>(jndiNames.size());
+        for (String name : jndiNames) {
+            try {
+                Realm realm = (Realm) lookup(name, Realm.class);
+                realms.add(realm);
+            } catch (Exception e) {
+                throw new IllegalStateException("Unable to look up realm with jndi name '" + name + "'.", e);
+            }
+        }
+        return realms.isEmpty() ? null : realms;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/jndi/package-info.java b/core/src/main/java/org/apache/shiro/realm/jndi/package-info.java
new file mode 100644
index 0000000..4aa3f93
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/jndi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support for acquiring Realms from JNDI, particularly useful for configuring Shiro in JEE or EJB environments. */
+package org.apache.shiro.realm.jndi;
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/AbstractLdapRealm.java b/core/src/main/java/org/apache/shiro/realm/ldap/AbstractLdapRealm.java
new file mode 100644
index 0000000..cfd3519
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/AbstractLdapRealm.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.NamingException;
+
+/**
+ * <p>A {@link org.apache.shiro.realm.Realm} that authenticates with an LDAP
+ * server to build the Subject for a user.  This implementation only returns roles for a
+ * particular user, and not permissions - but it can be subclassed to build a permission
+ * list as well.</p>
+ *
+ * <p>Implementations would need to implement the
+ * {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken ,LdapContextFactory)} and
+ * {@link #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection ,LdapContextFactory)} abstract methods.</p>
+ *
+ * <p>By default, this implementation will create an instance of {@link DefaultLdapContextFactory} to use for
+ * creating LDAP connections using the principalSuffix, searchBase, url, systemUsername, and systemPassword properties
+ * specified on the realm.  The remaining settings use the defaults of {@link DefaultLdapContextFactory}, which are usually
+ * sufficient.  If more customized connections are needed, you should inject a custom {@link LdapContextFactory}, which
+ * will cause these properties specified on the realm to be ignored.</p>
+ *
+ * @see #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken , LdapContextFactory)
+ * @see #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection , LdapContextFactory)
+ * @since 0.1
+ */
+public abstract class AbstractLdapRealm extends AuthorizingRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractLdapRealm.class);
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    protected String principalSuffix = null;
+
+    protected String searchBase = null;
+
+    protected String url = null;
+
+    protected String systemUsername = null;
+
+    protected String systemPassword = null;
+
+    private LdapContextFactory ldapContextFactory = null;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param principalSuffix the suffix.
+     * @see DefaultLdapContextFactory#setPrincipalSuffix(String)
+     */
+    public void setPrincipalSuffix(String principalSuffix) {
+        this.principalSuffix = principalSuffix;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param searchBase the search base.
+     * @see DefaultLdapContextFactory#setSearchBase(String)
+     */
+    public void setSearchBase(String searchBase) {
+        this.searchBase = searchBase;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param url the LDAP url.
+     * @see DefaultLdapContextFactory#setUrl(String)
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param systemUsername the username to use when logging into the LDAP server for authorization.
+     * @see DefaultLdapContextFactory#setSystemUsername(String)
+     */
+    public void setSystemUsername(String systemUsername) {
+        this.systemUsername = systemUsername;
+    }
+
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param systemPassword the password to use when logging into the LDAP server for authorization.
+     * @see DefaultLdapContextFactory#setSystemPassword(String)
+     */
+    public void setSystemPassword(String systemPassword) {
+        this.systemPassword = systemPassword;
+    }
+
+
+    /**
+     * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
+     * authentication and authorization.  If this is set, the {@link LdapContextFactory} provided will be used.
+     * Otherwise, a {@link DefaultLdapContextFactory} instance will be created based on the properties specified
+     * in this realm.
+     *
+     * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
+     */
+    public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
+        this.ldapContextFactory = ldapContextFactory;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S                |
+    ============================================*/
+
+    protected void onInit() {
+        super.onInit();
+        ensureContextFactory();
+    }
+
+    private LdapContextFactory ensureContextFactory() {
+        if (this.ldapContextFactory == null) {
+
+            if (log.isDebugEnabled()) {
+                log.debug("No LdapContextFactory specified - creating a default instance.");
+            }
+
+            DefaultLdapContextFactory defaultFactory = new DefaultLdapContextFactory();
+            defaultFactory.setPrincipalSuffix(this.principalSuffix);
+            defaultFactory.setSearchBase(this.searchBase);
+            defaultFactory.setUrl(this.url);
+            defaultFactory.setSystemUsername(this.systemUsername);
+            defaultFactory.setSystemPassword(this.systemPassword);
+
+            this.ldapContextFactory = defaultFactory;
+        }
+        return this.ldapContextFactory;
+    }
+
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        AuthenticationInfo info;
+        try {
+            info = queryForAuthenticationInfo(token, ensureContextFactory());
+        } catch (javax.naming.AuthenticationException e) {
+            throw new AuthenticationException("LDAP authentication failed.", e);
+        } catch (NamingException e) {
+            String msg = "LDAP naming error while attempting to authenticate user.";
+            throw new AuthenticationException(msg, e);
+        }
+
+        return info;
+    }
+
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        AuthorizationInfo info;
+        try {
+            info = queryForAuthorizationInfo(principals, ensureContextFactory());
+        } catch (NamingException e) {
+            String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
+            throw new AuthorizationException(msg, e);
+        }
+
+        return info;
+    }
+
+
+    /**
+     * <p>Abstract method that should be implemented by subclasses to builds an
+     * {@link AuthenticationInfo} object by querying the LDAP context for the
+     * specified username.</p>
+     *
+     * @param token              the authentication token given during authentication.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException;
+
+
+    /**
+     * <p>Abstract method that should be implemented by subclasses to builds an
+     * {@link AuthorizationInfo} object by querying the LDAP context for the
+     * specified principal.</p>
+     *
+     * @param principal          the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal, LdapContextFactory ldapContextFactory) throws NamingException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapContextFactory.java b/core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapContextFactory.java
new file mode 100644
index 0000000..1ed2db8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapContextFactory.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>Default implementation of {@link LdapContextFactory} that can be configured or extended to
+ * customize the way {@link javax.naming.ldap.LdapContext} objects are retrieved.</p>
+ * <p/>
+ * <p>This implementation of {@link LdapContextFactory} is used by the {@link AbstractLdapRealm} if a
+ * factory is not explictly configured.</p>
+ * <p/>
+ * <p>Connection pooling is enabled by default on this factory, but can be disabled using the
+ * {@link #usePooling} property.</p>
+ *
+ * @since 0.2
+ * @deprecated replaced by the {@link JndiLdapContextFactory} implementation.  This implementation will be removed
+ * prior to Shiro 2.0
+ */
+ at Deprecated
+public class DefaultLdapContextFactory implements LdapContextFactory {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
+     * to enable LDAP connection pooling.
+     */
+    protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultLdapContextFactory.class);
+
+    protected String authentication = "simple";
+
+    protected String principalSuffix = null;
+
+    protected String searchBase = null;
+
+    protected String contextFactoryClassName = "com.sun.jndi.ldap.LdapCtxFactory";
+
+    protected String url = null;
+
+    protected String referral = "follow";
+
+    protected String systemUsername = null;
+
+    protected String systemPassword = null;
+
+    private boolean usePooling = true;
+
+    private Map<String, String> additionalEnvironment;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Sets the type of LDAP authentication to perform when connecting to the LDAP server.  Defaults to "simple"
+     *
+     * @param authentication the type of LDAP authentication to perform.
+     */
+    public void setAuthentication(String authentication) {
+        this.authentication = authentication;
+    }
+
+    /**
+     * A suffix appended to the username. This is typically for
+     * domain names.  (e.g. "@MyDomain.local")
+     *
+     * @param principalSuffix the suffix.
+     */
+    public void setPrincipalSuffix(String principalSuffix) {
+        this.principalSuffix = principalSuffix;
+    }
+
+    /**
+     * The search base for the search to perform in the LDAP server.
+     * (e.g. OU=OrganizationName,DC=MyDomain,DC=local )
+     *
+     * @param searchBase the search base.
+     * @deprecated this attribute existed, but was never used in Shiro 1.x.  It will be removed prior to Shiro 2.0.
+     */
+    @Deprecated
+    public void setSearchBase(String searchBase) {
+        this.searchBase = searchBase;
+    }
+
+    /**
+     * The context factory to use. This defaults to the SUN LDAP JNDI implementation
+     * but can be overridden to use custom LDAP factories.
+     *
+     * @param contextFactoryClassName the context factory that should be used.
+     */
+    public void setContextFactoryClassName(String contextFactoryClassName) {
+        this.contextFactoryClassName = contextFactoryClassName;
+    }
+
+    /**
+     * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
+     *
+     * @param url the LDAP url.
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Sets the LDAP referral property.  Defaults to "follow"
+     *
+     * @param referral the referral property.
+     */
+    public void setReferral(String referral) {
+        this.referral = referral;
+    }
+
+    /**
+     * The system username that will be used when connecting to the LDAP server to retrieve authorization
+     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
+     * only authentication.
+     *
+     * @param systemUsername the username to use when logging into the LDAP server for authorization.
+     */
+    public void setSystemUsername(String systemUsername) {
+        this.systemUsername = systemUsername;
+    }
+
+
+    /**
+     * The system password that will be used when connecting to the LDAP server to retrieve authorization
+     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
+     * only authentication.
+     *
+     * @param systemPassword the password to use when logging into the LDAP server for authorization.
+     */
+    public void setSystemPassword(String systemPassword) {
+        this.systemPassword = systemPassword;
+    }
+
+    /**
+     * Determines whether or not LdapContext pooling is enabled for connections made using the system
+     * user account.  In the default implementation, this simply
+     * sets the <tt>com.sun.jndi.ldap.connect.pool</tt> property in the LDAP context environment.  If you use an
+     * LDAP Context Factory that is not Sun's default implementation, you will need to override the
+     * default behavior to use this setting in whatever way your underlying LDAP ContextFactory
+     * supports.  By default, pooling is enabled.
+     *
+     * @param usePooling true to enable pooling, or false to disable it.
+     */
+    public void setUsePooling(boolean usePooling) {
+        this.usePooling = usePooling;
+    }
+
+    /**
+     * These entries are added to the environment map before initializing the LDAP context.
+     *
+     * @param additionalEnvironment additional environment entries to be configured on the LDAP context.
+     */
+    public void setAdditionalEnvironment(Map<String, String> additionalEnvironment) {
+        this.additionalEnvironment = additionalEnvironment;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+    public LdapContext getSystemLdapContext() throws NamingException {
+        return getLdapContext(systemUsername, systemPassword);
+    }
+
+    /**
+     * Deprecated - use {@link #getLdapContext(Object, Object)} instead.  This will be removed before Apache Shiro 2.0.
+     *
+     * @param username the username to use when creating the connection.
+     * @param password the password to use when creating the connection.
+     * @return a {@code LdapContext} bound using the given username and password.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than
+     *             String principals and credentials can be used.  Shiro no longer calls this method - it will be
+     *             removed before the 2.0 release.
+     */
+    @Deprecated
+    public LdapContext getLdapContext(String username, String password) throws NamingException {
+        if (username != null && principalSuffix != null) {
+            username += principalSuffix;
+        }
+        return getLdapContext((Object) username, password);
+    }
+
+    public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException {
+        if (url == null) {
+            throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
+        }
+
+        Hashtable<String, Object> env = new Hashtable<String, Object>();
+
+        env.put(Context.SECURITY_AUTHENTICATION, authentication);
+        if (principal != null) {
+            env.put(Context.SECURITY_PRINCIPAL, principal);
+        }
+        if (credentials!= null) {
+            env.put(Context.SECURITY_CREDENTIALS, credentials);
+        }
+        env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
+        env.put(Context.PROVIDER_URL, url);
+        env.put(Context.REFERRAL, referral);
+
+        // Only pool connections for system contexts
+        if (usePooling && principal != null && principal.equals(systemUsername)) {
+            // Enable connection pooling
+            env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
+        }
+
+        if (additionalEnvironment != null) {
+            env.putAll(additionalEnvironment);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Initializing LDAP context using URL [" + url + "] and username [" + systemUsername + "] " +
+                    "with pooling [" + (usePooling ? "enabled" : "disabled") + "]");
+        }
+
+        return new InitialLdapContext(env, null);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapContextFactory.java b/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapContextFactory.java
new file mode 100644
index 0000000..7800296
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapContextFactory.java
@@ -0,0 +1,507 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * {@link LdapContextFactory} implementation using the default Sun/Oracle JNDI Ldap API, utilizing JNDI
+ * environment properties and an {@link javax.naming.InitialContext}.
+ * <h2>Configuration</h2>
+ * This class basically wraps a default template JNDI environment properties Map.  This properties map is the base
+ * configuration template used to acquire JNDI {@link LdapContext} connections at runtime.  The
+ * {@link #getLdapContext(Object, Object)} method implementation merges this default template with other properties
+ * accessible at runtime only (for example per-method principals and credentials).  The constructed runtime map is the
+ * one used to acquire the {@link LdapContext}.
+ * <p/>
+ * The template can be configured directly via the {@link #getEnvironment()}/{@link #setEnvironment(java.util.Map)}
+ * properties directly if necessary, but it is usually more convenient to use the supporting wrapper get/set methods
+ * for various environment properties.  These wrapper methods interact with the environment
+ * template on your behalf, leaving your configuration cleaner and easier to understand.
+ * <p/>
+ * For example, consider the following two identical configurations:
+ * <pre>
+ * [main]
+ * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
+ * ldapRealm.contextFactory.url = ldap://localhost:389
+ * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
+ * </pre>
+ * and
+ * <pre>
+ * [main]
+ * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
+ * ldapRealm.contextFactory.environment[java.naming.provider.url] = ldap://localhost:389
+ * ldapRealm.contextFactory.environment[java.naming.security.authentication] = DIGEST-MD5
+ * </pre>
+ * As you can see, the 2nd configuration block is a little more difficult to read and also requires knowledge
+ * of the underlying JNDI Context property keys.  The first is easier to read and understand.
+ * <p/>
+ * Note that occasionally it will be necessary to use the latter configuration style to set environment properties
+ * where no corresponding wrapper method exists.  In this case, the hybrid approach is still a little easier to read.
+ * For example:
+ * <pre>
+ * [main]
+ * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
+ * ldapRealm.contextFactory.url = ldap://localhost:389
+ * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
+ * ldapRealm.contextFactory.environment[some.other.obscure.jndi.key] = some value
+ * </pre>
+ *
+ * @since 1.1
+ */
+public class JndiLdapContextFactory implements LdapContextFactory {
+
+    /*-------------------------------------------
+     |             C O N S T A N T S            |
+     ===========================================*/
+    /**
+     * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
+     * to enable LDAP connection pooling.
+     */
+    protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
+    protected static final String DEFAULT_CONTEXT_FACTORY_CLASS_NAME = "com.sun.jndi.ldap.LdapCtxFactory";
+    protected static final String SIMPLE_AUTHENTICATION_MECHANISM_NAME = "simple";
+    protected static final String DEFAULT_REFERRAL = "follow";
+
+    private static final Logger log = LoggerFactory.getLogger(JndiLdapContextFactory.class);
+
+    /*-------------------------------------------
+     |    I N S T A N C E   V A R I A B L E S   |
+     ============================================*/
+    private Map<String, Object> environment;
+    private boolean poolingEnabled;
+    private String systemPassword;
+    private String systemUsername;
+
+    /*-------------------------------------------
+     |         C O N S T R U C T O R S          |
+     ===========================================*/
+
+    /**
+     * Default no-argument constructor that initializes the backing {@link #getEnvironment() environment template} with
+     * the {@link #setContextFactoryClassName(String) contextFactoryClassName} equal to
+     * {@code com.sun.jndi.ldap.LdapCtxFactory} (the Sun/Oracle default) and the default
+     * {@link #setReferral(String) referral} behavior to {@code follow}.
+     */
+    public JndiLdapContextFactory() {
+        this.environment = new HashMap<String, Object>();
+        setContextFactoryClassName(DEFAULT_CONTEXT_FACTORY_CLASS_NAME);
+        setReferral(DEFAULT_REFERRAL);
+        poolingEnabled = true;
+    }
+
+    /*-------------------------------------------
+     |  A C C E S S O R S / M O D I F I E R S   |
+     ===========================================*/
+
+    /**
+     * Sets the type of LDAP authentication mechanism to use when connecting to the LDAP server.
+     * This is a wrapper method for setting the JNDI {@link #getEnvironment() environment template}'s
+     * {@link Context#SECURITY_AUTHENTICATION} property.
+     * <p/>
+     * "none" (i.e. anonymous) and "simple" authentications are supported automatically and don't need to be configured
+     * via this property.  However, if you require a different mechanism, such as a SASL or External mechanism, you
+     * must configure that explicitly via this property.  See the
+     * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP
+     * Authentication Mechanisms</a> for more information.
+     *
+     * @param authenticationMechanism the type of LDAP authentication to perform.
+     * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">
+     *      http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a>
+     */
+    public void setAuthenticationMechanism(String authenticationMechanism) {
+        setEnvironmentProperty(Context.SECURITY_AUTHENTICATION, authenticationMechanism);
+    }
+
+    /**
+     * Returns the type of LDAP authentication mechanism to use when connecting to the LDAP server.
+     * This is a wrapper method for getting the JNDI {@link #getEnvironment() environment template}'s
+     * {@link Context#SECURITY_AUTHENTICATION} property.
+     * <p/>
+     * If this property remains un-configured (i.e. {@code null} indicating the
+     * {@link #setAuthenticationMechanism(String)} method wasn't used), this indicates that the default JNDI
+     * "none" (anonymous) and "simple" authentications are supported automatically.  Any non-null value returned
+     * represents an explicitly configured mechanism (e.g. a SASL or external mechanism). See the
+     * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP
+     * Authentication Mechanisms</a> for more information.
+     *
+     * @return the type of LDAP authentication mechanism to use when connecting to the LDAP server.
+     * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">
+     *      http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a>
+     */
+    public String getAuthenticationMechanism() {
+        return (String) getEnvironmentProperty(Context.SECURITY_AUTHENTICATION);
+    }
+
+    /**
+     * The name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation
+     * but can be overridden to use custom LDAP factories.
+     * <p/>
+     * This is a wrapper method for setting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property.
+     *
+     * @param contextFactoryClassName the context factory that should be used.
+     */
+    public void setContextFactoryClassName(String contextFactoryClassName) {
+        setEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
+    }
+
+    /**
+     * Sets the name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation
+     * but can be overridden to use custom LDAP factories.
+     * <p/>
+     * This is a wrapper method for getting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property.
+     *
+     * @return the name of the ContextFactory class to use.
+     */
+    public String getContextFactoryClassName() {
+        return (String) getEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY);
+    }
+
+    /**
+     * Returns the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}).
+     * This property is the base configuration template to use for all connections.  This template is then
+     * merged with appropriate runtime values as necessary in the
+     * {@link #getLdapContext(Object, Object)} implementation.  The merged environment instance is what is used to
+     * acquire the {@link LdapContext} at runtime.
+     * <p/>
+     * Most other get/set methods in this class act as thin proxy wrappers that interact with this property.  The
+     * benefit of using them is you have an easier-to-use configuration mechanism compared to setting map properties
+     * based on JNDI context keys.
+     *
+     * @return the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext})
+     */
+    public Map getEnvironment() {
+        return this.environment;
+    }
+
+    /**
+     * Sets the base JNDI environment template to use when acquiring LDAP connections.  It is typically more common
+     * to use the other get/set methods in this class to set individual environment settings rather than use
+     * this method, but it is available for advanced users that want full control over the base JNDI environment
+     * settings.
+     * <p/>
+     * Note that this template only represents the base/default environment settings.  It is then merged with
+     * appropriate runtime values as necessary in the {@link #getLdapContext(Object, Object)} implementation.
+     * The merged environment instance is what is used to acquire the connection ({@link LdapContext}) at runtime.
+     *
+     * @param env the base JNDI environment template to use when acquiring LDAP connections.
+     */
+    @SuppressWarnings({"unchecked"})
+    public void setEnvironment(Map env) {
+        this.environment = env;
+    }
+
+    /**
+     * Returns the environment property value bound under the specified key.
+     *
+     * @param name the name of the environment property
+     * @return the property value or {@code null} if the value has not been set.
+     */
+    private Object getEnvironmentProperty(String name) {
+        return this.environment.get(name);
+    }
+
+    /**
+     * Will apply the value to the environment attribute if and only if the value is not null or empty.  If it is
+     * null or empty, the corresponding environment attribute will be removed.
+     *
+     * @param name  the environment property key
+     * @param value the environment property value.  A null/empty value will trigger removal.
+     */
+    private void setEnvironmentProperty(String name, String value) {
+        if (StringUtils.hasText(value)) {
+            this.environment.put(name, value);
+        } else {
+            this.environment.remove(name);
+        }
+    }
+
+    /**
+     * Returns whether or not connection pooling should be used when possible and appropriate.  This property is NOT
+     * backed by the {@link #getEnvironment() environment template} like most other properties in this class.  It
+     * is a flag to indicate that pooling is preferred.  The default value is {@code true}.
+     * <p/>
+     * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection
+     * being created is for the {@link #getSystemUsername() systemUsername} user.  Connection pooling is not used for
+     * general authentication attempts by application end-users because the probability of re-use for that same
+     * user-specific connection after an authentication attempt is extremely low.
+     * <p/>
+     * If this attribute is {@code true} and it has been determined that the connection is being made with the
+     * {@link #getSystemUsername() systemUsername}, the
+     * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific
+     * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}".  This means setting
+     * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using
+     * a custom {@link #getContextFactoryClassName() contextFactoryClassName}).
+     *
+     * @return whether or not connection pooling should be used when possible and appropriate
+     */
+    public boolean isPoolingEnabled() {
+        return poolingEnabled;
+    }
+
+    /**
+     * Sets whether or not connection pooling should be used when possible and appropriate.  This property is NOT
+     * a wrapper to the {@link #getEnvironment() environment template} like most other properties in this class.  It
+     * is a flag to indicate that pooling is preferred.  The default value is {@code true}.
+     * <p/>
+     * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection
+     * being created is for the {@link #getSystemUsername() systemUsername} user.  Connection pooling is not used for
+     * general authentication attempts by application end-users because the probability of re-use for that same
+     * user-specific connection after an authentication attempt is extremely low.
+     * <p/>
+     * If this attribute is {@code true} and it has been determined that the connection is being made with the
+     * {@link #getSystemUsername() systemUsername}, the
+     * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific
+     * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}".  This means setting
+     * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using
+     * a custom {@link #getContextFactoryClassName() contextFactoryClassName}).
+     *
+     * @param poolingEnabled whether or not connection pooling should be used when possible and appropriate
+     */
+    public void setPoolingEnabled(boolean poolingEnabled) {
+        this.poolingEnabled = poolingEnabled;
+    }
+
+    /**
+     * Sets the LDAP referral behavior when creating a connection.  Defaults to {@code follow}.  See the Sun/Oracle LDAP
+     * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more.
+     *
+     * @param referral the referral property.
+     * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a>
+     */
+    public void setReferral(String referral) {
+        setEnvironmentProperty(Context.REFERRAL, referral);
+    }
+
+    /**
+     * Returns the LDAP referral behavior when creating a connection.  Defaults to {@code follow}.
+     * See the Sun/Oracle LDAP
+     * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more.
+     *
+     * @return the LDAP referral behavior when creating a connection.
+     * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a>
+     */
+    public String getReferral() {
+        return (String) getEnvironmentProperty(Context.REFERRAL);
+    }
+
+    /**
+     * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>).  This must be configured.
+     *
+     * @param url the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
+     */
+    public void setUrl(String url) {
+        setEnvironmentProperty(Context.PROVIDER_URL, url);
+    }
+
+    /**
+     * Returns the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>).
+     * This must be configured.
+     *
+     * @return the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
+     */
+    public String getUrl() {
+        return (String) getEnvironmentProperty(Context.PROVIDER_URL);
+    }
+
+    /**
+     * Sets the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
+     * LDAP connection used for authorization queries.
+     * <p/>
+     * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
+     * checks.
+     *
+     * @param systemPassword the password of the {@link #setSystemUsername(String) systemUsername} that will be used
+     *                       when creating an LDAP connection used for authorization queries.
+     */
+    public void setSystemPassword(String systemPassword) {
+        this.systemPassword = systemPassword;
+    }
+
+    /**
+     * Returns the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
+     * LDAP connection used for authorization queries.
+     * <p/>
+     * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
+     * checks.
+     *
+     * @return the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
+     *         LDAP connection used for authorization queries.
+     */
+    public String getSystemPassword() {
+        return this.systemPassword;
+    }
+
+    /**
+     * Sets the system username that will be used when creating an LDAP connection used for authorization queries.
+     * The user must have the ability to query for authorization data for any application user.
+     * <p/>
+     * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
+     * checks.
+     *
+     * @param systemUsername the system username that will be used when creating an LDAP connection used for
+     *                       authorization queries.
+     */
+    public void setSystemUsername(String systemUsername) {
+        this.systemUsername = systemUsername;
+    }
+
+    /**
+     * Returns the system username that will be used when creating an LDAP connection used for authorization queries.
+     * The user must have the ability to query for authorization data for any application user.
+     * <p/>
+     * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
+     * checks.
+     *
+     * @return the system username that will be used when creating an LDAP connection used for authorization queries.
+     */
+    public String getSystemUsername() {
+        return systemUsername;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * This implementation delegates to {@link #getLdapContext(Object, Object)} using the
+     * {@link #getSystemUsername() systemUsername} and {@link #getSystemPassword() systemPassword} properties as
+     * arguments.
+     *
+     * @return the system LdapContext
+     * @throws NamingException if there is a problem connecting to the LDAP directory
+     */
+    public LdapContext getSystemLdapContext() throws NamingException {
+        return getLdapContext((Object)getSystemUsername(), getSystemPassword());
+    }
+
+    /**
+     * Deprecated - use {@link #getLdapContext(Object, Object)} instead.  This will be removed before Apache Shiro 2.0.
+     *
+     * @param username the username to use when creating the connection.
+     * @param password the password to use when creating the connection.
+     * @return a {@code LdapContext} bound using the given username and password.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than
+     *             String principals and credentials can be used.  Shiro no longer calls this method - it will be
+     *             removed before the 2.0 release.
+     */
+    @Deprecated
+    public LdapContext getLdapContext(String username, String password) throws NamingException {
+        return getLdapContext((Object) username, password);
+    }
+
+    /**
+     * Returns {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified
+     * account principal, {@code false} otherwise.
+     * <p/>
+     * This implementation returns {@code true} only if {@link #isPoolingEnabled()} and the principal equals the
+     * {@link #getSystemUsername()}.  The reasoning behind this is that connection pooling is not desirable for
+     * general authentication attempts by application end-users because the probability of re-use for that same
+     * user-specific connection after an authentication attempt is extremely low.
+     *
+     * @param principal the principal under which the connection will be made
+     * @return {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified
+     *         account principal, {@code false} otherwise.
+     */
+    protected boolean isPoolingConnections(Object principal) {
+        return isPoolingEnabled() && principal != null && principal.equals(getSystemUsername());
+    }
+
+    /**
+     * This implementation returns an LdapContext based on the configured JNDI/LDAP environment configuration.
+     * The environnmet (Map) used at runtime is created by merging the default/configured
+     * {@link #getEnvironment() environment template} with some runtime values as necessary (e.g. a principal and
+     * credential available at runtime only).
+     * <p/>
+     * After the merged Map instance is created, the LdapContext connection is
+     * {@link #createLdapContext(java.util.Hashtable) created} and returned.
+     *
+     * @param principal   the principal to use when acquiring a connection to the LDAP directory
+     * @param credentials the credentials (password, X.509 certificate, etc) to use when acquiring a connection to the
+     *                    LDAP directory
+     * @return the acquired {@code LdapContext} connection bound using the specified principal and credentials.
+     * @throws NamingException
+     * @throws IllegalStateException
+     */
+    public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException,
+            IllegalStateException {
+
+        String url = getUrl();
+        if (url == null) {
+            throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
+        }
+
+        //copy the environment template into the runtime instance that will be further edited based on
+        //the method arguments and other class attributes.
+        Hashtable<String, Object> env = new Hashtable<String, Object>(this.environment);
+
+        Object authcMech = getAuthenticationMechanism();
+        if (authcMech == null && (principal != null || credentials != null)) {
+            //authenticationMechanism has not been set, but either a principal and/or credentials were
+            //supplied, indicating that at least a 'simple' authentication attempt is indeed occurring - the Shiro
+            //end-user just didn't configure it explicitly.  So we set it to be 'simple' here as a convenience;
+            //the Sun provider implementation already does this same logic, but by repeating that logic here, we ensure
+            //this convenience exists regardless of provider implementation):
+            env.put(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION_MECHANISM_NAME);
+        }
+        if (principal != null) {
+            env.put(Context.SECURITY_PRINCIPAL, principal);
+        }
+        if (credentials != null) {
+            env.put(Context.SECURITY_CREDENTIALS, credentials);
+        }
+
+        boolean pooling = isPoolingConnections(principal);
+        if (pooling) {
+            env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Initializing LDAP context using URL [{}] and principal [{}] with pooling {}",
+                    new Object[]{url, principal, (pooling ? "enabled" : "disabled")});
+        }
+
+        return createLdapContext(env);
+    }
+
+    /**
+     * Creates and returns a new {@link javax.naming.ldap.InitialLdapContext} instance.  This method exists primarily
+     * to support testing where a mock LdapContext can be returned instead of actually creating a connection, but
+     * subclasses are free to provide a different implementation if necessary.
+     *
+     * @param env the JNDI environment settings used to create the LDAP connection
+     * @return an LdapConnection
+     * @throws NamingException if a problem occurs creating the connection
+     */
+    protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+        return new InitialLdapContext(env, null);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapRealm.java b/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapRealm.java
new file mode 100644
index 0000000..a642b2c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/JndiLdapRealm.java
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.ldap.UnsupportedAuthenticationMechanismException;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.AuthenticationNotSupportedException;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+/**
+ * An LDAP {@link org.apache.shiro.realm.Realm Realm} implementation utilizing Sun's/Oracle's
+ * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/jndi.html">JNDI API as an LDAP API</a>.  This is
+ * Shiro's default implementation for supporting LDAP, as using the JNDI API has been a common approach for Java LDAP
+ * support for many years.
+ * <p/>
+ * This realm implementation and its backing {@link JndiLdapContextFactory} should cover 99% of all Shiro-related LDAP
+ * authentication and authorization needs.  However, if it does not suit your needs, you might want to look into
+ * creating your own realm using an alternative, perhaps more robust, LDAP communication API, such as the
+ * <a href="http://directory.apache.org/api/">Apache LDAP API</a>.
+ * <h2>Authentication</h2>
+ * During an authentication attempt, if the submitted {@code AuthenticationToken}'s
+ * {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal() principal} is a simple username, but the
+ * LDAP directory expects a complete User Distinguished Name (User DN) to establish a connection, the
+ * {@link #setUserDnTemplate(String) userDnTemplate} property must be configured.  If not configured,
+ * the property will pass the simple username directly as the User DN, which is often incorrect in most LDAP
+ * environments (maybe Microsoft ActiveDirectory being the exception).
+ * <h2>Authorization</h2>
+ * By default, authorization is effectively disabled due to the default
+ * {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} implementation returning {@code null}.
+ * If you wish to perform authorization based on an LDAP schema, you must subclass this one
+ * and override that method to reflect your organization's data model.
+ * <h2>Configuration</h2>
+ * This class primarily provides the {@link #setUserDnTemplate(String) userDnTemplate} property to allow you to specify
+ * the your LDAP server's User DN format.  Most other configuration is performed via the nested
+ * {@link LdapContextFactory contextFactory} property.
+ * <p/>
+ * For example, defining this realm in Shiro .ini:
+ * <pre>
+ * [main]
+ * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
+ * ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
+ * ldapRealm.contextFactory.url = ldap://ldapHost:389
+ * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
+ * ldapRealm.contextFactory.environment[some.obscure.jndi.key] = some value
+ * ...
+ * </pre>
+ * The default {@link #setContextFactory contextFactory} instance is a {@link JndiLdapContextFactory}.  See that
+ * class's JavaDoc for more information on configuring the LDAP connection as well as specifying JNDI environment
+ * properties as necessary.
+ *
+ * @see JndiLdapContextFactory
+ *
+ * @since 1.1
+ */
+public class JndiLdapRealm extends AuthorizingRealm {
+
+    private static final Logger log = LoggerFactory.getLogger(JndiLdapRealm.class);
+
+    //The zero index currently means nothing, but could be utilized in the future for other substitution techniques.
+    private static final String USERDN_SUBSTITUTION_TOKEN = "{0}";
+
+    private String userDnPrefix;
+    private String userDnSuffix;
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    /**
+     * The LdapContextFactory instance used to acquire {@link javax.naming.ldap.LdapContext LdapContext}'s at runtime
+     * to acquire connections to the LDAP directory to perform authentication attempts and authorizatino queries.
+     */
+    private LdapContextFactory contextFactory;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * Default no-argument constructor that defaults the internal {@link LdapContextFactory} instance to a
+     * {@link JndiLdapContextFactory}.
+     */
+    public JndiLdapRealm() {
+        //Credentials Matching is not necessary - the LDAP directory will do it automatically:
+        setCredentialsMatcher(new AllowAllCredentialsMatcher());
+        //Any Object principal and Object credentials may be passed to the LDAP provider, so accept any token:
+        setAuthenticationTokenClass(AuthenticationToken.class);
+        this.contextFactory = new JndiLdapContextFactory();
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Returns the User DN prefix to use when building a runtime User DN value or {@code null} if no
+     * {@link #getUserDnTemplate() userDnTemplate} has been configured.  If configured, this value is the text that
+     * occurs before the {@link #USERDN_SUBSTITUTION_TOKEN} in the {@link #getUserDnTemplate() userDnTemplate} value.
+     *
+     * @return the the User DN prefix to use when building a runtime User DN value or {@code null} if no
+     *         {@link #getUserDnTemplate() userDnTemplate} has been configured.
+     */
+    protected String getUserDnPrefix() {
+        return userDnPrefix;
+    }
+
+    /**
+     * Returns the User DN suffix to use when building a runtime User DN value.  or {@code null} if no
+     * {@link #getUserDnTemplate() userDnTemplate} has been configured.  If configured, this value is the text that
+     * occurs after the {@link #USERDN_SUBSTITUTION_TOKEN} in the {@link #getUserDnTemplate() userDnTemplate} value.
+     *
+     * @return the User DN suffix to use when building a runtime User DN value or {@code null} if no
+     *         {@link #getUserDnTemplate() userDnTemplate} has been configured.
+     */
+    protected String getUserDnSuffix() {
+        return userDnSuffix;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Sets the User Distinguished Name (DN) template to use when creating User DNs at runtime.  A User DN is an LDAP
+     * fully-qualified unique user identifier which is required to establish a connection with the LDAP
+     * directory to authenticate users and query for authorization information.
+     * <h2>Usage</h2>
+     * User DN formats are unique to the LDAP directory's schema, and each environment differs - you will need to
+     * specify the format corresponding to your directory.  You do this by specifying the full User DN as normal, but
+     * but you use a <b>{@code {0}}</b> placeholder token in the string representing the location where the
+     * user's submitted principal (usually a username or uid) will be substituted at runtime.
+     * <p/>
+     * For example,  if your directory
+     * uses an LDAP {@code uid} attribute to represent usernames, the User DN for the {@code jsmith} user may look like
+     * this:
+     * <p/>
+     * <pre>uid=jsmith,ou=users,dc=mycompany,dc=com</pre>
+     * <p/>
+     * in which case you would set this property with the following template value:
+     * <p/>
+     * <pre>uid=<b>{0}</b>,ou=users,dc=mycompany,dc=com</pre>
+     * <p/>
+     * If no template is configured, the raw {@code AuthenticationToken}
+     * {@link AuthenticationToken#getPrincipal() principal} will be used as the LDAP principal.  This is likely
+     * incorrect as most LDAP directories expect a fully-qualified User DN as opposed to the raw uid or username.  So,
+     * ensure you set this property to match your environment!
+     *
+     * @param template the User Distinguished Name template to use for runtime substitution
+     * @throws IllegalArgumentException if the template is null, empty, or does not contain the
+     *                                  {@code {0}} substitution token.
+     * @see LdapContextFactory#getLdapContext(Object,Object)
+     */
+    public void setUserDnTemplate(String template) throws IllegalArgumentException {
+        if (!StringUtils.hasText(template)) {
+            String msg = "User DN template cannot be null or empty.";
+            throw new IllegalArgumentException(msg);
+        }
+        int index = template.indexOf(USERDN_SUBSTITUTION_TOKEN);
+        if (index < 0) {
+            String msg = "User DN template must contain the '" +
+                    USERDN_SUBSTITUTION_TOKEN + "' replacement token to understand where to " +
+                    "insert the runtime authentication principal.";
+            throw new IllegalArgumentException(msg);
+        }
+        String prefix = template.substring(0, index);
+        String suffix = template.substring(prefix.length() + USERDN_SUBSTITUTION_TOKEN.length());
+        if (log.isDebugEnabled()) {
+            log.debug("Determined user DN prefix [{}] and suffix [{}]", prefix, suffix);
+        }
+        this.userDnPrefix = prefix;
+        this.userDnSuffix = suffix;
+    }
+
+    /**
+     * Returns the User Distinguished Name (DN) template to use when creating User DNs at runtime - see the
+     * {@link #setUserDnTemplate(String) setUserDnTemplate} JavaDoc for a full explanation.
+     *
+     * @return the User Distinguished Name (DN) template to use when creating User DNs at runtime.
+     */
+    public String getUserDnTemplate() {
+        return getUserDn(USERDN_SUBSTITUTION_TOKEN);
+    }
+
+    /**
+     * Returns the LDAP User Distinguished Name (DN) to use when acquiring an
+     * {@link javax.naming.ldap.LdapContext LdapContext} from the {@link LdapContextFactory}.
+     * <p/>
+     * If the the {@link #getUserDnTemplate() userDnTemplate} property has been set, this implementation will construct
+     * the User DN by substituting the specified {@code principal} into the configured template.  If the
+     * {@link #getUserDnTemplate() userDnTemplate} has not been set, the method argument will be returned directly
+     * (indicating that the submitted authentication token principal <em>is</em> the User DN).
+     *
+     * @param principal the principal to substitute into the configured {@link #getUserDnTemplate() userDnTemplate}.
+     * @return the constructed User DN to use at runtime when acquiring an {@link javax.naming.ldap.LdapContext}.
+     * @throws IllegalArgumentException if the method argument is null or empty
+     * @throws IllegalStateException    if the {@link #getUserDnTemplate userDnTemplate} has not been set.
+     * @see LdapContextFactory#getLdapContext(Object, Object)
+     */
+    protected String getUserDn(String principal) throws IllegalArgumentException, IllegalStateException {
+        if (!StringUtils.hasText(principal)) {
+            throw new IllegalArgumentException("User principal cannot be null or empty for User DN construction.");
+        }
+        String prefix = getUserDnPrefix();
+        String suffix = getUserDnSuffix();
+        if (prefix == null && suffix == null) {
+            log.debug("userDnTemplate property has not been configured, indicating the submitted " +
+                    "AuthenticationToken's principal is the same as the User DN.  Returning the method argument " +
+                    "as is.");
+            return principal;
+        }
+
+        int prefixLength = prefix != null ? prefix.length() : 0;
+        int suffixLength = suffix != null ? suffix.length() : 0;
+        StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);
+        if (prefixLength > 0) {
+            sb.append(prefix);
+        }
+        sb.append(principal);
+        if (suffixLength > 0) {
+            sb.append(suffix);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Sets the LdapContextFactory instance used to acquire connections to the LDAP directory during authentication
+     * attempts and authorization queries.  Unless specified otherwise, the default is a {@link JndiLdapContextFactory}
+     * instance.
+     *
+     * @param contextFactory the LdapContextFactory instance used to acquire connections to the LDAP directory during
+     *                       authentication attempts and authorization queries
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setContextFactory(LdapContextFactory contextFactory) {
+        this.contextFactory = contextFactory;
+    }
+
+    /**
+     * Returns the LdapContextFactory instance used to acquire connections to the LDAP directory during authentication
+     * attempts and authorization queries.  Unless specified otherwise, the default is a {@link JndiLdapContextFactory}
+     * instance.
+     *
+     * @return the LdapContextFactory instance used to acquire connections to the LDAP directory during
+     *         authentication attempts and authorization queries
+     */
+    public LdapContextFactory getContextFactory() {
+        return this.contextFactory;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S                |
+    ============================================*/
+
+    /**
+     * Delegates to {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, LdapContextFactory)},
+     * wrapping any {@link NamingException}s in a Shiro {@link AuthenticationException} to satisfy the parent method
+     * signature.
+     *
+     * @param token the authentication token containing the user's principal and credentials.
+     * @return the {@link AuthenticationInfo} acquired after a successful authentication attempt
+     * @throws AuthenticationException if the authentication attempt fails or if a
+     *                                 {@link NamingException} occurs.
+     */
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        AuthenticationInfo info;
+        try {
+            info = queryForAuthenticationInfo(token, getContextFactory());
+        } catch (AuthenticationNotSupportedException e) {
+            String msg = "Unsupported configured authentication mechanism";
+            throw new UnsupportedAuthenticationMechanismException(msg, e);
+        } catch (javax.naming.AuthenticationException e) {
+            throw new AuthenticationException("LDAP authentication failed.", e);
+        } catch (NamingException e) {
+            String msg = "LDAP naming error while attempting to authenticate user.";
+            throw new AuthenticationException(msg, e);
+        }
+
+        return info;
+    }
+
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        AuthorizationInfo info;
+        try {
+            info = queryForAuthorizationInfo(principals, getContextFactory());
+        } catch (NamingException e) {
+            String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
+            throw new AuthorizationException(msg, e);
+        }
+
+        return info;
+    }
+
+    /**
+     * Returns the principal to use when creating the LDAP connection for an authentication attempt.
+     * <p/>
+     * This implementation uses a heuristic: it checks to see if the specified token's
+     * {@link AuthenticationToken#getPrincipal() principal} is a {@code String}, and if so,
+     * {@link #getUserDn(String) converts it} from what is
+     * assumed to be a raw uid or username {@code String} into a User DN {@code String}.  Almost all LDAP directories
+     * expect the authentication connection to present a User DN and not an unqualified username or uid.
+     * <p/>
+     * If the token's {@code principal} is not a String, it is assumed to already be in the format supported by the
+     * underlying {@link LdapContextFactory} implementation and the raw principal is returned directly.
+     *
+     * @param token the {@link AuthenticationToken} submitted during the authentication process
+     * @return the User DN or raw principal to use to acquire the LdapContext.
+     * @see LdapContextFactory#getLdapContext(Object, Object)
+     */
+    protected Object getLdapPrincipal(AuthenticationToken token) {
+        Object principal = token.getPrincipal();
+        if (principal instanceof String) {
+            String sPrincipal = (String) principal;
+            return getUserDn(sPrincipal);
+        }
+        return principal;
+    }
+
+    /**
+     * This implementation opens an LDAP connection using the token's
+     * {@link #getLdapPrincipal(org.apache.shiro.authc.AuthenticationToken) discovered principal} and provided
+     * {@link AuthenticationToken#getCredentials() credentials}.  If the connection opens successfully, the
+     * authentication attempt is immediately considered successful and a new
+     * {@link AuthenticationInfo} instance is
+     * {@link #createAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, Object, Object, javax.naming.ldap.LdapContext) created}
+     * and returned.  If the connection cannot be opened, either because LDAP authentication failed or some other
+     * JNDI problem, an {@link NamingException} will be thrown.
+     *
+     * @param token              the submitted authentication token that triggered the authentication attempt.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthenticationInfo} instance representing the authenticated user's information.
+     * @throws NamingException if any LDAP errors occur.
+     */
+    protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
+                                                            LdapContextFactory ldapContextFactory)
+            throws NamingException {
+
+        Object principal = token.getPrincipal();
+        Object credentials = token.getCredentials();
+
+        log.debug("Authenticating user '{}' through LDAP", principal);
+
+        principal = getLdapPrincipal(token);
+
+        LdapContext ctx = null;
+        try {
+            ctx = ldapContextFactory.getLdapContext(principal, credentials);
+            //context was opened successfully, which means their credentials were valid.  Return the AuthenticationInfo:
+            return createAuthenticationInfo(token, principal, credentials, ctx);
+        } finally {
+            LdapUtils.closeContext(ctx);
+        }
+    }
+
+    /**
+     * Returns the {@link AuthenticationInfo} resulting from a Subject's successful LDAP authentication attempt.
+     * <p/>
+     * This implementation ignores the {@code ldapPrincipal}, {@code ldapCredentials}, and the opened
+     * {@code ldapContext} arguments and merely returns an {@code AuthenticationInfo} instance mirroring the
+     * submitted token's principal and credentials.  This is acceptable because this method is only ever invoked after
+     * a successful authentication attempt, which means the provided principal and credentials were correct, and can
+     * be used directly to populate the (now verified) {@code AuthenticationInfo}.
+     * <p/>
+     * Subclasses however are free to override this method for more advanced construction logic.
+     *
+     * @param token           the submitted {@code AuthenticationToken} that resulted in a successful authentication
+     * @param ldapPrincipal   the LDAP principal used when creating the LDAP connection.  Unlike the token's
+     *                        {@link AuthenticationToken#getPrincipal() principal}, this value is usually a constructed
+     *                        User DN and not a simple username or uid.  The exact value is depending on the
+     *                        configured
+     *                        <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">
+     *                        LDAP authentication mechanism</a> in use.
+     * @param ldapCredentials the LDAP credentials used when creating the LDAP connection.
+     * @param ldapContext     the LdapContext created that resulted in a successful authentication.  It can be used
+     *                        further by subclasses for more complex operations.  It does not need to be closed -
+     *                        it will be closed automatically after this method returns.
+     * @return the {@link AuthenticationInfo} resulting from a Subject's successful LDAP authentication attempt.
+     * @throws NamingException if there was any problem using the {@code LdapContext}
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected AuthenticationInfo createAuthenticationInfo(AuthenticationToken token, Object ldapPrincipal,
+                                                          Object ldapCredentials, LdapContext ldapContext)
+            throws NamingException {
+        return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
+    }
+
+
+    /**
+     * Method that should be implemented by subclasses to build an
+     * {@link AuthorizationInfo} object by querying the LDAP context for the
+     * specified principal.</p>
+     *
+     * @param principals          the principals of the Subject whose AuthenticationInfo should be queried from the LDAP server.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
+                                                          LdapContextFactory ldapContextFactory) throws NamingException {
+        return null;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/LdapContextFactory.java b/core/src/main/java/org/apache/shiro/realm/ldap/LdapContextFactory.java
new file mode 100644
index 0000000..2e572fd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/LdapContextFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+/**
+ * Interface that encapsulates the creation of {@code LdapContext} objects that are used by {@link JndiLdapRealm}s to
+ * perform authentication attempts and query for authorization data.
+ *
+ * @since 0.2
+ */
+public interface LdapContextFactory {
+
+    /**
+     * Creates (or retrieves from a pool) a {@code LdapContext} connection bound using the system account, or
+     * anonymously if no system account is configured.
+     *
+     * @return a {@code LdapContext} bound by the system account, or bound anonymously if no system account
+     *         is configured.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     */
+    LdapContext getSystemLdapContext() throws NamingException;
+
+    /**
+     * Creates (or retrieves from a pool) a {@code LdapContext} connection bound using the username and password
+     * specified.
+     *
+     * @param username the username to use when creating the connection.
+     * @param password the password to use when creating the connection.
+     * @return a {@code LdapContext} bound using the given username and password.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than
+     * String principals and credentials can be used.
+     */
+    @Deprecated
+    LdapContext getLdapContext(String username, String password) throws NamingException;
+
+    /**
+     * Creates (or retrieves from a pool) an {@code LdapContext} connection bound using the specified principal and
+     * credentials.  The format of the principal and credentials are whatever is supported by the underlying
+     * LDAP {@link javax.naming.spi.InitialContextFactory InitialContextFactory} implementation.  The default Sun
+     * (now Oracle) implementation supports
+     * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">anonymous, simple, and
+     * SASL-based mechanisms</a>.
+     * <p/>
+     * This method was added in Shiro 1.1 to address the fact that principals and credentials can be more than just
+     * {@code String} user DNs and passwords for connecting to LDAP.  For example, the credentials can be an
+     * {@code X.509} certificate.
+     *
+     * @param principal   the principal to use when acquiring a connection to the LDAP directory
+     * @param credentials the credentials (password, X.509 certificate, etc) to use when acquiring a connection to the
+     *                    LDAP directory
+     * @return the acquired {@code LdapContext} connection bound using the specified principal and credentials.
+     * @throws NamingException if unable to acquire a connection.
+     * @since 1.1
+     */
+    LdapContext getLdapContext(Object principal, Object credentials) throws NamingException;
+    
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/LdapUtils.java b/core/src/main/java/org/apache/shiro/realm/ldap/LdapUtils.java
new file mode 100644
index 0000000..750b017
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/LdapUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.ldap.LdapContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class providing static methods to make working with LDAP
+ * easier.
+ *
+ * @since 0.2
+ */
+public final class LdapUtils {
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(LdapUtils.class);
+
+    /**
+     * Closes an LDAP context, logging any errors, but not throwing
+     * an exception if there is a failure.
+     *
+     * @param ctx the LDAP context to close.
+     */
+    public static void closeContext(LdapContext ctx) {
+        try {
+            if (ctx != null) {
+                ctx.close();
+            }
+        } catch (NamingException e) {
+            log.error("Exception while closing LDAP context. ", e);
+        }
+    }
+
+    /**
+     * Helper method used to retrieve all attribute values from a particular context attribute.
+     *
+     * @param attr the LDAP attribute.
+     * @return the values of the attribute.
+     * @throws javax.naming.NamingException if there is an LDAP error while reading the values.
+     */
+    public static Collection<String> getAllAttributeValues(Attribute attr) throws NamingException {
+        Set<String> values = new HashSet<String>();
+        NamingEnumeration ne = null;
+        try {
+            ne = attr.getAll();
+            while (ne.hasMore()) {
+                String value = (String) ne.next();
+                values.add(value);
+            }
+        } finally {
+            closeEnumeration(ne);
+        }
+
+        return values;
+    }
+
+    //added based on SHIRO-127, per Emmanuel's comment [1]
+    // [1] https://issues.apache.org/jira/browse/SHIRO-127?focusedCommentId=12891380&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#action_12891380
+
+    public static void closeEnumeration(NamingEnumeration ne) {
+        try {
+            if (ne != null) {
+                ne.close();
+            }
+        } catch (NamingException e) {
+            log.error("Exception while closing NamingEnumeration: ", e);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/ldap/package-info.java b/core/src/main/java/org/apache/shiro/realm/ldap/package-info.java
new file mode 100644
index 0000000..5223544
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/ldap/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Realms that acquire security data from an LDAP (Lightweight Directory Access Protocol) server
+ * utilizing LDAP/Naming APIs.
+ */
+package org.apache.shiro.realm.ldap;
diff --git a/core/src/main/java/org/apache/shiro/realm/package-info.java b/core/src/main/java/org/apache/shiro/realm/package-info.java
new file mode 100644
index 0000000..56a09ff
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components and sub-packages used in supporting the core {@link org.apache.shiro.realm.Realm Realm} interface.
+ * <p/>
+ * Take particular note of the multiple sub-packages with existing Realm implementations supporting many
+ * environments that you can use use directly or extend for custom behavior.
+ */
+package org.apache.shiro.realm;
diff --git a/core/src/main/java/org/apache/shiro/realm/text/IniRealm.java b/core/src/main/java/org/apache/shiro/realm/text/IniRealm.java
new file mode 100644
index 0000000..3a0540c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/text/IniRealm.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.text;
+
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link org.apache.shiro.realm.Realm Realm} implementation that creates
+ * {@link org.apache.shiro.authc.SimpleAccount SimpleAccount} instances based on
+ * {@link Ini} configuration.
+ * <p/>
+ * This implementation looks for two {@link Ini.Section sections} in the {@code Ini} configuration:
+ * <pre>
+ * [users]
+ * # One or more {@link org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions(String) user definitions}
+ * ...
+ * [roles]
+ * # One or more {@link org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions(String) role definitions}</pre>
+ * <p/>
+ * This class also supports setting the {@link #setResourcePath(String) resourcePath} property to create account
+ * data from an .ini resource.  This will only be used if there isn't already account data in the Realm.
+ *
+ * @since 1.0
+ */
+public class IniRealm extends TextConfigurationRealm {
+
+    public static final String USERS_SECTION_NAME = "users";
+    public static final String ROLES_SECTION_NAME = "roles";
+
+    private static transient final Logger log = LoggerFactory.getLogger(IniRealm.class);
+
+    private String resourcePath;
+    private Ini ini; //reference added in 1.2 for SHIRO-322
+
+    public IniRealm() {
+        super();
+    }
+
+    /**
+     * This constructor will immediately process the definitions in the {@code Ini} argument.  If you need to perform
+     * additional configuration before processing (e.g. setting a permissionResolver, etc), do not call this
+     * constructor.  Instead, do the following:
+     * <ol>
+     * <li>Call the default no-arg constructor</li>
+     * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
+     * <li>Set any other configuration properties</li>
+     * <li>Call {@link #init()}</li>
+     * </ol>
+     *
+     * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
+     */
+    public IniRealm(Ini ini) {
+        this();
+        processDefinitions(ini);
+    }
+
+    /**
+     * This constructor will immediately process the definitions in the {@code Ini} resolved from the specified
+     * {@code resourcePath}.  If you need to perform additional configuration before processing (e.g. setting a
+     * permissionResolver, etc), do not call this constructor.  Instead, do the following:
+     * <ol>
+     * <li>Call the default no-arg constructor</li>
+     * <li>Set the Ini instance you wish to use via {@code #setIni}</li>
+     * <li>Set any other configuration properties</li>
+     * <li>Call {@link #init()}</li>
+     * </ol>
+     *
+     * @param resourcePath the resource path of the Ini config which will be inspected to create accounts, groups and
+     *                     permissions for this realm.
+     */
+    public IniRealm(String resourcePath) {
+        this();
+        Ini ini = Ini.fromResourcePath(resourcePath);
+        this.ini = ini;
+        this.resourcePath = resourcePath;
+        processDefinitions(ini);
+    }
+
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    /**
+     * Returns the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
+     * realm, particularly useful in Dependency Injection environments.
+     * 
+     * @return the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
+     */
+    public Ini getIni() {
+        return ini;
+    }
+
+    /**
+     * Sets the Ini instance used to configure this realm.  Provided for JavaBeans-style configuration of this
+     * realm, particularly useful in Dependency Injection environments.
+     * 
+     * @param ini the Ini instance which will be inspected to create accounts, groups and permissions for this realm.
+     */
+    public void setIni(Ini ini) {
+        this.ini = ini;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+
+        // This is an in-memory realm only - no need for an additional cache when we're already
+        // as memory-efficient as we can be.
+        
+        Ini ini = getIni();
+        String resourcePath = getResourcePath();
+                
+        if (!CollectionUtils.isEmpty(this.users) || !CollectionUtils.isEmpty(this.roles)) {
+            if (!CollectionUtils.isEmpty(ini)) {
+                log.warn("Users or Roles are already populated.  Configured Ini instance will be ignored.");
+            }
+            if (StringUtils.hasText(resourcePath)) {
+                log.warn("Users or Roles are already populated.  resourcePath '{}' will be ignored.", resourcePath);
+            }
+            
+            log.debug("Instance is already populated with users or roles.  No additional user/role population " +
+                    "will be performed.");
+            return;
+        }
+        
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("No INI instance configuration present.  Checking resourcePath...");
+            
+            if (StringUtils.hasText(resourcePath)) {
+                log.debug("Resource path {} defined.  Creating INI instance.", resourcePath);
+                ini = Ini.fromResourcePath(resourcePath);
+                if (!CollectionUtils.isEmpty(ini)) {
+                    setIni(ini);
+                }
+            }
+        }
+        
+        if (CollectionUtils.isEmpty(ini)) {
+            String msg = "Ini instance and/or resourcePath resulted in null or empty Ini configuration.  Cannot " +
+                    "load account data.";
+            throw new IllegalStateException(msg);
+        }
+
+        processDefinitions(ini);
+    }
+
+    private void processDefinitions(Ini ini) {
+        if (CollectionUtils.isEmpty(ini)) {
+            log.warn("{} defined, but the ini instance is null or empty.", getClass().getSimpleName());
+            return;
+        }
+
+        Ini.Section rolesSection = ini.getSection(ROLES_SECTION_NAME);
+        if (!CollectionUtils.isEmpty(rolesSection)) {
+            log.debug("Discovered the [{}] section.  Processing...", ROLES_SECTION_NAME);
+            processRoleDefinitions(rolesSection);
+        }
+
+        Ini.Section usersSection = ini.getSection(USERS_SECTION_NAME);
+        if (!CollectionUtils.isEmpty(usersSection)) {
+            log.debug("Discovered the [{}] section.  Processing...", USERS_SECTION_NAME);
+            processUserDefinitions(usersSection);
+        } else {
+            log.info("{} defined, but there is no [{}] section defined.  This realm will not be populated with any " +
+                    "users and it is assumed that they will be populated programatically.  Users must be defined " +
+                    "for this Realm instance to be useful.", getClass().getSimpleName(), USERS_SECTION_NAME);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/text/PropertiesRealm.java b/core/src/main/java/org/apache/shiro/realm/text/PropertiesRealm.java
new file mode 100644
index 0000000..cbdb31d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/text/PropertiesRealm.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.text;
+
+import org.apache.shiro.ShiroException;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.Destroyable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link TextConfigurationRealm} that defers all logic to the parent class, but just enables
+ * {@link java.util.Properties Properties} based configuration in addition to the parent class's String configuration.
+ * <p/>
+ * This class allows processing of a single .properties file for user, role, and
+ * permission configuration.
+ * <p/>
+ * The {@link #setResourcePath resourcePath} <em>MUST</em> be set before this realm can be initialized.  You
+ * can specify any resource path supported by
+ * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
+ * <p/>
+ * The Properties format understood by this implementation must be written as follows:
+ * <p/>
+ * Each line's key/value pair represents either a user-to-role(s) mapping <em>or</em> a role-to-permission(s)
+ * mapping.
+ * <p/>
+ * The user-to-role(s) lines have this format:</p>
+ * <p/>
+ * <code><b>user.</b><em>username</em> = <em>password</em>,role1,role2,...</code></p>
+ * <p/>
+ * Note that each key is prefixed with the token <b>{@code user.}</b>  Each value must adhere to the
+ * the {@link #setUserDefinitions(String) setUserDefinitions(String)} JavaDoc.
+ * <p/>
+ * The role-to-permission(s) lines have this format:</p>
+ * <p/>
+ * <code><b>role.</b><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code>
+ * <p/>
+ * where each key is prefixed with the token <b>{@code role.}</b> and the value adheres to the format specified in
+ * the {@link #setRoleDefinitions(String) setRoleDefinitions(String)} JavaDoc.
+ * <p/>
+ * Here is an example of a very simple properties definition that conforms to the above format rules and corresponding
+ * method JavaDocs:
+ * <p/>
+ * <code>user.root = <em>rootPassword</em>,administrator<br/>
+ * user.jsmith = <em>jsmithPassword</em>,manager,engineer,employee<br/>
+ * user.abrown = <em>abrownPassword</em>,qa,employee<br/>
+ * user.djones = <em>djonesPassword</em>,qa,contractor<br/>
+ * <br/>
+ * role.administrator = *<br/>
+ * role.manager = "user:read,write", file:execute:/usr/local/emailManagers.sh<br/>
+ * role.engineer = "file:read,execute:/usr/local/tomcat/bin/startup.sh"<br/>
+ * role.employee = application:use:wiki<br/>
+ * role.qa = "server:view,start,shutdown,restart:someQaServer", server:view:someProductionServer<br/>
+ * role.contractor = application:use:timesheet</code>
+ *
+ * @since 0.2
+ */
+public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
+
+    //TODO - complete JavaDoc
+
+    /*-------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
+    private static final String USERNAME_PREFIX = "user.";
+    private static final String ROLENAME_PREFIX = "role.";
+    private static final String DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties";
+
+    /*-------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(PropertiesRealm.class);
+
+    protected ExecutorService scheduler = null;
+    protected boolean useXmlFormat = false;
+    protected String resourcePath = DEFAULT_RESOURCE_PATH;
+    protected long fileLastModified;
+    protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS;
+
+    public PropertiesRealm() {
+        super();
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Determines whether or not the properties XML format should be used.  For more information, see
+     * {@link Properties#loadFromXML(java.io.InputStream)}
+     *
+     * @param useXmlFormat true to use XML or false to use the normal format.  Defaults to false.
+     */
+    public void setUseXmlFormat(boolean useXmlFormat) {
+        this.useXmlFormat = useXmlFormat;
+    }
+
+    /**
+     * Sets the path of the properties file to load user, role, and permission information from.  The properties
+     * file will be loaded using {@link ResourceUtils#getInputStreamForPath(String)} so any convention recongized
+     * by that method is accepted here.  For example, to load a file from the classpath use
+     * {@code classpath:myfile.properties}; to load a file from disk simply specify the full path; to load
+     * a file from a URL use {@code url:www.mysite.com/myfile.properties}.
+     *
+     * @param resourcePath the path to load the properties file from.  This is a required property.
+     */
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    /**
+     * Sets the interval in seconds at which the property file will be checked for changes and reloaded.  If this is
+     * set to zero or less, property file reloading will be disabled.  If it is set to 1 or greater, then a
+     * separate thread will be created to monitor the propery file for changes and reload the file if it is updated.
+     *
+     * @param reloadIntervalSeconds the interval in seconds at which the property file should be examined for changes.
+     *                              If set to zero or less, reloading is disabled.
+     */
+    public void setReloadIntervalSeconds(int reloadIntervalSeconds) {
+        this.reloadIntervalSeconds = reloadIntervalSeconds;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    @Override
+    public void onInit() {
+        super.onInit();
+        //TODO - cleanup - this method shouldn't be necessary
+        afterRoleCacheSet();
+    }
+
+    protected void afterRoleCacheSet() {
+        loadProperties();
+        //we can only determine if files have been modified at runtime (not classpath entries or urls), so only
+        //start the thread in this case:
+        if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && scheduler == null) {
+            startReloadThread();
+        }
+    }
+
+    /**
+     * Destroy reload scheduler if one exists.
+     */
+    public void destroy() {
+        try {
+            if (scheduler != null) {
+                scheduler.shutdown();
+            }
+        } catch (Exception e) {
+            if (log.isInfoEnabled()) {
+                log.info("Unable to cleanly shutdown Scheduler.  Ignoring (shutting down)...", e);
+            }
+        } finally {
+            scheduler = null;
+        }
+    }
+
+    protected void startReloadThread() {
+        if (this.reloadIntervalSeconds > 0) {
+            this.scheduler = Executors.newSingleThreadScheduledExecutor();
+            ((ScheduledExecutorService) this.scheduler).scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS);
+        }
+    }
+
+    public void run() {
+        try {
+            reloadPropertiesIfNecessary();
+        } catch (Exception e) {
+            if (log.isErrorEnabled()) {
+                log.error("Error while reloading property files for realm.", e);
+            }
+        }
+    }
+
+    private void loadProperties() {
+        if (resourcePath == null || resourcePath.length() == 0) {
+            throw new IllegalStateException("The resourcePath property is not set.  " +
+                    "It must be set prior to this realm being initialized.");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Loading user security information from file [" + resourcePath + "]...");
+        }
+
+        Properties properties = loadProperties(resourcePath);
+        createRealmEntitiesFromProperties(properties);
+    }
+
+    private Properties loadProperties(String resourcePath) {
+        Properties props = new Properties();
+
+        InputStream is = null;
+        try {
+
+            if (log.isDebugEnabled()) {
+                log.debug("Opening input stream for path [" + resourcePath + "]...");
+            }
+
+            is = ResourceUtils.getInputStreamForPath(resourcePath);
+            if (useXmlFormat) {
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Loading properties from path [" + resourcePath + "] in XML format...");
+                }
+
+                props.loadFromXML(is);
+            } else {
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Loading properties from path [" + resourcePath + "]...");
+                }
+
+                props.load(is);
+            }
+
+        } catch (IOException e) {
+            throw new ShiroException("Error reading properties path [" + resourcePath + "].  " +
+                    "Initializing of the realm from this file failed.", e);
+        } finally {
+            ResourceUtils.close(is);
+        }
+
+        return props;
+    }
+
+
+    private void reloadPropertiesIfNecessary() {
+        if (isSourceModified()) {
+            restart();
+        }
+    }
+
+    private boolean isSourceModified() {
+        //we can only check last modified times on files - classpath and URL entries can't tell us modification times
+        return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && isFileModified();
+    }
+
+    private boolean isFileModified() {
+        //SHIRO-394: strip file prefix before constructing the File instance:
+        String fileNameWithoutPrefix = this.resourcePath.substring(this.resourcePath.indexOf(":") + 1);
+        File propertyFile = new File(fileNameWithoutPrefix);
+        long currentLastModified = propertyFile.lastModified();
+        if (currentLastModified > this.fileLastModified) {
+            this.fileLastModified = currentLastModified;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void restart() {
+        if (resourcePath == null || resourcePath.length() == 0) {
+            throw new IllegalStateException("The resourcePath property is not set.  " +
+                    "It must be set prior to this realm being initialized.");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Loading user security information from file [" + resourcePath + "]...");
+        }
+
+        try {
+            destroy();
+        } catch (Exception e) {
+            //ignored
+        }
+        init();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void createRealmEntitiesFromProperties(Properties properties) {
+
+        StringBuilder userDefs = new StringBuilder();
+        StringBuilder roleDefs = new StringBuilder();
+
+        Enumeration<String> propNames = (Enumeration<String>) properties.propertyNames();
+
+        while (propNames.hasMoreElements()) {
+
+            String key = propNames.nextElement().trim();
+            String value = properties.getProperty(key).trim();
+            if (log.isTraceEnabled()) {
+                log.trace("Processing properties line - key: [" + key + "], value: [" + value + "].");
+            }
+
+            if (isUsername(key)) {
+                String username = getUsername(key);
+                userDefs.append(username).append(" = ").append(value).append("\n");
+            } else if (isRolename(key)) {
+                String rolename = getRolename(key);
+                roleDefs.append(rolename).append(" = ").append(value).append("\n");
+            } else {
+                String msg = "Encountered unexpected key/value pair.  All keys must be prefixed with either '" +
+                        USERNAME_PREFIX + "' or '" + ROLENAME_PREFIX + "'.";
+                throw new IllegalStateException(msg);
+            }
+        }
+
+        setUserDefinitions(userDefs.toString());
+        setRoleDefinitions(roleDefs.toString());
+        processDefinitions();
+    }
+
+    protected String getName(String key, String prefix) {
+        return key.substring(prefix.length(), key.length());
+    }
+
+    protected boolean isUsername(String key) {
+        return key != null && key.startsWith(USERNAME_PREFIX);
+    }
+
+    protected boolean isRolename(String key) {
+        return key != null && key.startsWith(ROLENAME_PREFIX);
+    }
+
+    protected String getUsername(String key) {
+        return getName(key, USERNAME_PREFIX);
+    }
+
+    protected String getRolename(String key) {
+        return getName(key, ROLENAME_PREFIX);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java b/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java
new file mode 100644
index 0000000..51c2ce2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.text;
+
+import org.apache.shiro.authc.SimpleAccount;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.SimpleRole;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.realm.SimpleAccountRealm;
+import org.apache.shiro.util.PermissionUtils;
+import org.apache.shiro.util.StringUtils;
+
+import java.text.ParseException;
+import java.util.*;
+
+
+/**
+ * A SimpleAccountRealm that enables text-based configuration of the initial User, Role, and Permission objects
+ * created at startup.
+ * <p/>
+ * Each User account definition specifies the username, password, and roles for a user.  Each Role definition
+ * specifies a name and an optional collection of assigned Permissions.  Users can be assigned Roles, and Roles can be
+ * assigned Permissions.  By transitive association, each User 'has' all of their Role's Permissions.
+ * <p/>
+ * User and user-to-role definitions are specified via the {@link #setUserDefinitions} method and
+ * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method.
+ *
+ * @since 0.9
+ */
+public class TextConfigurationRealm extends SimpleAccountRealm {
+
+    //TODO - complete JavaDoc
+
+    private volatile String userDefinitions;
+    private volatile String roleDefinitions;
+
+    public TextConfigurationRealm() {
+        super();
+    }
+
+    /**
+     * Will call 'processDefinitions' on startup.
+     *
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-223">SHIRO-223</a>
+     */
+    @Override
+    protected void onInit() {
+        super.onInit();
+        processDefinitions();
+    }
+
+    public String getUserDefinitions() {
+        return userDefinitions;
+    }
+
+    /**
+     * <p>Sets a newline (\n) delimited String that defines user-to-password-and-role(s) key/value pairs according
+     * to the following format:
+     * <p/>
+     * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p>
+     * <p/>
+     * <p>Here are some examples of what these lines might look like:</p>
+     * <p/>
+     * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/>
+     * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/>
+     * abrown = <em>abrownsPassword</em>, qa, employee<br/>
+     * djones = <em>djonesPassword</em>, qa, contractor<br/>
+     * guest = <em>guestPassword</em></code></p>
+     *
+     * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements
+     */
+    public void setUserDefinitions(String userDefinitions) {
+        this.userDefinitions = userDefinitions;
+    }
+
+    public String getRoleDefinitions() {
+        return roleDefinitions;
+    }
+
+    /**
+     * Sets a newline (\n) delimited String that defines role-to-permission definitions.
+     * <p/>
+     * <p>Each line within the string must define a role-to-permission(s) key/value mapping with the
+     * equals character signifies the key/value separation, like so:</p>
+     * <p/>
+     * <p><code><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
+     * <p/>
+     * <p>where <em>permissionDefinition</em> is an arbitrary String, but must people will want to use
+     * Strings that conform to the {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission}
+     * format for ease of use and flexibility.  Note that if an individual <em>permissionDefnition</em> needs to
+     * be internally comma-delimited (e.g. <code>printer:5thFloor:print,info</code>), you will need to surround that
+     * definition with double quotes (") to avoid parsing errors (e.g.
+     * <code>"printer:5thFloor:print,info"</code>).
+     * <p/>
+     * <p><b>NOTE:</b> if you have roles that don't require permission associations, don't include them in this
+     * definition - just defining the role name in the {@link #setUserDefinitions(String) userDefinitions} is
+     * enough to create the role if it does not yet exist.  This property is really only for configuring realms that
+     * have one or more assigned Permission.
+     *
+     * @param roleDefinitions the role definitions to be parsed at initialization
+     */
+    public void setRoleDefinitions(String roleDefinitions) {
+        this.roleDefinitions = roleDefinitions;
+    }
+
+    protected void processDefinitions() {
+        try {
+            processRoleDefinitions();
+            processUserDefinitions();
+        } catch (ParseException e) {
+            String msg = "Unable to parse user and/or role definitions.";
+            throw new ConfigurationException(msg, e);
+        }
+    }
+
+    protected void processRoleDefinitions() throws ParseException {
+        String roleDefinitions = getRoleDefinitions();
+        if (roleDefinitions == null) {
+            return;
+        }
+        Map<String, String> roleDefs = toMap(toLines(roleDefinitions));
+        processRoleDefinitions(roleDefs);
+    }
+
+    protected void processRoleDefinitions(Map<String, String> roleDefs) {
+        if (roleDefs == null || roleDefs.isEmpty()) {
+            return;
+        }
+        for (String rolename : roleDefs.keySet()) {
+            String value = roleDefs.get(rolename);
+
+            SimpleRole role = getRole(rolename);
+            if (role == null) {
+                role = new SimpleRole(rolename);
+                add(role);
+            }
+
+            Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
+            role.setPermissions(permissions);
+        }
+    }
+
+    protected void processUserDefinitions() throws ParseException {
+        String userDefinitions = getUserDefinitions();
+        if (userDefinitions == null) {
+            return;
+        }
+
+        Map<String, String> userDefs = toMap(toLines(userDefinitions));
+
+        processUserDefinitions(userDefs);
+    }
+
+    protected void processUserDefinitions(Map<String, String> userDefs) {
+        if (userDefs == null || userDefs.isEmpty()) {
+            return;
+        }
+        for (String username : userDefs.keySet()) {
+
+            String value = userDefs.get(username);
+
+            String[] passwordAndRolesArray = StringUtils.split(value);
+
+            String password = passwordAndRolesArray[0];
+
+            SimpleAccount account = getUser(username);
+            if (account == null) {
+                account = new SimpleAccount(username, password, getName());
+                add(account);
+            }
+            account.setCredentials(password);
+
+            if (passwordAndRolesArray.length > 1) {
+                for (int i = 1; i < passwordAndRolesArray.length; i++) {
+                    String rolename = passwordAndRolesArray[i];
+                    account.addRole(rolename);
+
+                    SimpleRole role = getRole(rolename);
+                    if (role != null) {
+                        account.addObjectPermissions(role.getPermissions());
+                    }
+                }
+            } else {
+                account.setRoles(null);
+            }
+        }
+    }
+
+    protected static Set<String> toLines(String s) {
+        LinkedHashSet<String> set = new LinkedHashSet<String>();
+        Scanner scanner = new Scanner(s);
+        while (scanner.hasNextLine()) {
+            set.add(scanner.nextLine());
+        }
+        return set;
+    }
+
+    protected static Map<String, String> toMap(Collection<String> keyValuePairs) throws ParseException {
+        if (keyValuePairs == null || keyValuePairs.isEmpty()) {
+            return null;
+        }
+
+        Map<String, String> pairs = new HashMap<String, String>();
+        for (String pairString : keyValuePairs) {
+            String[] pair = StringUtils.splitKeyValue(pairString);
+            if (pair != null) {
+                pairs.put(pair[0].trim(), pair[1].trim());
+            }
+        }
+
+        return pairs;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/realm/text/package-info.java b/core/src/main/java/org/apache/shiro/realm/text/package-info.java
new file mode 100644
index 0000000..9d6d055
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/realm/text/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Realms that acquire security data from text-based data sources such as <code>File</code>s or
+ * text streams.
+ */
+package org.apache.shiro.realm.text;
diff --git a/core/src/main/java/org/apache/shiro/session/ExpiredSessionException.java b/core/src/main/java/org/apache/shiro/session/ExpiredSessionException.java
new file mode 100644
index 0000000..0bb6a05
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/ExpiredSessionException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * A special case of a StoppedSessionException.  An expired session is a session that has
+ * stopped explicitly due to inactivity (i.e. time-out), as opposed to stopping due to log-out or
+ * other reason.
+ *
+ * @since 0.1
+ */
+public class ExpiredSessionException extends StoppedSessionException {
+
+    /**
+     * Creates a new ExpiredSessionException.
+     */
+    public ExpiredSessionException() {
+        super();
+    }
+
+    /**
+     * Constructs a new ExpiredSessionException.
+     *
+     * @param message the reason for the exception
+     */
+    public ExpiredSessionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new ExpiredSessionException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExpiredSessionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new ExpiredSessionException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public ExpiredSessionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/InvalidSessionException.java b/core/src/main/java/org/apache/shiro/session/InvalidSessionException.java
new file mode 100644
index 0000000..8908202
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/InvalidSessionException.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * Exception thrown when attempting to interact with the system under an established session
+ * when that session is considered invalid.  The meaning of the term 'invalid' is based on
+ * application behavior.  For example, a Session is considered invalid if it has been explicitly
+ * stopped (e.g. when a user logs-out or when explicitly
+ * {@link Session#stop() stopped} programmatically.  A Session can also be
+ * considered invalid if it has expired.
+ *
+ * @see StoppedSessionException
+ * @see ExpiredSessionException
+ * @see UnknownSessionException
+ * @since 0.1
+ */
+public class InvalidSessionException extends SessionException {
+
+    /**
+     * Creates a new InvalidSessionException.
+     */
+    public InvalidSessionException() {
+        super();
+    }
+
+    /**
+     * Constructs a new InvalidSessionException.
+     *
+     * @param message the reason for the exception
+     */
+    public InvalidSessionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new InvalidSessionException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public InvalidSessionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new InvalidSessionException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public InvalidSessionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/ProxiedSession.java b/core/src/main/java/org/apache/shiro/session/ProxiedSession.java
new file mode 100644
index 0000000..b07a884
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/ProxiedSession.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * Simple <code>Session</code> implementation that immediately delegates all corresponding calls to an
+ * underlying proxied session instance.
+ * <p/>
+ * This class is mostly useful for framework subclassing to intercept certain <code>Session</code> calls
+ * and perform additional logic.
+ *
+ * @since 0.9
+ */
+public class ProxiedSession implements Session {
+
+    /**
+     * The proxied instance
+     */
+    protected final Session delegate;
+
+    /**
+     * Constructs an instance that proxies the specified <code>target</code>.  Subclasses may access this
+     * target via the <code>protected final 'delegate'</code> attribute, i.e. <code>this.delegate</code>.
+     *
+     * @param target the specified target <code>Session</code> to proxy.
+     */
+    public ProxiedSession(Session target) {
+        if (target == null) {
+            throw new IllegalArgumentException("Target session to proxy cannot be null.");
+        }
+        delegate = target;
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Serializable getId() {
+        return delegate.getId();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Date getStartTimestamp() {
+        return delegate.getStartTimestamp();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Date getLastAccessTime() {
+        return delegate.getLastAccessTime();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public long getTimeout() throws InvalidSessionException {
+        return delegate.getTimeout();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
+        delegate.setTimeout(maxIdleTimeInMillis);
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public String getHost() {
+        return delegate.getHost();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public void touch() throws InvalidSessionException {
+        delegate.touch();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public void stop() throws InvalidSessionException {
+        delegate.stop();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
+        return delegate.getAttributeKeys();
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Object getAttribute(Object key) throws InvalidSessionException {
+        return delegate.getAttribute(key);
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public void setAttribute(Object key, Object value) throws InvalidSessionException {
+        delegate.setAttribute(key, value);
+    }
+
+    /**
+     * Immediately delegates to the underlying proxied session.
+     */
+    public Object removeAttribute(Object key) throws InvalidSessionException {
+        return delegate.removeAttribute(key);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/Session.java b/core/src/main/java/org/apache/shiro/session/Session.java
new file mode 100644
index 0000000..7abe2ed
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/Session.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * A {@code Session} is a stateful data context associated with a single Subject (user, daemon process,
+ * etc) who interacts with a software system over a period of time.
+ * <p/>
+ * A {@code Session} is intended to be managed by the business tier and accessible via other
+ * tiers without being tied to any given client technology.  This is a <em>great</em> benefit to Java
+ * systems, since until now, the only viable session mechanisms were the
+ * {@code javax.servlet.http.HttpSession} or Stateful Session EJB's, which many times
+ * unnecessarily coupled applications to web or ejb technologies.
+ *
+ * @since 0.1
+ */
+public interface Session {
+
+    /**
+     * Returns the unique identifier assigned by the system upon session creation.
+     * <p/>
+     * All return values from this method are expected to have proper {@code toString()},
+     * {@code equals()}, and {@code hashCode()} implementations. Good candidates for such
+     * an identifier are {@link java.util.UUID UUID}s, {@link java.lang.Integer Integer}s, and
+     * {@link java.lang.String String}s.
+     *
+     * @return The unique identifier assigned to the session upon creation.
+     */
+    Serializable getId();
+
+    /**
+     * Returns the time the session was started; that is, the time the system created the instance.
+     *
+     * @return The time the system created the session.
+     */
+    Date getStartTimestamp();
+
+    /**
+     * Returns the last time the application received a request or method invocation from the user associated
+     * with this session.  Application calls to this method do not affect this access time.
+     *
+     * @return The time the user last interacted with the system.
+     * @see #touch()
+     */
+    Date getLastAccessTime();
+
+    /**
+     * Returns the time in milliseconds that the session session may remain idle before expiring.
+     * <ul>
+     * <li>A negative return value means the session will never expire.</li>
+     * <li>A non-negative return value (0 or greater) means the session expiration will occur if idle for that
+     * length of time.</li>
+     * </ul>
+     * <b>*Note:</b> if you are used to the {@code HttpSession}'s {@code getMaxInactiveInterval()} method, the scale on
+     * this method is different: Shiro Sessions use millisecond values for timeout whereas
+     * {@code HttpSession.getMaxInactiveInterval} uses seconds.  Always use millisecond values with Shiro sessions.
+     *
+     * @return the time in milliseconds the session may remain idle before expiring.
+     * @throws InvalidSessionException if the session has been stopped or expired prior to calling this method.
+     * @since 0.2
+     */
+    long getTimeout() throws InvalidSessionException;
+
+    /**
+     * Sets the time in milliseconds that the session may remain idle before expiring.
+     * <ul>
+     * <li>A negative value means the session will never expire.</li>
+     * <li>A non-negative value (0 or greater) means the session expiration will occur if idle for that
+     * length of time.</li>
+     * </ul>
+     * <p/>
+     * <b>*Note:</b> if you are used to the {@code HttpSession}'s {@code getMaxInactiveInterval()} method, the scale on
+     * this method is different: Shiro Sessions use millisecond values for timeout whereas
+     * {@code HttpSession.getMaxInactiveInterval} uses seconds.  Always use millisecond values with Shiro sessions.
+     *
+     * @param maxIdleTimeInMillis the time in milliseconds that the session may remain idle before expiring.
+     * @throws InvalidSessionException if the session has been stopped or expired prior to calling this method.
+     * @since 0.2
+     */
+    void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
+
+    /**
+     * Returns the host name or IP string of the host that originated this session, or {@code null}
+     * if the host is unknown.
+     *
+     * @return the host name or IP string of the host that originated this session, or {@code null}
+     *         if the host address is unknown.
+     */
+    String getHost();
+
+    /**
+     * Explicitly updates the {@link #getLastAccessTime() lastAccessTime} of this session to the current time when
+     * this method is invoked.  This method can be used to ensure a session does not time out.
+     * <p/>
+     * Most programmers won't use this method directly and will instead rely on the last access time to be updated
+     * automatically as a result of an incoming web request or remote procedure call/method invocation.
+     * <p/>
+     * However, this method is particularly useful when supporting rich-client applications such as
+     * Java Web Start appp, Java or Flash applets, etc.  Although rare, it is possible in a rich-client
+     * environment that a user continuously interacts with the client-side application without a
+     * server-side method call ever being invoked.  If this happens over a long enough period of
+     * time, the user's server-side session could time-out.  Again, such cases are rare since most
+     * rich-clients frequently require server-side method invocations.
+     * <p/>
+     * In this example though, the user's session might still be considered valid because
+     * the user is actively "using" the application, just not communicating with the
+     * server. But because no server-side method calls are invoked, there is no way for the server
+     * to know if the user is sitting idle or not, so it must assume so to maintain session
+     * integrity.  This {@code touch()} method could be invoked by the rich-client application code during those
+     * times to ensure that the next time a server-side method is invoked, the invocation will not
+     * throw an {@link ExpiredSessionException ExpiredSessionException}.  In short terms, it could be used periodically
+     * to ensure a session does not time out.
+     * <p/>
+     * How often this rich-client "maintenance" might occur is entirely dependent upon
+     * the application and would be based on variables such as session timeout configuration,
+     * usage characteristics of the client application, network utilization and application server
+     * performance.
+     *
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling this method.
+     */
+    void touch() throws InvalidSessionException;
+
+    /**
+     * Explicitly stops (invalidates) this session and releases all associated resources.
+     * <p/>
+     * If this session has already been authenticated (i.e. the {@code Subject} that
+     * owns this session has logged-in), calling this method explicitly might have undesired side effects:
+     * <p/>
+     * It is common for a {@code Subject} implementation to retain authentication state in the
+     * {@code Session}.  If the session
+     * is explicitly stopped by application code by calling this method directly, it could clear out any
+     * authentication state that might exist, thereby effectively "unauthenticating" the {@code Subject}.
+     * <p/>
+     * As such, you might consider {@link org.apache.shiro.subject.Subject#logout logging-out} the 'owning'
+     * {@code Subject} instead of manually calling this method, as a log out is expected to stop the
+     * corresponding session automatically, and also allows framework code to execute additional cleanup logic.
+     *
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling this method.
+     */
+    void stop() throws InvalidSessionException;
+
+    /**
+     * Returns the keys of all the attributes stored under this session.  If there are no
+     * attributes, this returns an empty collection.
+     *
+     * @return the keys of all attributes stored under this session, or an empty collection if
+     *         there are no session attributes.
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling this method.
+     * @since 0.2
+     */
+    Collection<Object> getAttributeKeys() throws InvalidSessionException;
+
+    /**
+     * Returns the object bound to this session identified by the specified key.  If there is no
+     * object bound under the key, {@code null} is returned.
+     *
+     * @param key the unique name of the object bound to this session
+     * @return the object bound under the specified {@code key} name or {@code null} if there is
+     *         no object bound under that name.
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling
+     *                                 this method.
+     */
+    Object getAttribute(Object key) throws InvalidSessionException;
+
+    /**
+     * Binds the specified {@code value} to this session, uniquely identified by the specifed
+     * {@code key} name.  If there is already an object bound under the {@code key} name, that
+     * existing object will be replaced by the new {@code value}.
+     * <p/>
+     * If the {@code value} parameter is null, it has the same effect as if
+     * {@link #removeAttribute(Object) removeAttribute} was called.
+     *
+     * @param key   the name under which the {@code value} object will be bound in this session
+     * @param value the object to bind in this session.
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling
+     *                                 this method.
+     */
+    void setAttribute(Object key, Object value) throws InvalidSessionException;
+
+    /**
+     * Removes (unbinds) the object bound to this session under the specified {@code key} name.
+     *
+     * @param key the name uniquely identifying the object to remove
+     * @return the object removed or {@code null} if there was no object bound under the name
+     *         {@code key}.
+     * @throws InvalidSessionException if this session has stopped or expired prior to calling
+     *                                 this method.
+     */
+    Object removeAttribute(Object key) throws InvalidSessionException;
+}
diff --git a/core/src/main/java/org/apache/shiro/session/SessionException.java b/core/src/main/java/org/apache/shiro/session/SessionException.java
new file mode 100644
index 0000000..324172f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/SessionException.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * General security exception attributed to problems during interaction with the system during
+ * a session.
+ *
+ * @since 0.1
+ */
+public class SessionException extends ShiroException {
+
+    /**
+     * Creates a new SessionException.
+     */
+    public SessionException() {
+        super();
+    }
+
+    /**
+     * Constructs a new SessionException.
+     *
+     * @param message the reason for the exception
+     */
+    public SessionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new SessionException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public SessionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new SessionException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public SessionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/SessionListener.java b/core/src/main/java/org/apache/shiro/session/SessionListener.java
new file mode 100644
index 0000000..1d4f4a3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/SessionListener.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * Interface to be implemented by components that wish to be notified of events that occur during a
+ * {@link Session Session}'s life cycle.
+ *
+ * @since 0.9
+ */
+public interface SessionListener {
+
+    /**
+     * Notification callback that occurs when the corresponding Session has started.
+     *
+     * @param session the session that has started.
+     */
+    void onStart(Session session);
+
+    /**
+     * Notification callback that occurs when the corresponding Session has stopped, either programmatically via
+     * {@link Session#stop} or automatically upon a subject logging out.
+     *
+     * @param session the session that has stopped.
+     */
+    void onStop(Session session);
+
+    /**
+     * Notification callback that occurs when the corresponding Session has expired.
+     * <p/>
+     * <b>Note</b>: this method is almost never called at the exact instant that the {@code Session} expires.  Almost all
+     * session management systems, including Shiro's implementations, lazily validate sessions - either when they
+     * are accessed or during a regular validation interval.  It would be too resource intensive to monitor every
+     * single session instance to know the exact instant it expires.
+     * <p/>
+     * If you need to perform time-based logic when a session expires, it is best to write it based on the
+     * session's {@link org.apache.shiro.session.Session#getLastAccessTime() lastAccessTime} and <em>not</em> the time
+     * when this method is called.
+     *
+     * @param session the session that has expired.
+     */
+    void onExpiration(Session session);
+}
diff --git a/core/src/main/java/org/apache/shiro/session/SessionListenerAdapter.java b/core/src/main/java/org/apache/shiro/session/SessionListenerAdapter.java
new file mode 100644
index 0000000..24d5860
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/SessionListenerAdapter.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * Simple adapter implementation of the {@link SessionListener} interface, effectively providing
+ * no-op implementations of all methods.
+ *
+ * @since 1.0
+ */
+public class SessionListenerAdapter implements SessionListener {
+
+    /**
+     * Adapter no-op implemenation - does nothing and returns immediately.
+     *
+     * @param session the session that has started.
+     */
+    public void onStart(Session session) {
+        //no-op
+    }
+
+    /**
+     * Adapter no-op implemenation - does nothing and returns immediately.
+     *
+     * @param session the session that has stopped.
+     */
+    public void onStop(Session session) {
+        //no-op
+    }
+
+    /**
+     * Adapter no-op implemenation - does nothing and returns immediately.
+     *
+     * @param session the session that has expired.
+     */
+    public void onExpiration(Session session) {
+        //no-op
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/StoppedSessionException.java b/core/src/main/java/org/apache/shiro/session/StoppedSessionException.java
new file mode 100644
index 0000000..fbfd506
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/StoppedSessionException.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * Exception thrown when attempting to interact with the system under a session that has been
+ * stopped.  A session may be stopped in any number of ways, most commonly due to explicit
+ * stopping (e.g. from logging out), or due to expiration.
+ *
+ * @since 0.1
+ */
+public class StoppedSessionException extends InvalidSessionException {
+
+    /**
+     * Creates a new StoppedSessionException.
+     */
+    public StoppedSessionException() {
+        super();
+    }
+
+    /**
+     * Constructs a new StoppedSessionException.
+     *
+     * @param message the reason for the exception
+     */
+    public StoppedSessionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new StoppedSessionException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public StoppedSessionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new StoppedSessionException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public StoppedSessionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java b/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
new file mode 100644
index 0000000..f9a1e04
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/UnknownSessionException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session;
+
+/**
+ * Exception thrown when attempting to interact with the system under the pretense of a
+ * particular session (e.g. under a specific session id), and that session does not exist in
+ * the system.
+ *
+ * @since 0.1
+ */
+public class UnknownSessionException extends InvalidSessionException {
+
+    /**
+     * Creates a new UnknownSessionException.
+     */
+    public UnknownSessionException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnknownSessionException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnknownSessionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnknownSessionException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownSessionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnknownSessionException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownSessionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
new file mode 100644
index 0000000..e3826d0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/AbstractNativeSessionManager.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.session.*;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+
+/**
+ * Abstract implementation supporting the {@link NativeSessionManager NativeSessionManager} interface, supporting
+ * {@link SessionListener SessionListener}s and application of the
+ * {@link #getGlobalSessionTimeout() globalSessionTimeout}.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager {
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractSessionManager.class);
+
+    private Collection<SessionListener> listeners;
+
+    public AbstractNativeSessionManager() {
+        this.listeners = new ArrayList<SessionListener>();
+    }
+
+    public void setSessionListeners(Collection<SessionListener> listeners) {
+        this.listeners = listeners != null ? listeners : new ArrayList<SessionListener>();
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public Collection<SessionListener> getSessionListeners() {
+        return this.listeners;
+    }
+
+    public Session start(SessionContext context) {
+        Session session = createSession(context);
+        applyGlobalSessionTimeout(session);
+        onStart(session, context);
+        notifyStart(session);
+        //Don't expose the EIS-tier Session object to the client-tier:
+        return createExposedSession(session, context);
+    }
+
+    /**
+     * Creates a new {@code Session Session} instance based on the specified (possibly {@code null})
+     * initialization data.  Implementing classes must manage the persistent state of the returned session such that it
+     * could later be acquired via the {@link #getSession(SessionKey)} method.
+     *
+     * @param context the initialization data that can be used by the implementation or underlying
+     *                {@link SessionFactory} when instantiating the internal {@code Session} instance.
+     * @return the new {@code Session} instance.
+     * @throws org.apache.shiro.authz.HostUnauthorizedException
+     *                                if the system access control policy restricts access based
+     *                                on client location/IP and the specified hostAddress hasn't been enabled.
+     * @throws AuthorizationException if the system access control policy does not allow the currently executing
+     *                                caller to start sessions.
+     */
+    protected abstract Session createSession(SessionContext context) throws AuthorizationException;
+
+    protected void applyGlobalSessionTimeout(Session session) {
+        session.setTimeout(getGlobalSessionTimeout());
+        onChange(session);
+    }
+
+    /**
+     * Template method that allows subclasses to react to a new session being created.
+     * <p/>
+     * This method is invoked <em>before</em> any session listeners are notified.
+     *
+     * @param session the session that was just {@link #createSession created}.
+     * @param context the {@link SessionContext SessionContext} that was used to start the session.
+     */
+    protected void onStart(Session session, SessionContext context) {
+    }
+
+    public Session getSession(SessionKey key) throws SessionException {
+        Session session = lookupSession(key);
+        return session != null ? createExposedSession(session, key) : null;
+    }
+
+    private Session lookupSession(SessionKey key) throws SessionException {
+        if (key == null) {
+            throw new NullPointerException("SessionKey argument cannot be null.");
+        }
+        return doGetSession(key);
+    }
+
+    private Session lookupRequiredSession(SessionKey key) throws SessionException {
+        Session session = lookupSession(key);
+        if (session == null) {
+            String msg = "Unable to locate required Session instance based on SessionKey [" + key + "].";
+            throw new UnknownSessionException(msg);
+        }
+        return session;
+    }
+
+    protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException;
+
+    protected Session createExposedSession(Session session, SessionContext context) {
+        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
+    }
+
+    protected Session createExposedSession(Session session, SessionKey key) {
+        return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
+    }
+
+    /**
+     * Returns the session instance to use to pass to registered {@code SessionListener}s for notification
+     * that the session has been invalidated (stopped or expired).
+     * <p/>
+     * The default implementation returns an {@link ImmutableProxiedSession ImmutableProxiedSession} instance to ensure
+     * that the specified {@code session} argument is not modified by any listeners.
+     *
+     * @param session the {@code Session} object being invalidated.
+     * @return the {@code Session} instance to use to pass to registered {@code SessionListener}s for notification.
+     */
+    protected Session beforeInvalidNotification(Session session) {
+        return new ImmutableProxiedSession(session);
+    }
+
+    /**
+     * Notifies any interested {@link SessionListener}s that a Session has started.  This method is invoked
+     * <em>after</em> the {@link #onStart onStart} method is called.
+     *
+     * @param session the session that has just started that will be delivered to any
+     *                {@link #setSessionListeners(java.util.Collection) registered} session listeners.
+     * @see SessionListener#onStart(org.apache.shiro.session.Session)
+     */
+    protected void notifyStart(Session session) {
+        for (SessionListener listener : this.listeners) {
+            listener.onStart(session);
+        }
+    }
+
+    protected void notifyStop(Session session) {
+        Session forNotification = beforeInvalidNotification(session);
+        for (SessionListener listener : this.listeners) {
+            listener.onStop(forNotification);
+        }
+    }
+
+    protected void notifyExpiration(Session session) {
+        Session forNotification = beforeInvalidNotification(session);
+        for (SessionListener listener : this.listeners) {
+            listener.onExpiration(forNotification);
+        }
+    }
+
+    public Date getStartTimestamp(SessionKey key) {
+        return lookupRequiredSession(key).getStartTimestamp();
+    }
+
+    public Date getLastAccessTime(SessionKey key) {
+        return lookupRequiredSession(key).getLastAccessTime();
+    }
+
+    public long getTimeout(SessionKey key) throws InvalidSessionException {
+        return lookupRequiredSession(key).getTimeout();
+    }
+
+    public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException {
+        Session s = lookupRequiredSession(key);
+        s.setTimeout(maxIdleTimeInMillis);
+        onChange(s);
+    }
+
+    public void touch(SessionKey key) throws InvalidSessionException {
+        Session s = lookupRequiredSession(key);
+        s.touch();
+        onChange(s);
+    }
+
+    public String getHost(SessionKey key) {
+        return lookupRequiredSession(key).getHost();
+    }
+
+    public Collection<Object> getAttributeKeys(SessionKey key) {
+        Collection<Object> c = lookupRequiredSession(key).getAttributeKeys();
+        if (!CollectionUtils.isEmpty(c)) {
+            return Collections.unmodifiableCollection(c);
+        }
+        return Collections.emptySet();
+    }
+
+    public Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
+        return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
+    }
+
+    public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException {
+        if (value == null) {
+            removeAttribute(sessionKey, attributeKey);
+        } else {
+            Session s = lookupRequiredSession(sessionKey);
+            s.setAttribute(attributeKey, value);
+            onChange(s);
+        }
+    }
+
+    public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
+        Session s = lookupRequiredSession(sessionKey);
+        Object removed = s.removeAttribute(attributeKey);
+        if (removed != null) {
+            onChange(s);
+        }
+        return removed;
+    }
+
+    public boolean isValid(SessionKey key) {
+        try {
+            checkValid(key);
+            return true;
+        } catch (InvalidSessionException e) {
+            return false;
+        }
+    }
+
+    public void stop(SessionKey key) throws InvalidSessionException {
+        Session session = lookupRequiredSession(key);
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("Stopping session with id [" + session.getId() + "]");
+            }
+            session.stop();
+            onStop(session, key);
+            notifyStop(session);
+        } finally {
+            afterStopped(session);
+        }
+    }
+
+    protected void onStop(Session session, SessionKey key) {
+        onStop(session);
+    }
+
+    protected void onStop(Session session) {
+        onChange(session);
+    }
+
+    protected void afterStopped(Session session) {
+    }
+
+    public void checkValid(SessionKey key) throws InvalidSessionException {
+        //just try to acquire it.  If there is a problem, an exception will be thrown:
+        lookupRequiredSession(key);
+    }
+
+    protected void onChange(Session s) {
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java
new file mode 100644
index 0000000..5a296c1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/AbstractSessionManager.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.Session;
+
+/**
+ * Base abstract class of the {@link SessionManager SessionManager} interface, enabling configuration of an
+ * application-wide {@link #getGlobalSessionTimeout() globalSessionTimeout}.  Default global session timeout is
+ * {@code 30} minutes.
+ *
+ * @since 0.1
+ */
+//TODO - deprecate this class (see SHIRO-240):
+//This is only here to make available a common attribute 'globalSessionTimeout' to subclasses, particularly to make it
+//available to both AbstractNativeSessionManager and ServletContainerSessionManager subclass trees.  However, the
+//ServletContainerSessionManager implementation does not use this value
+//(see https://issues.apache.org/jira/browse/SHIRO-240 for why).  That means that only the Native session managers
+//need a globalSessionTimeout property, making this class unnecessary.
+public abstract class AbstractSessionManager implements SessionManager {
+
+    protected static final long MILLIS_PER_SECOND = 1000;
+    protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+    protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
+
+    /**
+     * Default main session timeout value, equal to {@code 30} minutes.
+     */
+    public static final long DEFAULT_GLOBAL_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE;
+
+    private long globalSessionTimeout = DEFAULT_GLOBAL_SESSION_TIMEOUT;
+
+    public AbstractSessionManager() {
+    }
+
+    /**
+     * Returns the system-wide default time in milliseconds that any session may remain idle before expiring. This
+     * value is the main default for all sessions and may be overridden on a <em>per-session</em> basis by calling
+     * {@code Subject.getSession().}{@link Session#setTimeout setTimeout(long)} if so desired.
+     * <ul>
+     * <li>A negative return value means sessions never expire.</li>
+     * <li>A non-negative return value (0 or greater) means session timeout will occur as expected.</li>
+     * </ul>
+     * <p/>
+     * Unless overridden via the {@link #setGlobalSessionTimeout} method, the default value is
+     * {@link #DEFAULT_GLOBAL_SESSION_TIMEOUT}.
+     *
+     * @return the time in milliseconds that any session may remain idle before expiring.
+     */
+    public long getGlobalSessionTimeout() {
+        return this.globalSessionTimeout;
+    }
+
+    /**
+     * Sets the system-wide default time in milliseconds that any session may remain idle before expiring. This
+     * value is the main default for all sessions and may be overridden on a <em>per-session</em> basis by calling
+     * {@code Subject.getSession().}{@link Session#setTimeout setTimeout(long)} if so desired.
+     * <p/>
+     * <ul>
+     * <li>A negative return value means sessions never expire.</li>
+     * <li>A non-negative return value (0 or greater) means session timeout will occur as expected.</li>
+     * </ul>
+     * <p/>
+     * Unless overridden by calling this method, the default value is {@link #DEFAULT_GLOBAL_SESSION_TIMEOUT}.
+     *
+     * @param globalSessionTimeout the time in milliseconds that any session may remain idel before expiring.
+     */
+    public void setGlobalSessionTimeout(long globalSessionTimeout) {
+        this.globalSessionTimeout = globalSessionTimeout;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
new file mode 100644
index 0000000..8432318
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.LifecycleUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+
+/**
+ * Default business-tier implementation of the {@link ValidatingSessionManager} interface.
+ *
+ * @since 0.1
+ */
+public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
+        implements ValidatingSessionManager, Destroyable {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractValidatingSessionManager.class);
+
+    /**
+     * The default interval at which sessions will be validated (1 hour);
+     * This can be overridden by calling {@link #setSessionValidationInterval(long)}
+     */
+    public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
+
+    protected boolean sessionValidationSchedulerEnabled;
+
+    /**
+     * Scheduler used to validate sessions on a regular basis.
+     */
+    protected SessionValidationScheduler sessionValidationScheduler;
+
+    protected long sessionValidationInterval;
+
+    public AbstractValidatingSessionManager() {
+        this.sessionValidationSchedulerEnabled = true;
+        this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
+    }
+
+    public boolean isSessionValidationSchedulerEnabled() {
+        return sessionValidationSchedulerEnabled;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) {
+        this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
+    }
+
+    public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
+        this.sessionValidationScheduler = sessionValidationScheduler;
+    }
+
+    public SessionValidationScheduler getSessionValidationScheduler() {
+        return sessionValidationScheduler;
+    }
+
+    private void enableSessionValidationIfNecessary() {
+        SessionValidationScheduler scheduler = getSessionValidationScheduler();
+        if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
+            enableSessionValidation();
+        }
+    }
+
+    /**
+     * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
+     * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
+     * never called) , this method allows one to specify how
+     * frequently session should be validated (to check for orphans).  The default value is
+     * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
+     * <p/>
+     * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
+     * validate sessions, and this attribute will be ignored.
+     * <p/>
+     * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
+     *
+     * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
+     */
+    public void setSessionValidationInterval(long sessionValidationInterval) {
+        this.sessionValidationInterval = sessionValidationInterval;
+    }
+
+    public long getSessionValidationInterval() {
+        return sessionValidationInterval;
+    }
+
+    @Override
+    protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
+        enableSessionValidationIfNecessary();
+
+        log.trace("Attempting to retrieve session with key {}", key);
+
+        Session s = retrieveSession(key);
+        if (s != null) {
+            validate(s, key);
+        }
+        return s;
+    }
+
+    /**
+     * Looks up a session from the underlying data store based on the specified session key.
+     *
+     * @param key the session key to use to look up the target session.
+     * @return the session identified by {@code sessionId}.
+     * @throws UnknownSessionException if there is no session identified by {@code sessionId}.
+     */
+    protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;
+
+    protected Session createSession(SessionContext context) throws AuthorizationException {
+        enableSessionValidationIfNecessary();
+        return doCreateSession(context);
+    }
+
+    protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
+
+    protected void validate(Session session, SessionKey key) throws InvalidSessionException {
+        try {
+            doValidate(session);
+        } catch (ExpiredSessionException ese) {
+            onExpiration(session, ese, key);
+            throw ese;
+        } catch (InvalidSessionException ise) {
+            onInvalidation(session, ise, key);
+            throw ise;
+        }
+    }
+
+    protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
+        log.trace("Session with id [{}] has expired.", s.getId());
+        try {
+            onExpiration(s);
+            notifyExpiration(s);
+        } finally {
+            afterExpired(s);
+        }
+    }
+
+    protected void onExpiration(Session session) {
+        onChange(session);
+    }
+
+    protected void afterExpired(Session session) {
+    }
+
+    protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
+        if (ise instanceof ExpiredSessionException) {
+            onExpiration(s, (ExpiredSessionException) ise, key);
+            return;
+        }
+        log.trace("Session with id [{}] is invalid.", s.getId());
+        try {
+            onStop(s);
+            notifyStop(s);
+        } finally {
+            afterStopped(s);
+        }
+    }
+
+    protected void doValidate(Session session) throws InvalidSessionException {
+        if (session instanceof ValidatingSession) {
+            ((ValidatingSession) session).validate();
+        } else {
+            String msg = "The " + getClass().getName() + " implementation only supports validating " +
+                    "Session implementations of the " + ValidatingSession.class.getName() + " interface.  " +
+                    "Please either implement this interface in your session implementation or override the " +
+                    AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    /**
+     * Subclass template hook in case per-session timeout is not based on
+     * {@link org.apache.shiro.session.Session#getTimeout()}.
+     * <p/>
+     * <p>This implementation merely returns {@link org.apache.shiro.session.Session#getTimeout()}</p>
+     *
+     * @param session the session for which to determine session timeout.
+     * @return the time in milliseconds the specified session may remain idle before expiring.
+     */
+    protected long getTimeout(Session session) {
+        return session.getTimeout();
+    }
+
+    protected SessionValidationScheduler createSessionValidationScheduler() {
+        ExecutorServiceSessionValidationScheduler scheduler;
+
+        if (log.isDebugEnabled()) {
+            log.debug("No sessionValidationScheduler set.  Attempting to create default instance.");
+        }
+        scheduler = new ExecutorServiceSessionValidationScheduler(this);
+        scheduler.setInterval(getSessionValidationInterval());
+        if (log.isTraceEnabled()) {
+            log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
+        }
+        return scheduler;
+    }
+
+    protected void enableSessionValidation() {
+        SessionValidationScheduler scheduler = getSessionValidationScheduler();
+        if (scheduler == null) {
+            scheduler = createSessionValidationScheduler();
+            setSessionValidationScheduler(scheduler);
+        }
+        if (log.isInfoEnabled()) {
+            log.info("Enabling session validation scheduler...");
+        }
+        scheduler.enableSessionValidation();
+        afterSessionValidationEnabled();
+    }
+
+    protected void afterSessionValidationEnabled() {
+    }
+
+    protected void disableSessionValidation() {
+        beforeSessionValidationDisabled();
+        SessionValidationScheduler scheduler = getSessionValidationScheduler();
+        if (scheduler != null) {
+            try {
+                scheduler.disableSessionValidation();
+                if (log.isInfoEnabled()) {
+                    log.info("Disabled session validation scheduler.");
+                }
+            } catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    String msg = "Unable to disable SessionValidationScheduler.  Ignoring (shutting down)...";
+                    log.debug(msg, e);
+                }
+            }
+            LifecycleUtils.destroy(scheduler);
+            setSessionValidationScheduler(null);
+        }
+    }
+
+    protected void beforeSessionValidationDisabled() {
+    }
+
+    public void destroy() {
+        disableSessionValidation();
+    }
+
+    /**
+     * @see ValidatingSessionManager#validateSessions()
+     */
+    public void validateSessions() {
+        if (log.isInfoEnabled()) {
+            log.info("Validating all active sessions...");
+        }
+
+        int invalidCount = 0;
+
+        Collection<Session> activeSessions = getActiveSessions();
+
+        if (activeSessions != null && !activeSessions.isEmpty()) {
+            for (Session s : activeSessions) {
+                try {
+                    //simulate a lookup key to satisfy the method signature.
+                    //this could probably stand to be cleaned up in future versions:
+                    SessionKey key = new DefaultSessionKey(s.getId());
+                    validate(s, key);
+                } catch (InvalidSessionException e) {
+                    if (log.isDebugEnabled()) {
+                        boolean expired = (e instanceof ExpiredSessionException);
+                        String msg = "Invalidated session with id [" + s.getId() + "]" +
+                                (expired ? " (expired)" : " (stopped)");
+                        log.debug(msg);
+                    }
+                    invalidCount++;
+                }
+            }
+        }
+
+        if (log.isInfoEnabled()) {
+            String msg = "Finished session validation.";
+            if (invalidCount > 0) {
+                msg += "  [" + invalidCount + "] sessions were stopped.";
+            } else {
+                msg += "  No sessions were stopped.";
+            }
+            log.info(msg);
+        }
+    }
+
+    protected abstract Collection<Session> getActiveSessions();
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java
new file mode 100644
index 0000000..69c6409
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionContext.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.util.MapContext;
+import org.apache.shiro.util.StringUtils;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Default implementation of the {@link SessionContext} interface which provides getters and setters that
+ * wrap interaction with the underlying backing context map.
+ *
+ * @since 1.0
+ */
+public class DefaultSessionContext extends MapContext implements SessionContext {
+
+    private static final long serialVersionUID = -1424160751361252966L;
+
+    private static final String HOST = DefaultSessionContext.class.getName() + ".HOST";
+    private static final String SESSION_ID = DefaultSessionContext.class.getName() + ".SESSION_ID";
+
+    public DefaultSessionContext() {
+        super();
+    }
+
+    public DefaultSessionContext(Map<String, Object> map) {
+        super(map);
+    }
+
+    public String getHost() {
+        return getTypedValue(HOST, String.class);
+    }
+
+    public void setHost(String host) {
+        if (StringUtils.hasText(host)) {
+            put(HOST, host);
+        }
+    }
+
+    public Serializable getSessionId() {
+        return getTypedValue(SESSION_ID, Serializable.class);
+    }
+
+    public void setSessionId(Serializable sessionId) {
+        nullSafePut(SESSION_ID, sessionId);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java
new file mode 100644
index 0000000..134a857
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionKey.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of the {@link SessionKey} interface, which allows setting and retrieval of a concrete
+ * {@link #getSessionId() sessionId} that the {@code SessionManager} implementation can use to look up a
+ * {@code Session} instance.
+ *
+ * @since 1.0
+ */
+public class DefaultSessionKey implements SessionKey, Serializable {
+
+    private Serializable sessionId;
+
+    public DefaultSessionKey() {
+    }
+
+    public DefaultSessionKey(Serializable sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public void setSessionId(Serializable sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public Serializable getSessionId() {
+        return this.sessionId;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
new file mode 100644
index 0000000..da6930c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/DefaultSessionManager.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+
+/**
+ * Default business-tier implementation of a {@link ValidatingSessionManager}.  All session CRUD operations are
+ * delegated to an internal {@link SessionDAO}.
+ *
+ * @since 0.1
+ */
+public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);
+
+    private SessionFactory sessionFactory;
+
+    protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
+
+    private CacheManager cacheManager;
+
+    private boolean deleteInvalidSessions;
+
+    public DefaultSessionManager() {
+        this.deleteInvalidSessions = true;
+        this.sessionFactory = new SimpleSessionFactory();
+        this.sessionDAO = new MemorySessionDAO();
+    }
+
+    public void setSessionDAO(SessionDAO sessionDAO) {
+        this.sessionDAO = sessionDAO;
+        applyCacheManagerToSessionDAO();
+    }
+
+    public SessionDAO getSessionDAO() {
+        return this.sessionDAO;
+    }
+
+    /**
+     * Returns the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
+     * is a {@link SimpleSessionFactory}.
+     *
+     * @return the {@code SessionFactory} used to generate new {@link Session} instances.
+     * @since 1.0
+     */
+    public SessionFactory getSessionFactory() {
+        return sessionFactory;
+    }
+
+    /**
+     * Sets the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
+     * is a {@link SimpleSessionFactory}.
+     *
+     * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
+     * @since 1.0
+     */
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    /**
+     * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+     * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.  The
+     * default is {@code true} to ensure no orphans exist in the underlying data store.
+     * <h4>Usage</h4>
+     * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
+     * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
+     * job.  If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
+     * <p/>
+     * This property is provided because some systems need the ability to perform querying/reporting against sessions in
+     * the data store, even after they have stopped or expired.  Setting this attribute to {@code false} will allow
+     * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
+     * some other means (cron, quartz, etc).
+     *
+     * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
+     *         {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
+     * @since 1.0
+     */
+    public boolean isDeleteInvalidSessions() {
+        return deleteInvalidSessions;
+    }
+
+    /**
+     * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid.  Default
+     * value is {@code true} to ensure no orphans will exist in the underlying data store.
+     * <h4>WARNING</h4>
+     * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
+     * (quartz, cron, etc) external to Shiro's control.  See the
+     * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
+     *
+     * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
+     *                              to be invalid.
+     * @since 1.0
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
+        this.deleteInvalidSessions = deleteInvalidSessions;
+    }
+
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+        applyCacheManagerToSessionDAO();
+    }
+
+    /**
+     * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
+     * {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
+     * <p/>
+     * This method is called after setting a cacheManager via the
+     * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
+     * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
+     * in either case.
+     *
+     * @since 1.0
+     */
+    private void applyCacheManagerToSessionDAO() {
+        if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
+            ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
+        }
+    }
+
+    protected Session doCreateSession(SessionContext context) {
+        Session s = newSessionInstance(context);
+        if (log.isTraceEnabled()) {
+            log.trace("Creating session for host {}", s.getHost());
+        }
+        create(s);
+        return s;
+    }
+
+    protected Session newSessionInstance(SessionContext context) {
+        return getSessionFactory().createSession(context);
+    }
+
+    /**
+     * Persists the given session instance to an underlying EIS (Enterprise Information System).  This implementation
+     * delegates and calls
+     * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(org.apache.shiro.session.Session) create}(session);<code>
+     *
+     * @param session the Session instance to persist to the underlying EIS.
+     */
+    protected void create(Session session) {
+        if (log.isDebugEnabled()) {
+            log.debug("Creating new EIS record for new session instance [" + session + "]");
+        }
+        sessionDAO.create(session);
+    }
+
+    @Override
+    protected void onStop(Session session) {
+        if (session instanceof SimpleSession) {
+            SimpleSession ss = (SimpleSession) session;
+            Date stopTs = ss.getStopTimestamp();
+            ss.setLastAccessTime(stopTs);
+        }
+        onChange(session);
+    }
+
+    @Override
+    protected void afterStopped(Session session) {
+        if (isDeleteInvalidSessions()) {
+            delete(session);
+        }
+    }
+
+    protected void onExpiration(Session session) {
+        if (session instanceof SimpleSession) {
+            ((SimpleSession) session).setExpired(true);
+        }
+        onChange(session);
+    }
+
+    @Override
+    protected void afterExpired(Session session) {
+        if (isDeleteInvalidSessions()) {
+            delete(session);
+        }
+    }
+
+    protected void onChange(Session session) {
+        sessionDAO.update(session);
+    }
+
+    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
+        Serializable sessionId = getSessionId(sessionKey);
+        if (sessionId == null) {
+            log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +
+                    "session could not be found.", sessionKey);
+            return null;
+        }
+        Session s = retrieveSessionFromDataSource(sessionId);
+        if (s == null) {
+            //session ID was provided, meaning one is expected to be found, but we couldn't find one:
+            String msg = "Could not find session with ID [" + sessionId + "]";
+            throw new UnknownSessionException(msg);
+        }
+        return s;
+    }
+
+    protected Serializable getSessionId(SessionKey sessionKey) {
+        return sessionKey.getSessionId();
+    }
+
+    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
+        return sessionDAO.readSession(sessionId);
+    }
+
+    protected void delete(Session session) {
+        sessionDAO.delete(session);
+    }
+
+    protected Collection<Session> getActiveSessions() {
+        Collection<Session> active = sessionDAO.getActiveSessions();
+        return active != null ? active : Collections.<Session>emptySet();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java b/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
new file mode 100644
index 0000000..115b008
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/DelegatingSession.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * A DelegatingSession is a client-tier representation of a server side
+ * {@link org.apache.shiro.session.Session Session}.
+ * This implementation is basically a proxy to a server-side {@link NativeSessionManager NativeSessionManager},
+ * which will return the proper results for each method call.
+ * <p/>
+ * <p>A <tt>DelegatingSession</tt> will cache data when appropriate to avoid a remote method invocation,
+ * only communicating with the server when necessary.
+ * <p/>
+ * <p>Of course, if used in-process with a NativeSessionManager business POJO, as might be the case in a
+ * web-based application where the web classes and server-side business pojos exist in the same
+ * JVM, a remote method call will not be incurred.
+ *
+ * @since 0.1
+ */
+public class DelegatingSession implements Session, Serializable {
+
+    //TODO - complete JavaDoc
+
+    private final SessionKey key;
+
+    //cached fields to avoid a server-side method call if out-of-process:
+    private Date startTimestamp = null;
+    private String host = null;
+
+    /**
+     * Handle to the target NativeSessionManager that will support the delegate calls.
+     */
+    private final transient NativeSessionManager sessionManager;
+
+
+    public DelegatingSession(NativeSessionManager sessionManager, SessionKey key) {
+        if (sessionManager == null) {
+            throw new IllegalArgumentException("sessionManager argument cannot be null.");
+        }
+        if (key == null) {
+            throw new IllegalArgumentException("sessionKey argument cannot be null.");
+        }
+        if (key.getSessionId() == null) {
+            String msg = "The " + DelegatingSession.class.getName() + " implementation requires that the " +
+                    "SessionKey argument returns a non-null sessionId to support the " +
+                    "Session.getId() invocations.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.sessionManager = sessionManager;
+        this.key = key;
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#getId()
+     */
+    public Serializable getId() {
+        return key.getSessionId();
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#getStartTimestamp()
+     */
+    public Date getStartTimestamp() {
+        if (startTimestamp == null) {
+            startTimestamp = sessionManager.getStartTimestamp(key);
+        }
+        return startTimestamp;
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#getLastAccessTime()
+     */
+    public Date getLastAccessTime() {
+        //can't cache - only business pojo knows the accurate time:
+        return sessionManager.getLastAccessTime(key);
+    }
+
+    public long getTimeout() throws InvalidSessionException {
+        return sessionManager.getTimeout(key);
+    }
+
+    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
+        sessionManager.setTimeout(key, maxIdleTimeInMillis);
+    }
+
+    public String getHost() {
+        if (host == null) {
+            host = sessionManager.getHost(key);
+        }
+        return host;
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#touch()
+     */
+    public void touch() throws InvalidSessionException {
+        sessionManager.touch(key);
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#stop()
+     */
+    public void stop() throws InvalidSessionException {
+        sessionManager.stop(key);
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#getAttributeKeys
+     */
+    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
+        return sessionManager.getAttributeKeys(key);
+    }
+
+    /**
+     * @see org.apache.shiro.session.Session#getAttribute(Object key)
+     */
+    public Object getAttribute(Object attributeKey) throws InvalidSessionException {
+        return sessionManager.getAttribute(this.key, attributeKey);
+    }
+
+    /**
+     * @see Session#setAttribute(Object key, Object value)
+     */
+    public void setAttribute(Object attributeKey, Object value) throws InvalidSessionException {
+        if (value == null) {
+            removeAttribute(attributeKey);
+        } else {
+            sessionManager.setAttribute(this.key, attributeKey, value);
+        }
+    }
+
+    /**
+     * @see Session#removeAttribute(Object key)
+     */
+    public Object removeAttribute(Object attributeKey) throws InvalidSessionException {
+        return sessionManager.removeAttribute(this.key, attributeKey);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.java b/core/src/main/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.java
new file mode 100644
index 0000000..6c24c1b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * SessionValidationScheduler implementation that uses a
+ * {@link ScheduledExecutorService} to call {@link ValidatingSessionManager#validateSessions()} every
+ * <em>{@link #getInterval interval}</em> milliseconds.
+ *
+ * @since 0.9
+ */
+public class ExecutorServiceSessionValidationScheduler implements SessionValidationScheduler, Runnable {
+
+    //TODO - complete JavaDoc
+
+    /** Private internal log instance. */
+    private static final Logger log = LoggerFactory.getLogger(ExecutorServiceSessionValidationScheduler.class);
+
+    ValidatingSessionManager sessionManager;
+    private ScheduledExecutorService service;
+    private long interval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
+    private boolean enabled = false;
+
+    public ExecutorServiceSessionValidationScheduler() {
+        super();
+    }
+
+    public ExecutorServiceSessionValidationScheduler(ValidatingSessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+    }
+
+    public ValidatingSessionManager getSessionManager() {
+        return sessionManager;
+    }
+
+    public void setSessionManager(ValidatingSessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+    }
+
+    public long getInterval() {
+        return interval;
+    }
+
+    public void setInterval(long interval) {
+        this.interval = interval;
+    }
+
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    /**
+     * Creates a single thread {@link ScheduledExecutorService} to validate sessions at fixed intervals 
+     * and enables this scheduler. The executor is created as a daemon thread to allow JVM to shut down
+     */
+    //TODO Implement an integration test to test for jvm exit as part of the standalone example
+    // (so we don't have to change the unit test execution model for the core module)
+    public void enableSessionValidation() {
+        if (this.interval > 0l) {
+            this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {  
+	        public Thread newThread(Runnable r) {  
+	            Thread thread = new Thread(r);  
+	            thread.setDaemon(true);  
+	            return thread;  
+                }  
+            });                  
+            this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
+            this.enabled = true;
+        }
+    }
+
+    public void run() {
+        if (log.isDebugEnabled()) {
+            log.debug("Executing session validation...");
+        }
+        long startTime = System.currentTimeMillis();
+        this.sessionManager.validateSessions();
+        long stopTime = System.currentTimeMillis();
+        if (log.isDebugEnabled()) {
+            log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
+        }
+    }
+
+    public void disableSessionValidation() {
+        this.service.shutdownNow();
+        this.enabled = false;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/ImmutableProxiedSession.java b/core/src/main/java/org/apache/shiro/session/mgt/ImmutableProxiedSession.java
new file mode 100644
index 0000000..8b8c74a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/ImmutableProxiedSession.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.ProxiedSession;
+import org.apache.shiro.session.Session;
+
+
+/**
+ * Implementation of the {@link Session Session} interface that proxies another <code>Session</code>, but does not
+ * allow any 'write' operations to the underlying session. It allows 'read' operations only.
+ * <p/>
+ * The <code>Session</code> write operations are defined as follows.  A call to any of these methods on this
+ * proxy will immediately result in an {@link InvalidSessionException} being thrown:
+ * <ul>
+ * <li>{@link Session#setTimeout(long) Session.setTimeout(long)}</li>
+ * <li>{@link Session#touch() Session.touch()}</li>
+ * <li>{@link Session#stop() Session.stop()}</li>
+ * <li>{@link Session#setAttribute(Object, Object) Session.setAttribute(key,value)}</li>
+ * <li>{@link Session#removeAttribute(Object) Session.removeAttribute(key)}</li>
+ * </ul>
+ * Any other method invocation not listed above will result in a corresponding call to the underlying <code>Session</code>.
+ *
+ * @since 0.9
+ */
+public class ImmutableProxiedSession extends ProxiedSession {
+
+    /**
+     * Constructs a new instance of this class proxying the specified <code>Session</code>.
+     *
+     * @param target the target <code>Session</code> to proxy.
+     */
+    public ImmutableProxiedSession(Session target) {
+        super(target);
+    }
+
+    /**
+     * Simply throws an <code>InvalidSessionException</code> indicating that this proxy is immutable.  Used
+     * only in the Session's 'write' methods documented in the top class-level JavaDoc.
+     *
+     * @throws InvalidSessionException in all cases - used by the Session 'write' method implementations.
+     */
+    protected void throwImmutableException() throws InvalidSessionException {
+        String msg = "This session is immutable and read-only - it cannot be altered.  This is usually because " +
+                "the session has been stopped or expired already.";
+        throw new InvalidSessionException(msg);
+    }
+
+    /**
+     * Immediately {@link #throwImmutableException() throws} an <code>InvalidSessionException</code> in all
+     * cases because this proxy is immutable.
+     */
+    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
+        throwImmutableException();
+    }
+
+    /**
+     * Immediately {@link #throwImmutableException() throws} an <code>InvalidSessionException</code> in all
+     * cases because this proxy is immutable.
+     */
+    public void touch() throws InvalidSessionException {
+        throwImmutableException();
+    }
+
+    /**
+     * Immediately {@link #throwImmutableException() throws} an <code>InvalidSessionException</code> in all
+     * cases because this proxy is immutable.
+     */
+    public void stop() throws InvalidSessionException {
+        throwImmutableException();
+    }
+
+    /**
+     * Immediately {@link #throwImmutableException() throws} an <code>InvalidSessionException</code> in all
+     * cases because this proxy is immutable.
+     */
+    public void setAttribute(Object key, Object value) throws InvalidSessionException {
+        throwImmutableException();
+    }
+
+    /**
+     * Immediately {@link #throwImmutableException() throws} an <code>InvalidSessionException</code> in all
+     * cases because this proxy is immutable.
+     */
+    public Object removeAttribute(Object key) throws InvalidSessionException {
+        throwImmutableException();
+        //we should never ever reach this point due to the exception being thrown.
+        throw new InternalError("This code should never execute - please report this as a bug!");
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/NativeSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/NativeSessionManager.java
new file mode 100644
index 0000000..8c1259e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/NativeSessionManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.InvalidSessionException;
+
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * A {@code Native} session manager is one that manages sessions natively - that is, it is directly responsible
+ * for the creation, persistence and removal of {@link org.apache.shiro.session.Session Session} instances and their
+ * lifecycles.
+ *
+ * @since 1.0
+ */
+public interface NativeSessionManager extends SessionManager {
+
+    /**
+     * Returns the time the associated {@code Session} started (was created).
+     *
+     * @param key the session key to use to look up the target session.
+     * @return the time the specified {@code Session} started (was created).
+     * @see org.apache.shiro.session.Session#getStartTimestamp()
+     */
+    Date getStartTimestamp(SessionKey key);
+
+    /**
+     * Returns the time the associated {@code Session} last interacted with the system.
+     *
+     * @param key the session key to use to look up the target session.
+     * @return time the session last accessed the system
+     * @see org.apache.shiro.session.Session#getLastAccessTime()
+     * @see org.apache.shiro.session.Session#touch()
+     */
+    Date getLastAccessTime(SessionKey key);
+
+    /**
+     * Returns {@code true} if the associated session is valid (it exists and is not stopped nor expired),
+     * {@code false} otherwise.
+     *
+     * @param key the session key to use to look up the target session.
+     * @return {@code true} if the session is valid (exists and is not stopped or expired), {@code false} otherwise.
+     */
+    boolean isValid(SessionKey key);
+
+    /**
+     * Returns quietly if the associated session is valid (it exists and is not stopped or expired) or throws
+     * an {@link org.apache.shiro.session.InvalidSessionException} indicating that the session id is invalid.  This
+     * might be preferred to be used instead of {@link #isValid} since any exception thrown will definitively explain
+     * the reason for invalidation.
+     *
+     * @param key the session key to use to look up the target session.
+     * @throws org.apache.shiro.session.InvalidSessionException
+     *          if the session id is invalid (it does not exist or it is stopped or expired).
+     */
+    void checkValid(SessionKey key) throws InvalidSessionException;
+
+    /**
+     * Returns the time in milliseconds that the associated session may remain idle before expiring.
+     * <ul>
+     * <li>A negative return value means the session will never expire.</li>
+     * <li>A non-negative return value (0 or greater) means the session expiration will occur if idle for that
+     * length of time.</li>
+     * </ul>
+     *
+     * @param key the session key to use to look up the target session.
+     * @return the time in milliseconds that the associated session may remain idle before expiring.
+     * @throws org.apache.shiro.session.InvalidSessionException
+     *          if the session has been stopped or expired prior to calling this method.
+     */
+    long getTimeout(SessionKey key) throws InvalidSessionException;
+
+    /**
+     * Sets the time in milliseconds that the associated session may remain idle before expiring.
+     * <ul>
+     * <li>A negative return value means the session will never expire.</li>
+     * <li>A non-negative return value (0 or greater) means the session expiration will occur if idle for that
+     * length of time.</li>
+     * </ul>
+     *
+     * @param key                 the session key to use to look up the target session.
+     * @param maxIdleTimeInMillis the time in milliseconds that the associated session may remain idle before expiring.
+     * @throws org.apache.shiro.session.InvalidSessionException
+     *          if the session has been stopped or expired prior to calling this method.
+     */
+    void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException;
+
+    /**
+     * Updates the last accessed time of the session identified by <code>sessionId</code>.  This
+     * can be used to explicitly ensure that a session does not time out.
+     *
+     * @param key the session key to use to look up the target session.
+     * @throws org.apache.shiro.session.InvalidSessionException
+     *          if the session has been stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#touch
+     */
+    void touch(SessionKey key) throws InvalidSessionException;
+
+    /**
+     * Returns the host name or IP string of the host where the session was started, if known.  If
+     * no host name or IP was specified when starting the session, this method returns {@code null}
+     *
+     * @param key the session key to use to look up the target session.
+     * @return the host name or ip address of the host where the session originated, if known.  If unknown,
+     *         this method returns {@code null}.
+     */
+    String getHost(SessionKey key);
+
+    /**
+     * Explicitly stops the associated session, thereby releasing all of its resources.
+     *
+     * @param key the session key to use to look up the target session.
+     * @throws InvalidSessionException if the session has stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#stop
+     */
+    void stop(SessionKey key) throws InvalidSessionException;
+
+    /**
+     * Returns all attribute keys maintained by the target session or an empty collection if there are no attributes.
+     *
+     * @param sessionKey the session key to use to look up the target session.
+     * @return all attribute keys maintained by the target session or an empty collection if there are no attributes.
+     * @throws InvalidSessionException if the associated session has stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#getAttributeKeys()
+     */
+    Collection<Object> getAttributeKeys(SessionKey sessionKey);
+
+    /**
+     * Returns the object bound to the associated session identified by the specified attribute key.  If there
+     * is no object bound under the attribute key for the given session, {@code null} is returned.
+     *
+     * @param sessionKey   session key to use to look up the target session.
+     * @param attributeKey the unique name of the object bound to the associated session
+     * @return the object bound under the {@code attributeKey} or {@code null} if there is no object bound.
+     * @throws InvalidSessionException if the specified session has stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#getAttribute(Object key)
+     */
+    Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
+
+    /**
+     * Binds the specified {@code value} to the associated session uniquely identified by the {@code attributeKey}.
+     * If there is already a session attribute bound under the {@code attributeKey}, that existing object will be
+     * replaced by the new {@code value}.
+     * <p/>
+     * If the {@code value} parameter is null, it has the same effect as if the
+     * {@link #removeAttribute(SessionKey sessionKey, Object attributeKey)} method was called.
+     *
+     * @param sessionKey   the session key to use to look up the target session.
+     * @param attributeKey the key under which the {@code value} object will be bound in this session
+     * @param value        the object to bind in this session.
+     * @throws InvalidSessionException if the specified session has stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#setAttribute(Object key, Object value)
+     */
+    void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException;
+
+    /**
+     * Removes (unbinds) the object bound to associated {@code Session} under the given {@code attributeKey}.
+     *
+     * @param sessionKey   session key to use to look up the target session.
+     * @param attributeKey the key uniquely identifying the object to remove
+     * @return the object removed or {@code null} if there was no object bound under the specified {@code attributeKey}.
+     * @throws InvalidSessionException if the specified session has stopped or expired prior to calling this method.
+     * @see org.apache.shiro.session.Session#removeAttribute(Object key)
+     */
+    Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SessionContext.java b/core/src/main/java/org/apache/shiro/session/mgt/SessionContext.java
new file mode 100644
index 0000000..b5506df
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SessionContext.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * A {@code SessionContext} is a 'bucket' of data presented to a {@link SessionFactory SessionFactory} which interprets
+ * this data to construct {@link org.apache.shiro.session.Session Session} instances.  It is essentially a Map of data
+ * with a few additional type-safe methods for easy retrieval of objects commonly used to construct Subject instances.
+ * <p/>
+ * While this interface contains type-safe setters and getters for common data types, the map can contain anything
+ * additional that might be needed by the {@code SessionFactory} implementation to construct {@code Session} instances.
+ * <p/>
+ * <b>USAGE</b>: Most Shiro end-users will never use a {@code SubjectContext} instance directly and instead will call
+ * the {@code Subject.}{@link org.apache.shiro.subject.Subject#getSession() getSession()} or
+ * {@code Subject.}{@link org.apache.shiro.subject.Subject#getSession(boolean) getSession(boolean)} methods (which
+ * will usually use {@code SessionContext} instances to start a session with the application's
+ * {@link SessionManager SessionManager}.
+ *
+ * @see org.apache.shiro.session.mgt.SessionManager#start SessionManager.start(SessionContext)
+ * @see org.apache.shiro.session.mgt.SessionFactory SessionFactory
+ * @since 1.0
+ */
+public interface SessionContext extends Map<String, Object> {
+
+    /**
+     * Sets the originating host name or IP address (as a String) from where the {@code Subject} is initiating the
+     * {@code Session}.
+     * <p/>
+     * In web-based systems, this host can be inferred from the incoming request, e.g.
+     * {@code javax.servlet.ServletRequest#getRemoteAddr()} or {@code javax.servlet.ServletRequest#getRemoteHost()}
+     * methods, or in socket-based systems, it can be obtained via inspecting the socket
+     * initiator's host IP.
+     * <p/>
+     * Most secure environments <em>should</em> specify a valid, non-{@code null} {@code host}, since knowing the
+     * {@code host} allows for more flexibility when securing a system: by requiring an host, access control policies
+     * can also ensure access is restricted to specific client <em>locations</em> in addition to {@code Subject}
+     * principals, if so desired.
+     * <p/>
+     * <b>Caveat</b> - if clients to your system are on a
+     * public network (as would be the case for a public web site), odds are high the clients can be
+     * behind a NAT (Network Address Translation) router or HTTP proxy server.  If so, all clients
+     * accessing your system behind that router or proxy will have the same originating host.
+     * If your system is configured to allow only one session per host, then the next request from a
+     * different NAT or proxy client will fail and access will be denied for that client.  Just be
+     * aware that host-based security policies are best utilized in LAN or private WAN environments
+     * when you can be ensure clients will not share IPs or be behind such NAT routers or
+     * proxy servers.
+     *
+     * @param host the originating host name or IP address (as a String) from where the {@code Subject} is
+     *             initiating the {@code Session}.
+     * @since 1.0
+     */
+    void setHost(String host);
+
+    /**
+     * Returns the originating host name or IP address (as a String) from where the {@code Subject} is initiating the
+     * {@code Session}.
+     * <p/>
+     * See the {@link #setHost(String) setHost(String)} JavaDoc for more about security policies based on the
+     * {@code Session} host.
+     *
+     * @return the originating host name or IP address (as a String) from where the {@code Subject} is initiating the
+     *         {@code Session}.
+     * @see #setHost(String) setHost(String)
+     */
+    String getHost();
+
+    Serializable getSessionId();
+
+    void setSessionId(Serializable sessionId);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SessionFactory.java b/core/src/main/java/org/apache/shiro/session/mgt/SessionFactory.java
new file mode 100644
index 0000000..6b49796
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SessionFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.Session;
+
+/**
+ * A simple factory class that instantiates concrete {@link Session Session} instances.  This is mainly a
+ * mechanism to allow instances to be created at runtime if they need to be different the
+ * defaults.  It is not used by end-users of the framework, but rather those configuring Shiro to work in an
+ * application, and is typically injected into the {@link org.apache.shiro.mgt.SecurityManager SecurityManager} or a
+ * {@link SessionManager}.
+ *
+ * @since 1.0
+ */
+public interface SessionFactory {
+
+    /**
+     * Creates a new {@code Session} instance based on the specified contextual initialization data.
+     *
+     * @param initData the initialization data to be used during {@link Session} creation.
+     * @return a new {@code Session} instance.
+     * @since 1.0
+     */
+    Session createSession(SessionContext initData);
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SessionKey.java b/core/src/main/java/org/apache/shiro/session/mgt/SessionKey.java
new file mode 100644
index 0000000..4f53475
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SessionKey.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import java.io.Serializable;
+
+/**
+ * A {@code SessionKey} is a key that allows look-up of any particular {@link org.apache.shiro.session.Session Session}
+ * instance.  This is not to be confused what is probably better recognized as a session <em>attribute</em> key - a key
+ * that is used to acquire a session attribute via the
+ * {@link org.apache.shiro.session.Session#getAttribute(Object) Session.getAttribute} method.  A {@code SessionKey}
+ * looks up a Session object directly.
+ * <p/>
+ * While a {@code SessionKey} allows lookup of <em>any</em> Session that might exist, this is not something in practice
+ * done too often by most Shiro end-users.  Instead, it is usually more convenient to acquire the currently executing
+ * {@code Subject}'s session via the {@link org.apache.shiro.subject.Subject#getSession} method.  This interface and
+ * its usages are best suited for framework development.
+ *
+ * @since 1.0
+ */
+public interface SessionKey {
+
+    /**
+     * Returns the id of the session to acquire.
+     * <p/>
+     * Acquiring sessions by ID only is a suitable strategy when sessions are natively managed by Shiro directly.
+     * For example, the Servlet specification does not have an API that allows session acquisition by session ID, so
+     * the session ID alone is not sufficient for ServletContainer-based SessionManager implementations.
+     *
+     * @return the id of the session to acquire.
+     */
+    Serializable getSessionId();
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/SessionManager.java
new file mode 100644
index 0000000..b23af57
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SessionManager.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+
+/**
+ * A SessionManager manages the creation, maintenance, and clean-up of all application
+ * {@link org.apache.shiro.session.Session Session}s.
+ *
+ * @since 0.1
+ */
+public interface SessionManager {
+
+    /**
+     * Starts a new session based on the specified contextual initialization data, which can be used by the underlying
+     * implementation to determine how exactly to create the internal Session instance.
+     * <p/>
+     * This method is mainly used in framework development, as the implementation will often relay the argument
+     * to an underlying {@link SessionFactory} which could use the context to construct the internal Session
+     * instance in a specific manner.  This allows pluggable {@link org.apache.shiro.session.Session Session} creation
+     * logic by simply injecting a {@code SessionFactory} into the {@code SessionManager} instance.
+     *
+     * @param context the contextual initialization data that can be used by the implementation or underlying
+     *                {@link SessionFactory} when instantiating the internal {@code Session} instance.
+     * @return the newly created session.
+     * @see SessionFactory#createSession(SessionContext)
+     * @since 1.0
+     */
+    Session start(SessionContext context);
+
+    /**
+     * Retrieves the session corresponding to the specified contextual data (such as a session ID if applicable), or
+     * {@code null} if no Session could be found.  If a session is found but invalid (stopped or expired), a
+     * {@link SessionException} will be thrown.
+     *
+     * @param key the Session key to use to look-up the Session
+     * @return the {@code Session} instance corresponding to the given lookup key or {@code null} if no session
+     *         could be acquired.
+     * @throws SessionException if a session was found but it was invalid (stopped/expired).
+     * @since 1.0
+     */
+    Session getSession(SessionKey key) throws SessionException;
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SessionValidationScheduler.java b/core/src/main/java/org/apache/shiro/session/mgt/SessionValidationScheduler.java
new file mode 100644
index 0000000..633239d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SessionValidationScheduler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+/**
+ * Interface that should be implemented by classes that can control validating sessions on a regular
+ * basis.  This interface is used as a delegate for session validation by the {@link org.apache.shiro.session.mgt.DefaultSessionManager}
+ *
+ * @see org.apache.shiro.session.mgt.DefaultSessionManager#setSessionValidationScheduler(SessionValidationScheduler)
+ * @since 0.1
+ */
+public interface SessionValidationScheduler {
+
+    /**
+     * Returns <code>true</code> if this Scheduler is enabled and ready to begin validation at the appropriate time,
+     * <code>false</code> otherwise.
+     * <p/>
+     * It does <em>not</em> indicate if the validation is actually executing at that instant - only that it is prepared
+     * to do so at the appropriate time.
+     *
+     * @return <code>true</code> if this Scheduler is enabled and ready to begin validation at the appropriate time,
+     * <code>false</code> otherwise.
+     */
+    boolean isEnabled();
+
+    /**
+     * Enables the session validation job.
+     */
+    void enableSessionValidation();
+
+    /**
+     * Disables the session validation job.
+     */
+    void disableSessionValidation();
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java b/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
new file mode 100644
index 0000000..e7101b4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SimpleSession.java
@@ -0,0 +1,541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.StoppedSessionException;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.util.*;
+
+
+/**
+ * Simple {@link org.apache.shiro.session.Session} JavaBeans-compatible POJO implementation, intended to be used on the
+ * business/server tier.
+ *
+ * @since 0.1
+ */
+public class SimpleSession implements ValidatingSession, Serializable {
+
+    // Serialization reminder:
+    // You _MUST_ change this number if you introduce a change to this class
+    // that is NOT serialization backwards compatible.  Serialization-compatible
+    // changes do not require a change to this number.  If you need to generate
+    // a new number in this case, use the JDK's 'serialver' program to generate it.
+    private static final long serialVersionUID = -7125642695178165650L;
+
+    //TODO - complete JavaDoc
+    private transient static final Logger log = LoggerFactory.getLogger(SimpleSession.class);
+
+    protected static final long MILLIS_PER_SECOND = 1000;
+    protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+    protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
+
+    //serialization bitmask fields. DO NOT CHANGE THE ORDER THEY ARE DECLARED!
+    static int bitIndexCounter = 0;
+    private static final int ID_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int START_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int STOP_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int LAST_ACCESS_TIME_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int TIMEOUT_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int EXPIRED_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int HOST_BIT_MASK = 1 << bitIndexCounter++;
+    private static final int ATTRIBUTES_BIT_MASK = 1 << bitIndexCounter++;
+
+    // ==============================================================
+    // NOTICE:
+    //
+    // The following fields are marked as transient to avoid double-serialization.
+    // They are in fact serialized (even though 'transient' usually indicates otherwise),
+    // but they are serialized explicitly via the writeObject and readObject implementations
+    // in this class.
+    //
+    // If we didn't declare them as transient, the out.defaultWriteObject(); call in writeObject would
+    // serialize all non-transient fields as well, effectively doubly serializing the fields (also
+    // doubling the serialization size).
+    //
+    // This finding, with discussion, was covered here:
+    //
+    // http://mail-archives.apache.org/mod_mbox/shiro-user/201109.mbox/%3C4E81BCBD.8060909@metaphysis.net%3E
+    //
+    // ==============================================================
+    private transient Serializable id;
+    private transient Date startTimestamp;
+    private transient Date stopTimestamp;
+    private transient Date lastAccessTime;
+    private transient long timeout;
+    private transient boolean expired;
+    private transient String host;
+    private transient Map<Object, Object> attributes;
+
+    public SimpleSession() {
+        this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; //TODO - remove concrete reference to DefaultSessionManager
+        this.startTimestamp = new Date();
+        this.lastAccessTime = this.startTimestamp;
+    }
+
+    public SimpleSession(String host) {
+        this();
+        this.host = host;
+    }
+
+    public Serializable getId() {
+        return this.id;
+    }
+
+    public void setId(Serializable id) {
+        this.id = id;
+    }
+
+    public Date getStartTimestamp() {
+        return startTimestamp;
+    }
+
+    public void setStartTimestamp(Date startTimestamp) {
+        this.startTimestamp = startTimestamp;
+    }
+
+    /**
+     * Returns the time the session was stopped, or <tt>null</tt> if the session is still active.
+     * <p/>
+     * A session may become stopped under a number of conditions:
+     * <ul>
+     * <li>If the user logs out of the system, their current session is terminated (released).</li>
+     * <li>If the session expires</li>
+     * <li>The application explicitly calls {@link #stop()}</li>
+     * <li>If there is an internal system error and the session state can no longer accurately
+     * reflect the user's behavior, such in the case of a system crash</li>
+     * </ul>
+     * <p/>
+     * Once stopped, a session may no longer be used.  It is locked from all further activity.
+     *
+     * @return The time the session was stopped, or <tt>null</tt> if the session is still
+     *         active.
+     */
+    public Date getStopTimestamp() {
+        return stopTimestamp;
+    }
+
+    public void setStopTimestamp(Date stopTimestamp) {
+        this.stopTimestamp = stopTimestamp;
+    }
+
+    public Date getLastAccessTime() {
+        return lastAccessTime;
+    }
+
+    public void setLastAccessTime(Date lastAccessTime) {
+        this.lastAccessTime = lastAccessTime;
+    }
+
+    /**
+     * Returns true if this session has expired, false otherwise.  If the session has
+     * expired, no further user interaction with the system may be done under this session.
+     *
+     * @return true if this session has expired, false otherwise.
+     */
+    public boolean isExpired() {
+        return expired;
+    }
+
+    public void setExpired(boolean expired) {
+        this.expired = expired;
+    }
+
+    public long getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(long timeout) {
+        this.timeout = timeout;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public Map<Object, Object> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(Map<Object, Object> attributes) {
+        this.attributes = attributes;
+    }
+
+    public void touch() {
+        this.lastAccessTime = new Date();
+    }
+
+    public void stop() {
+        if (this.stopTimestamp == null) {
+            this.stopTimestamp = new Date();
+        }
+    }
+
+    protected boolean isStopped() {
+        return getStopTimestamp() != null;
+    }
+
+    protected void expire() {
+        stop();
+        this.expired = true;
+    }
+
+    /**
+     * @since 0.9
+     */
+    public boolean isValid() {
+        return !isStopped() && !isExpired();
+    }
+
+    /**
+     * Determines if this session is expired.
+     *
+     * @return true if the specified session has expired, false otherwise.
+     */
+    protected boolean isTimedOut() {
+
+        if (isExpired()) {
+            return true;
+        }
+
+        long timeout = getTimeout();
+
+        if (timeout >= 0l) {
+
+            Date lastAccessTime = getLastAccessTime();
+
+            if (lastAccessTime == null) {
+                String msg = "session.lastAccessTime for session with id [" +
+                        getId() + "] is null.  This value must be set at " +
+                        "least once, preferably at least upon instantiation.  Please check the " +
+                        getClass().getName() + " implementation and ensure " +
+                        "this value will be set (perhaps in the constructor?)";
+                throw new IllegalStateException(msg);
+            }
+
+            // Calculate at what time a session would have been last accessed
+            // for it to be expired at this point.  In other words, subtract
+            // from the current time the amount of time that a session can
+            // be inactive before expiring.  If the session was last accessed
+            // before this time, it is expired.
+            long expireTimeMillis = System.currentTimeMillis() - timeout;
+            Date expireTime = new Date(expireTimeMillis);
+            return lastAccessTime.before(expireTime);
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("No timeout for session with id [" + getId() +
+                        "].  Session is not considered expired.");
+            }
+        }
+
+        return false;
+    }
+
+    public void validate() throws InvalidSessionException {
+        //check for stopped:
+        if (isStopped()) {
+            //timestamp is set, so the session is considered stopped:
+            String msg = "Session with id [" + getId() + "] has been " +
+                    "explicitly stopped.  No further interaction under this session is " +
+                    "allowed.";
+            throw new StoppedSessionException(msg);
+        }
+
+        //check for expiration
+        if (isTimedOut()) {
+            expire();
+
+            //throw an exception explaining details of why it expired:
+            Date lastAccessTime = getLastAccessTime();
+            long timeout = getTimeout();
+
+            Serializable sessionId = getId();
+
+            DateFormat df = DateFormat.getInstance();
+            String msg = "Session with id [" + sessionId + "] has expired. " +
+                    "Last access time: " + df.format(lastAccessTime) +
+                    ".  Current time: " + df.format(new Date()) +
+                    ".  Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds (" +
+                    timeout / MILLIS_PER_MINUTE + " minutes)";
+            if (log.isTraceEnabled()) {
+                log.trace(msg);
+            }
+            throw new ExpiredSessionException(msg);
+        }
+    }
+
+    private Map<Object, Object> getAttributesLazy() {
+        Map<Object, Object> attributes = getAttributes();
+        if (attributes == null) {
+            attributes = new HashMap<Object, Object>();
+            setAttributes(attributes);
+        }
+        return attributes;
+    }
+
+    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
+        Map<Object, Object> attributes = getAttributes();
+        if (attributes == null) {
+            return Collections.emptySet();
+        }
+        return attributes.keySet();
+    }
+
+    public Object getAttribute(Object key) {
+        Map<Object, Object> attributes = getAttributes();
+        if (attributes == null) {
+            return null;
+        }
+        return attributes.get(key);
+    }
+
+    public void setAttribute(Object key, Object value) {
+        if (value == null) {
+            removeAttribute(key);
+        } else {
+            getAttributesLazy().put(key, value);
+        }
+    }
+
+    public Object removeAttribute(Object key) {
+        Map<Object, Object> attributes = getAttributes();
+        if (attributes == null) {
+            return null;
+        } else {
+            return attributes.remove(key);
+        }
+    }
+
+    /**
+     * Returns {@code true} if the specified argument is an {@code instanceof} {@code SimpleSession} and both
+     * {@link #getId() id}s are equal.  If the argument is a {@code SimpleSession} and either 'this' or the argument
+     * does not yet have an ID assigned, the value of {@link #onEquals(SimpleSession) onEquals} is returned, which
+     * does a necessary attribute-based comparison when IDs are not available.
+     * <p/>
+     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+     * avoid the more expensive attributes-based comparison.
+     *
+     * @param obj the object to compare with this one for equality.
+     * @return {@code true} if this object is equivalent to the specified argument, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof SimpleSession) {
+            SimpleSession other = (SimpleSession) obj;
+            Serializable thisId = getId();
+            Serializable otherId = other.getId();
+            if (thisId != null && otherId != null) {
+                return thisId.equals(otherId);
+            } else {
+                //fall back to an attribute based comparison:
+                return onEquals(other);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Provides an attribute-based comparison (no ID comparison) - incurred <em>only</em> when 'this' or the
+     * session object being compared for equality do not have a session id.
+     *
+     * @param ss the SimpleSession instance to compare for equality.
+     * @return true if all the attributes, except the id, are equal to this object's attributes.
+     * @since 1.0
+     */
+    protected boolean onEquals(SimpleSession ss) {
+        return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null) &&
+                (getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null) &&
+                (getLastAccessTime() != null ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null) &&
+                (getTimeout() == ss.getTimeout()) &&
+                (isExpired() == ss.isExpired()) &&
+                (getHost() != null ? getHost().equals(ss.getHost()) : ss.getHost() == null) &&
+                (getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
+    }
+
+    /**
+     * Returns the hashCode.  If the {@link #getId() id} is not {@code null}, its hashcode is returned immediately.
+     * If it is {@code null}, an attributes-based hashCode will be calculated and returned.
+     * <p/>
+     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
+     * avoid the more expensive attributes-based calculation.
+     *
+     * @return this object's hashCode
+     * @since 1.0
+     */
+    @Override
+    public int hashCode() {
+        Serializable id = getId();
+        if (id != null) {
+            return id.hashCode();
+        }
+        int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
+        hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
+        hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
+        hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
+        hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
+        hashCode = 31 * hashCode + (getHost() != null ? getHost().hashCode() : 0);
+        hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
+        return hashCode;
+    }
+
+    /**
+     * Returns the string representation of this SimpleSession, equal to
+     * <code>getClass().getName() + ",id=" + getId()</code>.
+     *
+     * @return the string representation of this SimpleSession, equal to
+     *         <code>getClass().getName() + ",id=" + getId()</code>.
+     * @since 1.0
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getName()).append(",id=").append(getId());
+        return sb.toString();
+    }
+
+    /**
+     * Serializes this object to the specified output stream for JDK Serialization.
+     *
+     * @param out output stream used for Object serialization.
+     * @throws IOException if any of this object's fields cannot be written to the stream.
+     * @since 1.0
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        short alteredFieldsBitMask = getAlteredFieldsBitMask();
+        out.writeShort(alteredFieldsBitMask);
+        if (id != null) {
+            out.writeObject(id);
+        }
+        if (startTimestamp != null) {
+            out.writeObject(startTimestamp);
+        }
+        if (stopTimestamp != null) {
+            out.writeObject(stopTimestamp);
+        }
+        if (lastAccessTime != null) {
+            out.writeObject(lastAccessTime);
+        }
+        if (timeout != 0l) {
+            out.writeLong(timeout);
+        }
+        if (expired) {
+            out.writeBoolean(expired);
+        }
+        if (host != null) {
+            out.writeUTF(host);
+        }
+        if (!CollectionUtils.isEmpty(attributes)) {
+            out.writeObject(attributes);
+        }
+    }
+
+    /**
+     * Reconstitutes this object based on the specified InputStream for JDK Serialization.
+     *
+     * @param in the input stream to use for reading data to populate this object.
+     * @throws IOException            if the input stream cannot be used.
+     * @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
+     * @since 1.0
+     */
+    @SuppressWarnings({"unchecked"})
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        short bitMask = in.readShort();
+
+        if (isFieldPresent(bitMask, ID_BIT_MASK)) {
+            this.id = (Serializable) in.readObject();
+        }
+        if (isFieldPresent(bitMask, START_TIMESTAMP_BIT_MASK)) {
+            this.startTimestamp = (Date) in.readObject();
+        }
+        if (isFieldPresent(bitMask, STOP_TIMESTAMP_BIT_MASK)) {
+            this.stopTimestamp = (Date) in.readObject();
+        }
+        if (isFieldPresent(bitMask, LAST_ACCESS_TIME_BIT_MASK)) {
+            this.lastAccessTime = (Date) in.readObject();
+        }
+        if (isFieldPresent(bitMask, TIMEOUT_BIT_MASK)) {
+            this.timeout = in.readLong();
+        }
+        if (isFieldPresent(bitMask, EXPIRED_BIT_MASK)) {
+            this.expired = in.readBoolean();
+        }
+        if (isFieldPresent(bitMask, HOST_BIT_MASK)) {
+            this.host = in.readUTF();
+        }
+        if (isFieldPresent(bitMask, ATTRIBUTES_BIT_MASK)) {
+            this.attributes = (Map<Object, Object>) in.readObject();
+        }
+    }
+
+    /**
+     * Returns a bit mask used during serialization indicating which fields have been serialized. Fields that have been
+     * altered (not null and/or not retaining the class defaults) will be serialized and have 1 in their respective
+     * index, fields that are null and/or retain class default values have 0.
+     *
+     * @return a bit mask used during serialization indicating which fields have been serialized.
+     * @since 1.0
+     */
+    private short getAlteredFieldsBitMask() {
+        int bitMask = 0;
+        bitMask = id != null ? bitMask | ID_BIT_MASK : bitMask;
+        bitMask = startTimestamp != null ? bitMask | START_TIMESTAMP_BIT_MASK : bitMask;
+        bitMask = stopTimestamp != null ? bitMask | STOP_TIMESTAMP_BIT_MASK : bitMask;
+        bitMask = lastAccessTime != null ? bitMask | LAST_ACCESS_TIME_BIT_MASK : bitMask;
+        bitMask = timeout != 0l ? bitMask | TIMEOUT_BIT_MASK : bitMask;
+        bitMask = expired ? bitMask | EXPIRED_BIT_MASK : bitMask;
+        bitMask = host != null ? bitMask | HOST_BIT_MASK : bitMask;
+        bitMask = !CollectionUtils.isEmpty(attributes) ? bitMask | ATTRIBUTES_BIT_MASK : bitMask;
+        return (short) bitMask;
+    }
+
+    /**
+     * Returns {@code true} if the given {@code bitMask} argument indicates that the specified field has been
+     * serialized and therefore should be read during deserialization, {@code false} otherwise.
+     *
+     * @param bitMask      the aggregate bitmask for all fields that have been serialized.  Individual bits represent
+     *                     the fields that have been serialized.  A bit set to 1 means that corresponding field has
+     *                     been serialized, 0 means it hasn't been serialized.
+     * @param fieldBitMask the field bit mask constant identifying which bit to inspect (corresponds to a class attribute).
+     * @return {@code true} if the given {@code bitMask} argument indicates that the specified field has been
+     *         serialized and therefore should be read during deserialization, {@code false} otherwise.
+     * @since 1.0
+     */
+    private static boolean isFieldPresent(short bitMask, int fieldBitMask) {
+        return (bitMask & fieldBitMask) != 0;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/SimpleSessionFactory.java b/core/src/main/java/org/apache/shiro/session/mgt/SimpleSessionFactory.java
new file mode 100644
index 0000000..56b8c2c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/SimpleSessionFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.Session;
+
+/**
+ * {@code SessionFactory} implementation that generates {@link SimpleSession} instances.
+ *
+ * @since 1.0
+ */
+public class SimpleSessionFactory implements SessionFactory {
+
+    /**
+     * Creates a new {@link SimpleSession SimpleSession} instance retaining the context's
+     * {@link SessionContext#getHost() host} if one can be found.
+     *
+     * @param initData the initialization data to be used during {@link Session} creation.
+     * @return a new {@link SimpleSession SimpleSession} instance
+     */
+    public Session createSession(SessionContext initData) {
+        if (initData != null) {
+            String host = initData.getHost();
+            if (host != null) {
+                return new SimpleSession(host);
+            }
+        }
+        return new SimpleSession();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSession.java b/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSession.java
new file mode 100644
index 0000000..8b88228
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSession.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+
+
+/**
+ * A <code>ValidatingSession</code> is a <code>Session</code> that is capable of determining it is valid or not and
+ * is able to validate itself if necessary.
+ * <p/>
+ * Validation is usually an exercise of determining when the session was last accessed or modified and determining if
+ * that time is longer than a specified allowed duration.
+ * 
+ * @since 0.9
+ */
+public interface ValidatingSession extends Session {
+
+    boolean isValid();
+
+    void validate() throws InvalidSessionException;
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSessionManager.java b/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSessionManager.java
new file mode 100644
index 0000000..ba2c0b4
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/ValidatingSessionManager.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+/**
+ * A ValidatingSessionManager is a SessionManager that can proactively validate any or all sessions
+ * that may be expired.
+ *
+ * @since 0.1
+ */
+public interface ValidatingSessionManager extends SessionManager {
+
+    /**
+     * Performs session validation for all open/active sessions in the system (those that
+     * have not been stopped or expired), and validates each one.  If a session is
+     * found to be invalid (e.g. it has expired), it is updated and saved to the EIS.
+     * <p/>
+     * This method is necessary in order to handle orphaned sessions and is expected to be run at
+     * a regular interval, such as once an hour, once a day or once a week, etc.
+     * The "best" frequency to run this method is entirely dependent upon the application
+     * and would be based on factors such as performance, average number of active users, hours of
+     * least activity, and other things.
+     * <p/>
+     * Most enterprise applications use a request/response programming model.
+     * This is obvious in the case of web applications due to the HTTP protocol, but it is
+     * equally true of remote client applications making remote method invocations.  The server
+     * essentially sits idle and only "works" when responding to client requests and/or
+     * method invocations.  This type of model is particularly efficent since it means the
+     * security system only has to validate a session during those cases.  Such
+     * "lazy" behavior enables the system to lie stateless and/or idle and only incur
+     * overhead for session validation when necessary.
+     * <p/>
+     * However, if a client forgets to log-out, or in the event of a server failure, it is
+     * possible for sessions to be orphaned since no further requests would utilize that session.
+     * Because of these lower-probability cases, it might be required to regularly clean-up the sessions
+     * maintained by the system, especially if sessions are backed by a persistent data store.
+     * <p/>
+     * Even in applications that aren't primarily based on a request/response model,
+     * such as those that use enterprise asynchronous messaging (where data is pushed to
+     * a client without first receiving a client request), it is almost always acceptable to
+     * utilize this lazy approach and run this method at defined interval.
+     * <p/>
+     * Systems that want to proactively validate individual sessions may simply call the
+     * {@link #getSession(SessionKey) getSession(SessionKey)} method on any
+     * {@code ValidatingSessionManager} instance as that method is expected to
+     * validate the session before retrieving it.  Note that even with proactive calls to {@code getSession},
+     * this {@code validateSessions()} method should be invoked regularly anyway to <em>guarantee</em> no
+     * orphans exist.
+     * <p/>
+     * <b>Note:</b> Shiro supports automatic execution of this method at a regular interval
+     * by using {@link SessionValidationScheduler}s.  The Shiro default SecurityManager implementations
+     * needing session validation will create and use one by default if one is not provided by the
+     * application configuration.
+     */
+    void validateSessions();
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java
new file mode 100644
index 0000000..b8055e0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/AbstractSessionDAO.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.SimpleSession;
+
+import java.io.Serializable;
+
+
+/**
+ * An abstract {@code SessionDAO} implementation that performs some sanity checks on session creation and reading and
+ * allows for pluggable Session ID generation strategies if desired.  The {@code SessionDAO}
+ * {@link SessionDAO#update update} and {@link SessionDAO#delete delete} methods are left to
+ * subclasses.
+ * <h3>Session ID Generation</h3>
+ * This class also allows for plugging in a {@link SessionIdGenerator} for custom ID generation strategies.  This is
+ * optional, as the default generator is probably sufficient for most cases.  Subclass implementations that do use a
+ * generator (default or custom) will want to call the
+ * {@link #generateSessionId(org.apache.shiro.session.Session)} method from within their {@link #doCreate}
+ * implementations.
+ * <p/>
+ * Subclass implementations that rely on the EIS data store to generate the ID automatically (e.g. when the session
+ * ID is also an auto-generated primary key), they can simply ignore the {@code SessionIdGenerator} concept
+ * entirely and just return the data store's ID from the {@link #doCreate} implementation.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractSessionDAO implements SessionDAO {
+
+    /**
+     * Optional SessionIdGenerator instance available to subclasses via the
+     * {@link #generateSessionId(org.apache.shiro.session.Session)} method.
+     */
+    private SessionIdGenerator sessionIdGenerator;
+
+    /**
+     * Default no-arg constructor that defaults the {@link #setSessionIdGenerator sessionIdGenerator} to be a
+     * {@link org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator}.
+     */
+    public AbstractSessionDAO() {
+        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();
+    }
+
+    /**
+     * Returns the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     * method.  Unless overridden by the {@link #setSessionIdGenerator(SessionIdGenerator)} method, the default instance
+     * is a {@link JavaUuidSessionIdGenerator}.
+     *
+     * @return the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     *         method.
+     */
+    public SessionIdGenerator getSessionIdGenerator() {
+        return sessionIdGenerator;
+    }
+
+    /**
+     * Sets the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
+     * method.  Unless overridden by this method, the default instance ss a {@link JavaUuidSessionIdGenerator}.
+     *
+     * @param sessionIdGenerator the {@code SessionIdGenerator} to use in the
+     *                           {@link #generateSessionId(org.apache.shiro.session.Session)} method.
+     */
+    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
+        this.sessionIdGenerator = sessionIdGenerator;
+    }
+
+    /**
+     * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
+     * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
+     * instance and then create a record with this ID in the EIS data store.
+     * <p/>
+     * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
+     * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
+     * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
+     * if desired.
+     * <p/>
+     * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
+     * the ID.
+     *
+     * @param session the new session instance for which an ID will be generated and then assigned
+     * @return the generated ID to assign
+     */
+    protected Serializable generateSessionId(Session session) {
+        if (this.sessionIdGenerator == null) {
+            String msg = "sessionIdGenerator attribute has not been configured.";
+            throw new IllegalStateException(msg);
+        }
+        return this.sessionIdGenerator.generateId(session);
+    }
+
+    /**
+     * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
+     * asserting that the returned sessionId is not null.
+     *
+     * @param session Session object to create in the EIS and associate with an ID.
+     */
+    public Serializable create(Session session) {
+        Serializable sessionId = doCreate(session);
+        verifySessionId(sessionId);
+        return sessionId;
+    }
+
+    /**
+     * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
+     * already in use.
+     *
+     * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
+     */
+    private void verifySessionId(Serializable sessionId) {
+        if (sessionId == null) {
+            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    /**
+     * Utility method available to subclasses that wish to
+     * assign a generated session ID to the session instance directly.  This method is not used by the
+     * {@code AbstractSessionDAO} implementation directly, but it is provided so subclasses don't
+     * need to know the {@code Session} implementation if they don't need to.
+     * <p/>
+     * This default implementation casts the argument to a {@link SimpleSession}, Shiro's default EIS implementation.
+     *
+     * @param session   the session instance to which the sessionId will be applied
+     * @param sessionId the id to assign to the specified session instance.
+     */
+    protected void assignSessionId(Session session, Serializable sessionId) {
+        ((SimpleSession) session).setId(sessionId);
+    }
+
+    /**
+     * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
+     *
+     * @param session the Session instance to persist to the EIS.
+     * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
+     *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
+     */
+    protected abstract Serializable doCreate(Session session);
+
+    /**
+     * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
+     * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
+     * {@link UnknownSessionException} will be thrown.
+     *
+     * @param sessionId the id of the session to retrieve from the EIS.
+     * @return the session identified by <tt>sessionId</tt> in the EIS.
+     * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
+     */
+    public Session readSession(Serializable sessionId) throws UnknownSessionException {
+        Session s = doReadSession(sessionId);
+        if (s == null) {
+            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
+        }
+        return s;
+    }
+
+    /**
+     * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
+     * session with that ID could not be found.
+     *
+     * @param sessionId the id of the <tt>Session</tt> to retrieve.
+     * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
+     *         session with that ID could not be found.
+     */
+    protected abstract Session doReadSession(Serializable sessionId);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
new file mode 100644
index 0000000..aa0a854
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/CachingSessionDAO.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.cache.CacheManagerAware;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.session.mgt.ValidatingSession;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
+ * use it and the underlying EIS (Enterprise Information System) session backing store (for example, filesystem,
+ * database, enterprise grid/cloud, etc).
+ * <p/>
+ * This implementation caches all active sessions in a configured
+ * {@link #getActiveSessionsCache() activeSessionsCache}.  This property is {@code null} by default and if one is
+ * not explicitly set, a {@link #setCacheManager cacheManager} is expected to be configured which will in turn be used
+ * to acquire the {@code Cache} instance to use for the {@code activeSessionsCache}.
+ * <p/>
+ * All {@code SessionDAO} methods are implemented by this class to employ
+ * caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
+ * subclasses (doCreate, doRead, etc).
+ *
+ * @since 0.2
+ */
+public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
+
+    /**
+     * The default active sessions cache name, equal to {@code shiro-activeSessionCache}.
+     */
+    public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
+
+    /**
+     * The CacheManager to use to acquire the Session cache.
+     */
+    private CacheManager cacheManager;
+
+    /**
+     * The Cache instance responsible for caching Sessions.
+     */
+    private Cache<Serializable, Session> activeSessions;
+
+    /**
+     * The name of the session cache, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
+     */
+    private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
+
+    /**
+     * Default no-arg constructor.
+     */
+    public CachingSessionDAO() {
+    }
+
+    /**
+     * Sets the cacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
+     * one is not configured.
+     *
+     * @param cacheManager the manager to use for constructing the session cache.
+     */
+    public void setCacheManager(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+    }
+
+    /**
+     * Returns the CacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
+     * one is not configured.  That is, the {@code CacheManager} will only be used if the
+     * {@link #getActiveSessionsCache() activeSessionsCache} property is {@code null}.
+     *
+     * @return the CacheManager used by the implementation that creates the activeSessions Cache.
+     */
+    public CacheManager getCacheManager() {
+        return cacheManager;
+    }
+
+    /**
+     * Returns the name of the actives sessions cache to be returned by the {@code CacheManager}.  Unless
+     * overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
+     *
+     * @return the name of the active sessions cache.
+     */
+    public String getActiveSessionsCacheName() {
+        return activeSessionsCacheName;
+    }
+
+    /**
+     * Sets the name of the active sessions cache to be returned by the {@code CacheManager}.  Defaults to
+     * {@link #ACTIVE_SESSION_CACHE_NAME}.
+     *
+     * @param activeSessionsCacheName the name of the active sessions cache to be returned by the {@code CacheManager}.
+     */
+    public void setActiveSessionsCacheName(String activeSessionsCacheName) {
+        this.activeSessionsCacheName = activeSessionsCacheName;
+    }
+
+    /**
+     * Returns the cache instance to use for storing active sessions.  If one is not available (it is {@code null}),
+     * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
+     * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
+     *
+     * @return the cache instance to use for storing active sessions or {@code null} if the {@code Cache} instance
+     *         should be retrieved from the
+     */
+    public Cache<Serializable, Session> getActiveSessionsCache() {
+        return this.activeSessions;
+    }
+
+    /**
+     * Sets the cache instance to use for storing active sessions.  If one is not set (it remains {@code null}),
+     * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
+     * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
+     *
+     * @param cache the cache instance to use for storing active sessions or {@code null} if the cache is to be
+     *              acquired from the {@link #setCacheManager configured} {@code CacheManager}.
+     */
+    public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
+        this.activeSessions = cache;
+    }
+
+    /**
+     * Returns the active sessions cache, but if that cache instance is null, first lazily creates the cache instance
+     * via the {@link #createActiveSessionsCache()} method and then returns the instance.
+     * <p/>
+     * Note that this method will only return a non-null value code if the {@code CacheManager} has been set.  If
+     * not set, there will be no cache.
+     *
+     * @return the active sessions cache instance.
+     */
+    private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
+        if (this.activeSessions == null) {
+            this.activeSessions = createActiveSessionsCache();
+        }
+        return activeSessions;
+    }
+
+    /**
+     * Creates a cache instance used to store active sessions.  Creation is done by first
+     * {@link #getCacheManager() acquiring} the {@code CacheManager}.  If the cache manager is not null, the
+     * cache returned is that resulting from the following call:
+     * <pre>       String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
+     * cacheManager.getCache(name);</pre>
+     *
+     * @return a cache instance used to store active sessions, or {@code null} if the {@code CacheManager} has
+     *         not been set.
+     */
+    protected Cache<Serializable, Session> createActiveSessionsCache() {
+        Cache<Serializable, Session> cache = null;
+        CacheManager mgr = getCacheManager();
+        if (mgr != null) {
+            String name = getActiveSessionsCacheName();
+            cache = mgr.getCache(name);
+        }
+        return cache;
+    }
+
+    /**
+     * Calls {@code super.create(session)}, then caches the session keyed by the returned {@code sessionId}, and then
+     * returns this {@code sessionId}.
+     *
+     * @param session Session object to create in the EIS and then cache.
+     */
+    public Serializable create(Session session) {
+        Serializable sessionId = super.create(session);
+        cache(session, sessionId);
+        return sessionId;
+    }
+
+    /**
+     * Returns the cached session with the corresponding {@code sessionId} or {@code null} if there is
+     * no session cached under that id (or if there is no Cache).
+     *
+     * @param sessionId the id of the cached session to acquire.
+     * @return the cached session with the corresponding {@code sessionId}, or {@code null} if the session
+     *         does not exist or is not cached.
+     */
+    protected Session getCachedSession(Serializable sessionId) {
+        Session cached = null;
+        if (sessionId != null) {
+            Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
+            if (cache != null) {
+                cached = getCachedSession(sessionId, cache);
+            }
+        }
+        return cached;
+    }
+
+    /**
+     * Returns the Session with the specified id from the specified cache.  This method simply calls
+     * {@code cache.get(sessionId)} and can be overridden by subclasses for custom acquisition behavior.
+     *
+     * @param sessionId the id of the session to acquire.
+     * @param cache     the cache to acquire the session from
+     * @return the cached session, or {@code null} if the session wasn't in the cache.
+     */
+    protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
+        return cache.get(sessionId);
+    }
+
+    /**
+     * Caches the specified session under the cache entry key of {@code sessionId}.
+     *
+     * @param session   the session to cache
+     * @param sessionId the session id, to be used as the cache entry key.
+     * @since 1.0
+     */
+    protected void cache(Session session, Serializable sessionId) {
+        if (session == null || sessionId == null) {
+            return;
+        }
+        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
+        if (cache == null) {
+            return;
+        }
+        cache(session, sessionId, cache);
+    }
+
+    /**
+     * Caches the specified session in the given cache under the key of {@code sessionId}.  This implementation
+     * simply calls {@code cache.put(sessionId,session)} and can be overridden for custom behavior.
+     *
+     * @param session   the session to cache
+     * @param sessionId the id of the session, expected to be the cache key.
+     * @param cache     the cache to store the session
+     */
+    protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
+        cache.put(sessionId, session);
+    }
+
+    /**
+     * Attempts to acquire the Session from the cache first using the session ID as the cache key.  If no session
+     * is found, {@code super.readSession(sessionId)} is called to perform the actual retrieval.
+     *
+     * @param sessionId the id of the session to retrieve from the EIS.
+     * @return the session identified by {@code sessionId} in the EIS.
+     * @throws UnknownSessionException if the id specified does not correspond to any session in the cache or EIS.
+     */
+    public Session readSession(Serializable sessionId) throws UnknownSessionException {
+        Session s = getCachedSession(sessionId);
+        if (s == null) {
+            s = super.readSession(sessionId);
+        }
+        return s;
+    }
+
+    /**
+     * Updates the state of the given session to the EIS by first delegating to
+     * {@link #doUpdate(org.apache.shiro.session.Session)}.  If the session is a {@link ValidatingSession}, it will
+     * be added to the cache only if it is {@link ValidatingSession#isValid()} and if invalid, will be removed from the
+     * cache.  If it is not a {@code ValidatingSession} instance, it will be added to the cache in any event.
+     *
+     * @param session the session object to update in the EIS.
+     * @throws UnknownSessionException if no existing EIS session record exists with the
+     *                                 identifier of {@link Session#getId() session.getId()}
+     */
+    public void update(Session session) throws UnknownSessionException {
+        doUpdate(session);
+        if (session instanceof ValidatingSession) {
+            if (((ValidatingSession) session).isValid()) {
+                cache(session, session.getId());
+            } else {
+                uncache(session);
+            }
+        } else {
+            cache(session, session.getId());
+        }
+    }
+
+    /**
+     * Subclass implementation hook to actually persist the {@code Session}'s state to the underlying EIS.
+     *
+     * @param session the session object whose state will be propagated to the EIS.
+     */
+    protected abstract void doUpdate(Session session);
+
+    /**
+     * Removes the specified session from any cache and then permanently deletes the session from the EIS by
+     * delegating to {@link #doDelete}.
+     *
+     * @param session the session to remove from caches and permanently delete from the EIS.
+     */
+    public void delete(Session session) {
+        uncache(session);
+        doDelete(session);
+    }
+
+    /**
+     * Subclass implementation hook to permanently delete the given Session from the underlying EIS.
+     *
+     * @param session the session instance to permanently delete from the EIS.
+     */
+    protected abstract void doDelete(Session session);
+
+    /**
+     * Removes the specified Session from the cache.
+     *
+     * @param session the session to remove from the cache.
+     */
+    protected void uncache(Session session) {
+        if (session == null) {
+            return;
+        }
+        Serializable id = session.getId();
+        if (id == null) {
+            return;
+        }
+        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
+        if (cache != null) {
+            cache.remove(id);
+        }
+    }
+
+    /**
+     * Returns all active sessions in the system.
+     * <p/>
+     * <p>This implementation merely returns the sessions found in the activeSessions cache.  Subclass implementations
+     * may wish to override this method to retrieve them in a different way, perhaps by an RDBMS query or by other
+     * means.
+     *
+     * @return the sessions found in the activeSessions cache.
+     */
+    public Collection<Session> getActiveSessions() {
+        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
+        if (cache != null) {
+            return cache.values();
+        } else {
+            return Collections.emptySet();
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java
new file mode 100644
index 0000000..0896fba
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/EnterpriseCacheSessionDAO.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.cache.AbstractCacheManager;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.MapCache;
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * SessionDAO implementation that relies on an enterprise caching product as the EIS system of record for all sessions.
+ * It is expected that an injected {@link org.apache.shiro.cache.Cache Cache} or
+ * {@link org.apache.shiro.cache.CacheManager CacheManager} is backed by an enterprise caching product that can support
+ * all application sessions and/or provide disk paging for resilient data storage.
+ * <h2>Production Note</h2>
+ * This implementation defaults to using an in-memory map-based {@code CacheManager}, which is great for testing but
+ * will typically not scale for production environments and could easily cause {@code OutOfMemoryException}s.  Just
+ * don't forget to configure<b>*</b> an instance of this class with a production-grade {@code CacheManager} that can
+ * handle disk paging for large numbers of sessions and you'll be fine.
+ * <p/>
+ * <b>*</b>If you configure Shiro's {@code SecurityManager} instance with such a {@code CacheManager}, it will be
+ * automatically applied to an instance of this class and you won't need to explicitly set it in configuration.
+ * <h3>Implementation Details</h3>
+ * This implementation relies heavily on the {@link CachingSessionDAO parent class}'s transparent caching behavior for
+ * all storage operations with the enterprise caching product.  Because the parent class uses a {@code Cache} or
+ * {@code CacheManager} to perform caching, and the cache is considered the system of record, nothing further needs to
+ * be done for the {@link #doReadSession}, {@link #doUpdate} and {@link #doDelete} method implementations.  This class
+ * implements those methods as required by the parent class, but they essentially do nothing.
+ *
+ * @since 1.0
+ */
+public class EnterpriseCacheSessionDAO extends CachingSessionDAO {
+
+    public EnterpriseCacheSessionDAO() {
+        setCacheManager(new AbstractCacheManager() {
+            @Override
+            protected Cache<Serializable, Session> createCache(String name) throws CacheException {
+                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
+            }
+        });
+    }
+
+    protected Serializable doCreate(Session session) {
+        Serializable sessionId = generateSessionId(session);
+        assignSessionId(session, sessionId);
+        return sessionId;
+    }
+
+    protected Session doReadSession(Serializable sessionId) {
+        return null; //should never execute because this implementation relies on parent class to access cache, which
+        //is where all sessions reside - it is the cache implementation that determines if the
+        //cache is memory only or disk-persistent, etc.
+    }
+
+    protected void doUpdate(Session session) {
+        //does nothing - parent class persists to cache.
+    }
+
+    protected void doDelete(Session session) {
+        //does nothing - parent class removes from cache.
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
new file mode 100644
index 0000000..96cdf4b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/JavaUuidSessionIdGenerator.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * {@link SessionIdGenerator} that generates String values of JDK {@link java.util.UUID}'s as the session IDs.
+ *
+ * @since 1.0
+ */
+public class JavaUuidSessionIdGenerator implements SessionIdGenerator {
+
+    /**
+     * Ignores the method argument and simply returns
+     * {@code UUID}.{@link java.util.UUID#randomUUID() randomUUID()}.{@code toString()}.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the String value of the JDK's next {@link UUID#randomUUID() randomUUID()}.
+     */
+    public Serializable generateId(Session session) {
+        return UUID.randomUUID().toString();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
new file mode 100644
index 0000000..b1ed4e0
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/MemorySessionDAO.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+
+/**
+ * Simple memory-based implementation of the SessionDAO that stores all of its sessions in an in-memory
+ * {@link ConcurrentMap}.  <b>This implementation does not page to disk and is therefore unsuitable for applications
+ * that could experience a large amount of sessions</b> and would therefore cause {@code OutOfMemoryException}s.  It is
+ * <em>not</em> recommended for production use in most environments.
+ * <h2>Memory Restrictions</h2>
+ * If your application is expected to host many sessions beyond what can be stored in the
+ * memory available to the JVM, it is highly recommended to use a different {@code SessionDAO} implementation which
+ * uses a more expansive or permanent backing data store.
+ * <p/>
+ * In this case, it is recommended to instead use a custom
+ * {@link CachingSessionDAO} implementation that communicates with a higher-capacity data store of your choice
+ * (file system, database, etc).
+ * <h2>Changes in 1.0</h2>
+ * This implementation prior to 1.0 used to subclass the {@link CachingSessionDAO}, but this caused problems with many
+ * cache implementations that would expunge entries due to TTL settings, resulting in Sessions that would be randomly
+ * (and permanently) lost.  The Shiro 1.0 release refactored this implementation to be 100% memory-based (without
+ * {@code Cache} usage to avoid this problem.
+ *
+ * @see CachingSessionDAO
+ * @since 0.1
+ */
+public class MemorySessionDAO extends AbstractSessionDAO {
+
+    private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
+
+    private ConcurrentMap<Serializable, Session> sessions;
+
+    public MemorySessionDAO() {
+        this.sessions = new ConcurrentHashMap<Serializable, Session>();
+    }
+
+    protected Serializable doCreate(Session session) {
+        Serializable sessionId = generateSessionId(session);
+        assignSessionId(session, sessionId);
+        storeSession(sessionId, session);
+        return sessionId;
+    }
+
+    protected Session storeSession(Serializable id, Session session) {
+        if (id == null) {
+            throw new NullPointerException("id argument cannot be null.");
+        }
+        return sessions.putIfAbsent(id, session);
+    }
+
+    protected Session doReadSession(Serializable sessionId) {
+        return sessions.get(sessionId);
+    }
+
+    public void update(Session session) throws UnknownSessionException {
+        storeSession(session.getId(), session);
+    }
+
+    public void delete(Session session) {
+        if (session == null) {
+            throw new NullPointerException("session argument cannot be null.");
+        }
+        Serializable id = session.getId();
+        if (id != null) {
+            sessions.remove(id);
+        }
+    }
+
+    public Collection<Session> getActiveSessions() {
+        Collection<Session> values = sessions.values();
+        if (CollectionUtils.isEmpty(values)) {
+            return Collections.emptySet();
+        } else {
+            return Collections.unmodifiableCollection(values);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java
new file mode 100644
index 0000000..09cf0cc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Random;
+
+/**
+ * Generates session IDs by using a {@link Random} instance to generate random IDs. The default {@code Random}
+ * implementation is a {@link java.security.SecureRandom SecureRandom} with the {@code SHA1PRNG} algorithm.
+ *
+ * @since 1.0
+ */
+public class RandomSessionIdGenerator implements SessionIdGenerator {
+
+    private static final Logger log = LoggerFactory.getLogger(RandomSessionIdGenerator.class);
+
+    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
+    private Random random;
+
+    public RandomSessionIdGenerator() {
+        try {
+            this.random = java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
+        } catch (java.security.NoSuchAlgorithmException e) {
+            log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
+                    "platform's default SecureRandom algorithm.", e);
+            this.random = new java.security.SecureRandom();
+        }
+    }
+
+    public Random getRandom() {
+        return this.random;
+    }
+
+    public void setRandom(Random random) {
+        this.random = random;
+    }
+
+    /**
+     * Returns the String value of the configured {@link Random}'s {@link Random#nextLong() nextLong()} invocation.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the String value of the configured {@link Random}'s {@link Random#nextLong()} invocation.
+     */
+    public Serializable generateId(Session session) {
+        //ignore the argument - just call the Random:
+        return Long.toString(getRandom().nextLong());
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionDAO.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionDAO.java
new file mode 100644
index 0000000..351a3cc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionDAO.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.UnknownSessionException;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+
+/**
+ * Data Access Object design pattern specification to enable {@link Session} access to an
+ * EIS (Enterprise Information System).  It provides your four typical CRUD methods:
+ * {@link #create}, {@link #readSession(java.io.Serializable)}, {@link #update(org.apache.shiro.session.Session)},
+ * and {@link #delete(org.apache.shiro.session.Session)}.
+ * <p/>
+ * The remaining {@link #getActiveSessions()} method exists as a support mechanism to pre-emptively orphaned sessions,
+ * typically by {@link org.apache.shiro.session.mgt.ValidatingSessionManager ValidatingSessionManager}s), and should
+ * be as efficient as possible, especially if there are thousands of active sessions.  Large scale/high performance
+ * implementations will often return a subset of the total active sessions and perform validation a little more
+ * frequently, rather than return a massive set and infrequently validate.
+ *
+ * @since 0.1
+ */
+public interface SessionDAO {
+
+    /**
+     * Inserts a new Session record into the underling EIS (e.g. Relational database, file system, persistent cache,
+     * etc, depending on the DAO implementation).
+     * <p/>
+     * After this method is invoked, the {@link org.apache.shiro.session.Session#getId()}
+     * method executed on the argument must return a valid session identifier.  That is, the following should
+     * always be true:
+     * <pre>
+     * Serializable id = create( session );
+     * id.equals( session.getId() ) == true</pre>
+     * <p/>
+     * Implementations are free to throw any exceptions that might occur due to
+     * integrity violation constraints or other EIS related errors.
+     *
+     * @param session the {@link org.apache.shiro.session.Session} object to create in the EIS.
+     * @return the EIS id (e.g. primary key) of the created {@code Session} object.
+     */
+    Serializable create(Session session);
+
+    /**
+     * Retrieves the session from the EIS uniquely identified by the specified
+     * {@code sessionId}.
+     *
+     * @param sessionId the system-wide unique identifier of the Session object to retrieve from
+     *                  the EIS.
+     * @return the persisted session in the EIS identified by {@code sessionId}.
+     * @throws UnknownSessionException if there is no EIS record for any session with the
+     *                                 specified {@code sessionId}
+     */
+    Session readSession(Serializable sessionId) throws UnknownSessionException;
+
+    /**
+     * Updates (persists) data from a previously created Session instance in the EIS identified by
+     * {@code {@link Session#getId() session.getId()}}.  This effectively propagates
+     * the data in the argument to the EIS record previously saved.
+     * <p/>
+     * In addition to UnknownSessionException, implementations are free to throw any other
+     * exceptions that might occur due to integrity violation constraints or other EIS related
+     * errors.
+     *
+     * @param session the Session to update
+     * @throws org.apache.shiro.session.UnknownSessionException
+     *          if no existing EIS session record exists with the
+     *          identifier of {@link Session#getId() session.getSessionId()}
+     */
+    void update(Session session) throws UnknownSessionException;
+
+    /**
+     * Deletes the associated EIS record of the specified {@code session}.  If there never
+     * existed a session EIS record with the identifier of
+     * {@link Session#getId() session.getId()}, then this method does nothing.
+     *
+     * @param session the session to delete.
+     */
+    void delete(Session session);
+
+    /**
+     * Returns all sessions in the EIS that are considered active, meaning all sessions that
+     * haven't been stopped/expired.  This is primarily used to validate potential orphans.
+     * <p/>
+     * If there are no active sessions in the EIS, this method may return an empty collection or {@code null}.
+     * <h4>Performance</h4>
+     * This method should be as efficient as possible, especially in larger systems where there might be
+     * thousands of active sessions.  Large scale/high performance
+     * implementations will often return a subset of the total active sessions and perform validation a little more
+     * frequently, rather than return a massive set and validate infrequently.  If efficient and possible, it would
+     * make sense to return the oldest unstopped sessions available, ordered by
+     * {@link org.apache.shiro.session.Session#getLastAccessTime() lastAccessTime}.
+     * <h4>Smart Results</h4>
+     * <em>Ideally</em> this method would only return active sessions that the EIS was certain should be invalided.
+     * Typically that is any session that is not stopped and where its lastAccessTimestamp is older than the session
+     * timeout.
+     * <p/>
+     * For example, if sessions were backed by a relational database or SQL-92 'query-able' enterprise cache, you might
+     * return something similar to the results returned by this query (assuming
+     * {@link org.apache.shiro.session.mgt.SimpleSession SimpleSession}s were being stored):
+     * <pre>
+     * select * from sessions s where s.lastAccessTimestamp < ? and s.stopTimestamp is null
+     * </pre>
+     * where the {@code ?} parameter is a date instance equal to 'now' minus the session timeout
+     * (e.g. now - 30 minutes).
+     *
+     * @return a Collection of {@code Session}s that are considered active, or an
+     *         empty collection or {@code null} if there are no active sessions.
+     */
+    Collection<Session> getActiveSessions();
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java
new file mode 100644
index 0000000..c8a5ea1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/SessionIdGenerator.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.eis;
+
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+
+/**
+ * Interface allowing pluggable session ID generation strategies to be used with various {@link SessionDAO}
+ * implementations.
+ * <h2>Usage</h2>
+ * SessionIdGenerators are usually only used when ID generation is separate from creating the
+ * Session record in the EIS data store.  Some EIS data stores, such as relational databases, can generate the id
+ * at the same time the record is created, such as when using auto-generated primary keys.  In these cases, a
+ * SessionIdGenerator does not need to be configured.
+ * <p/>
+ * However, if you want to customize how session IDs are created before persisting the Session record into the data
+ * store, you can implement this interface and typically inject it into an {@link AbstractSessionDAO} instance.
+ *
+ * @see org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator JavaUuidSessionIdGenerator
+ * @see org.apache.shiro.session.mgt.eis.RandomSessionIdGenerator RandomSessionIdGenerator
+ * @since 1.0
+ */
+public interface SessionIdGenerator {
+
+    /**
+     * Generates a new ID to be applied to the specified {@code Session} instance.
+     *
+     * @param session the {@link Session} instance to which the ID will be applied.
+     * @return the id to assign to the specified {@link Session} instance before adding a record to the EIS data store.
+     */
+    Serializable generateId(Session session);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/eis/package-info.java b/core/src/main/java/org/apache/shiro/session/mgt/eis/package-info.java
new file mode 100644
index 0000000..18efcd9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/eis/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * EIS (Enterprise Information System)-tier components that can perform CRUD operations for sessions
+ * using any EIS API.
+ */
+package org.apache.shiro.session.mgt.eis;
diff --git a/core/src/main/java/org/apache/shiro/session/mgt/package-info.java b/core/src/main/java/org/apache/shiro/session/mgt/package-info.java
new file mode 100644
index 0000000..9cb05d3
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/mgt/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * {@link org.apache.shiro.session.mgt.SessionManager SessionManager} components supporting enterprise session management.
+ */
+package org.apache.shiro.session.mgt;
diff --git a/core/src/main/java/org/apache/shiro/session/package-info.java b/core/src/main/java/org/apache/shiro/session/package-info.java
new file mode 100644
index 0000000..4489231
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/session/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components related to managing sessions, the time-based data contexts in which a Subject
+ * interacts with an application.
+ * <p/>
+ * Sessions in Shiro are completely POJO-based and do not <em>require</em> an application to use Web-based
+ * or EJB-based session management infrastructure - the client and/or server technoloy is irrelevent in
+ * Shiro's architecture, allowing session management to be employed in the smallest standalone application
+ * to the largest enterprise deployments.
+ * <p/>
+ * This design decision opens up a new world to Java applications - most notably the ability to participate in
+ * a session regardless if the client is using HTTP, custom sockets, web services, or even non-Java progamming
+ * languages. Aside from Shiro, there is currently no technology in Java today allows this heterogenous
+ * client-session capability.
+ * <p/>
+ * Also because of this freedom, Shiro naturally supports Single Sign-On for any application as well, using
+ * this heterogeneous session support.
+ */
+package org.apache.shiro.session;
diff --git a/core/src/main/java/org/apache/shiro/subject/ExecutionException.java b/core/src/main/java/org/apache/shiro/subject/ExecutionException.java
new file mode 100644
index 0000000..e4898ca
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/ExecutionException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Exception wrapping any potential checked exception thrown when a {@code Subject} executes a
+ * {@link java.util.concurrent.Callable}.  This is a nicer alternative than forcing calling code to catch
+ * a normal checked {@code Exception} when it may not be necessary.
+ * <p/>
+ * If thrown, the causing exception will always be accessible via the {@link #getCause() getCause()} method.
+ *
+ * @since 1.0
+ */
+public class ExecutionException extends ShiroException {
+
+    public ExecutionException(Throwable cause) {
+        super(cause);
+    }
+
+    public ExecutionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/MutablePrincipalCollection.java b/core/src/main/java/org/apache/shiro/subject/MutablePrincipalCollection.java
new file mode 100644
index 0000000..251b699
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/MutablePrincipalCollection.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import java.util.Collection;
+
+
+/**
+ * A {@link PrincipalCollection} that allows modification.
+ *
+ * @since 0.9
+ */
+public interface MutablePrincipalCollection extends PrincipalCollection {
+
+    /**
+     * Adds the given principal to this collection.
+     *
+     * @param principal the principal to be added.
+     * @param realmName the realm this principal came from.
+     */
+    void add(Object principal, String realmName);
+
+    /**
+     * Adds all of the principals in the given collection to this collection.
+     *
+     * @param principals the principals to be added.
+     * @param realmName  the realm these principals came from.
+     */
+    void addAll(Collection principals, String realmName);
+
+    /**
+     * Adds all of the principals from the given principal collection to this collection.
+     *
+     * @param principals the principals to add.
+     */
+    void addAll(PrincipalCollection principals);
+
+    /**
+     * Removes all Principals in this collection.
+     */
+    void clear();
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/PrincipalCollection.java b/core/src/main/java/org/apache/shiro/subject/PrincipalCollection.java
new file mode 100644
index 0000000..adfd273
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/PrincipalCollection.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A collection of all principals associated with a corresponding {@link Subject Subject}.  A <em>principal</em> is
+ * just a security term for an identifying attribute, such as a username or user id or social security number or
+ * anything else that can be considered an 'identifying' attribute for a {@code Subject}.
+ * <p/>
+ * A PrincipalCollection organizes its internal principals based on the {@code Realm} where they came from when the
+ * Subject was first created.  To obtain the principal(s) for a specific Realm, see the {@link #fromRealm} method.  You
+ * can also see which realms contributed to this collection via the {@link #getRealmNames() getRealmNames()} method.
+ *
+ * @see #getPrimaryPrincipal()
+ * @see #fromRealm(String realmName)
+ * @see #getRealmNames()
+ * @since 0.9
+ */
+public interface PrincipalCollection extends Iterable, Serializable {
+
+    /**
+     * Returns the primary principal used application-wide to uniquely identify the owning account/Subject.
+     * <p/>
+     * The value is usually always a uniquely identifying attribute specific to the data source that retrieved the
+     * account data.  Some examples:
+     * <ul>
+     * <li>a {@link java.util.UUID UUID}</li>
+     * <li>a {@code long} value such as a surrogate primary key in a relational database</li>
+     * <li>an LDAP UUID or static DN</li>
+     * <li>a String username unique across all user accounts</li>
+     * </ul>
+     * <h3>Multi-Realm Applications</h3>
+     * In a single-{@code Realm} application, typically there is only ever one unique principal to retain and that
+     * is the value returned from this method.  However, in a multi-{@code Realm} application, where the
+     * {@code PrincipalCollection} might retain principals across more than one realm, the value returned from this
+     * method should be the single principal that uniquely identifies the subject for the entire application.
+     * <p/>
+     * That value is of course application specific, but most applications will typically choose one of the primary
+     * principals from one of the {@code Realm}s.
+     * <p/>
+     * Shiro's default implementations of this interface make this
+     * assumption by usually simply returning {@link #iterator()}.{@link java.util.Iterator#next() next()}, which just
+     * returns the first returned principal obtained from the first consulted/configured {@code Realm} during the
+     * authentication attempt.  This means in a multi-{@code Realm} application, {@code Realm} configuraiton order
+     * matters if you want to retain this default heuristic.
+     * <p/>
+     * If this heuristic is not sufficient, most Shiro end-users will need to implement a custom
+     * {@link org.apache.shiro.authc.pam.AuthenticationStrategy}.  An {@code AuthenticationStrategy} has exact control
+     * over the {@link PrincipalCollection} returned at the end of an authentication attempt via the
+     * <code>AuthenticationStrategy#{@link org.apache.shiro.authc.pam.AuthenticationStrategy#afterAllAttempts(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo) afterAllAttempts}</code>
+     * implementation.
+     *
+     * @return the primary principal used to uniquely identify the owning account/Subject
+     * @since 1.0
+     */
+    Object getPrimaryPrincipal();
+
+    /**
+     * Returns the first discovered principal assignable from the specified type, or {@code null} if there are none
+     * of the specified type.
+     * <p/>
+     * Note that this will return {@code null} if the 'owning' subject has not yet logged in.
+     *
+     * @param type the type of the principal that should be returned.
+     * @return a principal of the specified type or {@code null} if there isn't one of the specified type.
+     */
+    <T> T oneByType(Class<T> type);
+
+    /**
+     * Returns all principals assignable from the specified type, or an empty Collection if no principals of that
+     * type are contained.
+     * <p/>
+     * Note that this will return an empty Collection if the 'owning' subject has not yet logged in.
+     *
+     * @param type the type of the principals that should be returned.
+     * @return a Collection of principals that are assignable from the specified type, or
+     *         an empty Collection if no principals of this type are associated.
+     */
+    <T> Collection<T> byType(Class<T> type);
+
+    /**
+     * Returns a single Subject's principals retrieved from all configured Realms as a List, or an empty List if
+     * there are not any principals.
+     * <p/>
+     * Note that this will return an empty List if the 'owning' subject has not yet logged in.
+     *
+     * @return a single Subject's principals retrieved from all configured Realms as a List.
+     */
+    List asList();
+
+    /**
+     * Returns a single Subject's principals retrieved from all configured Realms as a Set, or an empty Set if there
+     * are not any principals.
+     * <p/>
+     * Note that this will return an empty Set if the 'owning' subject has not yet logged in.
+     *
+     * @return a single Subject's principals retrieved from all configured Realms as a Set.
+     */
+    Set asSet();
+
+    /**
+     * Returns a single Subject's principals retrieved from the specified Realm <em>only</em> as a Collection, or an empty
+     * Collection if there are not any principals from that realm.
+     * <p/>
+     * Note that this will return an empty Collection if the 'owning' subject has not yet logged in.
+     *
+     * @param realmName the name of the Realm from which the principals were retrieved.
+     * @return the Subject's principals from the specified Realm only as a Collection or an empty Collection if there
+     *         are not any principals from that realm.
+     */
+    Collection fromRealm(String realmName);
+
+    /**
+     * Returns the realm names that this collection has principals for.
+     *
+     * @return the names of realms that this collection has one or more principals for.
+     */
+    Set<String> getRealmNames();
+
+    /**
+     * Returns {@code true} if this collection is empty, {@code false} otherwise.
+     *
+     * @return {@code true} if this collection is empty, {@code false} otherwise.
+     */
+    boolean isEmpty();
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/PrincipalMap.java b/core/src/main/java/org/apache/shiro/subject/PrincipalMap.java
new file mode 100644
index 0000000..1f7a01d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/PrincipalMap.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import java.util.Map;
+
+/**
+ * EXPERIMENTAL - DO NOT USE YET
+ * <p/>
+ * A {@code PrincipalMap} is map of all of a subject's principals - its identifying attributes like username, userId,
+ * etc.
+ * <p/>
+ * The {@link Map} methods allow you to interact with a unified representation of
+ * all of the Subject's principals, even if they came from different realms.  You can think of the {@code Map} methods
+ * as the general purpose API for a Subject's principals.  That is, you can access a principal generally:
+ * <pre>
+ * Object principal = subject.getPrincipals().get(principalName);
+ * </pre>
+ * For example, to get the Subject's username (if the
+ * username principal indeed exists and was populated by a Realm), you can do the following:
+ * <pre>
+ * String username = (String)subject.getPrincipals().get("username");
+ * </pre>
+ * <h3>Multi-Realm Environments</h3>
+ * If your application uses multiple realms, the {@code Map} methods reflect
+ * the the aggregate principals from <em>all</em> realms that authenticated the owning {@code Subject}.
+ * <p/>
+ * But in these multi-realm environments, it is often convenient or necessary to acquire only the principals contributed
+ * by a specific realm (often in a realm implementation itself).  This {@code PrincipalMap} interface satisfies
+ * those needs by providing additional realm-specific accessor/mutator methods.
+ *
+ * @author Les Hazlewood
+ * @since 1.2
+ */
+public interface PrincipalMap extends PrincipalCollection, Map<String,Object> {
+
+    Map<String,Object> getRealmPrincipals(String realmName);
+
+    Map<String,Object> setRealmPrincipals(String realmName, Map<String,Object> principals);
+
+    Object setRealmPrincipal(String realmName, String principalName, Object principal);
+
+    Object getRealmPrincipal(String realmName, String realmPrincipal);
+
+    Object removeRealmPrincipal(String realmName, String principalName);
+
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/SimplePrincipalCollection.java b/core/src/main/java/org/apache/shiro/subject/SimplePrincipalCollection.java
new file mode 100644
index 0000000..9b17f2a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/SimplePrincipalCollection.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.*;
+
+
+/**
+ * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally
+ * by storing them in a {@link LinkedHashMap}.
+ *
+ * @since 0.9
+ */
+ at SuppressWarnings({"unchecked"})
+public class SimplePrincipalCollection implements MutablePrincipalCollection {
+
+    // Serialization reminder:
+    // You _MUST_ change this number if you introduce a change to this class
+    // that is NOT serialization backwards compatible.  Serialization-compatible
+    // changes do not require a change to this number.  If you need to generate
+    // a new number in this case, use the JDK's 'serialver' program to generate it.
+    private static final long serialVersionUID = -6305224034025797558L;
+
+    //TODO - complete JavaDoc
+
+    private Map<String, Set> realmPrincipals;
+
+    private transient String cachedToString; //cached toString() result, as this can be printed many times in logging
+
+    public SimplePrincipalCollection() {
+    }
+
+    public SimplePrincipalCollection(Object principal, String realmName) {
+        if (principal instanceof Collection) {
+            addAll((Collection) principal, realmName);
+        } else {
+            add(principal, realmName);
+        }
+    }
+
+    public SimplePrincipalCollection(Collection principals, String realmName) {
+        addAll(principals, realmName);
+    }
+
+    public SimplePrincipalCollection(PrincipalCollection principals) {
+        addAll(principals);
+    }
+
+    protected Collection getPrincipalsLazy(String realmName) {
+        if (realmPrincipals == null) {
+            realmPrincipals = new LinkedHashMap<String, Set>();
+        }
+        Set principals = realmPrincipals.get(realmName);
+        if (principals == null) {
+            principals = new LinkedHashSet();
+            realmPrincipals.put(realmName, principals);
+        }
+        return principals;
+    }
+
+    /**
+     * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are
+     * no principals yet.
+     * <p/>
+     * The 'first available principal' is interpreted as the principal that would be returned by
+     * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code>
+     *
+     * @inheritDoc
+     */
+    public Object getPrimaryPrincipal() {
+        if (isEmpty()) {
+            return null;
+        }
+        return iterator().next();
+    }
+
+    public void add(Object principal, String realmName) {
+        if (realmName == null) {
+            throw new IllegalArgumentException("realmName argument cannot be null.");
+        }
+        if (principal == null) {
+            throw new IllegalArgumentException("principal argument cannot be null.");
+        }
+        this.cachedToString = null;
+        getPrincipalsLazy(realmName).add(principal);
+    }
+
+    public void addAll(Collection principals, String realmName) {
+        if (realmName == null) {
+            throw new IllegalArgumentException("realmName argument cannot be null.");
+        }
+        if (principals == null) {
+            throw new IllegalArgumentException("principals argument cannot be null.");
+        }
+        if (principals.isEmpty()) {
+            throw new IllegalArgumentException("principals argument cannot be an empty collection.");
+        }
+        this.cachedToString = null;
+        getPrincipalsLazy(realmName).addAll(principals);
+    }
+
+    public void addAll(PrincipalCollection principals) {
+        if (principals.getRealmNames() != null) {
+            for (String realmName : principals.getRealmNames()) {
+                for (Object principal : principals.fromRealm(realmName)) {
+                    add(principal, realmName);
+                }
+            }
+        }
+    }
+
+    public <T> T oneByType(Class<T> type) {
+        if (realmPrincipals == null || realmPrincipals.isEmpty()) {
+            return null;
+        }
+        Collection<Set> values = realmPrincipals.values();
+        for (Set set : values) {
+            for (Object o : set) {
+                if (type.isAssignableFrom(o.getClass())) {
+                    return (T) o;
+                }
+            }
+        }
+        return null;
+    }
+
+    public <T> Collection<T> byType(Class<T> type) {
+        if (realmPrincipals == null || realmPrincipals.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        Set<T> typed = new LinkedHashSet<T>();
+        Collection<Set> values = realmPrincipals.values();
+        for (Set set : values) {
+            for (Object o : set) {
+                if (type.isAssignableFrom(o.getClass())) {
+                    typed.add((T) o);
+                }
+            }
+        }
+        if (typed.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        return Collections.unmodifiableSet(typed);
+    }
+
+    public List asList() {
+        Set all = asSet();
+        if (all.isEmpty()) {
+            return Collections.EMPTY_LIST;
+        }
+        return Collections.unmodifiableList(new ArrayList(all));
+    }
+
+    public Set asSet() {
+        if (realmPrincipals == null || realmPrincipals.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        Set aggregated = new LinkedHashSet();
+        Collection<Set> values = realmPrincipals.values();
+        for (Set set : values) {
+            aggregated.addAll(set);
+        }
+        if (aggregated.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        return Collections.unmodifiableSet(aggregated);
+    }
+
+    public Collection fromRealm(String realmName) {
+        if (realmPrincipals == null || realmPrincipals.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        Set principals = realmPrincipals.get(realmName);
+        if (principals == null || principals.isEmpty()) {
+            principals = Collections.EMPTY_SET;
+        }
+        return Collections.unmodifiableSet(principals);
+    }
+
+    public Set<String> getRealmNames() {
+        if (realmPrincipals == null) {
+            return null;
+        } else {
+            return realmPrincipals.keySet();
+        }
+    }
+
+    public boolean isEmpty() {
+        return realmPrincipals == null || realmPrincipals.isEmpty();
+    }
+
+    public void clear() {
+        this.cachedToString = null;
+        if (realmPrincipals != null) {
+            realmPrincipals.clear();
+            realmPrincipals = null;
+        }
+    }
+
+    public Iterator iterator() {
+        return asSet().iterator();
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof SimplePrincipalCollection) {
+            SimplePrincipalCollection other = (SimplePrincipalCollection) o;
+            return this.realmPrincipals != null ? this.realmPrincipals.equals(other.realmPrincipals) : other.realmPrincipals == null;
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        if (this.realmPrincipals != null && !realmPrincipals.isEmpty()) {
+            return realmPrincipals.hashCode();
+        }
+        return super.hashCode();
+    }
+
+    /**
+     * Returns a simple string representation suitable for printing.
+     *
+     * @return a simple string representation suitable for printing.
+     * @since 1.0
+     */
+    public String toString() {
+        if (this.cachedToString == null) {
+            Set<Object> principals = asSet();
+            if (!CollectionUtils.isEmpty(principals)) {
+                this.cachedToString = StringUtils.toString(principals.toArray());
+            } else {
+                this.cachedToString = "empty";
+            }
+        }
+        return this.cachedToString;
+    }
+
+
+    /**
+     * Serialization write support.
+     * <p/>
+     * NOTE: Don't forget to change the serialVersionUID constant at the top of this class
+     * if you make any backwards-incompatible serializatoin changes!!!
+     * (use the JDK 'serialver' program for this)
+     *
+     * @param out output stream provided by Java serialization
+     * @throws IOException if there is a stream error
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        boolean principalsExist = !CollectionUtils.isEmpty(realmPrincipals);
+        out.writeBoolean(principalsExist);
+        if (principalsExist) {
+            out.writeObject(realmPrincipals);
+        }
+    }
+
+    /**
+     * Serialization read support - reads in the Map principals collection if it exists in the
+     * input stream.
+     * <p/>
+     * NOTE: Don't forget to change the serialVersionUID constant at the top of this class
+     * if you make any backwards-incompatible serializatoin changes!!!
+     * (use the JDK 'serialver' program for this)
+     *
+     * @param in input stream provided by
+     * @throws IOException            if there is an input/output problem
+     * @throws ClassNotFoundException if the underlying Map implementation class is not available to the classloader.
+     */
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        boolean principalsExist = in.readBoolean();
+        if (principalsExist) {
+            this.realmPrincipals = (Map<String, Set>) in.readObject();
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/SimplePrincipalMap.java b/core/src/main/java/org/apache/shiro/subject/SimplePrincipalMap.java
new file mode 100644
index 0000000..f526866
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/SimplePrincipalMap.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * Default implementation of the {@link PrincipalMap} interface.
+ *
+ * *EXPERIMENTAL for Shiro 1.2 - DO NOT USE YET*
+ *
+ * @author Les Hazlewood
+ * @since 1.2
+ */
+public class SimplePrincipalMap implements PrincipalMap {
+
+    //Key: realm name, Value: map of principals specific to that realm
+    //                        internal map - key: principal name, value: principal
+    private Map<String, Map<String, Object>> realmPrincipals;
+
+    //maintains the principals from all realms plus any that are modified via the Map modification methods
+    //this ensures a fast lookup of any named principal instead of needing to iterate over
+    //the realmPrincipals for each lookup.
+    private Map<String, Object> combinedPrincipals;
+
+    public SimplePrincipalMap() {
+        this(null);
+    }
+
+    public SimplePrincipalMap(Map<String, Map<String, Object>> backingMap) {
+        if (!CollectionUtils.isEmpty(backingMap)) {
+            this.realmPrincipals = backingMap;
+            for (Map<String, Object> principals : this.realmPrincipals.values()) {
+                if (!CollectionUtils.isEmpty(principals) ) {
+                    ensureCombinedPrincipals().putAll(principals);
+                }
+            }
+        }
+    }
+
+    public int size() {
+        return CollectionUtils.size(this.combinedPrincipals);
+    }
+
+    protected Map<String, Object> ensureCombinedPrincipals() {
+        if (this.combinedPrincipals == null) {
+            this.combinedPrincipals = new HashMap<String, Object>();
+        }
+        return this.combinedPrincipals;
+    }
+
+    public boolean containsKey(Object o) {
+        return this.combinedPrincipals != null && this.combinedPrincipals.containsKey(o);
+    }
+
+    public boolean containsValue(Object o) {
+        return this.combinedPrincipals != null && this.combinedPrincipals.containsKey(o);
+    }
+
+    public Object get(Object o) {
+        return this.combinedPrincipals != null && this.combinedPrincipals.containsKey(o);
+    }
+
+    public Object put(String s, Object o) {
+        return ensureCombinedPrincipals().put(s, o);
+    }
+
+    public Object remove(Object o) {
+        return this.combinedPrincipals != null ? this.combinedPrincipals.remove(o) : null;
+    }
+
+    public void putAll(Map<? extends String, ?> map) {
+        if (!CollectionUtils.isEmpty(map)) {
+            ensureCombinedPrincipals().putAll(map);
+        }
+    }
+
+    public Set<String> keySet() {
+        return CollectionUtils.isEmpty(this.combinedPrincipals) ?
+                Collections.<String>emptySet() :
+                Collections.unmodifiableSet(this.combinedPrincipals.keySet());
+    }
+
+    public Collection<Object> values() {
+        return CollectionUtils.isEmpty(this.combinedPrincipals) ?
+                Collections.emptySet() :
+                Collections.unmodifiableCollection(this.combinedPrincipals.values());
+    }
+
+    public Set<Entry<String, Object>> entrySet() {
+        return CollectionUtils.isEmpty(this.combinedPrincipals) ?
+                Collections.<Entry<String,Object>>emptySet() :
+                Collections.unmodifiableSet(this.combinedPrincipals.entrySet());
+    }
+
+    public void clear() {
+        this.realmPrincipals = null;
+        this.combinedPrincipals = null;
+    }
+
+    public Object getPrimaryPrincipal() {
+        //heuristic - just use the first one we come across:
+        return !CollectionUtils.isEmpty(this.combinedPrincipals) ?
+                this.combinedPrincipals.values().iterator().next() :
+                null;
+    }
+
+    public <T> T oneByType(Class<T> type) {
+        if (CollectionUtils.isEmpty(this.combinedPrincipals)) {
+            return null;
+        }
+        for( Object value : this.combinedPrincipals.values()) {
+            if (type.isInstance(value) ) {
+                return type.cast(value);
+            }
+        }
+        return null;
+    }
+
+    public <T> Collection<T> byType(Class<T> type) {
+        if (CollectionUtils.isEmpty(this.combinedPrincipals)) {
+            return Collections.emptySet();
+        }
+        Collection<T> instances = null;
+        for( Object value : this.combinedPrincipals.values()) {
+            if (type.isInstance(value) ) {
+                if (instances == null) {
+                    instances = new ArrayList<T>();
+                }
+                instances.add(type.cast(value));
+            }
+        }
+        return instances != null ? instances : Collections.<T>emptyList();
+    }
+
+    public List asList() {
+        if (CollectionUtils.isEmpty(this.combinedPrincipals)) {
+            return Collections.emptyList();
+        }
+        List<Object> list = new ArrayList<Object>(this.combinedPrincipals.size());
+        list.addAll(this.combinedPrincipals.values());
+        return list;
+    }
+
+    public Set asSet() {
+        if (CollectionUtils.isEmpty(this.combinedPrincipals)) {
+            return Collections.emptySet();
+        }
+        Set<Object> set = new HashSet<Object>(this.combinedPrincipals.size());
+        set.addAll(this.combinedPrincipals.values());
+        return set;
+    }
+
+    public Collection fromRealm(String realmName) {
+        if (CollectionUtils.isEmpty(this.realmPrincipals)) {
+            return Collections.emptySet();
+        }
+        Map<String,Object> principals = this.realmPrincipals.get(realmName);
+        if (CollectionUtils.isEmpty(principals)) {
+            return Collections.emptySet();
+        }
+        return Collections.unmodifiableCollection(principals.values());
+    }
+
+    public Set<String> getRealmNames() {
+        if (CollectionUtils.isEmpty(this.realmPrincipals)) {
+            return Collections.emptySet();
+        }
+        return Collections.unmodifiableSet(this.realmPrincipals.keySet());
+    }
+
+    public boolean isEmpty() {
+        return CollectionUtils.isEmpty(this.combinedPrincipals);
+    }
+
+    public Iterator iterator() {
+        return asList().iterator();
+    }
+
+    public Map<String, Object> getRealmPrincipals(String name) {
+        if (this.realmPrincipals == null) {
+            return null;
+        }
+        Map<String,Object> principals = this.realmPrincipals.get(name);
+        if (principals == null) {
+            return null;
+        }
+        return Collections.unmodifiableMap(principals);
+    }
+
+    public Map<String,Object> setRealmPrincipals(String realmName, Map<String, Object> principals) {
+        if (realmName == null) {
+            throw new NullPointerException("realmName argument cannot be null.");
+        }
+        if (this.realmPrincipals == null) {
+            if (!CollectionUtils.isEmpty(principals)) {
+                this.realmPrincipals = new HashMap<String,Map<String,Object>>();
+                return this.realmPrincipals.put(realmName, new HashMap<String,Object>(principals));
+            } else {
+                return null;
+            }
+        } else {
+            Map<String,Object> existingPrincipals = this.realmPrincipals.remove(realmName);
+            if (!CollectionUtils.isEmpty(principals)) {
+                this.realmPrincipals.put(realmName, new HashMap<String,Object>(principals));
+            }
+            return existingPrincipals;
+        }
+    }
+
+    public Object setRealmPrincipal(String realmName, String principalName, Object principal) {
+        if (realmName == null) {
+            throw new NullPointerException("realmName argument cannot be null.");
+        }
+        if (principalName == null) {
+            throw new NullPointerException(("principalName argument cannot be null."));
+        }
+        if (principal == null) {
+            return removeRealmPrincipal(realmName, principalName);
+        }
+        if (this.realmPrincipals == null) {
+            this.realmPrincipals = new HashMap<String,Map<String,Object>>();
+        }
+        Map<String,Object> principals = this.realmPrincipals.get(realmName);
+        if (principals == null) {
+            principals = new HashMap<String,Object>();
+            this.realmPrincipals.put(realmName, principals);
+        }
+        return principals.put(principalName, principal);
+    }
+
+    public Object getRealmPrincipal(String realmName, String principalName) {
+        if (realmName == null) {
+            throw new NullPointerException("realmName argument cannot be null.");
+        }
+        if (principalName == null) {
+            throw new NullPointerException(("principalName argument cannot be null."));
+        }
+        if (this.realmPrincipals == null) {
+            return null;
+        }
+        Map<String,Object> principals = this.realmPrincipals.get(realmName);
+        if (principals != null) {
+            return principals.get(principalName);
+        }
+        return null;
+    }
+
+    public Object removeRealmPrincipal(String realmName, String principalName) {
+        if (realmName == null) {
+            throw new NullPointerException("realmName argument cannot be null.");
+        }
+        if (principalName == null) {
+            throw new NullPointerException(("principalName argument cannot be null."));
+        }
+        if (this.realmPrincipals == null) {
+            return null;
+        }
+        Map<String,Object> principals = this.realmPrincipals.get(realmName);
+        if (principals != null) {
+            return principals.remove(principalName);
+        }
+        return null;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/Subject.java b/core/src/main/java/org/apache/shiro/subject/Subject.java
new file mode 100644
index 0000000..eb2f5dc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/Subject.java
@@ -0,0 +1,850 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.mgt.SubjectFactory;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A {@code Subject} represents state and security operations for a <em>single</em> application user.
+ * These operations include authentication (login/logout), authorization (access control), and
+ * session access. It is Shiro's primary mechanism for single-user security functionality.
+ * <h3>Acquiring a Subject</h3>
+ * To acquire the currently-executing {@code Subject}, application developers will almost always use
+ * {@code SecurityUtils}:
+ * <pre>
+ * {@link SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}</pre>
+ * Almost all security operations should be performed with the {@code Subject} returned from this method.
+ * <h3>Permission methods</h3>
+ * Note that there are many *Permission methods in this interface overloaded to accept String arguments instead of
+ * {@link Permission Permission} instances. They are a convenience allowing the caller to use a String representation of
+ * a {@link Permission Permission} if desired.  The underlying Authorization subsystem implementations will usually
+ * simply convert these String values to {@link Permission Permission} instances and then just call the corresponding
+ * type-safe method.  (Shiro's default implementations do String-to-Permission conversion for these methods using
+ * {@link org.apache.shiro.authz.permission.PermissionResolver PermissionResolver}s.)
+ * <p/>
+ * These overloaded *Permission methods forgo type-saftey for the benefit of convenience and simplicity,
+ * so you should choose which ones to use based on your preferences and needs.
+ *
+ * @since 0.1
+ */
+public interface Subject {
+
+    /**
+     * Returns this Subject's application-wide uniquely identifying principal, or {@code null} if this
+     * Subject is anonymous because it doesn't yet have any associated account data (for example,
+     * if they haven't logged in).
+     * <p/>
+     * The term <em>principal</em> is just a fancy security term for any identifying attribute(s) of an application
+     * user, such as a username, or user id, or public key, or anything else you might use in your application to
+     * identify a user.
+     * <h4>Uniqueness</h4>
+     * Although given names and family names (first/last) are technically considered principals as well,
+     * Shiro expects the object returned from this method to be an identifying attribute unique across
+     * your entire application.
+     * <p/>
+     * This implies that things like given names and family names are usually poor
+     * candidates as return values since they are rarely guaranteed to be unique;  Things often used for this value:
+     * <ul>
+     * <li>A {@code long} RDBMS surrogate primary key</li>
+     * <li>An application-unique username</li>
+     * <li>A {@link java.util.UUID UUID}</li>
+     * <li>An LDAP Unique ID</li>
+     * </ul>
+     * or any other similar suitable unique mechanism valuable to your application.
+     * <p/>
+     * Most implementations will simply return
+     * <code>{@link #getPrincipals()}.{@link org.apache.shiro.subject.PrincipalCollection#getPrimaryPrincipal() getPrimaryPrincipal()}</code>
+     *
+     * @return this Subject's application-specific unique identity.
+     * @see org.apache.shiro.subject.PrincipalCollection#getPrimaryPrincipal()
+     */
+    Object getPrincipal();
+
+    /**
+     * Returns this Subject's principals (identifying attributes) in the form of a {@code PrincipalCollection} or
+     * {@code null} if this Subject is anonymous because it doesn't yet have any associated account data (for example,
+     * if they haven't logged in).
+     * <p/>
+     * The word "principals" is nothing more than a fancy security term for identifying attributes associated
+     * with a Subject, aka, application user.  For example, user id, a surname (family/last name), given (first) name,
+     * social security number, nickname, username, etc, are all examples of a principal.
+     *
+     * @return all of this Subject's principals (identifying attributes).
+     * @see #getPrincipal()
+     * @see org.apache.shiro.subject.PrincipalCollection#getPrimaryPrincipal()
+     */
+    PrincipalCollection getPrincipals();
+
+    /**
+     * Returns {@code true} if this Subject is permitted to perform an action or access a resource summarized by the
+     * specified permission string.
+     * <p/>
+     * This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param permission the String representation of a Permission that is being checked.
+     * @return true if this Subject is permitted, false otherwise.
+     * @see #isPermitted(Permission permission)
+     * @since 0.9
+     */
+    boolean isPermitted(String permission);
+
+    /**
+     * Returns {@code true} if this Subject is permitted to perform an action or access a resource summarized by the
+     * specified permission.
+     * <p/>
+     * More specifically, this method determines if any {@code Permission}s associated
+     * with the subject {@link Permission#implies(Permission) imply} the specified permission.
+     *
+     * @param permission the permission that is being checked.
+     * @return true if this Subject is permitted, false otherwise.
+     */
+    boolean isPermitted(Permission permission);
+
+    /**
+     * Checks if this Subject implies the given permission strings and returns a boolean array indicating which
+     * permissions are implied.
+     * <p/>
+     * This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param permissions the String representations of the Permissions that are being checked.
+     * @return a boolean array where indices correspond to the index of the
+     *         permissions in the given list.  A true value at an index indicates this Subject is permitted for
+     *         for the associated {@code Permission} string in the list.  A false value at an index
+     *         indicates otherwise.
+     * @since 0.9
+     */
+    boolean[] isPermitted(String... permissions);
+
+    /**
+     * Checks if this Subject implies the given Permissions and returns a boolean array indicating which permissions
+     * are implied.
+     * <p/>
+     * More specifically, this method should determine if each {@code Permission} in
+     * the array is {@link Permission#implies(Permission) implied} by permissions
+     * already associated with the subject.
+     * <p/>
+     * This is primarily a performance-enhancing method to help reduce the number of
+     * {@link #isPermitted} invocations over the wire in client/server systems.
+     *
+     * @param permissions the permissions that are being checked.
+     * @return a boolean array where indices correspond to the index of the
+     *         permissions in the given list.  A true value at an index indicates this Subject is permitted for
+     *         for the associated {@code Permission} object in the list.  A false value at an index
+     *         indicates otherwise.
+     */
+    boolean[] isPermitted(List<Permission> permissions);
+
+    /**
+     * Returns {@code true} if this Subject implies all of the specified permission strings, {@code false} otherwise.
+     * <p/>
+     * This is an overloaded method for the corresponding type-safe {@link org.apache.shiro.authz.Permission Permission}
+     * variant.  Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param permissions the String representations of the Permissions that are being checked.
+     * @return true if this Subject has all of the specified permissions, false otherwise.
+     * @see #isPermittedAll(Collection)
+     * @since 0.9
+     */
+    boolean isPermittedAll(String... permissions);
+
+    /**
+     * Returns {@code true} if this Subject implies all of the specified permissions, {@code false} otherwise.
+     * <p/>
+     * More specifically, this method determines if all of the given {@code Permission}s are
+     * {@link Permission#implies(Permission) implied by} permissions already associated with this Subject.
+     *
+     * @param permissions the permissions to check.
+     * @return true if this Subject has all of the specified permissions, false otherwise.
+     */
+    boolean isPermittedAll(Collection<Permission> permissions);
+
+    /**
+     * Ensures this Subject implies the specified permission String.
+     * <p/>
+     * If this subject's existing associated permissions do not {@link Permission#implies(Permission)} imply}
+     * the given permission, an {@link org.apache.shiro.authz.AuthorizationException} will be thrown.
+     * <p/>
+     * This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param permission the String representation of the Permission to check.
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if the user does not have the permission.
+     * @since 0.9
+     */
+    void checkPermission(String permission) throws AuthorizationException;
+
+    /**
+     * Ensures this Subject {@link Permission#implies(Permission) implies} the specified {@code Permission}.
+     * <p/>
+     * If this subject's existing associated permissions do not {@link Permission#implies(Permission) imply}
+     * the given permission, an {@link org.apache.shiro.authz.AuthorizationException} will be thrown.
+     *
+     * @param permission the Permission to check.
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if this Subject does not have the permission.
+     */
+    void checkPermission(Permission permission) throws AuthorizationException;
+
+    /**
+     * Ensures this Subject
+     * {@link org.apache.shiro.authz.Permission#implies(org.apache.shiro.authz.Permission) implies} all of the
+     * specified permission strings.
+     * <p/>
+     * If this subject's existing associated permissions do not
+     * {@link org.apache.shiro.authz.Permission#implies(org.apache.shiro.authz.Permission) imply} all of the given permissions,
+     * an {@link org.apache.shiro.authz.AuthorizationException} will be thrown.
+     * <p/>
+     * This is an overloaded method for the corresponding type-safe {@link Permission Permission} variant.
+     * Please see the class-level JavaDoc for more information on these String-based permission methods.
+     *
+     * @param permissions the string representations of Permissions to check.
+     * @throws AuthorizationException if this Subject does not have all of the given permissions.
+     * @since 0.9
+     */
+    void checkPermissions(String... permissions) throws AuthorizationException;
+
+    /**
+     * Ensures this Subject
+     * {@link org.apache.shiro.authz.Permission#implies(org.apache.shiro.authz.Permission) implies} all of the
+     * specified permission strings.
+     * <p/>
+     * If this subject's existing associated permissions do not
+     * {@link org.apache.shiro.authz.Permission#implies(org.apache.shiro.authz.Permission) imply} all of the given permissions,
+     * an {@link org.apache.shiro.authz.AuthorizationException} will be thrown.
+     *
+     * @param permissions the Permissions to check.
+     * @throws AuthorizationException if this Subject does not have all of the given permissions.
+     */
+    void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;
+
+    /**
+     * Returns {@code true} if this Subject has the specified role, {@code false} otherwise.
+     *
+     * @param roleIdentifier the application-specific role identifier (usually a role id or role name).
+     * @return {@code true} if this Subject has the specified role, {@code false} otherwise.
+     */
+    boolean hasRole(String roleIdentifier);
+
+    /**
+     * Checks if this Subject has the specified roles, returning a boolean array indicating
+     * which roles are associated.
+     * <p/>
+     * This is primarily a performance-enhancing method to help reduce the number of
+     * {@link #hasRole} invocations over the wire in client/server systems.
+     *
+     * @param roleIdentifiers the application-specific role identifiers to check (usually role ids or role names).
+     * @return a boolean array where indices correspond to the index of the
+     *         roles in the given identifiers.  A true value indicates this Subject has the
+     *         role at that index.  False indicates this Subject does not have the role at that index.
+     */
+    boolean[] hasRoles(List<String> roleIdentifiers);
+
+    /**
+     * Returns {@code true} if this Subject has all of the specified roles, {@code false} otherwise.
+     *
+     * @param roleIdentifiers the application-specific role identifiers to check (usually role ids or role names).
+     * @return true if this Subject has all the roles, false otherwise.
+     */
+    boolean hasAllRoles(Collection<String> roleIdentifiers);
+
+    /**
+     * Asserts this Subject has the specified role by returning quietly if they do or throwing an
+     * {@link org.apache.shiro.authz.AuthorizationException} if they do not.
+     *
+     * @param roleIdentifier the application-specific role identifier (usually a role id or role name ).
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if this Subject does not have the role.
+     */
+    void checkRole(String roleIdentifier) throws AuthorizationException;
+
+    /**
+     * Asserts this Subject has all of the specified roles by returning quietly if they do or throwing an
+     * {@link org.apache.shiro.authz.AuthorizationException} if they do not.
+     *
+     * @param roleIdentifiers the application-specific role identifiers to check (usually role ids or role names).
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if this Subject does not have all of the specified roles.
+     */
+    void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;
+
+    /**
+     * Same as {@link #checkRoles(Collection<String> roleIdentifiers) checkRoles(Collection<String> roleIdentifiers)} but
+     * doesn't require a collection as a an argument.
+     * Asserts this Subject has all of the specified roles by returning quietly if they do or throwing an
+     * {@link org.apache.shiro.authz.AuthorizationException} if they do not.
+     *
+     * @param roleIdentifiers roleIdentifiers the application-specific role identifiers to check (usually role ids or role names).
+     * @throws AuthorizationException org.apache.shiro.authz.AuthorizationException
+     *          if this Subject does not have all of the specified roles.
+     * @since 1.1.0
+     */
+    void checkRoles(String... roleIdentifiers) throws AuthorizationException;
+
+    /**
+     * Performs a login attempt for this Subject/user.  If unsuccessful,
+     * an {@link AuthenticationException} is thrown, the subclass of which identifies why the attempt failed.
+     * If successful, the account data associated with the submitted principals/credentials will be
+     * associated with this {@code Subject} and the method will return quietly.
+     * <p/>
+     * Upon returning quietly, this {@code Subject} instance can be considered
+     * authenticated and {@link #getPrincipal() getPrincipal()} will be non-null and
+     * {@link #isAuthenticated() isAuthenticated()} will be {@code true}.
+     *
+     * @param token the token encapsulating the subject's principals and credentials to be passed to the
+     *              Authentication subsystem for verification.
+     * @throws org.apache.shiro.authc.AuthenticationException
+     *          if the authentication attempt fails.
+     * @since 0.9
+     */
+    void login(AuthenticationToken token) throws AuthenticationException;
+
+    /**
+     * Returns {@code true} if this Subject/user proved their identity <em>during their current session</em>
+     * by providing valid credentials matching those known to the system, {@code false} otherwise.
+     * <p/>
+     * Note that even if this Subject's identity has been remembered via 'remember me' services, this method will
+     * still return {@code false} unless the user has actually logged in with proper credentials <em>during their
+     * current session</em>.  See the {@link #isRemembered() isRemembered()} method JavaDoc for more.
+     *
+     * @return {@code true} if this Subject proved their identity during their current session
+     *         by providing valid credentials matching those known to the system, {@code false} otherwise.
+     * @since 0.9
+     */
+    boolean isAuthenticated();
+
+
+    /**
+     * Returns {@code true} if this {@code Subject} has an identity (it is not anonymous) and the identity
+     * (aka {@link #getPrincipals() principals}) is remembered from a successful authentication during a previous
+     * session.
+     * <p/>
+     * Although the underlying implementation determines exactly how this method functions, most implementations have
+     * this method act as the logical equivalent to this code:
+     * <pre>
+     * {@link #getPrincipal() getPrincipal()} != null && !{@link #isAuthenticated() isAuthenticated()}</pre>
+     * <p/>
+     * Note as indicated by the above code example, if a {@code Subject} is remembered, they are
+     * <em>NOT</em> considered authenticated.  A check against {@link #isAuthenticated() isAuthenticated()} is a more
+     * strict check than that reflected by this method.  For example, a check to see if a subject can access financial
+     * information should almost always depend on {@link #isAuthenticated() isAuthenticated()} to <em>guarantee</em> a
+     * verified identity, and not this method.
+     * <p/>
+     * Once the subject is authenticated, they are no longer considered only remembered because their identity would
+     * have been verified during the current session.
+     * <h4>Remembered vs Authenticated</h4>
+     * Authentication is the process of <em>proving</em> you are who you say you are.  When a user is only remembered,
+     * the remembered identity gives the system an idea who that user probably is, but in reality, has no way of
+     * absolutely <em>guaranteeing</em> if the remembered {@code Subject} represents the user currently
+     * using the application.
+     * <p/>
+     * So although many parts of the application can still perform user-specific logic based on the remembered
+     * {@link #getPrincipals() principals}, such as customized views, it should never perform highly-sensitive
+     * operations until the user has legitimately verified their identity by executing a successful authentication
+     * attempt.
+     * <p/>
+     * We see this paradigm all over the web, and we will use <a href="http://www.amazon.com">Amazon.com</a> as an
+     * example:
+     * <p/>
+     * When you visit Amazon.com and perform a login and ask it to 'remember me', it will set a cookie with your
+     * identity.  If you don't log out and your session expires, and you come back, say the next day, Amazon still knows
+     * who you <em>probably</em> are: you still see all of your book and movie recommendations and similar user-specific
+     * features since these are based on your (remembered) user id.
+     * <p/>
+     * BUT, if you try to do something sensitive, such as access your account's billing data, Amazon forces you
+     * to do an actual log-in, requiring your username and password.
+     * <p/>
+     * This is because although amazon.com assumed your identity from 'remember me', it recognized that you were not
+     * actually authenticated.  The only way to really guarantee you are who you say you are, and therefore allow you
+     * access to sensitive account data, is to force you to perform an actual successful authentication.  You can
+     * check this guarantee via the {@link #isAuthenticated() isAuthenticated()} method and not via this method.
+     *
+     * @return {@code true} if this {@code Subject}'s identity (aka {@link #getPrincipals() principals}) is
+     *         remembered from a successful authentication during a previous session, {@code false} otherwise.
+     * @since 1.0
+     */
+    boolean isRemembered();
+
+    /**
+     * Returns the application {@code Session} associated with this Subject.  If no session exists when this
+     * method is called, a new session will be created, associated with this Subject, and then returned.
+     *
+     * @return the application {@code Session} associated with this Subject.
+     * @see #getSession(boolean)
+     * @since 0.2
+     */
+    Session getSession();
+
+    /**
+     * Returns the application {@code Session} associated with this Subject.  Based on the boolean argument,
+     * this method functions as follows:
+     * <ul>
+     * <li>If there is already an existing session associated with this {@code Subject}, it is returned and
+     * the {@code create} argument is ignored.</li>
+     * <li>If no session exists and {@code create} is {@code true}, a new session will be created, associated with
+     * this {@code Subject} and then returned.</li>
+     * <li>If no session exists and {@code create} is {@code false}, {@code null} is returned.</li>
+     * </ul>
+     *
+     * @param create boolean argument determining if a new session should be created or not if there is no existing session.
+     * @return the application {@code Session} associated with this {@code Subject} or {@code null} based
+     *         on the above described logic.
+     * @since 0.2
+     */
+    Session getSession(boolean create);
+
+    /**
+     * Logs out this Subject and invalidates and/or removes any associated entities,
+     * such as a {@link Session Session} and authorization data.  After this method is called, the Subject is
+     * considered 'anonymous' and may continue to be used for another log-in if desired.
+     * <h3>Web Environment Warning</h3>
+     * Calling this method in web environments will usually remove any associated session cookie as part of
+     * session invalidation.  Because cookies are part of the HTTP header, and headers can only be set before the
+     * response body (html, image, etc) is sent, this method in web environments must be called before <em>any</em>
+     * content has been rendered.
+     * <p/>
+     * The typical approach most applications use in this scenario is to redirect the user to a different
+     * location (e.g. home page) immediately after calling this method.  This is an effect of the HTTP protocol
+     * itself and not a reflection of Shiro's implementation.
+     * <p/>
+     * Non-HTTP environments may of course use a logged-out subject for login again if desired.
+     */
+    void logout();
+
+    /**
+     * Associates the specified {@code Callable} with this {@code Subject} instance and then executes it on the
+     * currently running thread.  If you want to execute the {@code Callable} on a different thread, it is better to
+     * use the {@link #associateWith(Callable)} method instead.
+     *
+     * @param callable the Callable to associate with this subject and then execute.
+     * @param <V>      the type of return value the {@code Callable} will return
+     * @return the resulting object returned by the {@code Callable}'s execution.
+     * @throws ExecutionException if the {@code Callable}'s {@link Callable#call call} method throws an exception.
+     * @since 1.0
+     */
+    <V> V execute(Callable<V> callable) throws ExecutionException;
+
+    /**
+     * Associates the specified {@code Runnable} with this {@code Subject} instance and then executes it on the
+     * currently running thread.  If you want to execute the {@code Runnable} on a different thread, it is better to
+     * use the {@link #associateWith(Runnable)} method instead.
+     * <p/>
+     * <b>Note</b>: This method is primarily provided to execute existing/legacy Runnable implementations.  It is better
+     * for new code to use {@link #execute(Callable)} since that supports the ability to return values and catch
+     * exceptions.
+     *
+     * @param runnable the {@code Runnable} to associate with this {@code Subject} and then execute.
+     * @since 1.0
+     */
+    void execute(Runnable runnable);
+
+    /**
+     * Returns a {@code Callable} instance matching the given argument while additionally ensuring that it will
+     * retain and execute under this Subject's identity.  The returned object can be used with an
+     * {@link java.util.concurrent.ExecutorService ExecutorService} to execute as this Subject.
+     * <p/>
+     * This will effectively ensure that any calls to
+     * {@code SecurityUtils}.{@link SecurityUtils#getSubject() getSubject()} and related functionality will continue
+     * to function properly on any thread that executes the returned {@code Callable} instance.
+     *
+     * @param callable the callable to execute as this {@code Subject}
+     * @param <V>      the {@code Callable}s return value type
+     * @return a {@code Callable} that can be run as this {@code Subject}.
+     * @since 1.0
+     */
+    <V> Callable<V> associateWith(Callable<V> callable);
+
+    /**
+     * Returns a {@code Runnable} instance matching the given argument while additionally ensuring that it will
+     * retain and execute under this Subject's identity.  The returned object can be used with an
+     * {@link java.util.concurrent.Executor Executor} or another thread to execute as this Subject.
+     * <p/>
+     * This will effectively ensure that any calls to
+     * {@code SecurityUtils}.{@link SecurityUtils#getSubject() getSubject()} and related functionality will continue
+     * to function properly on any thread that executes the returned {@code Runnable} instance.
+     * <p/>
+     * *Note that if you need a return value to be returned as a result of the runnable's execution or if you need to
+     * react to any Exceptions, it is highly recommended to use the
+     * {@link #associateWith(java.util.concurrent.Callable) createCallable} method instead of this one.
+     *
+     * @param runnable the runnable to execute as this {@code Subject}
+     * @return a {@code Runnable} that can be run as this {@code Subject} on another thread.
+     * @see #associateWith (java.util.concurrent.Callable)
+     * @since 1.0
+     */
+    Runnable associateWith(Runnable runnable);
+
+    /**
+     * Allows this subject to 'run as' or 'assume' another identity indefinitely.  This can only be
+     * called when the {@code Subject} instance already has an identity (i.e. they are remembered from a previous
+     * log-in or they have authenticated during their current session).
+     * <p/>
+     * Some notes about {@code runAs}:
+     * <ul>
+     * <li>You can tell if a {@code Subject} is 'running as' another identity by calling the
+     * {@link #isRunAs() isRunAs()} method.</li>
+     * <li>If running as another identity, you can determine what the previous 'pre run as' identity
+     * was by calling the {@link #getPreviousPrincipals() getPreviousPrincipals()} method.</li>
+     * <li>When you want a {@code Subject} to stop running as another identity, you can return to its previous
+     * 'pre run as' identity by calling the {@link #releaseRunAs() releaseRunAs()} method.</li>
+     * </ul>
+     *
+     * @param principals the identity to 'run as', aka the identity to <em>assume</em> indefinitely.
+     * @throws NullPointerException  if the specified principals collection is {@code null} or empty.
+     * @throws IllegalStateException if this {@code Subject} does not yet have an identity of its own.
+     * @since 1.0
+     */
+    void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;
+
+    /**
+     * Returns {@code true} if this {@code Subject} is 'running as' another identity other than its original one or
+     * {@code false} otherwise (normal {@code Subject} state).  See the {@link #runAs runAs} method for more
+     * information.
+     *
+     * @return {@code true} if this {@code Subject} is 'running as' another identity other than its original one or
+     *         {@code false} otherwise (normal {@code Subject} state).
+     * @see #runAs
+     * @since 1.0
+     */
+    boolean isRunAs();
+
+    /**
+     * Returns the previous 'pre run as' identity of this {@code Subject} before assuming the current
+     * {@link #runAs runAs} identity, or {@code null} if this {@code Subject} is not operating under an assumed
+     * identity (normal state). See the {@link #runAs runAs} method for more information.
+     *
+     * @return the previous 'pre run as' identity of this {@code Subject} before assuming the current
+     *         {@link #runAs runAs} identity, or {@code null} if this {@code Subject} is not operating under an assumed
+     *         identity (normal state).
+     * @see #runAs
+     * @since 1.0
+     */
+    PrincipalCollection getPreviousPrincipals();
+
+    /**
+     * Releases the current 'run as' (assumed) identity and reverts back to the previous 'pre run as'
+     * identity that existed before {@code #runAs runAs} was called.
+     * <p/>
+     * This method returne 'run as' (assumed) identity being released or {@code null} if this {@code Subject} is not
+     * operating under an assumed identity.
+     *
+     * @return the 'run as' (assumed) identity being released or {@code null} if this {@code Subject} is not operating
+     *         under an assumed identity.
+     * @see #runAs
+     * @since 1.0
+     */
+    PrincipalCollection releaseRunAs();
+
+    /**
+     * Builder design pattern implementation for creating {@link Subject} instances in a simplified way without
+     * requiring knowledge of Shiro's construction techniques.
+     * <p/>
+     * <b>NOTE</b>: This is provided for framework development support only and should typically never be used by
+     * application developers.  {@code Subject} instances should generally be acquired by using
+     * <code>SecurityUtils.{@link SecurityUtils#getSubject() getSubject()}</code>
+     * <h4>Usage</h4>
+     * The simplest usage of this builder is to construct an anonymous, session-less {@code Subject} instance:
+     * <pre>
+     * Subject subject = new Subject.{@link #Builder() Builder}().{@link #buildSubject() buildSubject()};</pre>
+     * The default, no-arg {@code Subject.Builder()} constructor shown above will use the application's
+     * currently accessible {@code SecurityManager} via
+     * <code>SecurityUtils.{@link SecurityUtils#getSecurityManager() getSecurityManager()}</code>.  You may also
+     * specify the exact {@code SecurityManager} instance to be used by the additional
+     * <code>Subject.{@link #Builder(org.apache.shiro.mgt.SecurityManager) Builder(securityManager)}</code>
+     * constructor if desired.
+     * <p/>
+     * All other methods may be called before the {@link #buildSubject() buildSubject()} method to
+     * provide context on how to construct the {@code Subject} instance.  For example, if you have a session id and
+     * want to acquire the {@code Subject} that 'owns' that session (assuming the session exists and is not expired):
+     * <pre>
+     * Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject();</pre>
+     * <p/>
+     * Similarly, if you want a {@code Subject} instance reflecting a certain identity:
+     * <pre>
+     * PrincipalCollection principals = new SimplePrincipalCollection("username", <em>yourRealmName</em>);
+     * Subject subject = new Subject.Builder().principals(principals).build();</pre>
+     * <p/>
+     * <b>Note*</b> that the returned {@code Subject} instance is <b>not</b> automatically bound to the application (thread)
+     * for further use.  That is,
+     * {@link org.apache.shiro.SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}
+     * will not automatically return the same instance as what is returned by the builder.  It is up to the framework
+     * developer to bind the built {@code Subject} for continued use if desired.
+     *
+     * @since 1.0
+     */
+    public static class Builder {
+
+        /**
+         * Hold all contextual data via the Builder instance's method invocations to be sent to the
+         * {@code SecurityManager} during the {@link #buildSubject} call.
+         */
+        private final SubjectContext subjectContext;
+
+        /**
+         * The SecurityManager to invoke during the {@link #buildSubject} call.
+         */
+        private final SecurityManager securityManager;
+
+        /**
+         * Constructs a new {@link Subject.Builder} instance, using the {@code SecurityManager} instance available
+         * to the calling code as determined by a call to {@link org.apache.shiro.SecurityUtils#getSecurityManager()}
+         * to build the {@code Subject} instance.
+         */
+        public Builder() {
+            this(SecurityUtils.getSecurityManager());
+        }
+
+        /**
+         * Constructs a new {@link Subject.Builder} instance which will use the specified {@code SecurityManager} when
+         * building the {@code Subject} instance.
+         *
+         * @param securityManager the {@code SecurityManager} to use when building the {@code Subject} instance.
+         */
+        public Builder(SecurityManager securityManager) {
+            if (securityManager == null) {
+                throw new NullPointerException("SecurityManager method argument cannot be null.");
+            }
+            this.securityManager = securityManager;
+            this.subjectContext = newSubjectContextInstance();
+            if (this.subjectContext == null) {
+                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
+                        "cannot be null.");
+            }
+            this.subjectContext.setSecurityManager(securityManager);
+        }
+
+        /**
+         * Creates a new {@code SubjectContext} instance to be used to populate with subject contextual data that
+         * will then be sent to the {@code SecurityManager} to create a new {@code Subject} instance.
+         *
+         * @return a new {@code SubjectContext} instance
+         */
+        protected SubjectContext newSubjectContextInstance() {
+            return new DefaultSubjectContext();
+        }
+
+        /**
+         * Returns the backing context used to build the {@code Subject} instance, available to subclasses
+         * since the {@code context} class attribute is marked as {@code private}.
+         *
+         * @return the backing context used to build the {@code Subject} instance, available to subclasses.
+         */
+        protected SubjectContext getSubjectContext() {
+            return this.subjectContext;
+        }
+
+        /**
+         * Enables building a {@link Subject Subject} instance that owns the {@link Session Session} with the
+         * specified {@code sessionId}.
+         * <p/>
+         * Usually when specifying a {@code sessionId}, no other {@code Builder} methods would be specified because
+         * everything else (principals, inet address, etc) can usually be reconstructed based on the referenced
+         * session alone.  In other words, this is almost always sufficient:
+         * <pre>
+         * new Subject.Builder().sessionId(sessionId).buildSubject();</pre>
+         * <p/>
+         * <b>Although simple in concept, this method provides very powerful functionality previously absent in almost
+         * all Java environments:</b>
+         * <p/>
+         * The ability to reference a {@code Subject} and their server-side session
+         * <em>across clients of different mediums</em> such as web applications, Java applets,
+         * standalone C# clients over XML-RPC and/or SOAP, and many others. This is a <em>huge</em>
+         * benefit in heterogeneous enterprise applications.
+         * <p/>
+         * To maintain session integrity across client mediums, the {@code sessionId} <b>must</b> be transmitted
+         * to all client mediums securely (e.g. over SSL) to prevent man-in-the-middle attacks.  This
+         * is nothing new - all web applications are susceptible to the same problem when transmitting
+         * {@code Cookie}s or when using URL rewriting.  As long as the
+         * {@code sessionId} is transmitted securely, session integrity can be maintained.
+         *
+         * @param sessionId the id of the session that backs the desired Subject being acquired.
+         * @return this {@code Builder} instance for method chaining.
+         */
+        public Builder sessionId(Serializable sessionId) {
+            if (sessionId != null) {
+                this.subjectContext.setSessionId(sessionId);
+            }
+            return this;
+        }
+
+        /**
+         * Ensures the {@code Subject} being built will reflect the specified host name or IP as its originating
+         * location.
+         *
+         * @param host the host name or IP address to use as the {@code Subject}'s originating location.
+         * @return this {@code Builder} instance for method chaining.
+         */
+        public Builder host(String host) {
+            if (StringUtils.hasText(host)) {
+                this.subjectContext.setHost(host);
+            }
+            return this;
+        }
+
+        /**
+         * Ensures the {@code Subject} being built will use the specified {@link Session} instance.  Note that it is
+         * more common to use the {@link #sessionId sessionId} builder method rather than having to construct a
+         * {@code Session} instance for this method.
+         *
+         * @param session the session to use as the {@code Subject}'s {@link Session}
+         * @return this {@code Builder} instance for method chaining.
+         */
+        public Builder session(Session session) {
+            if (session != null) {
+                this.subjectContext.setSession(session);
+            }
+            return this;
+        }
+
+        /**
+         * Ensures the {@code Subject} being built will reflect the specified principals (aka identity).
+         * <p/>
+         * For example, if your application's unique identifier for users is a {@code String} username, and you wanted
+         * to create a {@code Subject} instance that reflected a user whose username is
+         * '{@code jsmith}', and you knew the Realm that could acquire {@code jsmith}'s principals based on the username
+         * was named "{@code myRealm}", you might create the '{@code jsmith} {@code Subject} instance this
+         * way:
+         * <pre>
+         * PrincipalCollection identity = new {@link org.apache.shiro.subject.SimplePrincipalCollection#SimplePrincipalCollection(Object, String) SimplePrincipalCollection}("jsmith", "myRealm");
+         * Subject jsmith = new Subject.Builder().principals(identity).buildSubject();</pre>
+         * <p/>
+         * Similarly, if your application's unique identifier for users is a {@code long} value (such as might be used
+         * as a primary key in a relational database) and you were using a {@code JDBC}
+         * {@code Realm} named, (unimaginatively) "jdbcRealm", you might create the Subject
+         * instance this way:
+         * <pre>
+         * long userId = //get user ID from somewhere
+         * PrincipalCollection userIdentity = new {@link org.apache.shiro.subject.SimplePrincipalCollection#SimplePrincipalCollection(Object, String) SimplePrincipalCollection}(<em>userId</em>, "jdbcRealm");
+         * Subject user = new Subject.Builder().principals(identity).buildSubject();</pre>
+         *
+         * @param principals the principals to use as the {@code Subject}'s identity.
+         * @return this {@code Builder} instance for method chaining.
+         */
+        public Builder principals(PrincipalCollection principals) {
+            if (!CollectionUtils.isEmpty(principals)) {
+                this.subjectContext.setPrincipals(principals);
+            }
+            return this;
+        }
+
+        /**
+         * Configures whether or not the created Subject instance can create a new {@code Session} if one does not
+         * already exist.  If set to {@code false}, any application calls to
+         * {@code subject.getSession()} or {@code subject.getSession(true))} will result in a SessionException.
+         * <p/>
+         * This setting is {@code true} by default, as most applications find value in sessions.
+         *
+         * @param enabled whether or not the created Subject instance can create a new {@code Session} if one does not
+         *                already exist.
+         * @return this {@code Builder} instance for method chaining.
+         * @since 1.2
+         */
+        public Builder sessionCreationEnabled(boolean enabled) {
+            this.subjectContext.setSessionCreationEnabled(enabled);
+            return this;
+        }
+
+        /**
+         * Ensures the {@code Subject} being built will be considered
+         * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated}.  Per the
+         * {@link org.apache.shiro.subject.Subject#isAuthenticated() isAuthenticated()} JavaDoc, be careful
+         * when specifying {@code true} - you should know what you are doing and have a good reason for ignoring Shiro's
+         * default authentication state mechanisms.
+         *
+         * @param authenticated whether or not the built {@code Subject} will be considered authenticated.
+         * @return this {@code Builder} instance for method chaining.
+         * @see org.apache.shiro.subject.Subject#isAuthenticated()
+         */
+        public Builder authenticated(boolean authenticated) {
+            this.subjectContext.setAuthenticated(authenticated);
+            return this;
+        }
+
+        /**
+         * Allows custom attributes to be added to the underlying context {@code Map} used to construct the
+         * {@link Subject} instance.
+         * <p/>
+         * A {@code null} key throws an {@link IllegalArgumentException}. A {@code null} value effectively removes
+         * any previously stored attribute under the given key from the context map.
+         * <p/>
+         * <b>*NOTE*:</b> This method is only useful when configuring Shiro with a custom {@link SubjectFactory}
+         * implementation.  This method allows end-users to append additional data to the context map which the
+         * {@code SubjectFactory} implementation can use when building custom Subject instances. As such, this method
+         * is only useful when a custom {@code SubjectFactory} implementation has been configured.
+         *
+         * @param attributeKey   the key under which the corresponding value will be stored in the context {@code Map}.
+         * @param attributeValue the value to store in the context map under the specified {@code attributeKey}.
+         * @return this {@code Builder} instance for method chaining.
+         * @throws IllegalArgumentException if the {@code attributeKey} is {@code null}.
+         * @see SubjectFactory#createSubject(SubjectContext)
+         */
+        public Builder contextAttribute(String attributeKey, Object attributeValue) {
+            if (attributeKey == null) {
+                String msg = "Subject context map key cannot be null.";
+                throw new IllegalArgumentException(msg);
+            }
+            if (attributeValue == null) {
+                this.subjectContext.remove(attributeKey);
+            } else {
+                this.subjectContext.put(attributeKey, attributeValue);
+            }
+            return this;
+        }
+
+        /**
+         * Creates and returns a new {@code Subject} instance reflecting the cumulative state acquired by the
+         * other methods in this class.
+         * <p/>
+         * This {@code Builder} instance will still retain the underlying state after this method is called - it
+         * will not clear it; repeated calls to this method will return multiple {@link Subject} instances, all
+         * reflecting the exact same state.  If a new (different) {@code Subject} is to be constructed, a new
+         * {@code Builder} instance must be created.
+         * <p/>
+         * <b>Note</b> that the returned {@code Subject} instance is <b>not</b> automatically bound to the application
+         * (thread) for further use.  That is,
+         * {@link org.apache.shiro.SecurityUtils SecurityUtils}.{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}
+         * will not automatically return the same instance as what is returned by the builder.  It is up to the
+         * framework developer to bind the returned {@code Subject} for continued use if desired.
+         *
+         * @return a new {@code Subject} instance reflecting the cumulative state acquired by the
+         *         other methods in this class.
+         */
+        public Subject buildSubject() {
+            return this.securityManager.createSubject(this.subjectContext);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/SubjectContext.java b/core/src/main/java/org/apache/shiro/subject/SubjectContext.java
new file mode 100644
index 0000000..3cb7a88
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/SubjectContext.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * A {@code SubjectContext} is a 'bucket' of data presented to a {@link SecurityManager} which interprets
+ * this data to construct {@link org.apache.shiro.subject.Subject Subject} instances.  It is essentially a Map of data
+ * with a few additional type-safe methods for easy retrieval of objects commonly used to construct Subject instances.
+ * <p/>
+ * While this interface contains type-safe setters and getters for common data types, the map can contain anything
+ * additional that might be needed by the {@link SecurityManager} or
+ * {@link org.apache.shiro.mgt.SubjectFactory SubjectFactory} implementation to construct {@code Subject} instances.
+ * <h2>Data Resolution</h2>
+ * The {@link SubjectContext} interface also allows for heuristic resolution of data used to construct a subject
+ * instance.  That is, if an attribute has not been explicitly provided via a setter method, the {@code resolve*}
+ * methods can use heuristics to obtain that data in another way from other attributes.
+ * <p/>
+ * For example, if one calls {@link #getPrincipals()} and no principals are returned, perhaps the principals exist
+ * in the {@link #getSession() session} or another attribute in the context.  The {@link #resolvePrincipals()} will know
+ * how to resolve the principals based on heuristics.  If the {@code resolve*} methods return {@code null} then the
+ * data could not be achieved by any heuristics and must be considered as not available in the context.
+ * <p/>
+ * The general idea is that the normal getters can be called to see if the value was explicitly set.  The
+ * {@code resolve*} methods should be used when actually constructing the {@code Subject} instance to ensure the most
+ * specific/accurate data can be used.
+ * <p/>
+ * <b>USAGE</b>: Most Shiro end-users will never use a {@code SubjectContext} instance directly and instead will use a
+ * {@link Subject.Builder} (which internally uses a {@code SubjectContext}) and build {@code Subject} instances that
+ * way.
+ *
+ * @see org.apache.shiro.mgt.SecurityManager#createSubject SecurityManager.createSubject
+ * @see org.apache.shiro.mgt.SubjectFactory SubjectFactory
+ * @since 1.0
+ */
+public interface SubjectContext extends Map<String, Object> {
+
+    /**
+     * Returns the SecurityManager instance that should be used to back the constructed {@link Subject} instance or
+     * {@code null} if one has not yet been provided to this context.
+     *
+     * @return the SecurityManager instance that should be used to back the constructed {@link Subject} instance or
+     *         {@code null} if one has not yet been provided to this context.
+     */
+    SecurityManager getSecurityManager();
+
+    /**
+     * Sets the SecurityManager instance that should be used to back the constructed {@link Subject} instance
+     * (typically used to support {@link org.apache.shiro.subject.support.DelegatingSubject DelegatingSubject} implementations).
+     *
+     * @param securityManager the SecurityManager instance that should be used to back the constructed {@link Subject}
+     *                        instance.
+     */
+    void setSecurityManager(SecurityManager securityManager);
+
+    /**
+     * Resolves the {@code SecurityManager} instance that should be used to back the constructed {@link Subject}
+     * instance (typically used to support {@link org.apache.shiro.subject.support.DelegatingSubject DelegatingSubject} implementations).
+     *
+     * @return the {@code SecurityManager} instance that should be used to back the constructed {@link Subject}
+     *         instance
+     */
+    SecurityManager resolveSecurityManager();
+
+    /**
+     * Returns the session id of the session that should be associated with the constructed {@link Subject} instance.
+     * <p/>
+     * The construction process is expected to resolve the session with the specified id and then construct the Subject
+     * instance based on the resolved session.
+     *
+     * @return the session id of the session that should be associated with the constructed {@link Subject} instance.
+     */
+    Serializable getSessionId();
+
+    /**
+     * Sets the session id of the session that should be associated with the constructed {@link Subject} instance.
+     * <p/>
+     * The construction process is expected to resolve the session with the specified id and then construct the Subject
+     * instance based on the resolved session.
+     *
+     * @param sessionId the session id of the session that should be associated with the constructed {@link Subject}
+     *                  instance.
+     */
+    void setSessionId(Serializable sessionId);
+
+    /**
+     * Returns any existing {@code Subject} that may be in use at the time the new {@code Subject} instance is
+     * being created.
+     * <p/>
+     * This is typically used in the case where the existing {@code Subject} instance returned by
+     * this method is unauthenticated and a new {@code Subject} instance is being created to reflect a successful
+     * authentication - you want to return most of the state of the previous {@code Subject} instance when creating the
+     * newly authenticated instance.
+     *
+     * @return any existing {@code Subject} that may be in use at the time the new {@code Subject} instance is
+     *         being created.
+     */
+    Subject getSubject();
+
+    /**
+     * Sets the existing {@code Subject} that may be in use at the time the new {@code Subject} instance is
+     * being created.
+     * <p/>
+     * This is typically used in the case where the existing {@code Subject} instance returned by
+     * this method is unauthenticated and a new {@code Subject} instance is being created to reflect a successful
+     * authentication - you want to return most of the state of the previous {@code Subject} instance when creating the
+     * newly authenticated instance.
+     *
+     * @param subject the existing {@code Subject} that may be in use at the time the new {@code Subject} instance is
+     *                being created.
+     */
+    void setSubject(Subject subject);
+
+    /**
+     * Returns the principals (aka identity) that the constructed {@code Subject} should reflect.
+     *
+     * @return the principals (aka identity) that the constructed {@code Subject} should reflect.
+     */
+    PrincipalCollection getPrincipals();
+
+    PrincipalCollection resolvePrincipals();
+
+    /**
+     * Sets the principals (aka identity) that the constructed {@code Subject} should reflect.
+     *
+     * @param principals the principals (aka identity) that the constructed {@code Subject} should reflect.
+     */
+    void setPrincipals(PrincipalCollection principals);
+
+    /**
+     * Returns the {@code Session} to use when building the {@code Subject} instance.  Note that it is more
+     * common to specify a {@link #setSessionId sessionId} to acquire the desired session rather than having to
+     * construct a {@code Session} to be returned by this method.
+     *
+     * @return the {@code Session} to use when building the {@code Subject} instance.
+     */
+    Session getSession();
+
+    /**
+     * Sets the {@code Session} to use when building the {@code Subject} instance.  Note that it is more
+     * common to specify a {@link #setSessionId sessionId} to automatically resolve the desired session rather than
+     * constructing a {@code Session} to call this method.
+     *
+     * @param session the {@code Session} to use when building the {@code Subject} instance.
+     */
+    void setSession(Session session);
+
+    Session resolveSession();
+
+    /**
+     * Returns {@code true} if the constructed {@code Subject} should be considered authenticated, {@code false}
+     * otherwise.  Be careful setting this value to {@code true} - you should know what you are doing and have a good
+     * reason for ignoring Shiro's default authentication state mechanisms.
+     *
+     * @return {@code true} if the constructed {@code Subject} should be considered authenticated, {@code false}
+     *         otherwise.
+     */
+    boolean isAuthenticated();
+
+    /**
+     * Sets whether or not the constructed {@code Subject} instance should be considered as authenticated.  Be careful
+     * when specifying {@code true} - you should know what you are doing and have a good reason for ignoring Shiro's
+     * default authentication state mechanisms.
+     *
+     * @param authc whether or not the constructed {@code Subject} instance should be considered as authenticated.
+     */
+    void setAuthenticated(boolean authc);
+
+    /**
+     * Returns {@code true} if the constructed {@code Subject} should be allowed to create a session, {@code false}
+     * otherwise.  Shiro's configuration defaults to {@code true} as most applications find value in Sessions.
+     *
+     * @return {@code true} if the constructed {@code Subject} should be allowed to create sessions, {@code false}
+     * otherwise.
+     * @since 1.2
+     */
+    boolean isSessionCreationEnabled();
+
+    /**
+     * Sets whether or not the constructed {@code Subject} instance should be allowed to create a session,
+     * {@code false} otherwise.
+     *
+     * @param enabled whether or not the constructed {@code Subject} instance should be allowed to create a session,
+     * {@code false} otherwise.
+     * @since 1.2
+     */
+    void setSessionCreationEnabled(boolean enabled);
+
+    boolean resolveAuthenticated();
+
+    AuthenticationInfo getAuthenticationInfo();
+
+    void setAuthenticationInfo(AuthenticationInfo info);
+
+    AuthenticationToken getAuthenticationToken();
+
+    void setAuthenticationToken(AuthenticationToken token);
+
+    /**
+     * Returns the host name or IP that should reflect the constructed {@code Subject}'s originating location.
+     *
+     * @return the host name or IP that should reflect the constructed {@code Subject}'s originating location.
+     */
+    String getHost();
+
+    /**
+     * Sets the host name or IP that should reflect the constructed {@code Subject}'s originating location.
+     *
+     * @param host the host name or IP that should reflect the constructed {@code Subject}'s originating location.
+     */
+    void setHost(String host);
+
+    String resolveHost();
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/package-info.java b/core/src/main/java/org/apache/shiro/subject/package-info.java
new file mode 100644
index 0000000..6995f78
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components supporting the {@link org.apache.shiro.subject.Subject Subject} interface, the most important concept in
+ * Shiro's API.
+ * <p/>
+ * A <code>Subject</code> is <em>the</em> primary component when using Shiro programatically for single-user
+ * security operations, and it is the handle to any accessible user security data.  All single-user
+ * authentication, authorization and session operations are performed via a <code>Subject</code> instance.
+ */
+package org.apache.shiro.subject;
diff --git a/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java b/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java
new file mode 100644
index 0000000..227c89d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/DefaultSubjectContext.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.UnavailableSecurityManagerException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.HostAuthenticationToken;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.MapContext;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of the {@link SubjectContext} interface.  Note that the getters and setters are not
+ * simple pass-through methods to an underlying attribute;  the getters will employ numerous heuristics to acquire
+ * their data attribute as best as possible (for example, if {@link #getPrincipals} is invoked, if the principals aren't
+ * in the backing map, it might check to see if there is a subject or session in the map and attempt to acquire the
+ * principals from those objects).
+ *
+ * @since 1.0
+ */
+public class DefaultSubjectContext extends MapContext implements SubjectContext {
+
+    private static final String SECURITY_MANAGER = DefaultSubjectContext.class.getName() + ".SECURITY_MANAGER";
+
+    private static final String SESSION_ID = DefaultSubjectContext.class.getName() + ".SESSION_ID";
+
+    private static final String AUTHENTICATION_TOKEN = DefaultSubjectContext.class.getName() + ".AUTHENTICATION_TOKEN";
+
+    private static final String AUTHENTICATION_INFO = DefaultSubjectContext.class.getName() + ".AUTHENTICATION_INFO";
+
+    private static final String SUBJECT = DefaultSubjectContext.class.getName() + ".SUBJECT";
+
+    private static final String PRINCIPALS = DefaultSubjectContext.class.getName() + ".PRINCIPALS";
+
+    private static final String SESSION = DefaultSubjectContext.class.getName() + ".SESSION";
+
+    private static final String AUTHENTICATED = DefaultSubjectContext.class.getName() + ".AUTHENTICATED";
+
+    private static final String HOST = DefaultSubjectContext.class.getName() + ".HOST";
+
+    public static final String SESSION_CREATION_ENABLED = DefaultSubjectContext.class.getName() + ".SESSION_CREATION_ENABLED";
+
+    /**
+     * The session key that is used to store subject principals.
+     */
+    public static final String PRINCIPALS_SESSION_KEY = DefaultSubjectContext.class.getName() + "_PRINCIPALS_SESSION_KEY";
+
+    /**
+     * The session key that is used to store whether or not the user is authenticated.
+     */
+    public static final String AUTHENTICATED_SESSION_KEY = DefaultSubjectContext.class.getName() + "_AUTHENTICATED_SESSION_KEY";
+
+    private static final transient Logger log = LoggerFactory.getLogger(DefaultSubjectContext.class);
+
+    public DefaultSubjectContext() {
+        super();
+    }
+
+    public DefaultSubjectContext(SubjectContext ctx) {
+        super(ctx);
+    }
+
+    public SecurityManager getSecurityManager() {
+        return getTypedValue(SECURITY_MANAGER, SecurityManager.class);
+    }
+
+    public void setSecurityManager(SecurityManager securityManager) {
+        nullSafePut(SECURITY_MANAGER, securityManager);
+    }
+
+    public SecurityManager resolveSecurityManager() {
+        SecurityManager securityManager = getSecurityManager();
+        if (securityManager == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("No SecurityManager available in subject context map.  " +
+                        "Falling back to SecurityUtils.getSecurityManager() lookup.");
+            }
+            try {
+                securityManager = SecurityUtils.getSecurityManager();
+            } catch (UnavailableSecurityManagerException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);
+                }
+            }
+        }
+        return securityManager;
+    }
+
+    public Serializable getSessionId() {
+        return getTypedValue(SESSION_ID, Serializable.class);
+    }
+
+    public void setSessionId(Serializable sessionId) {
+        nullSafePut(SESSION_ID, sessionId);
+    }
+
+    public Subject getSubject() {
+        return getTypedValue(SUBJECT, Subject.class);
+    }
+
+    public void setSubject(Subject subject) {
+        nullSafePut(SUBJECT, subject);
+    }
+
+    public PrincipalCollection getPrincipals() {
+        return getTypedValue(PRINCIPALS, PrincipalCollection.class);
+    }
+
+    public void setPrincipals(PrincipalCollection principals) {
+        if (!CollectionUtils.isEmpty(principals)) {
+            put(PRINCIPALS, principals);
+        }
+    }
+
+    public PrincipalCollection resolvePrincipals() {
+        PrincipalCollection principals = getPrincipals();
+
+        if (CollectionUtils.isEmpty(principals)) {
+            //check to see if they were just authenticated:
+            AuthenticationInfo info = getAuthenticationInfo();
+            if (info != null) {
+                principals = info.getPrincipals();
+            }
+        }
+
+        if (CollectionUtils.isEmpty(principals)) {
+            Subject subject = getSubject();
+            if (subject != null) {
+                principals = subject.getPrincipals();
+            }
+        }
+
+        if (CollectionUtils.isEmpty(principals)) {
+            //try the session:
+            Session session = resolveSession();
+            if (session != null) {
+                principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
+            }
+        }
+
+        return principals;
+    }
+
+
+    public Session getSession() {
+        return getTypedValue(SESSION, Session.class);
+    }
+
+    public void setSession(Session session) {
+        nullSafePut(SESSION, session);
+    }
+
+    public Session resolveSession() {
+        Session session = getSession();
+        if (session == null) {
+            //try the Subject if it exists:
+            Subject existingSubject = getSubject();
+            if (existingSubject != null) {
+                session = existingSubject.getSession(false);
+            }
+        }
+        return session;
+    }
+
+    public boolean isSessionCreationEnabled() {
+        Boolean val = getTypedValue(SESSION_CREATION_ENABLED, Boolean.class);
+        return val == null || val;
+    }
+
+    public void setSessionCreationEnabled(boolean enabled) {
+        nullSafePut(SESSION_CREATION_ENABLED, enabled);
+    }
+
+    public boolean isAuthenticated() {
+        Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);
+        return authc != null && authc;
+    }
+
+    public void setAuthenticated(boolean authc) {
+        put(AUTHENTICATED, authc);
+    }
+
+    public boolean resolveAuthenticated() {
+        Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);
+        if (authc == null) {
+            //see if there is an AuthenticationInfo object.  If so, the very presence of one indicates a successful
+            //authentication attempt:
+            AuthenticationInfo info = getAuthenticationInfo();
+            authc = info != null;
+        }
+        if (!authc) {
+            //fall back to a session check:
+            Session session = resolveSession();
+            if (session != null) {
+                Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);
+                authc = sessionAuthc != null && sessionAuthc;
+            }
+        }
+
+        return authc;
+    }
+
+    public AuthenticationInfo getAuthenticationInfo() {
+        return getTypedValue(AUTHENTICATION_INFO, AuthenticationInfo.class);
+    }
+
+    public void setAuthenticationInfo(AuthenticationInfo info) {
+        nullSafePut(AUTHENTICATION_INFO, info);
+    }
+
+    public AuthenticationToken getAuthenticationToken() {
+        return getTypedValue(AUTHENTICATION_TOKEN, AuthenticationToken.class);
+    }
+
+    public void setAuthenticationToken(AuthenticationToken token) {
+        nullSafePut(AUTHENTICATION_TOKEN, token);
+    }
+
+    public String getHost() {
+        return getTypedValue(HOST, String.class);
+    }
+
+    public void setHost(String host) {
+        if (StringUtils.hasText(host)) {
+            put(HOST, host);
+        }
+    }
+
+    public String resolveHost() {
+        String host = getHost();
+
+        if (host == null) {
+            //check to see if there is an AuthenticationToken from which to retrieve it:
+            AuthenticationToken token = getAuthenticationToken();
+            if (token instanceof HostAuthenticationToken) {
+                host = ((HostAuthenticationToken) token).getHost();
+            }
+        }
+
+        if (host == null) {
+            Session session = resolveSession();
+            if (session != null) {
+                host = session.getHost();
+            }
+        }
+
+        return host;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java b/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
new file mode 100644
index 0000000..e47a165
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.HostAuthenticationToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.ProxiedSession;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.session.mgt.DefaultSessionContext;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.subject.ExecutionException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Implementation of the {@code Subject} interface that delegates
+ * method calls to an underlying {@link org.apache.shiro.mgt.SecurityManager SecurityManager} instance for security checks.
+ * It is essentially a {@code SecurityManager} proxy.
+ * <p/>
+ * This implementation does not maintain state such as roles and permissions (only {@code Subject}
+ * {@link #getPrincipals() principals}, such as usernames or user primary keys) for better performance in a stateless
+ * architecture.  It instead asks the underlying {@code SecurityManager} every time to perform
+ * the authorization check.
+ * <p/>
+ * A common misconception in using this implementation is that an EIS resource (RDBMS, etc) would
+ * be "hit" every time a method is called.  This is not necessarily the case and is
+ * up to the implementation of the underlying {@code SecurityManager} instance.  If caching of authorization
+ * data is desired (to eliminate EIS round trips and therefore improve database performance), it is considered
+ * much more elegant to let the underlying {@code SecurityManager} implementation or its delegate components
+ * manage caching, not this class.  A {@code SecurityManager} is considered a business-tier component,
+ * where caching strategies are better managed.
+ * <p/>
+ * Applications from large and clustered to simple and JVM-local all benefit from
+ * stateless architectures.  This implementation plays a part in the stateless programming
+ * paradigm and should be used whenever possible.
+ *
+ * @since 0.1
+ */
+public class DelegatingSubject implements Subject {
+
+    private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class);
+
+    private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
+            DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";
+
+    protected PrincipalCollection principals;
+    protected boolean authenticated;
+    protected String host;
+    protected Session session;
+    /**
+     * @since 1.2
+     */
+    protected boolean sessionCreationEnabled;
+
+    protected transient SecurityManager securityManager;
+
+    public DelegatingSubject(SecurityManager securityManager) {
+        this(null, false, null, null, securityManager);
+    }
+
+    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
+                             Session session, SecurityManager securityManager) {
+        this(principals, authenticated, host, session, true, securityManager);
+    }
+
+    //since 1.2
+    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
+                             Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
+        if (securityManager == null) {
+            throw new IllegalArgumentException("SecurityManager argument cannot be null.");
+        }
+        this.securityManager = securityManager;
+        this.principals = principals;
+        this.authenticated = authenticated;
+        this.host = host;
+        if (session != null) {
+            this.session = decorate(session);
+        }
+        this.sessionCreationEnabled = sessionCreationEnabled;
+    }
+
+    protected Session decorate(Session session) {
+        if (session == null) {
+            throw new IllegalArgumentException("session cannot be null");
+        }
+        return new StoppingAwareProxiedSession(session, this);
+    }
+
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+
+    protected boolean hasPrincipals() {
+        return !CollectionUtils.isEmpty(getPrincipals());
+    }
+
+    /**
+     * Returns the host name or IP associated with the client who created/is interacting with this Subject.
+     *
+     * @return the host name or IP associated with the client who created/is interacting with this Subject.
+     */
+    public String getHost() {
+        return this.host;
+    }
+
+    private Object getPrimaryPrincipal(PrincipalCollection principals) {
+        if (!CollectionUtils.isEmpty(principals)) {
+            return principals.getPrimaryPrincipal();
+        }
+        return null;
+    }
+
+    /**
+     * @see Subject#getPrincipal()
+     */
+    public Object getPrincipal() {
+        return getPrimaryPrincipal(getPrincipals());
+    }
+
+    public PrincipalCollection getPrincipals() {
+        List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
+        return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
+    }
+
+    public boolean isPermitted(String permission) {
+        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
+    }
+
+    public boolean isPermitted(Permission permission) {
+        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
+    }
+
+    public boolean[] isPermitted(String... permissions) {
+        if (hasPrincipals()) {
+            return securityManager.isPermitted(getPrincipals(), permissions);
+        } else {
+            return new boolean[permissions.length];
+        }
+    }
+
+    public boolean[] isPermitted(List<Permission> permissions) {
+        if (hasPrincipals()) {
+            return securityManager.isPermitted(getPrincipals(), permissions);
+        } else {
+            return new boolean[permissions.size()];
+        }
+    }
+
+    public boolean isPermittedAll(String... permissions) {
+        return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
+    }
+
+    public boolean isPermittedAll(Collection<Permission> permissions) {
+        return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
+    }
+
+    protected void assertAuthzCheckPossible() throws AuthorizationException {
+        if (!hasPrincipals()) {
+            String msg = "This subject is anonymous - it does not have any identifying principals and " +
+                    "authorization operations require an identity to check against.  A Subject instance will " +
+                    "acquire these identifying principals automatically after a successful login is performed " +
+                    "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
+                    "functionality is enabled by the SecurityManager.  This exception can also occur when a " +
+                    "previously logged-in Subject has logged out which " +
+                    "makes it anonymous again.  Because an identity is currently not known due to any of these " +
+                    "conditions, authorization is denied.";
+            throw new UnauthenticatedException(msg);
+        }
+    }
+
+    public void checkPermission(String permission) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkPermission(getPrincipals(), permission);
+    }
+
+    public void checkPermission(Permission permission) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkPermission(getPrincipals(), permission);
+    }
+
+    public void checkPermissions(String... permissions) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkPermissions(getPrincipals(), permissions);
+    }
+
+    public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkPermissions(getPrincipals(), permissions);
+    }
+
+    public boolean hasRole(String roleIdentifier) {
+        return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
+    }
+
+    public boolean[] hasRoles(List<String> roleIdentifiers) {
+        if (hasPrincipals()) {
+            return securityManager.hasRoles(getPrincipals(), roleIdentifiers);
+        } else {
+            return new boolean[roleIdentifiers.size()];
+        }
+    }
+
+    public boolean hasAllRoles(Collection<String> roleIdentifiers) {
+        return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
+    }
+
+    public void checkRole(String role) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkRole(getPrincipals(), role);
+    }
+
+    public void checkRoles(String... roleIdentifiers) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkRoles(getPrincipals(), roleIdentifiers);
+    }
+
+    public void checkRoles(Collection<String> roles) throws AuthorizationException {
+        assertAuthzCheckPossible();
+        securityManager.checkRoles(getPrincipals(), roles);
+    }
+
+    public void login(AuthenticationToken token) throws AuthenticationException {
+        clearRunAsIdentitiesInternal();
+        Subject subject = securityManager.login(this, token);
+
+        PrincipalCollection principals;
+
+        String host = null;
+
+        if (subject instanceof DelegatingSubject) {
+            DelegatingSubject delegating = (DelegatingSubject) subject;
+            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
+            principals = delegating.principals;
+            host = delegating.host;
+        } else {
+            principals = subject.getPrincipals();
+        }
+
+        if (principals == null || principals.isEmpty()) {
+            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
+                    "empty value.  This value must be non null and populated with one or more elements.";
+            throw new IllegalStateException(msg);
+        }
+        this.principals = principals;
+        this.authenticated = true;
+        if (token instanceof HostAuthenticationToken) {
+            host = ((HostAuthenticationToken) token).getHost();
+        }
+        if (host != null) {
+            this.host = host;
+        }
+        Session session = subject.getSession(false);
+        if (session != null) {
+            this.session = decorate(session);
+        } else {
+            this.session = null;
+        }
+    }
+
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    public boolean isRemembered() {
+        PrincipalCollection principals = getPrincipals();
+        return principals != null && !principals.isEmpty() && !isAuthenticated();
+    }
+
+    /**
+     * Returns {@code true} if this Subject is allowed to create sessions, {@code false} otherwise.
+     *
+     * @return {@code true} if this Subject is allowed to create sessions, {@code false} otherwise.
+     * @since 1.2
+     */
+    protected boolean isSessionCreationEnabled() {
+        return this.sessionCreationEnabled;
+    }
+
+    public Session getSession() {
+        return getSession(true);
+    }
+
+    public Session getSession(boolean create) {
+        if (log.isTraceEnabled()) {
+            log.trace("attempting to get session; create = " + create +
+                    "; session is null = " + (this.session == null) +
+                    "; session has id = " + (this.session != null && session.getId() != null));
+        }
+
+        if (this.session == null && create) {
+
+            //added in 1.2:
+            if (!isSessionCreationEnabled()) {
+                String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
+                        "that there is either a programming error (using a session when it should never be " +
+                        "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
+                        "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
+                        "for more.";
+                throw new DisabledSessionException(msg);
+            }
+
+            log.trace("Starting session for host {}", getHost());
+            SessionContext sessionContext = createSessionContext();
+            Session session = this.securityManager.start(sessionContext);
+            this.session = decorate(session);
+        }
+        return this.session;
+    }
+
+    protected SessionContext createSessionContext() {
+        SessionContext sessionContext = new DefaultSessionContext();
+        if (StringUtils.hasText(host)) {
+            sessionContext.setHost(host);
+        }
+        return sessionContext;
+    }
+
+    private void clearRunAsIdentitiesInternal() {
+        //try/catch added for SHIRO-298
+        try {
+            clearRunAsIdentities();
+        } catch (SessionException se) {
+            log.debug("Encountered session exception trying to clear 'runAs' identities during logout.  This " +
+                    "can generally safely be ignored.", se);
+        }
+    }
+
+    public void logout() {
+        try {
+            clearRunAsIdentitiesInternal();
+            this.securityManager.logout(this);
+        } finally {
+            this.session = null;
+            this.principals = null;
+            this.authenticated = false;
+            //Don't set securityManager to null here - the Subject can still be
+            //used, it is just considered anonymous at this point.  The SecurityManager instance is
+            //necessary if the subject would log in again or acquire a new session.  This is in response to
+            //https://issues.apache.org/jira/browse/JSEC-22
+            //this.securityManager = null;
+        }
+    }
+
+    private void sessionStopped() {
+        this.session = null;
+    }
+
+    public <V> V execute(Callable<V> callable) throws ExecutionException {
+        Callable<V> associated = associateWith(callable);
+        try {
+            return associated.call();
+        } catch (Throwable t) {
+            throw new ExecutionException(t);
+        }
+    }
+
+    public void execute(Runnable runnable) {
+        Runnable associated = associateWith(runnable);
+        associated.run();
+    }
+
+    public <V> Callable<V> associateWith(Callable<V> callable) {
+        return new SubjectCallable<V>(this, callable);
+    }
+
+    public Runnable associateWith(Runnable runnable) {
+        if (runnable instanceof Thread) {
+            String msg = "This implementation does not support Thread arguments because of JDK ThreadLocal " +
+                    "inheritance mechanisms required by Shiro.  Instead, the method argument should be a non-Thread " +
+                    "Runnable and the return value from this method can then be given to an ExecutorService or " +
+                    "another Thread.";
+            throw new UnsupportedOperationException(msg);
+        }
+        return new SubjectRunnable(this, runnable);
+    }
+
+    private class StoppingAwareProxiedSession extends ProxiedSession {
+
+        private final DelegatingSubject owner;
+
+        private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
+            super(target);
+            owner = owningSubject;
+        }
+
+        public void stop() throws InvalidSessionException {
+            super.stop();
+            owner.sessionStopped();
+        }
+    }
+
+
+    // ======================================
+    // 'Run As' support implementations
+    // ======================================
+
+    public void runAs(PrincipalCollection principals) {
+        if (!hasPrincipals()) {
+            String msg = "This subject does not yet have an identity.  Assuming the identity of another " +
+                    "Subject is only allowed for Subjects with an existing identity.  Try logging this subject in " +
+                    "first, or using the " + Subject.Builder.class.getName() + " to build ad hoc Subject instances " +
+                    "with identities as necessary.";
+            throw new IllegalStateException(msg);
+        }
+        pushIdentity(principals);
+    }
+
+    public boolean isRunAs() {
+        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
+        return !CollectionUtils.isEmpty(stack);
+    }
+
+    public PrincipalCollection getPreviousPrincipals() {
+        PrincipalCollection previousPrincipals = null;
+        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
+        int stackSize = stack != null ? stack.size() : 0;
+        if (stackSize > 0) {
+            if (stackSize == 1) {
+                previousPrincipals = this.principals;
+            } else {
+                //always get the one behind the current:
+                assert stack != null;
+                previousPrincipals = stack.get(1);
+            }
+        }
+        return previousPrincipals;
+    }
+
+    public PrincipalCollection releaseRunAs() {
+        return popIdentity();
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<PrincipalCollection> getRunAsPrincipalsStack() {
+        Session session = getSession(false);
+        if (session != null) {
+            return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
+        }
+        return null;
+    }
+
+    private void clearRunAsIdentities() {
+        Session session = getSession(false);
+        if (session != null) {
+            session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
+        }
+    }
+
+    private void pushIdentity(PrincipalCollection principals) throws NullPointerException {
+        if (CollectionUtils.isEmpty(principals)) {
+            String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality.";
+            throw new NullPointerException(msg);
+        }
+        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
+        if (stack == null) {
+            stack = new CopyOnWriteArrayList<PrincipalCollection>();
+        }
+        stack.add(0, principals);
+        Session session = getSession();
+        session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
+    }
+
+    private PrincipalCollection popIdentity() {
+        PrincipalCollection popped = null;
+
+        List<PrincipalCollection> stack = getRunAsPrincipalsStack();
+        if (!CollectionUtils.isEmpty(stack)) {
+            popped = stack.remove(0);
+            Session session;
+            if (!CollectionUtils.isEmpty(stack)) {
+                //persist the changed stack to the session
+                session = getSession();
+                session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
+            } else {
+                //stack is empty, remove it from the session:
+                clearRunAsIdentities();
+            }
+        }
+
+        return popped;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/DisabledSessionException.java b/core/src/main/java/org/apache/shiro/subject/support/DisabledSessionException.java
new file mode 100644
index 0000000..833ae2c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/DisabledSessionException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.session.SessionException;
+
+/**
+ * Exception thrown if attempting to create a new {@code Subject}
+ * {@link org.apache.shiro.subject.Subject#getSession() session}, but that {@code Subject}'s sessions are disabled.
+ * <p/>
+ * Note that this exception represents an invalid API usage scenario - where Shiro has been configured to disable
+ * sessions for a particular subject, but a developer is attempting to use that Subject's session.
+ * <p/>
+ * In other words, if this exception is encountered, it should be resolved by a configuration change for Shiro and
+ * <em>not</em> by checking every Subject to see if they are enabled or not (which would likely introduce very
+ * ugly/paranoid code checks everywhere a session is needed). This is why there is no
+ * {@code subject.isSessionEnabled()} method.
+ *
+ * @since 1.2
+ */
+public class DisabledSessionException extends SessionException {
+
+    public DisabledSessionException(String message) {
+        super(message);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
new file mode 100644
index 0000000..d23971b
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadState;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A {@code SubjectCallable} associates a {@link Subject Subject} with a target/delegate
+ * {@link Callable Callable} to ensure proper {@code Subject} thread-state management when the {@code Callable} executes.
+ * This ensures that any calls to {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}
+ * during the target {@code Callable}'s execution still work correctly even if the {@code Callable} executes on a
+ * different thread than the one that created it.  This allows {@code Subject} access during asynchronous operations.
+ * <p/>
+ * When instances of this class execute (typically via a {@link java.util.concurrent.ExecutorService ExecutorService}),
+ * the following occurs:
+ * <ol>
+ * <li>The specified Subject any of its associated thread state is first bound to the thread that executes the
+ * {@code Callable}.</li>
+ * <li>The delegate/target {@code Callable} is {@link java.util.concurrent.Callable#call() executed}</li>
+ * <li>The previous thread state that might have existed before the {@code Subject} was bound is fully restored</li>
+ * </ol>
+ * <p/>
+ * This behavior ensures that the thread that executes this {@code Callable}, which is often a different thread than
+ * the one that created the instance, retains a {@code Subject} to support {@code SecurityUtils.getSubject()}
+ * invocations. It also guarantees that the running thread remains 'clean' in any thread-pooled environments.
+ *
+ * <h3>Usage</h3>
+ *
+ * This is typically considered a support class and is not often directly referenced.  Most people prefer to use
+ * the {@code Subject.}{@link Subject#associateWith(Callable) associateWith} method, which will automatically return
+ * an instance of this class.
+ * <p/>
+ * An even more convenient alternative is to use a
+ * {@link org.apache.shiro.concurrent.SubjectAwareExecutorService SubjectAwareExecutorService}, which
+ * transparently uses instances of this class.
+ *
+ * @see Subject#associateWith(Callable)
+ * @see org.apache.shiro.concurrent.SubjectAwareExecutorService SubjectAwareExecutorService
+ * @since 1.0
+ */
+public class SubjectCallable<V> implements Callable<V> {
+
+    protected final ThreadState threadState;
+    private final Callable<V> callable;
+
+    public SubjectCallable(Subject subject, Callable<V> delegate) {
+        this(new SubjectThreadState(subject), delegate);
+    }
+
+    protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
+        if (threadState == null) {
+            throw new IllegalArgumentException("ThreadState argument cannot be null.");
+        }
+        this.threadState = threadState;
+        if (delegate == null) {
+            throw new IllegalArgumentException("Callable delegate instance cannot be null.");
+        }
+        this.callable = delegate;
+    }
+
+    public V call() throws Exception {
+        try {
+            threadState.bind();
+            return doCall(this.callable);
+        } finally {
+            threadState.restore();
+        }
+    }
+
+    protected V doCall(Callable<V> target) throws Exception {
+        return target.call();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
new file mode 100644
index 0000000..9e633dd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadState;
+
+/**
+ * A {@code SubjectRunnable} ensures that a target/delegate {@link Runnable Runnable} will execute such that any
+ * call to {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()} during the
+ * {@code Runnable}'s execution will return the associated {@code Subject} instance.  The {@code SubjectRunnable}
+ * instance can be run on any thread (the current thread or asynchronously on another thread) and the
+ * {@code SecurityUtils.getSubject()} call will still work properly.  This implementation also guarantees that Shiro's
+ * thread state will be identical before and after execution to ensure threads remain clean in any thread-pooled
+ * environment.
+ * <p/>
+ * When instances of this class {@link Runnable#run() run()}, the following occurs:
+ * <ol>
+ * <li>The Subject and any of its associated thread state is first bound to the thread that executes the
+ * {@code Runnable}.</li>
+ * <li>The delegate/target {@code Runnable} is {@link #doRun(Runnable) run}</li>
+ * <li>Any previous thread state that might have existed before the {@code Subject} was bound is fully restored</li>
+ * </ol>
+ * <p/>
+ *
+ * <h3>Usage</h3>
+ *
+ * This is typically considered a support class and is not often directly referenced.  Most people prefer to use
+ * the {@code Subject.}{@link Subject#execute(Runnable) execute} or
+ * {@code Subject.}{@link Subject#associateWith(Runnable) associateWith} methods, which transparently perform the
+ * necessary association logic.
+ * <p/>
+ * An even more convenient alternative is to use a
+ * {@link org.apache.shiro.concurrent.SubjectAwareExecutor SubjectAwareExecutor}, which transparently uses
+ * instances of this class but does not require referencing Shiro's API at all.
+ *
+ * @see Subject#associateWith(Runnable)
+ * @see org.apache.shiro.concurrent.SubjectAwareExecutor SubjectAwareExecutor
+ * @since 1.0
+ */
+public class SubjectRunnable implements Runnable {
+
+    protected final ThreadState threadState;
+    private final Runnable runnable;
+
+    /**
+     * Creates a new {@code SubjectRunnable} that, when executed, will execute the target {@code delegate}, but
+     * guarantees that it will run associated with the specified {@code Subject}.
+     *
+     * @param subject  the Subject to associate with the delegate's execution.
+     * @param delegate the runnable to run.
+     */
+    public SubjectRunnable(Subject subject, Runnable delegate) {
+        this(new SubjectThreadState(subject), delegate);
+    }
+
+    /**
+     * Creates a new {@code SubjectRunnable} that, when executed, will perform thread state
+     * {@link ThreadState#bind binding} and guaranteed {@link ThreadState#restore restoration} before and after the
+     * {@link Runnable Runnable}'s execution, respectively.
+     *
+     * @param threadState the thread state to bind and unbind before and after the runnable's execution.
+     * @param delegate    the delegate {@code Runnable} to execute when this instance is {@link #run() run()}.
+     * @throws IllegalArgumentException if either the {@code ThreadState} or {@link Runnable} arguments are {@code null}.
+     */
+    protected SubjectRunnable(ThreadState threadState, Runnable delegate) throws IllegalArgumentException {
+        if (threadState == null) {
+            throw new IllegalArgumentException("ThreadState argument cannot be null.");
+        }
+        this.threadState = threadState;
+        if (delegate == null) {
+            throw new IllegalArgumentException("Runnable argument cannot be null.");
+        }
+        this.runnable = delegate;
+    }
+
+    /**
+     * {@link ThreadState#bind Bind}s the Subject thread state, executes the target {@code Runnable} and then guarantees
+     * the previous thread state's {@link ThreadState#restore restoration}:
+     * <pre>
+     * try {
+     *     threadState.{@link ThreadState#bind bind()};
+     *     {@link #doRun doRun}(targetRunnable);
+     * } finally {
+     *     threadState.{@link ThreadState#restore restore()}
+     * }
+     * </pre>
+     */
+    public void run() {
+        try {
+            threadState.bind();
+            doRun(this.runnable);
+        } finally {
+            threadState.restore();
+        }
+    }
+
+    /**
+     * Simply calls the target {@link Runnable Runnable}'s {@link Runnable#run run()} method.
+     *
+     * @param runnable the target runnable to run.
+     */
+    protected void doRun(Runnable runnable) {
+        runnable.run();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
new file mode 100644
index 0000000..2e439b1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject.support;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.ThreadContext;
+import org.apache.shiro.util.ThreadState;
+
+import java.util.Map;
+
+/**
+ * Manages thread-state for {@link Subject Subject} access (supporting
+ * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()} calls)
+ * during a thread's execution.
+ * <p/>
+ * The {@link #bind bind} method will bind a {@link Subject} and a
+ * {@link org.apache.shiro.mgt.SecurityManager SecurityManager} to the {@link ThreadContext} so they can be retrieved
+ * from the {@code ThreadContext} later by any
+ * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()} calls that might occur during
+ * the thread's execution.
+ *
+ * @since 1.0
+ */
+public class SubjectThreadState implements ThreadState {
+
+    private Map<Object, Object> originalResources;
+
+    private final Subject subject;
+    private transient SecurityManager securityManager;
+
+    /**
+     * Creates a new {@code SubjectThreadState} that will bind and unbind the specified {@code Subject} to the
+     * thread
+     *
+     * @param subject the {@code Subject} instance to bind and unbind from the {@link ThreadContext}.
+     */
+    public SubjectThreadState(Subject subject) {
+        if (subject == null) {
+            throw new IllegalArgumentException("Subject argument cannot be null.");
+        }
+        this.subject = subject;
+
+        SecurityManager securityManager = null;
+        if ( subject instanceof DelegatingSubject) {
+            securityManager = ((DelegatingSubject)subject).getSecurityManager();
+        }
+        if ( securityManager == null) {
+            securityManager = ThreadContext.getSecurityManager();
+        }
+        this.securityManager = securityManager;
+    }
+
+    /**
+     * Returns the {@code Subject} instance managed by this {@code ThreadState} implementation.
+     *
+     * @return the {@code Subject} instance managed by this {@code ThreadState} implementation.
+     */
+    protected Subject getSubject() {
+        return this.subject;
+    }
+
+    /**
+     * Binds a {@link Subject} and {@link org.apache.shiro.mgt.SecurityManager SecurityManager} to the
+     * {@link ThreadContext} so they can be retrieved later by any
+     * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()} calls that might occur
+     * during the thread's execution.
+     * <p/>
+     * Prior to binding, the {@code ThreadContext}'s existing {@link ThreadContext#getResources() resources} are
+     * retained so they can be restored later via the {@link #restore restore} call.
+     */
+    public void bind() {
+        SecurityManager securityManager = this.securityManager;
+        if ( securityManager == null ) {
+            //try just in case the constructor didn't find one at the time:
+            securityManager = ThreadContext.getSecurityManager();
+        }
+        this.originalResources = ThreadContext.getResources();
+        ThreadContext.remove();
+
+        ThreadContext.bind(this.subject);
+        if (securityManager != null) {
+            ThreadContext.bind(securityManager);
+        }
+    }
+
+    /**
+     * {@link ThreadContext#remove Remove}s all thread-state that was bound by this instance.  If any previous
+     * thread-bound resources existed prior to the {@link #bind bind} call, they are restored back to the
+     * {@code ThreadContext} to ensure the thread state is exactly as it was before binding.
+     */
+    public void restore() {
+        ThreadContext.remove();
+        if (!CollectionUtils.isEmpty(this.originalResources)) {
+            ThreadContext.setResources(this.originalResources);
+        }
+    }
+
+    /**
+     * Completely {@link ThreadContext#remove removes} the {@code ThreadContext} state.  Typically this method should
+     * only be called in special cases - it is more 'correct' to {@link #restore restore} a thread to its previous
+     * state than to clear it entirely.
+     */
+    public void clear() {
+        ThreadContext.remove();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/subject/support/package-info.java b/core/src/main/java/org/apache/shiro/subject/support/package-info.java
new file mode 100644
index 0000000..f3441f9
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/subject/support/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Concrete support implementations of most of the {@code org.apache.shiro.subject} interfaces.
+ */
+package org.apache.shiro.subject.support;
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/shiro/util/AbstractFactory.java b/core/src/main/java/org/apache/shiro/util/AbstractFactory.java
new file mode 100644
index 0000000..52dcce7
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/AbstractFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @since 1.0
+ */
+public abstract class AbstractFactory<T> implements Factory<T> {
+
+    private boolean singleton;
+    private T singletonInstance;
+
+    public AbstractFactory() {
+        this.singleton = true;
+    }
+
+    public boolean isSingleton() {
+        return singleton;
+    }
+
+    public void setSingleton(boolean singleton) {
+        this.singleton = singleton;
+    }
+
+    public T getInstance() {
+        T instance;
+        if (isSingleton()) {
+            if (this.singletonInstance == null) {
+                this.singletonInstance = createInstance();
+            }
+            instance = this.singletonInstance;
+        } else {
+            instance = createInstance();
+        }
+        if (instance == null) {
+            String msg = "Factory 'createInstance' implementation returned a null object.";
+            throw new IllegalStateException(msg);
+        }
+        return instance;
+    }
+
+    protected abstract T createInstance();
+}
diff --git a/core/src/main/java/org/apache/shiro/util/AntPathMatcher.java b/core/src/main/java/org/apache/shiro/util/AntPathMatcher.java
new file mode 100644
index 0000000..f33f85e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/AntPathMatcher.java
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * <p>PathMatcher implementation for Ant-style path patterns.
+ * Examples are provided below.</p>
+ *
+ * <p>Part of this mapping code has been kindly borrowed from
+ * <a href="http://ant.apache.org">Apache Ant</a>.
+ *
+ * <p>The mapping matches URLs using the following rules:<br>
+ * <ul>
+ * <li>? matches one character</li>
+ * <li>* matches zero or more characters</li>
+ * <li>** matches zero or more 'directories' in a path</li>
+ * </ul>
+ *
+ * <p>Some examples:<br>
+ * <ul>
+ * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also
+ * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>
+ * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the
+ * <code>com</code> directory</li>
+ * <li><code>com/**/test.jsp</code> - matches all <code>test.jsp</code>
+ * files underneath the <code>com</code> path</li>
+ * <li><code>org/apache/shiro/**/*.jsp</code> - matches all <code>.jsp</code>
+ * files underneath the <code>org/apache/shiro</code> path</li>
+ * <li><code>org/**/servlet/bla.jsp</code> - matches
+ * <code>org/apache/shiro/servlet/bla.jsp</code> but also
+ * <code>org/apache/shiro/testing/servlet/bla.jsp</code> and
+ * <code>org/servlet/bla.jsp</code></li>
+ * </ul>
+ *
+ * <p><b>N.B.</b>: This class was borrowed (with much appreciation) from the
+ * <a href="http://www.springframework.org">Spring Framework</a> with modifications.  We didn't want to reinvent the
+ * wheel of great work they've done, but also didn't want to force every Shiro user to depend on Spring</p>
+ *
+ * <p>As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
+ * remained in tact.</p>
+ *
+ * @since 16.07.2003
+ */
+public class AntPathMatcher implements PatternMatcher {
+
+    //TODO - complete JavaDoc
+
+    /**
+     * Default path separator: "/"
+     */
+    public static final String DEFAULT_PATH_SEPARATOR = "/";
+
+    private String pathSeparator = DEFAULT_PATH_SEPARATOR;
+
+
+    /**
+     * Set the path separator to use for pattern parsing.
+     * Default is "/", as in Ant.
+     */
+    public void setPathSeparator(String pathSeparator) {
+        this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
+    }
+
+
+    public boolean isPattern(String path) {
+        return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
+    }
+
+    public boolean matches(String pattern, String source) {
+        return match(pattern, source);
+    }
+
+    public boolean match(String pattern, String path) {
+        return doMatch(pattern, path, true);
+    }
+
+    public boolean matchStart(String pattern, String path) {
+        return doMatch(pattern, path, false);
+    }
+
+
+    /**
+     * Actually match the given <code>path</code> against the given <code>pattern</code>.
+     *
+     * @param pattern   the pattern to match against
+     * @param path      the path String to test
+     * @param fullMatch whether a full pattern match is required
+     *                  (else a pattern match as far as the given base path goes is sufficient)
+     * @return <code>true</code> if the supplied <code>path</code> matched,
+     *         <code>false</code> if it didn't
+     */
+    protected boolean doMatch(String pattern, String path, boolean fullMatch) {
+        if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
+            return false;
+        }
+
+        String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+        String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+        int pattIdxStart = 0;
+        int pattIdxEnd = pattDirs.length - 1;
+        int pathIdxStart = 0;
+        int pathIdxEnd = pathDirs.length - 1;
+
+        // Match all elements up to the first **
+        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+            String patDir = pattDirs[pattIdxStart];
+            if ("**".equals(patDir)) {
+                break;
+            }
+            if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
+                return false;
+            }
+            pattIdxStart++;
+            pathIdxStart++;
+        }
+
+        if (pathIdxStart > pathIdxEnd) {
+            // Path is exhausted, only match if rest of pattern is * or **'s
+            if (pattIdxStart > pattIdxEnd) {
+                return (pattern.endsWith(this.pathSeparator) ?
+                        path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
+            }
+            if (!fullMatch) {
+                return true;
+            }
+            if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") &&
+                    path.endsWith(this.pathSeparator)) {
+                return true;
+            }
+            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+                if (!pattDirs[i].equals("**")) {
+                    return false;
+                }
+            }
+            return true;
+        } else if (pattIdxStart > pattIdxEnd) {
+            // String not exhausted, but pattern is. Failure.
+            return false;
+        } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
+            // Path start definitely matches due to "**" part in pattern.
+            return true;
+        }
+
+        // up to last '**'
+        while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+            String patDir = pattDirs[pattIdxEnd];
+            if (patDir.equals("**")) {
+                break;
+            }
+            if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
+                return false;
+            }
+            pattIdxEnd--;
+            pathIdxEnd--;
+        }
+        if (pathIdxStart > pathIdxEnd) {
+            // String is exhausted
+            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+                if (!pattDirs[i].equals("**")) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+            int patIdxTmp = -1;
+            for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
+                if (pattDirs[i].equals("**")) {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == pattIdxStart + 1) {
+                // '**/**' situation, so skip one
+                pattIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = (patIdxTmp - pattIdxStart - 1);
+            int strLength = (pathIdxEnd - pathIdxStart + 1);
+            int foundIdx = -1;
+
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    String subPat = (String) pattDirs[pattIdxStart + j + 1];
+                    String subStr = (String) pathDirs[pathIdxStart + i + j];
+                    if (!matchStrings(subPat, subStr)) {
+                        continue strLoop;
+                    }
+                }
+                foundIdx = pathIdxStart + i;
+                break;
+            }
+
+            if (foundIdx == -1) {
+                return false;
+            }
+
+            pattIdxStart = patIdxTmp;
+            pathIdxStart = foundIdx + patLength;
+        }
+
+        for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+            if (!pattDirs[i].equals("**")) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern pattern to match against.
+     *                Must not be <code>null</code>.
+     * @param str     string which must be matched against the pattern.
+     *                Must not be <code>null</code>.
+     * @return <code>true</code> if the string matches against the
+     *         pattern, or <code>false</code> otherwise.
+     */
+    private boolean matchStrings(String pattern, String str) {
+        char[] patArr = pattern.toCharArray();
+        char[] strArr = str.toCharArray();
+        int patIdxStart = 0;
+        int patIdxEnd = patArr.length - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strArr.length - 1;
+        char ch;
+
+        boolean containsStar = false;
+        for (char aPatArr : patArr) {
+            if (aPatArr == '*') {
+                containsStar = true;
+                break;
+            }
+        }
+
+        if (!containsStar) {
+            // No '*'s, so we make a shortcut
+            if (patIdxEnd != strIdxEnd) {
+                return false; // Pattern and string do not have the same size
+            }
+            for (int i = 0; i <= patIdxEnd; i++) {
+                ch = patArr[i];
+                if (ch != '?') {
+                    if (ch != strArr[i]) {
+                        return false;// Character mismatch
+                    }
+                }
+            }
+            return true; // String matches against pattern
+        }
+
+
+        if (patIdxEnd == 0) {
+            return true; // Pattern contains only '*', which matches anything
+        }
+
+        // Process characters before first star
+        while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+            if (ch != '?') {
+                if (ch != strArr[strIdxStart]) {
+                    return false;// Character mismatch
+                }
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // Process characters after last star
+        while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+            if (ch != '?') {
+                if (ch != strArr[strIdxEnd]) {
+                    return false;// Character mismatch
+                }
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // process pattern between stars. padIdxStart and patIdxEnd point
+        // always to a '*'.
+        while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+            int patIdxTmp = -1;
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (patArr[i] == '*') {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+                // Two stars next to each other, skip the first one.
+                patIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = (patIdxTmp - patIdxStart - 1);
+            int strLength = (strIdxEnd - strIdxStart + 1);
+            int foundIdx = -1;
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    ch = patArr[patIdxStart + j + 1];
+                    if (ch != '?') {
+                        if (ch != strArr[strIdxStart + i + j]) {
+                            continue strLoop;
+                        }
+                    }
+                }
+
+                foundIdx = strIdxStart + i;
+                break;
+            }
+
+            if (foundIdx == -1) {
+                return false;
+            }
+
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        // All characters in the string are used. Check if only '*'s are left
+        // in the pattern. If so, we succeeded. Otherwise failure.
+        for (int i = patIdxStart; i <= patIdxEnd; i++) {
+            if (patArr[i] != '*') {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Given a pattern and a full path, determine the pattern-mapped part.
+     * <p>For example:
+     * <ul>
+     * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
+     * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
+     * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
+     * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
+     * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
+     * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
+     * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
+     * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
+     * </ul>
+     * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
+     * and '<code>path</code>', but does <strong>not</strong> enforce this.
+     */
+    public String extractPathWithinPattern(String pattern, String path) {
+        String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+        String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+        StringBuilder buffer = new StringBuilder();
+
+        // Add any path parts that have a wildcarded pattern part.
+        int puts = 0;
+        for (int i = 0; i < patternParts.length; i++) {
+            String patternPart = patternParts[i];
+            if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
+                if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
+                    buffer.append(this.pathSeparator);
+                }
+                buffer.append(pathParts[i]);
+                puts++;
+            }
+        }
+
+        // Append any trailing path parts.
+        for (int i = patternParts.length; i < pathParts.length; i++) {
+            if (puts > 0 || i > 0) {
+                buffer.append(this.pathSeparator);
+            }
+            buffer.append(pathParts[i]);
+        }
+
+        return buffer.toString();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/ByteSource.java b/core/src/main/java/org/apache/shiro/util/ByteSource.java
new file mode 100644
index 0000000..85c1b35
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ByteSource.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * A {@code ByteSource} wraps a byte array and provides additional encoding operations.  Most users will find the
+ * {@link Util} inner class sufficient to construct ByteSource instances.
+ *
+ * @since 1.0
+ */
+public interface ByteSource {
+
+    /**
+     * Returns the wrapped byte array.
+     *
+     * @return the wrapped byte array.
+     */
+    byte[] getBytes();
+
+    /**
+     * Returns the <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hex</a>-formatted String representation of the
+     * underlying wrapped byte array.
+     *
+     * @return the <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hex</a>-formatted String representation of the
+     *         underlying wrapped byte array.
+     */
+    String toHex();
+
+    /**
+     * Returns the <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted String representation of the
+     * underlying wrapped byte array.
+     *
+     * @return the <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted String representation of the
+     *         underlying wrapped byte array.
+     */
+    String toBase64();
+
+    /**
+     * Returns {@code true} if the underlying wrapped byte array is null or empty (zero length), {@code false}
+     * otherwise.
+     *
+     * @return {@code true} if the underlying wrapped byte array is null or empty (zero length), {@code false}
+     *         otherwise.
+     * @since 1.2
+     */
+    boolean isEmpty();
+
+    /**
+     * Utility class that can construct ByteSource instances.  This is slightly nicer than needing to know the
+     * {@code ByteSource} implementation class to use.
+     *
+     * @since 1.2
+     */
+    public static final class Util {
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified byte array.
+         *
+         * @param bytes the bytes to represent as a {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified byte array.
+         */
+        public static ByteSource bytes(byte[] bytes) {
+            return new SimpleByteSource(bytes);
+        }
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified character array's bytes.  The byte
+         * array is obtained assuming {@code UTF-8} encoding.
+         *
+         * @param chars the character array to represent as a {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified character array's bytes.
+         */
+        public static ByteSource bytes(char[] chars) {
+            return new SimpleByteSource(chars);
+        }
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified string's bytes.  The byte
+         * array is obtained assuming {@code UTF-8} encoding.
+         *
+         * @param string the string to represent as a {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified string's bytes.
+         */
+        public static ByteSource bytes(String string) {
+            return new SimpleByteSource(string);
+        }
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified ByteSource.
+         *
+         * @param source the ByteSource to represent as a new {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified ByteSource.
+         */
+        public static ByteSource bytes(ByteSource source) {
+            return new SimpleByteSource(source);
+        }
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified File's bytes.
+         *
+         * @param file the file to represent as a {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified File's bytes.
+         */
+        public static ByteSource bytes(File file) {
+            return new SimpleByteSource(file);
+        }
+
+        /**
+         * Returns a new {@code ByteSource} instance representing the specified InputStream's bytes.
+         *
+         * @param stream the InputStream to represent as a {@code ByteSource} instance.
+         * @return a new {@code ByteSource} instance representing the specified InputStream's bytes.
+         */
+        public static ByteSource bytes(InputStream stream) {
+            return new SimpleByteSource(stream);
+        }
+
+        /**
+         * Returns {@code true} if the specified object can be easily represented as a {@code ByteSource} using
+         * the {@link ByteSource.Util}'s default heuristics, {@code false} otherwise.
+         * <p/>
+         * This implementation merely returns {@link SimpleByteSource}.{@link SimpleByteSource#isCompatible(Object) isCompatible(source)}.
+         *
+         * @param source the object to test to see if it can be easily converted to ByteSource instances using default
+         *               heuristics.
+         * @return {@code true} if the specified object can be easily represented as a {@code ByteSource} using
+         *         the {@link ByteSource.Util}'s default heuristics, {@code false} otherwise.
+         */
+        public static boolean isCompatible(Object source) {
+            return SimpleByteSource.isCompatible(source);
+        }
+
+        /**
+         * Returns a {@code ByteSource} instance representing the specified byte source argument.  If the argument
+         * <em>cannot</em> be easily converted to bytes (as is indicated by the {@link #isCompatible(Object)} JavaDoc),
+         * this method will throw an {@link IllegalArgumentException}.
+         *
+         * @param source the byte-backed instance that should be represented as a {@code ByteSource} instance.
+         * @return a {@code ByteSource} instance representing the specified byte source argument.
+         * @throws IllegalArgumentException if the argument <em>cannot</em> be easily converted to bytes
+         *                                  (as indicated by the {@link #isCompatible(Object)} JavaDoc)
+         */
+        public static ByteSource bytes(Object source) throws IllegalArgumentException {
+            if (source == null) {
+                return null;
+            }
+            if (!isCompatible(source)) {
+                String msg = "Unable to heuristically acquire bytes for object of type [" +
+                        source.getClass().getName() + "].  If this type is indeed a byte-backed data type, you might " +
+                        "want to write your own ByteSource implementation to extract its bytes explicitly.";
+                throw new IllegalArgumentException(msg);
+            }
+            if (source instanceof byte[]) {
+                return bytes((byte[]) source);
+            } else if (source instanceof ByteSource) {
+                return (ByteSource) source;
+            } else if (source instanceof char[]) {
+                return bytes((char[]) source);
+            } else if (source instanceof String) {
+                return bytes((String) source);
+            } else if (source instanceof File) {
+                return bytes((File) source);
+            } else if (source instanceof InputStream) {
+                return bytes((InputStream) source);
+            } else {
+                throw new IllegalStateException("Encountered unexpected byte source.  This is a bug - please notify " +
+                        "the Shiro developer list asap (the isCompatible implementation does not reflect this " +
+                        "method's implementation).");
+            }
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/ClassUtils.java b/core/src/main/java/org/apache/shiro/util/ClassUtils.java
new file mode 100644
index 0000000..dd46b06
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ClassUtils.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+
+
+/**
+ * Utility method library used to conveniently interact with <code>Class</code>es, such as acquiring them from the
+ * application <code>ClassLoader</code>s and instantiating Objects from them.
+ *
+ * @since 0.1
+ */
+public class ClassUtils {
+
+    //TODO - complete JavaDoc
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(ClassUtils.class);
+
+    /**
+     * @since 1.0
+     */
+    private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+        @Override
+        protected ClassLoader doGetClassLoader() throws Throwable {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    };
+
+    /**
+     * @since 1.0
+     */
+    private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+        @Override
+        protected ClassLoader doGetClassLoader() throws Throwable {
+            return ClassUtils.class.getClassLoader();
+        }
+    };
+
+    /**
+     * @since 1.0
+     */
+    private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+        @Override
+        protected ClassLoader doGetClassLoader() throws Throwable {
+            return ClassLoader.getSystemClassLoader();
+        }
+    };
+
+    /**
+     * Returns the specified resource by checking the current thread's
+     * {@link Thread#getContextClassLoader() context class loader}, then the
+     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
+     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order, using
+     * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}.
+     *
+     * @param name the name of the resource to acquire from the classloader(s).
+     * @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any
+     *         of the three mentioned ClassLoaders.
+     * @since 0.9
+     */
+    public static InputStream getResourceAsStream(String name) {
+
+        InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
+
+        if (is == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Resource [" + name + "] was not found via the thread context ClassLoader.  Trying the " +
+                        "current ClassLoader...");
+            }
+            is = CLASS_CL_ACCESSOR.getResourceStream(name);
+        }
+
+        if (is == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Resource [" + name + "] was not found via the current class loader.  Trying the " +
+                        "system/application ClassLoader...");
+            }
+            is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
+        }
+
+        if (is == null && log.isTraceEnabled()) {
+            log.trace("Resource [" + name + "] was not found via the thread context, current, or " +
+                    "system/application ClassLoaders.  All heuristics have been exhausted.  Returning null.");
+        }
+
+        return is;
+    }
+
+    /**
+     * Attempts to load the specified class name from the current thread's
+     * {@link Thread#getContextClassLoader() context class loader}, then the
+     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
+     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order.  If any of them cannot locate
+     * the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of
+     * the JRE's <code>ClassNotFoundException</code>.
+     *
+     * @param fqcn the fully qualified class name to load
+     * @return the located class
+     * @throws UnknownClassException if the class cannot be found.
+     */
+    public static Class forName(String fqcn) throws UnknownClassException {
+
+        Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
+
+        if (clazz == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to load class named [" + fqcn +
+                        "] from the thread context ClassLoader.  Trying the current ClassLoader...");
+            }
+            clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
+        }
+
+        if (clazz == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  " +
+                        "Trying the system/application ClassLoader...");
+            }
+            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
+        }
+
+        if (clazz == null) {
+            String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " +
+                    "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
+            throw new UnknownClassException(msg);
+        }
+
+        return clazz;
+    }
+
+    public static boolean isAvailable(String fullyQualifiedClassName) {
+        try {
+            forName(fullyQualifiedClassName);
+            return true;
+        } catch (UnknownClassException e) {
+            return false;
+        }
+    }
+
+    public static Object newInstance(String fqcn) {
+        return newInstance(forName(fqcn));
+    }
+
+    public static Object newInstance(String fqcn, Object... args) {
+        return newInstance(forName(fqcn), args);
+    }
+
+    public static Object newInstance(Class clazz) {
+        if (clazz == null) {
+            String msg = "Class method parameter cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        try {
+            return clazz.newInstance();
+        } catch (Exception e) {
+            throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e);
+        }
+    }
+
+    public static Object newInstance(Class clazz, Object... args) {
+        Class[] argTypes = new Class[args.length];
+        for (int i = 0; i < args.length; i++) {
+            argTypes[i] = args[i].getClass();
+        }
+        Constructor ctor = getConstructor(clazz, argTypes);
+        return instantiate(ctor, args);
+    }
+
+    public static Constructor getConstructor(Class clazz, Class... argTypes) {
+        try {
+            return clazz.getConstructor(argTypes);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalStateException(e);
+        }
+
+    }
+
+    public static Object instantiate(Constructor ctor, Object... args) {
+        try {
+            return ctor.newInstance(args);
+        } catch (Exception e) {
+            String msg = "Unable to instantiate Permission instance with constructor [" + ctor + "]";
+            throw new InstantiationException(msg, e);
+        }
+    }
+
+    /**
+     * @since 1.0
+     */
+    private static interface ClassLoaderAccessor {
+        Class loadClass(String fqcn);
+        InputStream getResourceStream(String name);
+    }
+
+    /**
+     * @since 1.0
+     */
+    private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor {
+
+        public Class loadClass(String fqcn) {
+            Class clazz = null;
+            ClassLoader cl = getClassLoader();
+            if (cl != null) {
+                try {
+                    clazz = cl.loadClass(fqcn);
+                } catch (ClassNotFoundException e) {
+                    if (log.isTraceEnabled()) {
+                        log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
+                    }
+                }
+            }
+            return clazz;
+        }
+
+        public InputStream getResourceStream(String name) {
+            InputStream is = null;
+            ClassLoader cl = getClassLoader();
+            if (cl != null) {
+                is = cl.getResourceAsStream(name);
+            }
+            return is;
+        }
+
+        protected final ClassLoader getClassLoader() {
+            try {
+                return doGetClassLoader();
+            } catch (Throwable t) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Unable to acquire ClassLoader.", t);
+                }
+            }
+            return null;
+        }
+
+        protected abstract ClassLoader doGetClassLoader() throws Throwable;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/CollectionUtils.java b/core/src/main/java/org/apache/shiro/util/CollectionUtils.java
new file mode 100644
index 0000000..0956efa
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/CollectionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.subject.PrincipalCollection;
+
+import java.util.*;
+
+/**
+ * Static helper class for use dealing with Collections.
+ *
+ * @since 0.9
+ */
+public class CollectionUtils {
+
+    //TODO - complete JavaDoc
+
+    public static <E> Set<E> asSet(E... elements) {
+        if (elements == null || elements.length == 0) {
+            return Collections.emptySet();
+        }
+        LinkedHashSet<E> set = new LinkedHashSet<E>(elements.length * 4 / 3 + 1);
+        Collections.addAll(set, elements);
+        return set;
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code Collection} is {@code null} or {@link Collection#isEmpty empty},
+     * {@code false} otherwise.
+     *
+     * @param c the collection to check
+     * @return {@code true} if the specified {@code Collection} is {@code null} or {@link Collection#isEmpty empty},
+     *         {@code false} otherwise.
+     * @since 1.0
+     */
+    public static boolean isEmpty(Collection c) {
+        return c == null || c.isEmpty();
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code Map} is {@code null} or {@link Map#isEmpty empty},
+     * {@code false} otherwise.
+     *
+     * @param m the {@code Map} to check
+     * @return {@code true} if the specified {@code Map} is {@code null} or {@link Map#isEmpty empty},
+     *         {@code false} otherwise.
+     * @since 1.0
+     */
+    public static boolean isEmpty(Map m) {
+        return m == null || m.isEmpty();
+    }
+
+    /**
+     * Returns the size of the specified collection or {@code 0} if the collection is {@code null}.
+     *
+     * @param c the collection to check
+     * @return the size of the specified collection or {@code 0} if the collection is {@code null}.
+     * @since 1.2
+     */
+    public static int size(Collection c) {
+        return c != null ? c.size() : 0;
+    }
+
+    /**
+     * Returns the size of the specified map or {@code 0} if the map is {@code null}.
+     *
+     * @param m the map to check
+     * @return the size of the specified map or {@code 0} if the map is {@code null}.
+     * @since 1.2
+     */
+    public static int size(Map m) {
+        return m != null ? m.size() : 0;
+    }
+
+
+    /**
+     * Returns {@code true} if the specified {@code PrincipalCollection} is {@code null} or
+     * {@link PrincipalCollection#isEmpty empty}, {@code false} otherwise.
+     *
+     * @param principals the principals to check.
+     * @return {@code true} if the specified {@code PrincipalCollection} is {@code null} or
+     *         {@link PrincipalCollection#isEmpty empty}, {@code false} otherwise.
+     * @since 1.0
+     */
+    public static boolean isEmpty(PrincipalCollection principals) {
+        return principals == null || principals.isEmpty();
+    }
+
+    public static <E> List<E> asList(E... elements) {
+        if (elements == null || elements.length == 0) {
+            return Collections.emptyList();
+        }
+        // Avoid integer overflow when a large array is passed in
+        int capacity = computeListCapacity(elements.length);
+        ArrayList<E> list = new ArrayList<E>(capacity);
+        Collections.addAll(list, elements);
+        return list;
+    }
+
+    /*public static <E> Deque<E> asDeque(E... elements) {
+        if (elements == null || elements.length == 0) {
+            return new ArrayDeque<E>();
+        }
+        // Avoid integer overflow when a large array is passed in
+        int capacity = computeListCapacity(elements.length);
+        ArrayDeque<E> deque = new ArrayDeque<E>(capacity);
+        Collections.addAll(deque, elements);
+        return deque;
+    }*/
+
+    static int computeListCapacity(int arraySize) {
+        return (int) Math.min(5L + arraySize + (arraySize / 10), Integer.MAX_VALUE);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/Destroyable.java b/core/src/main/java/org/apache/shiro/util/Destroyable.java
new file mode 100644
index 0000000..584cf97
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/Destroyable.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Shiro container-agnostic interface that indicates that this object requires a callback during destruction.
+ *
+ * @since 0.2
+ */
+public interface Destroyable {
+
+    /**
+     * Called when this object is being destroyed, allowing any necessary cleanup of internal resources.
+     *
+     * @throws Exception if an exception occurs during object destruction.
+     */
+    void destroy() throws Exception;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/Factory.java b/core/src/main/java/org/apache/shiro/util/Factory.java
new file mode 100644
index 0000000..908d31a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/Factory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Generics-aware interface supporting the
+ * <a href="http://en.wikipedia.org/wiki/Factory_method_pattern">Factory Method</a> design pattern.
+ *
+ * @param <T> The type of the instance returned by the Factory implementation.
+ * @since 1.0
+ */
+public interface Factory<T> {
+
+    /**
+     * Returns an instance of the required type.  The implementation determines whether or not a new or cached
+     * instance is created every time this method is called.
+     *
+     * @return an instance of the required type.
+     */
+    T getInstance();
+}
diff --git a/core/src/main/java/org/apache/shiro/util/Initializable.java b/core/src/main/java/org/apache/shiro/util/Initializable.java
new file mode 100644
index 0000000..167f444
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/Initializable.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Shiro container-agnostic interface that indicates that this object requires initialization.
+ *
+ * @since 0.2
+ */
+public interface Initializable {
+
+    /**
+     * Initializes this object.
+     *
+     * @throws org.apache.shiro.ShiroException
+     *          if an exception occurs during initialization.
+     */
+    void init() throws ShiroException;
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/InstantiationException.java b/core/src/main/java/org/apache/shiro/util/InstantiationException.java
new file mode 100644
index 0000000..cd0f916
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/InstantiationException.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Runtime exception thrown by the framework when unable to instantiate a Class via reflection.
+ *
+ * @since 0.2
+ */
+public class InstantiationException extends ShiroException
+{
+
+    /**
+     * Creates a new InstantiationException.
+     */
+    public InstantiationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new InstantiationException.
+     *
+     * @param message the reason for the exception
+     */
+    public InstantiationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new InstantiationException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public InstantiationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new InstantiationException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public InstantiationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java b/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
new file mode 100644
index 0000000..455d168
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Internal helper class used to find the Java/JDK version
+ * that Shiro is operating within, to allow for automatically
+ * adapting to the present platform's capabilities.
+ *
+ * <p>Note that Shiro does not support 1.2 or earlier JVMs - only 1.3 and later.
+ *
+ * <p><em>This class was borrowed and heavily based upon a nearly identical version found in
+ * the <a href="http://www.springframework.org/">Spring Framework</a>, with minor modifications.
+ * The original author names and copyright (Apache 2.0) has been left in place.  A special
+ * thanks to Rod Johnson, Juergen Hoeller, and Rick Evans for making this available.</em>
+ *
+ * @since 0.2
+ */
+public abstract class JavaEnvironment {
+
+    /**
+     * Constant identifying the 1.3.x JVM (JDK 1.3).
+     */
+    public static final int JAVA_13 = 0;
+
+    /**
+     * Constant identifying the 1.4.x JVM (J2SE 1.4).
+     */
+    public static final int JAVA_14 = 1;
+
+    /**
+     * Constant identifying the 1.5 JVM (Java 5).
+     */
+    public static final int JAVA_15 = 2;
+
+    /**
+     * Constant identifying the 1.6 JVM (Java 6).
+     */
+    public static final int JAVA_16 = 3;
+
+    /**
+     * Constant identifying the 1.7 JVM.
+     */
+    public static final int JAVA_17 = 4;
+
+    /** The virtual machine version, i.e. <code>System.getProperty("java.version");</code>. */
+    private static final String version;
+
+    /**
+     * The virtual machine <em>major</em> version.  For example, with a <code>version</code> of
+     * <code>1.5.6_10</code>, this would be <code>1.5</code>
+     */
+    private static final int majorVersion;
+
+    /**
+     * Static code initialization block that sets the
+     * <code>version</code> and <code>majorVersion</code> Class constants
+     * upon initialization.
+     */
+    static {
+        version = System.getProperty("java.version");
+        // version String should look like "1.4.2_10"
+        if (version.indexOf("1.7.") != -1) {
+            majorVersion = JAVA_17;
+        } else if (version.indexOf("1.6.") != -1) {
+            majorVersion = JAVA_16;
+        } else if (version.indexOf("1.5.") != -1) {
+            majorVersion = JAVA_15;
+        } else if (version.indexOf("1.4.") != -1) {
+            majorVersion = JAVA_14;
+        } else {
+            // else leave 1.3 as default (it's either 1.3 or unknown)
+            majorVersion = JAVA_13;
+        }
+    }
+
+
+    /**
+     * Return the full Java version string, as returned by
+     * <code>System.getProperty("java.version")</code>.
+     *
+     * @return the full Java version string
+     * @see System#getProperty(String)
+     */
+    public static String getVersion() {
+        return version;
+    }
+
+    /**
+     * Get the major version code. This means we can do things like
+     * <code>if (getMajorVersion() < JAVA_14)</code>.
+     *
+     * @return a code comparable to the JAVA_XX codes in this class
+     * @see #JAVA_13
+     * @see #JAVA_14
+     * @see #JAVA_15
+     * @see #JAVA_16
+     * @see #JAVA_17
+     */
+    public static int getMajorVersion() {
+        return majorVersion;
+    }
+
+    /**
+     * Convenience method to determine if the current JVM is at least Java 1.4.
+     *
+     * @return <code>true</code> if the current JVM is at least Java 1.4
+     * @see #getMajorVersion()
+     * @see #JAVA_14
+     * @see #JAVA_15
+     * @see #JAVA_16
+     * @see #JAVA_17
+     */
+    public static boolean isAtLeastVersion14() {
+        return getMajorVersion() >= JAVA_14;
+    }
+
+    /**
+     * Convenience method to determine if the current JVM is at least
+     * Java 1.5 (Java 5).
+     *
+     * @return <code>true</code> if the current JVM is at least Java 1.5
+     * @see #getMajorVersion()
+     * @see #JAVA_15
+     * @see #JAVA_16
+     * @see #JAVA_17
+     */
+    public static boolean isAtLeastVersion15() {
+        return getMajorVersion() >= JAVA_15;
+    }
+
+    /**
+     * Convenience method to determine if the current JVM is at least
+     * Java 1.6 (Java 6).
+     *
+     * @return <code>true</code> if the current JVM is at least Java 1.6
+     * @see #getMajorVersion()
+     * @see #JAVA_15
+     * @see #JAVA_16
+     * @see #JAVA_17
+     *
+     * @since 1.2
+     */
+    public static boolean isAtLeastVersion16() {
+        return getMajorVersion() >= JAVA_16;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/JdbcUtils.java b/core/src/main/java/org/apache/shiro/util/JdbcUtils.java
new file mode 100644
index 0000000..0c5f3ae
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/JdbcUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A set of static helper methods for managing JDBC API objects.
+ * <p/>
+ * <em>Note:</em> Some parts of this class were copied from the Spring Framework and then modified.
+ * They were copied here to prevent Spring dependencies in the Shiro core API.  The original license conditions
+ * (Apache 2.0) have been maintained.
+ *
+ * @since 0.2
+ */
+public class JdbcUtils {
+
+    /** Private internal log instance. */
+    private static final Logger log = LoggerFactory.getLogger(JdbcUtils.class);
+
+    /**
+     * Private constructor to prevent instantiation.
+     */
+    private JdbcUtils() {
+    }
+
+    /**
+     * Close the given JDBC Connection and ignore any thrown exception.
+     * This is useful for typical finally blocks in manual JDBC code.
+     *
+     * @param connection the JDBC Connection to close (may be <tt>null</tt>)
+     */
+    public static void closeConnection(Connection connection) {
+        if (connection != null) {
+            try {
+                connection.close();
+            } catch (SQLException ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Could not close JDBC Connection", ex);
+                }
+            } catch (Throwable ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Unexpected exception on closing JDBC Connection", ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the given JDBC Statement and ignore any thrown exception.
+     * This is useful for typical finally blocks in manual JDBC code.
+     *
+     * @param statement the JDBC Statement to close (may be <tt>null</tt>)
+     */
+    public static void closeStatement(Statement statement) {
+        if (statement != null) {
+            try {
+                statement.close();
+            } catch (SQLException ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Could not close JDBC Statement", ex);
+                }
+            } catch (Throwable ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Unexpected exception on closing JDBC Statement", ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the given JDBC ResultSet and ignore any thrown exception.
+     * This is useful for typical finally blocks in manual JDBC code.
+     *
+     * @param rs the JDBC ResultSet to close (may be <tt>null</tt>)
+     */
+    public static void closeResultSet(ResultSet rs) {
+        if (rs != null) {
+            try {
+                rs.close();
+            } catch (SQLException ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Could not close JDBC ResultSet", ex);
+                }
+            } catch (Throwable ex) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Unexpected exception on closing JDBC ResultSet", ex);
+                }
+            }
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java b/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java
new file mode 100644
index 0000000..e525f0f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/LifecycleUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.ShiroException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+
+/**
+ * Utility class to help call {@link org.apache.shiro.util.Initializable#init() Initializable.init()} and
+ * {@link org.apache.shiro.util.Destroyable#destroy() Destroyable.destroy()} methods cleanly on any object.
+ *
+ * @since 0.2
+ */
+public abstract class LifecycleUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(LifecycleUtils.class);
+
+    public static void init(Object o) throws ShiroException {
+        if (o instanceof Initializable) {
+            init((Initializable) o);
+        }
+    }
+
+    public static void init(Initializable initializable) throws ShiroException {
+        initializable.init();
+    }
+
+    /**
+     * Calls {@link #init(Object) init} for each object in the collection.  If the collection is {@code null} or empty,
+     * this method returns quietly.
+     *
+     * @param c the collection containing objects to {@link #init init}.
+     * @throws ShiroException if unable to initialize one or more instances.
+     * @since 0.9
+     */
+    public static void init(Collection c) throws ShiroException {
+        if (c == null || c.isEmpty()) {
+            return;
+        }
+        for (Object o : c) {
+            init(o);
+        }
+    }
+
+    public static void destroy(Object o) {
+        if (o instanceof Destroyable) {
+            destroy((Destroyable) o);
+        } else if (o instanceof Collection) {
+            destroy((Collection)o);
+        }
+    }
+
+    public static void destroy(Destroyable d) {
+        if (d != null) {
+            try {
+                d.destroy();
+            } catch (Throwable t) {
+                if (log.isDebugEnabled()) {
+                    String msg = "Unable to cleanly destroy instance [" + d + "] of type [" + d.getClass().getName() + "].";
+                    log.debug(msg, t);
+                }
+            }
+        }
+    }
+
+    /**
+     * Calls {@link #destroy(Object) destroy} for each object in the collection.
+     * If the collection is {@code null} or empty, this method returns quietly.
+     *
+     * @param c the collection of objects to destroy.
+     * @since 0.9
+     */
+    public static void destroy(Collection c) {
+        if (c == null || c.isEmpty()) {
+            return;
+        }
+
+        for (Object o : c) {
+            destroy(o);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/MapContext.java b/core/src/main/java/org/apache/shiro/util/MapContext.java
new file mode 100644
index 0000000..1ea2437
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/MapContext.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * A {@code MapContext} provides a common base for context-based data storage in a {@link Map}.  Type-safe attribute
+ * retrieval is provided for subclasses with the {@link #getTypedValue(String, Class)} method.
+ *
+ * @see org.apache.shiro.subject.SubjectContext SubjectContext
+ * @see org.apache.shiro.session.mgt.SessionContext SessionContext
+ * @since 1.0
+ */
+public class MapContext implements Map<String, Object>, Serializable {
+
+    private static final long serialVersionUID = 5373399119017820322L;
+
+    private final Map<String, Object> backingMap;
+
+    public MapContext() {
+        this.backingMap = new HashMap<String, Object>();
+    }
+
+    public MapContext(Map<String, Object> map) {
+        this();
+        if (!CollectionUtils.isEmpty(map)) {
+            this.backingMap.putAll(map);
+        }
+    }
+
+    /**
+     * Performs a {@link #get get} operation but additionally ensures that the value returned is of the specified
+     * {@code type}.  If there is no value, {@code null} is returned.
+     *
+     * @param key  the attribute key to look up a value
+     * @param type the expected type of the value
+     * @param <E>  the expected type of the value
+     * @return the typed value or {@code null} if the attribute does not exist.
+     */
+    @SuppressWarnings({"unchecked"})
+    protected <E> E getTypedValue(String key, Class<E> type) {
+        E found = null;
+        Object o = backingMap.get(key);
+        if (o != null) {
+            if (!type.isAssignableFrom(o.getClass())) {
+                String msg = "Invalid object found in SubjectContext Map under key [" + key + "].  Expected type " +
+                        "was [" + type.getName() + "], but the object under that key is of type " +
+                        "[" + o.getClass().getName() + "].";
+                throw new IllegalArgumentException(msg);
+            }
+            found = (E) o;
+        }
+        return found;
+    }
+
+    /**
+     * Places a value in this context map under the given key only if the given {@code value} argument is not null.
+     *
+     * @param key   the attribute key under which the non-null value will be stored
+     * @param value the non-null value to store.  If {@code null}, this method does nothing and returns immediately.
+     */
+    protected void nullSafePut(String key, Object value) {
+        if (value != null) {
+            put(key, value);
+        }
+    }
+
+    public int size() {
+        return backingMap.size();
+    }
+
+    public boolean isEmpty() {
+        return backingMap.isEmpty();
+    }
+
+    public boolean containsKey(Object o) {
+        return backingMap.containsKey(o);
+    }
+
+    public boolean containsValue(Object o) {
+        return backingMap.containsValue(o);
+    }
+
+    public Object get(Object o) {
+        return backingMap.get(o);
+    }
+
+    public Object put(String s, Object o) {
+        return backingMap.put(s, o);
+    }
+
+    public Object remove(Object o) {
+        return backingMap.remove(o);
+    }
+
+    public void putAll(Map<? extends String, ?> map) {
+        backingMap.putAll(map);
+    }
+
+    public void clear() {
+        backingMap.clear();
+    }
+
+    public Set<String> keySet() {
+        return Collections.unmodifiableSet(backingMap.keySet());
+    }
+
+    public Collection<Object> values() {
+        return Collections.unmodifiableCollection(backingMap.values());
+    }
+
+    public Set<Entry<String, Object>> entrySet() {
+        return Collections.unmodifiableSet(backingMap.entrySet());
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/Nameable.java b/core/src/main/java/org/apache/shiro/util/Nameable.java
new file mode 100644
index 0000000..17bdc45
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/Nameable.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+
+/**
+ * Interface implemented by components that can be named, such as via configuration, and wish to have that name
+ * set once it has been configured.
+ *
+ * @since 0.9
+ */
+public interface Nameable {
+
+    /**
+     * Sets the (preferably application unique) name for this component.
+     * @param name the preferably application unique name for this component.
+     */
+    void setName(String name);
+}
diff --git a/core/src/main/java/org/apache/shiro/util/PatternMatcher.java b/core/src/main/java/org/apache/shiro/util/PatternMatcher.java
new file mode 100644
index 0000000..c1761a7
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/PatternMatcher.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Interface for components that can match source strings against a specified pattern string.
+ * <p/>
+ * Different implementations can support different pattern types, for example, Ant style path expressions, or
+ * regular expressions, or other types of text based patterns.
+ *
+ * @see org.apache.shiro.util.AntPathMatcher AntPathMatcher
+ * @since 0.9 RC2
+ */
+public interface PatternMatcher {
+
+    /**
+     * Returns <code>true</code> if the given <code>source</code> matches the specified <code>pattern</code>,
+     * <code>false</code> otherwise.
+     *
+     * @param pattern the pattern to match against
+     * @param source  the source to match
+     * @return <code>true</code> if the given <code>source</code> matches the specified <code>pattern</code>,
+     *         <code>false</code> otherwise.
+     */
+    boolean matches(String pattern, String source);
+}
diff --git a/core/src/main/java/org/apache/shiro/util/PermissionUtils.java b/core/src/main/java/org/apache/shiro/util/PermissionUtils.java
new file mode 100644
index 0000000..5f6134d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/PermissionUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.PermissionResolver;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+
+/**
+ * Utility class to help with String-to-Permission object resolution.
+ *
+ * @since 0.1
+ */
+public class PermissionUtils {
+
+    public static Set<Permission> resolveDelimitedPermissions(String s, PermissionResolver permissionResolver) {
+        Set<String> permStrings = toPermissionStrings(s);
+        return resolvePermissions(permStrings, permissionResolver);
+    }
+
+    public static Set<String> toPermissionStrings(String permissionsString) {
+        String[] tokens = StringUtils.split(permissionsString);
+        if (tokens != null && tokens.length > 0) {
+            return new LinkedHashSet<String>(Arrays.asList(tokens));
+        }
+        return null;
+    }
+
+    public static Set<Permission> resolvePermissions(Collection<String> permissionStrings, PermissionResolver permissionResolver) {
+        Set<Permission> permissions = new LinkedHashSet<Permission>(permissionStrings.size());
+        for (String permissionString : permissionStrings) {
+            permissions.add(permissionResolver.resolvePermission(permissionString));
+        }
+        return permissions;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/RegExPatternMatcher.java b/core/src/main/java/org/apache/shiro/util/RegExPatternMatcher.java
new file mode 100644
index 0000000..b251ac5
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/RegExPatternMatcher.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * {@code PatternMatcher} implementation that uses standard {@link java.util.regex} objects.
+ *
+ * @see Pattern
+ * @since 1.0
+ */
+public class RegExPatternMatcher implements PatternMatcher {
+
+    /**
+     * Simple implementation that merely uses the default pattern comparison logic provided by the
+     * JDK.
+     * <p/>This implementation essentially executes the following:
+     * <pre>
+     * Pattern p = Pattern.compile(pattern);
+     * Matcher m = p.matcher(source);
+     * return m.matches();</pre>
+     * @param pattern the pattern to match against
+     * @param source  the source to match
+     * @return {@code true} if the source matches the required pattern, {@code false} otherwise.
+     */
+    public boolean matches(String pattern, String source) {
+        if (pattern == null) {
+            throw new IllegalArgumentException("pattern argument cannot be null.");
+        }
+        Pattern p = Pattern.compile(pattern);
+        Matcher m = p.matcher(source);
+        return m.matches();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java b/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java
new file mode 100644
index 0000000..ce27f0f
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.CodecSupport;
+import org.apache.shiro.codec.Hex;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Very simple {@link ByteSource ByteSource} implementation that maintains an internal {@code byte[]} array and uses the
+ * {@link Hex Hex} and {@link Base64 Base64} codec classes to support the
+ * {@link #toHex() toHex()} and {@link #toBase64() toBase64()} implementations.
+ * <p/>
+ * The constructors on this class accept the following implicit byte-backed data types and will convert them to
+ * a byte-array automatically:
+ * <ul>
+ * <li>byte[]</li>
+ * <li>char[]</li>
+ * <li>String</li>
+ * <li>{@link ByteSource ByteSource}</li>
+ * <li>{@link File File}</li>
+ * <li>{@link InputStream InputStream}</li>
+ * </ul>
+ *
+ * @since 1.0
+ */
+public class SimpleByteSource implements ByteSource {
+
+    private final byte[] bytes;
+    private String cachedHex;
+    private String cachedBase64;
+
+    public SimpleByteSource(byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    /**
+     * Creates an instance by converting the characters to a byte array (assumes UTF-8 encoding).
+     *
+     * @param chars the source characters to use to create the underlying byte array.
+     * @since 1.1
+     */
+    public SimpleByteSource(char[] chars) {
+        this.bytes = CodecSupport.toBytes(chars);
+    }
+
+    /**
+     * Creates an instance by converting the String to a byte array (assumes UTF-8 encoding).
+     *
+     * @param string the source string to convert to a byte array (assumes UTF-8 encoding).
+     * @since 1.1
+     */
+    public SimpleByteSource(String string) {
+        this.bytes = CodecSupport.toBytes(string);
+    }
+
+    /**
+     * Creates an instance using the sources bytes directly - it does not create a copy of the
+     * argument's byte array.
+     *
+     * @param source the source to use to populate the underlying byte array.
+     * @since 1.1
+     */
+    public SimpleByteSource(ByteSource source) {
+        this.bytes = source.getBytes();
+    }
+
+    /**
+     * Creates an instance by converting the file to a byte array.
+     *
+     * @param file the file from which to acquire bytes.
+     * @since 1.1
+     */
+    public SimpleByteSource(File file) {
+        this.bytes = new BytesHelper().getBytes(file);
+    }
+
+    /**
+     * Creates an instance by converting the stream to a byte array.
+     *
+     * @param stream the stream from which to acquire bytes.
+     * @since 1.1
+     */
+    public SimpleByteSource(InputStream stream) {
+        this.bytes = new BytesHelper().getBytes(stream);
+    }
+
+    /**
+     * Returns {@code true} if the specified object is a recognized data type that can be easily converted to
+     * bytes by instances of this class, {@code false} otherwise.
+     * <p/>
+     * This implementation returns {@code true} IFF the specified object is an instance of one of the following
+     * types:
+     * <ul>
+     * <li>{@code byte[]}</li>
+     * <li>{@code char[]}</li>
+     * <li>{@link ByteSource}</li>
+     * <li>{@link String}</li>
+     * <li>{@link File}</li>
+     * </li>{@link InputStream}</li>
+     * </ul>
+     *
+     * @param o the object to test to see if it can be easily converted to bytes by instances of this class.
+     * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
+     *         {@code false} otherwise.
+     * @since 1.2
+     */
+    public static boolean isCompatible(Object o) {
+        return o instanceof byte[] || o instanceof char[] || o instanceof String ||
+                o instanceof ByteSource || o instanceof File || o instanceof InputStream;
+    }
+
+    public byte[] getBytes() {
+        return this.bytes;
+    }
+
+    public boolean isEmpty() {
+        return this.bytes == null || this.bytes.length == 0;
+    }
+
+    public String toHex() {
+        if ( this.cachedHex == null ) {
+            this.cachedHex = Hex.encodeToString(getBytes());
+        }
+        return this.cachedHex;
+    }
+
+    public String toBase64() {
+        if ( this.cachedBase64 == null ) {
+            this.cachedBase64 = Base64.encodeToString(getBytes());
+        }
+        return this.cachedBase64;
+    }
+
+    public String toString() {
+        return toBase64();
+    }
+
+    public int hashCode() {
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof ByteSource) {
+            ByteSource bs = (ByteSource) o;
+            return Arrays.equals(getBytes(), bs.getBytes());
+        }
+        return false;
+    }
+
+    //will probably be removed in Shiro 2.0.  See SHIRO-203:
+    //https://issues.apache.org/jira/browse/SHIRO-203
+    private static final class BytesHelper extends CodecSupport {
+        public byte[] getBytes(File file) {
+            return toBytes(file);
+        }
+
+        public byte[] getBytes(InputStream stream) {
+            return toBytes(stream);
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/SoftHashMap.java b/core/src/main/java/org/apache/shiro/util/SoftHashMap.java
new file mode 100644
index 0000000..233883c
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/SoftHashMap.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/**
+ * A <code><em>Soft</em>HashMap</code> is a memory-constrained map that stores its <em>values</em> in
+ * {@link SoftReference SoftReference}s.  (Contrast this with the JDK's
+ * {@link WeakHashMap WeakHashMap}, which uses weak references for its <em>keys</em>, which is of little value if you
+ * want the cache to auto-resize itself based on memory constraints).
+ * <p/>
+ * Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory
+ * limitations and garbage collection.  This ensures that the cache will not cause memory leaks by holding strong
+ * references to all of its values.
+ * <p/>
+ * This class is a generics-enabled Map based on initial ideas from Heinz Kabutz's and Sydney Redelinghuys's
+ * <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version (with their approval)</a>, with
+ * continued modifications.
+ * <p/>
+ * This implementation is thread-safe and usable in concurrent environments.
+ *
+ * @since 1.0
+ */
+public class SoftHashMap<K, V> implements Map<K, V> {
+
+    /**
+     * The default value of the RETENTION_SIZE attribute, equal to 100.
+     */
+    private static final int DEFAULT_RETENTION_SIZE = 100;
+
+    /**
+     * The internal HashMap that will hold the SoftReference.
+     */
+    private final Map<K, SoftValue<V, K>> map;
+
+    /**
+     * The number of strong references to hold internally, that is, the number of instances to prevent
+     * from being garbage collected automatically (unlike other soft references).
+     */
+    private final int RETENTION_SIZE;
+
+    /**
+     * The FIFO list of strong references (not to be garbage collected), order of last access.
+     */
+    private final Queue<V> strongReferences; //guarded by 'strongReferencesLock'
+    private final ReentrantLock strongReferencesLock;
+
+    /**
+     * Reference queue for cleared SoftReference objects.
+     */
+    private final ReferenceQueue<? super V> queue;
+
+    /**
+     * Creates a new SoftHashMap with a default retention size size of
+     * {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
+     *
+     * @see #SoftHashMap(int)
+     */
+    public SoftHashMap() {
+        this(DEFAULT_RETENTION_SIZE);
+    }
+
+    /**
+     * Creates a new SoftHashMap with the specified retention size.
+     * <p/>
+     * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
+     * (ie 'retained') to prevent them from being eagerly garbage collected.  That is, the point of a SoftHashMap is to
+     * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
+     * elements retained after a GC due to the strong references.
+     * <p/>
+     * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
+     * than the actual <code>retentionSize</code> value.  This number is intended to be a best-effort retention low
+     * water mark.
+     *
+     * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
+     *                      (retained), preventing them from being eagerly garbage collected by the JVM.
+     */
+    @SuppressWarnings({"unchecked"})
+    public SoftHashMap(int retentionSize) {
+        super();
+        RETENTION_SIZE = Math.max(0, retentionSize);
+        queue = new ReferenceQueue<V>();
+        strongReferencesLock = new ReentrantLock();
+        map = new ConcurrentHashMap<K, SoftValue<V, K>>();
+        strongReferences = new ConcurrentLinkedQueue<V>();
+    }
+
+    /**
+     * Creates a {@code SoftHashMap} backed by the specified {@code source}, with a default retention
+     * size of {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
+     *
+     * @param source the backing map to populate this {@code SoftHashMap}
+     * @see #SoftHashMap(Map,int)
+     */
+    public SoftHashMap(Map<K, V> source) {
+        this(DEFAULT_RETENTION_SIZE);
+        putAll(source);
+    }
+
+    /**
+     * Creates a {@code SoftHashMap} backed by the specified {@code source}, with the specified retention size.
+     * <p/>
+     * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
+     * (ie 'retained') to prevent them from being eagerly garbage collected.  That is, the point of a SoftHashMap is to
+     * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
+     * elements retained after a GC due to the strong references.
+     * <p/>
+     * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
+     * than the actual <code>retentionSize</code> value.  This number is intended to be a best-effort retention low
+     * water mark.
+     *
+     * @param source        the backing map to populate this {@code SoftHashMap}
+     * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
+     *                      (retained), preventing them from being eagerly garbage collected by the JVM.
+     */
+    public SoftHashMap(Map<K, V> source, int retentionSize) {
+        this(retentionSize);
+        putAll(source);
+    }
+
+    public V get(Object key) {
+        processQueue();
+
+        V result = null;
+        SoftValue<V, K> value = map.get(key);
+
+        if (value != null) {
+            //unwrap the 'real' value from the SoftReference
+            result = value.get();
+            if (result == null) {
+                //The wrapped value was garbage collected, so remove this entry from the backing map:
+                //noinspection SuspiciousMethodCalls
+                map.remove(key);
+            } else {
+                //Add this value to the beginning of the strong reference queue (FIFO).
+                addToStrongReferences(result);
+            }
+        }
+        return result;
+    }
+
+    private void addToStrongReferences(V result) {
+        strongReferencesLock.lock();
+        try {
+            strongReferences.add(result);
+            trimStrongReferencesIfNecessary();
+        } finally {
+            strongReferencesLock.unlock();
+        }
+
+    }
+
+    //Guarded by the strongReferencesLock in the addToStrongReferences method
+
+    private void trimStrongReferencesIfNecessary() {
+        //trim the strong ref queue if necessary:
+        while (strongReferences.size() > RETENTION_SIZE) {
+            strongReferences.poll();
+        }
+    }
+
+    /**
+     * Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map
+     * by looking them up using the SoftValue.key data member.
+     */
+    private void processQueue() {
+        SoftValue sv;
+        while ((sv = (SoftValue) queue.poll()) != null) {
+            //noinspection SuspiciousMethodCalls
+            map.remove(sv.key); // we can access private data!
+        }
+    }
+
+    public boolean isEmpty() {
+        processQueue();
+        return map.isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        processQueue();
+        return map.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        processQueue();
+        Collection values = values();
+        return values != null && values.contains(value);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> m) {
+        if (m == null || m.isEmpty()) {
+            processQueue();
+            return;
+        }
+        for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public Set<K> keySet() {
+        processQueue();
+        return map.keySet();
+    }
+
+    public Collection<V> values() {
+        processQueue();
+        Collection<K> keys = map.keySet();
+        if (keys.isEmpty()) {
+            //noinspection unchecked
+            return Collections.EMPTY_SET;
+        }
+        Collection<V> values = new ArrayList<V>(keys.size());
+        for (K key : keys) {
+            V v = get(key);
+            if (v != null) {
+                values.add(v);
+            }
+        }
+        return values;
+    }
+
+    /**
+     * Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection.
+     */
+    public V put(K key, V value) {
+        processQueue(); // throw out garbage collected values first
+        SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
+        SoftValue<V, K> previous = map.put(key, sv);
+        addToStrongReferences(value);
+        return previous != null ? previous.get() : null;
+    }
+
+    public V remove(Object key) {
+        processQueue(); // throw out garbage collected values first
+        SoftValue<V, K> raw = map.remove(key);
+        return raw != null ? raw.get() : null;
+    }
+
+    public void clear() {
+        strongReferencesLock.lock();
+        try {
+            strongReferences.clear();
+        } finally {
+            strongReferencesLock.unlock();
+        }
+        processQueue(); // throw out garbage collected values
+        map.clear();
+    }
+
+    public int size() {
+        processQueue(); // throw out garbage collected values first
+        return map.size();
+    }
+
+    public Set<Map.Entry<K, V>> entrySet() {
+        processQueue(); // throw out garbage collected values first
+        Collection<K> keys = map.keySet();
+        if (keys.isEmpty()) {
+            //noinspection unchecked
+            return Collections.EMPTY_SET;
+        }
+
+        Map<K, V> kvPairs = new HashMap<K, V>(keys.size());
+        for (K key : keys) {
+            V v = get(key);
+            if (v != null) {
+                kvPairs.put(key, v);
+            }
+        }
+        return kvPairs.entrySet();
+    }
+
+    /**
+     * We define our own subclass of SoftReference which contains
+     * not only the value but also the key to make it easier to find
+     * the entry in the HashMap after it's been garbage collected.
+     */
+    private static class SoftValue<V, K> extends SoftReference<V> {
+
+        private final K key;
+
+        /**
+         * Constructs a new instance, wrapping the value, key, and queue, as
+         * required by the superclass.
+         *
+         * @param value the map value
+         * @param key   the map key
+         * @param queue the soft reference queue to poll to determine if the entry had been reaped by the GC.
+         */
+        private SoftValue(V value, K key, ReferenceQueue<? super V> queue) {
+            super(value, queue);
+            this.key = key;
+        }
+
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/StringUtils.java b/core/src/main/java/org/apache/shiro/util/StringUtils.java
new file mode 100644
index 0000000..15fd9e1
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/StringUtils.java
@@ -0,0 +1,502 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * <p>Simple utility class for String operations useful across the framework.
+ * <p/>
+ * <p>Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
+ * and in these cases, we have retained all license, copyright and author information.
+ *
+ * @since 0.9
+ */
+public class StringUtils {
+
+    //TODO - complete JavaDoc
+
+    /**
+     * Constant representing the empty string, equal to ""
+     */
+    public static final String EMPTY_STRING = "";
+
+    /**
+     * Constant representing the default delimiter character (comma), equal to <code>','</code>
+     */
+    public static final char DEFAULT_DELIMITER_CHAR = ',';
+
+    /**
+     * Constant representing the default quote character (double quote), equal to '"'</code>
+     */
+    public static final char DEFAULT_QUOTE_CHAR = '"';
+
+    /**
+     * Check whether the given String has actual text.
+     * More specifically, returns <code>true</code> if the string not <code>null</code>,
+     * its length is greater than 0, and it contains at least one non-whitespace character.
+     * <p/>
+     * <code>StringUtils.hasText(null) == false<br/>
+     * StringUtils.hasText("") == false<br/>
+     * StringUtils.hasText(" ") == false<br/>
+     * StringUtils.hasText("12345") == true<br/>
+     * StringUtils.hasText(" 12345 ") == true</code>
+     * <p/>
+     * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param str the String to check (may be <code>null</code>)
+     * @return <code>true</code> if the String is not <code>null</code>, its length is
+     *         greater than 0, and it does not contain whitespace only
+     * @see java.lang.Character#isWhitespace
+     */
+    public static boolean hasText(String str) {
+        if (!hasLength(str)) {
+            return false;
+        }
+        int strLen = str.length();
+        for (int i = 0; i < strLen; i++) {
+            if (!Character.isWhitespace(str.charAt(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check that the given String is neither <code>null</code> nor of length 0.
+     * Note: Will return <code>true</code> for a String that purely consists of whitespace.
+     * <p/>
+     * <code>StringUtils.hasLength(null) == false<br/>
+     * StringUtils.hasLength("") == false<br/>
+     * StringUtils.hasLength(" ") == true<br/>
+     * StringUtils.hasLength("Hello") == true</code>
+     * <p/>
+     * Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param str the String to check (may be <code>null</code>)
+     * @return <code>true</code> if the String is not null and has length
+     * @see #hasText(String)
+     */
+    public static boolean hasLength(String str) {
+        return (str != null && str.length() > 0);
+    }
+
+
+    /**
+     * Test if the given String starts with the specified prefix,
+     * ignoring upper/lower case.
+     * <p/>
+     * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param str    the String to check
+     * @param prefix the prefix to look for
+     * @return <code>true</code> starts with the specified prefix (ignoring case), <code>false</code> if it does not.
+     * @see java.lang.String#startsWith
+     */
+    public static boolean startsWithIgnoreCase(String str, String prefix) {
+        if (str == null || prefix == null) {
+            return false;
+        }
+        if (str.startsWith(prefix)) {
+            return true;
+        }
+        if (str.length() < prefix.length()) {
+            return false;
+        }
+        String lcStr = str.substring(0, prefix.length()).toLowerCase();
+        String lcPrefix = prefix.toLowerCase();
+        return lcStr.equals(lcPrefix);
+    }
+
+    /**
+     * Returns a 'cleaned' representation of the specified argument.  'Cleaned' is defined as the following:
+     * <p/>
+     * <ol>
+     * <li>If the specified <code>String</code> is <code>null</code>, return <code>null</code></li>
+     * <li>If not <code>null</code>, {@link String#trim() trim()} it.</li>
+     * <li>If the trimmed string is equal to the empty String (i.e. ""), return <code>null</code></li>
+     * <li>If the trimmed string is not the empty string, return the trimmed version</li>.
+     * </ol>
+     * <p/>
+     * Therefore this method always ensures that any given string has trimmed text, and if it doesn't, <code>null</code>
+     * is returned.
+     *
+     * @param in the input String to clean.
+     * @return a populated-but-trimmed String or <code>null</code> otherwise
+     */
+    public static String clean(String in) {
+        String out = in;
+
+        if (in != null) {
+            out = in.trim();
+            if (out.equals(EMPTY_STRING)) {
+                out = null;
+            }
+        }
+
+        return out;
+    }
+
+    /**
+     * Returns the specified array as a comma-delimited (',') string.
+     *
+     * @param array the array whose contents will be converted to a string.
+     * @return the array's contents as a comma-delimited (',') string.
+     * @since 1.0
+     */
+    public static String toString(Object[] array) {
+        return toDelimitedString(array, ",");
+    }
+
+    /**
+     * Returns the array's contents as a string, with each element delimited by the specified
+     * {@code delimiter} argument.  Useful for {@code toString()} implementations and log messages.
+     *
+     * @param array     the array whose contents will be converted to a string
+     * @param delimiter the delimiter to use between each element
+     * @return a single string, delimited by the specified {@code delimiter}.
+     * @since 1.0
+     */
+    public static String toDelimitedString(Object[] array, String delimiter) {
+        if (array == null || array.length == 0) {
+            return EMPTY_STRING;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < array.length; i++) {
+            if (i > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(array[i]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns the collection's contents as a string, with each element delimited by the specified
+     * {@code delimiter} argument.  Useful for {@code toString()} implementations and log messages.
+     *
+     * @param c         the collection whose contents will be converted to a string
+     * @param delimiter the delimiter to use between each element
+     * @return a single string, delimited by the specified {@code delimiter}.
+     * @since 1.2
+     */
+    public static String toDelimitedString(Collection c, String delimiter) {
+        if (c == null || c.isEmpty()) {
+            return EMPTY_STRING;
+        }
+        return join(c.iterator(), delimiter);
+    }
+
+    /**
+     * Tokenize the given String into a String array via a StringTokenizer.
+     * Trims tokens and omits empty tokens.
+     * <p>The given delimiters string is supposed to consist of any number of
+     * delimiter characters. Each of those characters can be used to separate
+     * tokens. A delimiter is always a single character; for multi-character
+     * delimiters, consider using <code>delimitedListToStringArray</code>
+     * <p/>
+     * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param str        the String to tokenize
+     * @param delimiters the delimiter characters, assembled as String
+     *                   (each of those characters is individually considered as delimiter).
+     * @return an array of the tokens
+     * @see java.util.StringTokenizer
+     * @see java.lang.String#trim()
+     */
+    public static String[] tokenizeToStringArray(String str, String delimiters) {
+        return tokenizeToStringArray(str, delimiters, true, true);
+    }
+
+    /**
+     * Tokenize the given String into a String array via a StringTokenizer.
+     * <p>The given delimiters string is supposed to consist of any number of
+     * delimiter characters. Each of those characters can be used to separate
+     * tokens. A delimiter is always a single character; for multi-character
+     * delimiters, consider using <code>delimitedListToStringArray</code>
+     * <p/>
+     * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param str               the String to tokenize
+     * @param delimiters        the delimiter characters, assembled as String
+     *                          (each of those characters is individually considered as delimiter)
+     * @param trimTokens        trim the tokens via String's <code>trim</code>
+     * @param ignoreEmptyTokens omit empty tokens from the result array
+     *                          (only applies to tokens that are empty after trimming; StringTokenizer
+     *                          will not consider subsequent delimiters as token in the first place).
+     * @return an array of the tokens (<code>null</code> if the input String
+     *         was <code>null</code>)
+     * @see java.util.StringTokenizer
+     * @see java.lang.String#trim()
+     */
+    @SuppressWarnings({"unchecked"})
+    public static String[] tokenizeToStringArray(
+            String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
+
+        if (str == null) {
+            return null;
+        }
+        StringTokenizer st = new StringTokenizer(str, delimiters);
+        List tokens = new ArrayList();
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (trimTokens) {
+                token = token.trim();
+            }
+            if (!ignoreEmptyTokens || token.length() > 0) {
+                tokens.add(token);
+            }
+        }
+        return toStringArray(tokens);
+    }
+
+    /**
+     * Copy the given Collection into a String array.
+     * The Collection must contain String elements only.
+     * <p/>
+     * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
+     *
+     * @param collection the Collection to copy
+     * @return the String array (<code>null</code> if the passed-in
+     *         Collection was <code>null</code>)
+     */
+    @SuppressWarnings({"unchecked"})
+    public static String[] toStringArray(Collection collection) {
+        if (collection == null) {
+            return null;
+        }
+        return (String[]) collection.toArray(new String[collection.size()]);
+    }
+
+    public static String[] splitKeyValue(String aLine) throws ParseException {
+        String line = clean(aLine);
+        if (line == null) {
+            return null;
+        }
+        String[] split = line.split(" ", 2);
+        if (split.length != 2) {
+            //fallback to checking for an equals sign
+            split = line.split("=", 2);
+            if (split.length != 2) {
+                String msg = "Unable to determine Key/Value pair from line [" + line + "].  There is no space from " +
+                        "which the split location could be determined.";
+                throw new ParseException(msg, 0);
+            }
+
+        }
+
+        split[0] = clean(split[0]);
+        split[1] = clean(split[1]);
+        if (split[1].startsWith("=")) {
+            //they used spaces followed by an equals followed by zero or more spaces to split the key/value pair, so
+            //remove the equals sign to result in only the key and values in the
+            split[1] = clean(split[1].substring(1));
+        }
+
+        if (split[0] == null) {
+            String msg = "No valid key could be found in line [" + line + "] to form a key/value pair.";
+            throw new ParseException(msg, 0);
+        }
+        if (split[1] == null) {
+            String msg = "No corresponding value could be found in line [" + line + "] for key [" + split[0] + "]";
+            throw new ParseException(msg, 0);
+        }
+
+        return split;
+    }
+
+    public static String[] split(String line) {
+        return split(line, DEFAULT_DELIMITER_CHAR);
+    }
+
+    public static String[] split(String line, char delimiter) {
+        return split(line, delimiter, DEFAULT_QUOTE_CHAR);
+    }
+
+    public static String[] split(String line, char delimiter, char quoteChar) {
+        return split(line, delimiter, quoteChar, quoteChar);
+    }
+
+    public static String[] split(String line, char delimiter, char beginQuoteChar, char endQuoteChar) {
+        return split(line, delimiter, beginQuoteChar, endQuoteChar, false, true);
+    }
+
+    /**
+     * Splits the specified delimited String into tokens, supporting quoted tokens so that quoted strings themselves
+     * won't be tokenized.
+     * <p/>
+     * This method's implementation is very loosely based (with significant modifications) on
+     * <a href="http://blogs.bytecode.com.au/glen">Glen Smith</a>'s open-source
+     * <a href="http://opencsv.svn.sourceforge.net/viewvc/opencsv/trunk/src/au/com/bytecode/opencsv/CSVReader.java?&view=markup">CSVReader.java</a>
+     * file.
+     * <p/>
+     * That file is Apache 2.0 licensed as well, making Glen's code a great starting point for us to modify to
+     * our needs.
+     *
+     * @param aLine          the String to parse
+     * @param delimiter      the delimiter by which the <tt>line</tt> argument is to be split
+     * @param beginQuoteChar the character signifying the start of quoted text (so the quoted text will not be split)
+     * @param endQuoteChar   the character signifying the end of quoted text
+     * @param retainQuotes   if the quotes themselves should be retained when constructing the corresponding token
+     * @param trimTokens     if leading and trailing whitespace should be trimmed from discovered tokens.
+     * @return the tokens discovered from parsing the given delimited <tt>line</tt>.
+     */
+    public static String[] split(String aLine, char delimiter, char beginQuoteChar, char endQuoteChar,
+                                 boolean retainQuotes, boolean trimTokens) {
+        String line = clean(aLine);
+        if (line == null) {
+            return null;
+        }
+
+        List<String> tokens = new ArrayList<String>();
+        StringBuilder sb = new StringBuilder();
+        boolean inQuotes = false;
+
+        for (int i = 0; i < line.length(); i++) {
+
+            char c = line.charAt(i);
+            if (c == beginQuoteChar) {
+                // this gets complex... the quote may end a quoted block, or escape another quote.
+                // do a 1-char lookahead:
+                if (inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
+                        && line.length() > (i + 1)  // there is indeed another character to check.
+                        && line.charAt(i + 1) == beginQuoteChar) { // ..and that char. is a quote also.
+                    // we have two quote chars in a row == one quote char, so consume them both and
+                    // put one on the token. we do *not* exit the quoted text.
+                    sb.append(line.charAt(i + 1));
+                    i++;
+                } else {
+                    inQuotes = !inQuotes;
+                    if (retainQuotes) {
+                        sb.append(c);
+                    }
+                }
+            } else if (c == endQuoteChar) {
+                inQuotes = !inQuotes;
+                if (retainQuotes) {
+                    sb.append(c);
+                }
+            } else if (c == delimiter && !inQuotes) {
+                String s = sb.toString();
+                if (trimTokens) {
+                    s = s.trim();
+                }
+                tokens.add(s);
+                sb = new StringBuilder(); // start work on next token
+            } else {
+                sb.append(c);
+            }
+        }
+        String s = sb.toString();
+        if (trimTokens) {
+            s = s.trim();
+        }
+        tokens.add(s);
+        return tokens.toArray(new String[tokens.size()]);
+    }
+
+    /**
+     * Joins the elements of the provided {@code Iterator} into
+     * a single String containing the provided elements.</p>
+     * <p/>
+     * No delimiter is added before or after the list.
+     * A {@code null} separator is the same as an empty String ("").</p>
+     * <p/>
+     * Copied from Commons Lang, version 3 (r1138702).</p>
+     *
+     * @param iterator  the {@code Iterator} of values to join together, may be null
+     * @param separator the separator character to use, null treated as ""
+     * @return the joined String, {@code null} if null iterator input
+     * @since 1.2
+     */
+    public static String join(Iterator<?> iterator, String separator) {
+        final String empty = "";
+
+        // handle null, zero and one elements before building a buffer
+        if (iterator == null) {
+            return null;
+        }
+        if (!iterator.hasNext()) {
+            return empty;
+        }
+        Object first = iterator.next();
+        if (!iterator.hasNext()) {
+            return first == null ? empty : first.toString();
+        }
+
+        // two or more elements
+        StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
+        if (first != null) {
+            buf.append(first);
+        }
+
+        while (iterator.hasNext()) {
+            if (separator != null) {
+                buf.append(separator);
+            }
+            Object obj = iterator.next();
+            if (obj != null) {
+                buf.append(obj);
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Splits the {@code delimited} string (delimited by the specified {@code separator} character) and returns the
+     * delimited values as a {@code Set}.
+     * <p/>
+     * If either argument is {@code null}, this method returns {@code null}.
+     *
+     * @param delimited the string to split
+     * @param separator the character that delineates individual tokens to split
+     * @return the delimited values as a {@code Set}.
+     * @since 1.2
+     */
+    public static Set<String> splitToSet(String delimited, String separator) {
+        if (delimited == null || separator == null) {
+            return null;
+        }
+        String[] split = split(delimited, separator.charAt(0));
+        return CollectionUtils.asSet(split);
+    }
+
+    /**
+     * Returns the input argument, but ensures the first character is capitalized (if possible).
+     * @param in the string to uppercase the first character.
+     * @return the input argument, but with the first character capitalized (if possible).
+     * @since 1.2
+     */
+    public static String uppercaseFirstChar(String in) {
+        if (in == null || in.length() == 0) {
+            return in;
+        }
+        int length = in.length();
+        StringBuilder sb = new StringBuilder(length);
+
+        sb.append(Character.toUpperCase(in.charAt(0)));
+        if (length > 1) {
+            String remaining = in.substring(1);
+            sb.append(remaining);
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/ThreadContext.java b/core/src/main/java/org/apache/shiro/util/ThreadContext.java
new file mode 100644
index 0000000..7dd3af2
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ThreadContext.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * A ThreadContext provides a means of binding and unbinding objects to the
+ * current thread based on key/value pairs.
+ * <p/>
+ * <p>An internal {@link java.util.HashMap} is used to maintain the key/value pairs
+ * for each thread.</p>
+ * <p/>
+ * <p>If the desired behavior is to ensure that bound data is not shared across
+ * threads in a pooled or reusable threaded environment, the application (or more likely a framework) must
+ * bind and remove any necessary values at the beginning and end of stack
+ * execution, respectively (i.e. individually explicitly or all via the <tt>clear</tt> method).</p>
+ *
+ * @see #remove()
+ * @since 0.1
+ */
+public abstract class ThreadContext {
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
+
+    public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
+    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
+
+    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
+
+    /**
+     * Default no-argument constructor.
+     */
+    protected ThreadContext() {
+    }
+
+    /**
+     * Returns the ThreadLocal Map. This Map is used internally to bind objects
+     * to the current thread by storing each object under a unique key.
+     *
+     * @return the map of bound resources
+     */
+    public static Map<Object, Object> getResources() {
+        return resources != null ? new HashMap<Object, Object>(resources.get()) : null;
+    }
+
+    /**
+     * Allows a caller to explicitly set the entire resource map.  This operation overwrites everything that existed
+     * previously in the ThreadContext - if you need to retain what was on the thread prior to calling this method,
+     * call the {@link #getResources()} method, which will give you the existing state.
+     *
+     * @param newResources the resources to replace the existing {@link #getResources() resources}.
+     * @since 1.0
+     */
+    public static void setResources(Map<Object, Object> newResources) {
+        if (CollectionUtils.isEmpty(newResources)) {
+            return;
+        }
+        resources.get().clear();
+        resources.get().putAll(newResources);
+    }
+
+    /**
+     * Returns the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
+     * is no value for that {@code key}.
+     *
+     * @param key the map key to use to lookup the value
+     * @return the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
+     *         is no value for that {@code key}.
+     * @since 1.0
+     */
+    private static Object getValue(Object key) {
+        return resources.get().get(key);
+    }
+
+    /**
+     * Returns the object for the specified <code>key</code> that is bound to
+     * the current thread.
+     *
+     * @param key the key that identifies the value to return
+     * @return the object keyed by <code>key</code> or <code>null</code> if
+     *         no value exists for the specified <code>key</code>
+     */
+    public static Object get(Object key) {
+        if (log.isTraceEnabled()) {
+            String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
+            log.trace(msg);
+        }
+
+        Object value = getValue(key);
+        if ((value != null) && log.isTraceEnabled()) {
+            String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
+                    key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
+            log.trace(msg);
+        }
+        return value;
+    }
+
+    /**
+     * Binds <tt>value</tt> for the given <code>key</code> to the current thread.
+     * <p/>
+     * <p>A <tt>null</tt> <tt>value</tt> has the same effect as if <tt>remove</tt> was called for the given
+     * <tt>key</tt>, i.e.:
+     * <p/>
+     * <pre>
+     * if ( value == null ) {
+     *     remove( key );
+     * }</pre>
+     *
+     * @param key   The key with which to identify the <code>value</code>.
+     * @param value The value to bind to the thread.
+     * @throws IllegalArgumentException if the <code>key</code> argument is <tt>null</tt>.
+     */
+    public static void put(Object key, Object value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+
+        if (value == null) {
+            remove(key);
+            return;
+        }
+
+        resources.get().put(key, value);
+
+        if (log.isTraceEnabled()) {
+            String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
+                    key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
+            log.trace(msg);
+        }
+    }
+
+    /**
+     * Unbinds the value for the given <code>key</code> from the current
+     * thread.
+     *
+     * @param key The key identifying the value bound to the current thread.
+     * @return the object unbound or <tt>null</tt> if there was nothing bound
+     *         under the specified <tt>key</tt> name.
+     */
+    public static Object remove(Object key) {
+        Object value = resources.get().remove(key);
+
+        if ((value != null) && log.isTraceEnabled()) {
+            String msg = "Removed value of type [" + value.getClass().getName() + "] for key [" +
+                    key + "]" + "from thread [" + Thread.currentThread().getName() + "]";
+            log.trace(msg);
+        }
+
+        return value;
+    }
+
+    /**
+     * {@link ThreadLocal#remove Remove}s the underlying {@link ThreadLocal ThreadLocal} from the thread.
+     * <p/>
+     * This method is meant to be the final 'clean up' operation that is called at the end of thread execution to
+     * prevent thread corruption in pooled thread environments.
+     *
+     * @since 1.0
+     */
+    public static void remove() {
+        resources.remove();
+    }
+
+    /**
+     * Convenience method that simplifies retrieval of the application's SecurityManager instance from the current
+     * thread. If there is no SecurityManager bound to the thread (probably because framework code did not bind it
+     * to the thread), this method returns <tt>null</tt>.
+     * <p/>
+     * It is merely a convenient wrapper for the following:
+     * <p/>
+     * <code>return (SecurityManager)get( SECURITY_MANAGER_KEY );</code>
+     * <p/>
+     * This method only returns the bound value if it exists - it does not remove it
+     * from the thread.  To remove it, one must call {@link #unbindSecurityManager() unbindSecurityManager()} instead.
+     *
+     * @return the Subject object bound to the thread, or <tt>null</tt> if there isn't one bound.
+     * @since 0.9
+     */
+    public static SecurityManager getSecurityManager() {
+        return (SecurityManager) get(SECURITY_MANAGER_KEY);
+    }
+
+
+    /**
+     * Convenience method that simplifies binding the application's SecurityManager instance to the ThreadContext.
+     * <p/>
+     * <p>The method's existence is to help reduce casting in code and to simplify remembering of
+     * ThreadContext key names.  The implementation is simple in that, if the SecurityManager is not <tt>null</tt>,
+     * it binds it to the thread, i.e.:
+     * <p/>
+     * <pre>
+     * if (securityManager != null) {
+     *     put( SECURITY_MANAGER_KEY, securityManager);
+     * }</pre>
+     *
+     * @param securityManager the application's SecurityManager instance to bind to the thread.  If the argument is
+     *                        null, nothing will be done.
+     * @since 0.9
+     */
+    public static void bind(SecurityManager securityManager) {
+        if (securityManager != null) {
+            put(SECURITY_MANAGER_KEY, securityManager);
+        }
+    }
+
+    /**
+     * Convenience method that simplifies removal of the application's SecurityManager instance from the thread.
+     * <p/>
+     * The implementation just helps reduce casting and remembering of the ThreadContext key name, i.e it is
+     * merely a conveient wrapper for the following:
+     * <p/>
+     * <code>return (SecurityManager)remove( SECURITY_MANAGER_KEY );</code>
+     * <p/>
+     * If you wish to just retrieve the object from the thread without removing it (so it can be retrieved later
+     * during thread execution), use the {@link #getSecurityManager() getSecurityManager()} method instead.
+     *
+     * @return the application's SecurityManager instance previously bound to the thread, or <tt>null</tt> if there
+     *         was none bound.
+     * @since 0.9
+     */
+    public static SecurityManager unbindSecurityManager() {
+        return (SecurityManager) remove(SECURITY_MANAGER_KEY);
+    }
+
+    /**
+     * Convenience method that simplifies retrieval of a thread-bound Subject.  If there is no
+     * Subject bound to the thread, this method returns <tt>null</tt>.  It is merely a convenient wrapper
+     * for the following:
+     * <p/>
+     * <code>return (Subject)get( SUBJECT_KEY );</code>
+     * <p/>
+     * This method only returns the bound value if it exists - it does not remove it
+     * from the thread.  To remove it, one must call {@link #unbindSubject() unbindSubject()} instead.
+     *
+     * @return the Subject object bound to the thread, or <tt>null</tt> if there isn't one bound.
+     * @since 0.2
+     */
+    public static Subject getSubject() {
+        return (Subject) get(SUBJECT_KEY);
+    }
+
+
+    /**
+     * Convenience method that simplifies binding a Subject to the ThreadContext.
+     * <p/>
+     * <p>The method's existence is to help reduce casting in your own code and to simplify remembering of
+     * ThreadContext key names.  The implementation is simple in that, if the Subject is not <tt>null</tt>,
+     * it binds it to the thread, i.e.:
+     * <p/>
+     * <pre>
+     * if (subject != null) {
+     *     put( SUBJECT_KEY, subject );
+     * }</pre>
+     *
+     * @param subject the Subject object to bind to the thread.  If the argument is null, nothing will be done.
+     * @since 0.2
+     */
+    public static void bind(Subject subject) {
+        if (subject != null) {
+            put(SUBJECT_KEY, subject);
+        }
+    }
+
+    /**
+     * Convenience method that simplifies removal of a thread-local Subject from the thread.
+     * <p/>
+     * The implementation just helps reduce casting and remembering of the ThreadContext key name, i.e it is
+     * merely a conveient wrapper for the following:
+     * <p/>
+     * <code>return (Subject)remove( SUBJECT_KEY );</code>
+     * <p/>
+     * If you wish to just retrieve the object from the thread without removing it (so it can be retrieved later during
+     * thread execution), you should use the {@link #getSubject() getSubject()} method for that purpose.
+     *
+     * @return the Subject object previously bound to the thread, or <tt>null</tt> if there was none bound.
+     * @since 0.2
+     */
+    public static Subject unbindSubject() {
+        return (Subject) remove(SUBJECT_KEY);
+    }
+    
+    private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {
+        protected Map<Object, Object> initialValue() {
+            return new HashMap<Object, Object>();
+        }
+
+        /**
+         * This implementation was added to address a
+         * <a href="http://jsecurity.markmail.org/search/?q=#query:+page:1+mid:xqi2yxurwmrpqrvj+state:results">
+         * user-reported issue</a>.
+         * @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method.
+         * @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap).
+         */
+        @SuppressWarnings({"unchecked"})
+        protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
+            if (parentValue != null) {
+                return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();
+            } else {
+                return null;
+            }
+        }
+    }
+}
+
diff --git a/core/src/main/java/org/apache/shiro/util/ThreadState.java b/core/src/main/java/org/apache/shiro/util/ThreadState.java
new file mode 100644
index 0000000..75b271e
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ThreadState.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * A {@code ThreadState} instance manages any state that might need to be bound and/or restored during a thread's
+ * execution.
+ * <h3>Usage</h3>
+ * Calling {@link #bind bind()} will place state on the currently executing thread to be accessed later during
+ * the thread's execution.
+ * <h4>WARNING</h4>
+ * After the thread is finished executing, or if an exception occurs, any previous state <b>MUST</b> be
+ * {@link #restore restored} to guarantee all threads stay clean in any thread-pooled environment.  This should always
+ * be done in a {@code try/finally} block:
+ * <pre>
+ * ThreadState state = //acquire or instantiate as necessary
+ * try {
+ *     state.bind();
+ *     doSomething(); //execute any logic downstream logic that might need to access the state
+ * } <b>finally {
+ *     state.restore();
+ * }</b>
+ * </pre>
+ *
+ * @since 1.0
+ */
+public interface ThreadState {
+
+    /**
+     * Binds any state that should be made accessible during a thread's execution.  This should typically always
+     * be called in a {@code try/finally} block paired with the {@link #restore} call to guarantee that the thread
+     * is cleanly restored back to its original state.  For example:
+     * <pre>
+     * ThreadState state = //acquire or instantiate as necessary
+     * <b>try {
+     *     state.bind();
+     *     doSomething(); //execute any logic downstream logic that might need to access the state
+     * } </b> finally {
+     *     state.restore();
+     * }
+     * </pre>
+     */
+    void bind();
+
+    /**
+     * Restores a thread to its state before bind {@link #bind bind} was invoked.  This should typically always be
+     * called in a {@code finally} block to guarantee that the thread is cleanly restored back to its original state
+     * before {@link #bind bind}'s bind was called.  For example:
+     * <pre>
+     * ThreadState state = //acquire or instantiate as necessary
+     * try {
+     *     state.bind();
+     *     doSomething(); //execute any logic downstream logic that might need to access the state
+     * } <b>finally {
+     *     state.restore();
+     * }</b>
+     * </pre>
+     */
+    void restore();
+
+    /**
+     * Completely clears/removes the {@code ThreadContext} state.  Typically this method should
+     * only be called in special cases - it is more 'correct' to {@link #restore restore} a thread to its previous
+     * state than to clear it entirely.
+     */
+    void clear();
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/UnavailableConstructorException.java b/core/src/main/java/org/apache/shiro/util/UnavailableConstructorException.java
new file mode 100644
index 0000000..ec5ffdd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/UnavailableConstructorException.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * Exception thrown when attempting to instantiate a Class via reflection, but a suitable constructor (depending
+ * on the number of expected arguments) doesn't exist or cannot be obtained.
+ *
+ * @since 0.2
+ */
+public class UnavailableConstructorException extends ShiroException
+{
+
+    /**
+     * Creates a new UnavailableConstructorException.
+     */
+    public UnavailableConstructorException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnavailableConstructorException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnavailableConstructorException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnavailableConstructorException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnavailableConstructorException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnavailableConstructorException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnavailableConstructorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/UnknownClassException.java b/core/src/main/java/org/apache/shiro/util/UnknownClassException.java
new file mode 100644
index 0000000..b5d96cd
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/UnknownClassException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.ShiroException;
+
+
+/**
+ * The Shiro framework's <code>RuntimeException</code> equivalent of the JDK's
+ * <code>ClassNotFoundException</code>, to maintain a RuntimeException paradigm.
+ *
+ * @since 0.1
+ */
+public class UnknownClassException extends ShiroException
+{
+
+    /**
+     * Creates a new UnknownClassException.
+     */
+    public UnknownClassException() {
+        super();
+    }
+
+    /**
+     * Constructs a new UnknownClassException.
+     *
+     * @param message the reason for the exception
+     */
+    public UnknownClassException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new UnknownClassException.
+     *
+     * @param cause the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownClassException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new UnknownClassException.
+     *
+     * @param message the reason for the exception
+     * @param cause   the underlying Throwable that caused this exception to be thrown.
+     */
+    public UnknownClassException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/util/package-info.java b/core/src/main/java/org/apache/shiro/util/package-info.java
new file mode 100644
index 0000000..742d86d
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Your run-of-the-mill 'util' pacakge for components and logic widely used across the framework that can't
+ * find their home into a proper OO hierarchy (or, most likely for things used across many hierarchies).
+ */
+package org.apache.shiro.util;
diff --git a/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy b/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy
new file mode 100644
index 0000000..761706b
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+import org.apache.shiro.crypto.hash.format.HashFormatFactory
+import org.apache.shiro.crypto.hash.format.HexFormat
+import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat
+import org.apache.shiro.crypto.hash.*
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link DefaultPasswordService} implementation.
+ *
+ * @since 1.2
+ */
+class DefaultPasswordServiceTest extends GroovyTestCase {
+
+    void testEncryptPasswordWithNullArgument() {
+        def service = new DefaultPasswordService()
+        assertNull service.encryptPassword(null)
+    }
+
+    void testHashPasswordWithNullArgument() {
+        def service = new DefaultPasswordService()
+        assertNull service.hashPassword(null)
+    }
+
+    void testEncryptPasswordDefault() {
+        def service = new DefaultPasswordService()
+        def encrypted = service.encryptPassword("12345")
+        assertTrue service.passwordsMatch("12345", encrypted)
+    }
+
+    void testEncryptPasswordWithInvalidMatch() {
+        def service = new DefaultPasswordService()
+        def encrypted = service.encryptPassword("ABCDEF")
+        assertFalse service.passwordsMatch("ABC", encrypted)
+    }
+
+    void testBackwardsCompatibility() {
+        def service = new DefaultPasswordService()
+        def encrypted = service.encryptPassword("12345")
+        def submitted = "12345"
+        assertTrue service.passwordsMatch(submitted, encrypted);
+
+        //change some settings:
+        service.hashService.hashAlgorithmName = "MD5"
+        service.hashService.hashIterations = 250000
+
+        def encrypted2 = service.encryptPassword(submitted)
+
+        assertFalse encrypted == encrypted2
+
+        assertTrue service.passwordsMatch(submitted, encrypted2)
+    }
+
+    void testHashFormatWarned() {
+        def service = new DefaultPasswordService()
+        service.hashFormat = new HexFormat()
+        assertTrue service.hashFormat instanceof HexFormat
+        service.encryptPassword("test")
+        assertTrue service.hashFormatWarned
+    }
+
+    void testPasswordsMatchWithNullOrEmpty() {
+        def service = new DefaultPasswordService()
+        assertTrue service.passwordsMatch(null, (String)null)
+        assertTrue service.passwordsMatch(null, (Hash)null)
+        assertTrue service.passwordsMatch("", (String)null)
+        assertTrue service.passwordsMatch(null, "")
+        assertFalse service.passwordsMatch(null, "12345")
+        assertFalse service.passwordsMatch(null, new Sha1Hash("test"))
+    }
+
+    void testCustomHashService() {
+        def hashService = createMock(HashService)
+
+        def hash = new Sha256Hash("test", new SecureRandomNumberGenerator().nextBytes(), 100);
+
+        expect(hashService.computeHash(isA(HashRequest))).andReturn hash
+
+        replay hashService
+
+        def service = new DefaultPasswordService()
+        service.hashService = hashService
+
+        def returnedHash = service.encryptPassword("test")
+
+        assertEquals new Shiro1CryptFormat().format(hash), returnedHash
+
+        verify hashService
+    }
+
+    void testCustomHashFormatFactory() {
+
+        def factory = createMock(HashFormatFactory)
+        def hash = new Sha512Hash("test", new SecureRandomNumberGenerator().nextBytes(), 100)
+        String saved = new Shiro1CryptFormat().format(hash)
+
+        expect(factory.getInstance(eq(saved))).andReturn(new Shiro1CryptFormat())
+
+        replay factory
+
+        def service = new DefaultPasswordService()
+        service.hashFormatFactory = factory
+
+        assertSame factory, service.hashFormatFactory
+
+        assertTrue service.passwordsMatch("test", saved)
+
+        verify factory
+    }
+
+    void testStringComparisonWhenNotUsingAParsableHashFormat() {
+
+        def service = new DefaultPasswordService()
+        service.hashFormat = new HexFormat()
+        //can't use random salts when using HexFormat:
+        service.hashService.generatePublicSalt = false
+
+        def formatted = service.encryptPassword("12345")
+
+        assertTrue service.passwordsMatch("12345", formatted)
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/authc/credential/PasswordMatcherTest.groovy b/core/src/test/groovy/org/apache/shiro/authc/credential/PasswordMatcherTest.groovy
new file mode 100644
index 0000000..4b37cec
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/authc/credential/PasswordMatcherTest.groovy
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential
+
+import org.apache.shiro.authc.AuthenticationInfo
+import org.apache.shiro.authc.AuthenticationToken
+import org.apache.shiro.crypto.hash.Sha256Hash
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link PasswordMatcher} implementation.
+ *
+ * @since 1.2
+ */
+class PasswordMatcherTest extends GroovyTestCase {
+
+    void testMissingPasswordService() {
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = null
+        try {
+            matcher.doCredentialsMatch(null, null)
+            fail "Test should fail due to lack of a configured PasswordService instance."
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    void testStringPasswordComparison() {
+        def service = createMock(PasswordService)
+        def token = createMock(AuthenticationToken)
+        def info = createMock(AuthenticationInfo)
+        //generate a stored password just for this test:
+        def submittedPassword = "plaintext"
+        def savedPassword = "encrypted"
+
+        expect(token.credentials).andReturn submittedPassword
+        expect(info.credentials).andReturn savedPassword
+
+        expect(service.passwordsMatch(eq(submittedPassword), eq(savedPassword))).andReturn true
+
+        replay token, info, service
+
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = service
+        assertSame service, matcher.passwordService
+
+        assertTrue matcher.doCredentialsMatch(token, info)
+
+        verify token, info, service
+    }
+
+    void testHashComparisonWithoutHashedPasswordService() {
+        def service = createMock(PasswordService)
+        def token = createMock(AuthenticationToken)
+        def info = createMock(AuthenticationInfo)
+        //generate a stored password just for this test:
+        def submittedPassword = "plaintext"
+        def savedPassword = new Sha256Hash("plaintext")
+
+        expect(token.credentials).andReturn submittedPassword
+        expect(info.credentials).andReturn savedPassword
+
+        replay token, info, service
+
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = service
+        assertSame service, matcher.passwordService
+
+        try {
+            assertTrue matcher.doCredentialsMatch(token, info)
+            fail "matcher should fail since PasswordService is not a HashingPasswordService"
+        } catch (IllegalStateException expected) {
+        }
+
+        verify token, info, service
+    }
+
+    void testHashComparison() {
+        def service = createMock(HashingPasswordService)
+        def token = createMock(AuthenticationToken)
+        def info = createMock(AuthenticationInfo)
+        //generate a stored password just for this test:
+        def submittedPassword = "plaintext"
+        def savedPassword = new Sha256Hash("plaintext")
+
+        expect(token.credentials).andReturn submittedPassword
+        expect(info.credentials).andReturn savedPassword
+
+        expect(service.passwordsMatch(submittedPassword, savedPassword)).andReturn true
+
+        replay token, info, service
+
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = service
+        assertSame service, matcher.passwordService
+
+        assertTrue matcher.doCredentialsMatch(token, info)
+
+        verify token, info, service
+    }
+
+    /**
+     * Asserts fix for https://issues.apache.org/jira/browse/SHIRO-363
+     */
+    void testCharArrayComparison() {
+        def service = createMock(PasswordService)
+        def token = createMock(AuthenticationToken)
+        def info = createMock(AuthenticationInfo)
+        //generate a stored password just for this test:
+        def submittedPassword = "foo"
+        def savedPasswordAsString = "foo";
+        def savedPassword = savedPasswordAsString.toCharArray()
+
+        expect(token.credentials).andReturn submittedPassword
+        expect(info.credentials).andReturn savedPassword
+
+        expect(service.passwordsMatch(eq(submittedPassword), eq(savedPasswordAsString))).andReturn true
+
+        replay token, info, service
+
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = service
+        assertSame service, matcher.passwordService
+
+        assertTrue matcher.doCredentialsMatch(token, info)
+
+        verify token, info, service
+    }
+
+    void testUnexpectedSavedCredentialsType() {
+        def service = createMock(HashingPasswordService)
+        def token = createMock(AuthenticationToken)
+        def info = createMock(AuthenticationInfo)
+        //generate a stored password just for this test:
+        def submittedPassword = "plaintext"
+        def savedPassword = 23
+
+        expect(token.credentials).andReturn submittedPassword
+        expect(info.credentials).andReturn savedPassword
+
+        replay token, info, service
+
+        def matcher = new PasswordMatcher()
+        matcher.passwordService = service
+        assertSame service, matcher.passwordService
+
+        try {
+            assertTrue matcher.doCredentialsMatch(token, info)
+            fail "Saved credentials should be either a String or Hash instance."
+        } catch (IllegalArgumentException expected) {
+        }
+
+        verify token, info, service
+
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/authc/pam/ModularRealmAuthenticatorTest.groovy b/core/src/test/groovy/org/apache/shiro/authc/pam/ModularRealmAuthenticatorTest.groovy
new file mode 100644
index 0000000..9e240a5
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/authc/pam/ModularRealmAuthenticatorTest.groovy
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam
+
+import org.apache.shiro.realm.Realm
+import org.apache.shiro.subject.PrincipalCollection
+import org.apache.shiro.authc.*
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link ModularRealmAuthenticator} implementation.
+ *
+ * @since 1.2
+ */
+class ModularRealmAuthenticatorTest extends GroovyTestCase {
+
+    void testNewInstance() {
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        assertNotNull mra.authenticationStrategy
+        assertTrue mra.authenticationStrategy instanceof AtLeastOneSuccessfulStrategy
+    }
+
+    void testDoAuthenticateNoRealms() {
+
+        def token = createStrictMock(AuthenticationToken)
+
+        replay token
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator();
+        try {
+            mra.doAuthenticate(token)
+            fail "ModularRealmAuthenticator should fail when no realms are configured."
+        } catch (IllegalStateException expected) {
+        }
+
+        verify token
+    }
+
+    void testSingleRealmAuthenticationSuccess() {
+
+        def realm = createStrictMock(Realm)
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        expect(realm.supports(same(token))).andReturn true
+        expect(realm.getAuthenticationInfo(same(token))).andReturn info
+
+        replay realm, token, info
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.realms = [realm]
+
+        assertSame info, mra.doAuthenticate(token)
+
+        verify realm, token, info
+    }
+
+    void testSingleRealmAuthenticationWithUnsupportedToken() {
+
+        def realm = createStrictMock(Realm)
+        def token = createStrictMock(AuthenticationToken)
+
+        expect(realm.supports(same(token))).andReturn false
+
+        replay realm, token
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.realms = [realm]
+
+        try {
+            mra.doAuthenticate(token)
+            fail "Should throw UnsupportedTokenException when single realm does not support a token."
+        } catch (UnsupportedTokenException expected) {
+        }
+
+        verify realm, token
+    }
+
+    void testSingleRealmAuthenticationWithNullAuthenticationInfo() {
+
+        def realm = createStrictMock(Realm)
+        def token = createStrictMock(AuthenticationToken)
+
+        expect(realm.supports(same(token))).andReturn true
+        expect(realm.getAuthenticationInfo(same(token))).andReturn null
+
+        replay realm, token
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.realms = [realm]
+
+        try {
+            mra.doAuthenticate(token)
+            fail "Should throw UnknownAccountException when single realm returns null."
+        } catch (UnknownAccountException expected) {
+        }
+
+        verify realm, token
+    }
+
+    void testMultiRealmAuthenticationSuccess() {
+
+        def realm1 = createStrictMock(Realm)
+        def realm1Info = createStrictMock(AuthenticationInfo)
+        def realm2 = createStrictMock(Realm)
+        def realm2Info = createStrictMock(AuthenticationInfo)
+        def token = createStrictMock(AuthenticationToken)
+        def aggregate = createStrictMock(AuthenticationInfo)
+        def strategy = createStrictMock(AuthenticationStrategy)
+        def realms = [realm1, realm2]
+
+
+        expect(strategy.beforeAllAttempts(same(realms), same(token))).andReturn aggregate
+
+        expect(strategy.beforeAttempt(same(realm1), same(token), same(aggregate))).andReturn aggregate
+        expect(realm1.supports(same(token))).andReturn true
+        expect(realm1.getAuthenticationInfo(same(token))).andReturn realm1Info
+        expect(strategy.afterAttempt(same(realm1), same(token), same(realm1Info), same(aggregate), isNull(Throwable))).andReturn aggregate
+
+        expect(strategy.beforeAttempt(same(realm2), same(token), same(aggregate))).andReturn aggregate
+        expect(realm2.supports(same(token))).andReturn true
+        expect(realm2.getAuthenticationInfo(same(token))).andReturn realm2Info
+        expect(strategy.afterAttempt(same(realm2), same(token), same(realm2Info), same(aggregate), isNull(Throwable))).andReturn aggregate
+
+        expect(strategy.afterAllAttempts(same(token), same(aggregate))).andReturn aggregate
+
+
+        replay realm1, realm1Info, realm2, realm2Info, token, aggregate, strategy
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.authenticationStrategy = strategy
+        mra.realms = realms
+
+        assertSame aggregate, mra.doAuthenticate(token)
+
+        verify realm1, realm1Info, realm2, realm2Info, token, aggregate, strategy
+    }
+
+    void testMultiRealmAuthenticationWithAuthenticationException() {
+
+        def realm1 = createStrictMock(Realm)
+        def realm1Info = createStrictMock(AuthenticationInfo)
+        def realm2 = createStrictMock(Realm)
+        def token = createStrictMock(AuthenticationToken)
+        def aggregate = createStrictMock(AuthenticationInfo)
+        def strategy = createStrictMock(AuthenticationStrategy)
+        def authcException = new AuthenticationException("test")
+        def realms = [realm1, realm2]
+
+
+        expect(strategy.beforeAllAttempts(same(realms), same(token))).andReturn aggregate
+
+        expect(strategy.beforeAttempt(same(realm1), same(token), same(aggregate))).andReturn aggregate
+        expect(realm1.supports(same(token))).andReturn true
+        expect(realm1.getAuthenticationInfo(same(token))).andReturn realm1Info
+        expect(strategy.afterAttempt(same(realm1), same(token), same(realm1Info), same(aggregate), isNull(Throwable))).andReturn aggregate
+
+        expect(strategy.beforeAttempt(same(realm2), same(token), same(aggregate))).andReturn aggregate
+        expect(realm2.supports(same(token))).andReturn true
+        expect(realm2.getAuthenticationInfo(same(token))).andThrow authcException
+        expect(strategy.afterAttempt(same(realm2), same(token), isNull(AuthenticationInfo), same(aggregate), same(authcException))).andReturn aggregate
+
+        expect(strategy.afterAllAttempts(same(token), same(aggregate))).andReturn aggregate
+
+
+        replay realm1, realm1Info, realm2, token, aggregate, strategy
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.authenticationStrategy = strategy
+        mra.realms = realms
+
+        assertSame aggregate, mra.doAuthenticate(token)
+
+        verify realm1, realm1Info, realm2, token, aggregate, strategy
+    }
+
+    void testOnLogout() {
+
+        def realm = createStrictMock(LogoutAwareRealm)
+        def principals = createStrictMock(PrincipalCollection)
+
+        realm.onLogout(same(principals))
+
+        replay realm, principals
+
+        ModularRealmAuthenticator mra = new ModularRealmAuthenticator()
+        mra.realms = [realm]
+        mra.onLogout(principals)
+
+        verify realm, principals
+    }
+
+    private static interface LogoutAwareRealm extends Realm, LogoutAware {
+
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy b/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy
new file mode 100644
index 0000000..1560c47
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.codec
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+
+/**
+ * Test cases for the {@link H64} implementation.
+ */
+class H64Test extends GroovyTestCase {
+
+    void testNothing(){}
+
+    public void testDefault() {
+        byte[] orig = new SecureRandomNumberGenerator().nextBytes(6).bytes
+
+        System.out.println("bytes: $orig");
+
+        String encoded = H64.encodeToString(orig)
+        System.out.println("encoded: $encoded");
+
+        assertNotNull orig
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/config/IniSecurityManagerFactoryTest.groovy b/core/src/test/groovy/org/apache/shiro/config/IniSecurityManagerFactoryTest.groovy
new file mode 100644
index 0000000..28f51f4
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/config/IniSecurityManagerFactoryTest.groovy
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config
+
+import org.apache.shiro.SecurityUtils
+import org.apache.shiro.authc.UsernamePasswordToken
+import org.apache.shiro.cache.Cache
+import org.apache.shiro.cache.MapCache
+import org.apache.shiro.crypto.hash.Sha256Hash
+import org.apache.shiro.mgt.DefaultSecurityManager
+import org.apache.shiro.mgt.SecurityManager
+import org.apache.shiro.realm.Realm
+import org.apache.shiro.realm.text.IniRealm
+import org.apache.shiro.realm.text.PropertiesRealm
+import org.apache.shiro.session.Session
+import org.apache.shiro.session.mgt.AbstractSessionManager
+import org.apache.shiro.session.mgt.DefaultSessionManager
+import org.apache.shiro.session.mgt.eis.CachingSessionDAO
+import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
+import org.apache.shiro.session.mgt.eis.SessionDAO
+import org.apache.shiro.subject.Subject
+
+/**
+ * Unit tests for the {@link IniSecurityManagerFactory} implementation.
+ *
+ * @since 1.0
+ */
+class IniSecurityManagerFactoryTest extends GroovyTestCase {
+
+    void testGetInstanceWithoutIni() {
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory();
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+    }
+
+    void testGetInstanceWithResourcePath() {
+        String path = "classpath:org/apache/shiro/config/IniSecurityManagerFactoryTest.ini";
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(path);
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+    }
+
+    void testGetInstanceWithEmptyIni() {
+        Ini ini = new Ini();
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+    }
+
+    void testGetInstanceWithSimpleIni() {
+        Ini ini = new Ini();
+        ini.setSectionProperty(IniSecurityManagerFactory.MAIN_SECTION_NAME,
+                "securityManager.sessionManager.globalSessionTimeout", "5000");
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+        assertEquals(5000, ((AbstractSessionManager) ((DefaultSecurityManager) sm).getSessionManager()).getGlobalSessionTimeout());
+    }
+
+    void testGetInstanceWithConfiguredRealm() {
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniSecurityManagerFactory.MAIN_SECTION_NAME);
+        section.put("propsRealm", PropertiesRealm.class.getName());
+        section.put("propsRealm.resourcePath",
+                "classpath:org/apache/shiro/config/IniSecurityManagerFactoryTest.propsRealm.properties");
+
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+        Collection<Realm> realms = ((DefaultSecurityManager) sm).getRealms();
+        assertEquals(1, realms.size());
+        Realm realm = realms.iterator().next();
+        assertTrue(realm instanceof PropertiesRealm);
+    }
+
+    void testGetInstanceWithAutomaticallyCreatedIniRealm() {
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        section.put("admin", "admin");
+
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultSecurityManager);
+        Collection<Realm> realms = ((DefaultSecurityManager) sm).getRealms();
+        assertEquals(1, realms.size());
+        Realm realm = realms.iterator().next();
+        assertTrue(realm instanceof IniRealm);
+        assertTrue(((IniRealm) realm).accountExists("admin"));
+    }
+
+    /**
+     * Test for issue <a href="https://issues.apache.org/jira/browse/SHIRO-125">SHIRO-125</a>.
+     */
+    void testImplicitIniRealmWithAdditionalRealmConfiguration() {
+
+        Ini ini = new Ini();
+
+        //The users section below should create an implicit 'iniRealm' instance in the
+        //main configuration.  So we should be able to set properties on it immediately
+        //such as the Sha256 credentials matcher:
+        Ini.Section main = ini.addSection("main");
+        main.put("credentialsMatcher", "org.apache.shiro.authc.credential.Sha256CredentialsMatcher");
+        main.put("iniRealm.credentialsMatcher", '$credentialsMatcher');
+
+        //create a users section - user 'admin', with a Sha256-hashed 'admin' password (hex encoded):
+        Ini.Section users = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        users.put("admin", new Sha256Hash("secret").toString());
+
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+
+        //go ahead and try to log in with the admin user, ensuring the
+        //iniRealm has a Sha256CredentialsMatcher enabled:
+
+        //try to log-in:
+        Subject subject = new Subject.Builder(sm).buildSubject();
+        //ensure thread clean-up after the login method returns.  Test cases only:
+        subject.execute(new Runnable() {
+            public void run() {
+                //the plain-text 'secret' should be converted to an Sha256 hash first
+                //by the CredentialsMatcher.  This should return quietly if
+                //this test case is valid:
+                SecurityUtils.getSubject().login(new UsernamePasswordToken("admin", "secret"));
+            }
+        });
+        assertTrue(subject.getPrincipal().equals("admin"));
+    }
+
+    /**
+     * Test for issue <a href="https://issues.apache.org/jira/browse/SHIRO-322">SHIRO-322</a>.
+     */
+    void testImplicitIniRealmWithConfiguredPermissionResolver() {
+        def ini = new Ini();
+        ini.load('''
+            [main]
+            # The MockPermissionResolver is a peer class to this test class.
+            permissionResolver = org.apache.shiro.config.MockPermissionResolver
+            iniRealm.permissionResolver = $permissionResolver
+
+            [users]
+            jsmith = secret, author
+
+            [roles]
+            author = book:write
+        ''');
+
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.instance
+        
+        def realm = sm.realms[0]
+        assertNotNull realm
+        def permResolver = realm.permissionResolver
+        assertTrue permResolver instanceof MockPermissionResolver
+        assertTrue permResolver.invoked
+    }
+
+    /**
+     * Test case for issue <a href="https://issues.apache.org/jira/browse/SHIRO-95">SHIRO-95</a>.
+     */
+    void testCacheManagerConfigOrderOfOperations() {
+
+        Ini ini = new Ini();
+        Ini.Section main = ini.addSection(IniSecurityManagerFactory.MAIN_SECTION_NAME);
+        //create a non-default CacheManager:
+        main.put("cacheManager", "org.apache.shiro.config.HashMapCacheManager");
+
+        //now add a session DAO after the cache manager has been set - this is what tests the user-reported issue
+        main.put("sessionDAO", "org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO");
+        main.put("securityManager.sessionManager.sessionDAO", '$sessionDAO');
+
+        //add the cache manager after the sessionDAO has been set:
+        main.put("securityManager.cacheManager", '$cacheManager');
+
+        //add a test user:
+        ini.setSectionProperty(IniRealm.USERS_SECTION_NAME, "admin", "admin");
+
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+
+        //try to log-in:
+        Subject subject = new Subject.Builder(sm).buildSubject();
+        subject.login(new UsernamePasswordToken("admin", "admin"));
+        Session session = subject.getSession();
+        session.setAttribute("hello", "world");
+        //session should have been started, and a cache is in use.  Assert that the SessionDAO is still using
+        //the cache instances provided by our custom CacheManager and not the Default MemoryConstrainedCacheManager
+
+        SessionDAO sessionDAO = ((DefaultSessionManager) ((DefaultSecurityManager) sm).getSessionManager()).getSessionDAO();
+        assertTrue(sessionDAO instanceof EnterpriseCacheSessionDAO);
+        CachingSessionDAO cachingSessionDAO = (CachingSessionDAO) sessionDAO;
+        Cache activeSessionsCache = cachingSessionDAO.getActiveSessionsCache();
+        assertTrue(activeSessionsCache instanceof MapCache);
+        MapCache mapCache = (MapCache) activeSessionsCache;
+
+        //this is the line that verifies Caches created by our specific CacheManager are not overwritten by the
+        //default cache manager's caches:
+        assertTrue(mapCache instanceof HashMapCacheManager.HashMapCache);
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/config/MockPermissionResolver.groovy b/core/src/test/groovy/org/apache/shiro/config/MockPermissionResolver.groovy
new file mode 100644
index 0000000..a75dba5
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/config/MockPermissionResolver.groovy
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config
+
+import org.apache.shiro.authz.Permission
+import org.apache.shiro.authz.permission.PermissionResolver
+import org.apache.shiro.authz.permission.WildcardPermission
+
+/**
+ * Test {@code PermissionResolver} implementation used in the {@link IniSecurityManagerFactoryTest}.
+ */
+class MockPermissionResolver implements PermissionResolver {
+
+    boolean invoked = false
+
+    Permission resolvePermission(String permissionString) {
+        invoked = true
+        return new WildcardPermission(permissionString)
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
new file mode 100644
index 0000000..e5431bd
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/config/ReflectionBuilderTest.groovy
@@ -0,0 +1,526 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config
+
+import org.apache.shiro.codec.Base64
+import org.apache.shiro.codec.CodecSupport
+import org.apache.shiro.codec.Hex
+import org.apache.shiro.realm.ldap.JndiLdapRealm
+import org.apache.shiro.util.CollectionUtils
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * Unit tests for the {@link ReflectionBuilder} implementation.
+ */
+class ReflectionBuilderTest {
+
+    @Test
+    void testStandardPropertyAssignment() {
+        ReflectionBuilder builder = new ReflectionBuilder();
+
+        CompositeBean cBean = new CompositeBean();
+        builder.applyProperty(cBean, 'stringProp', 'hello world')
+        builder.applyProperty(cBean, 'booleanProp', true)
+        builder.applyProperty(cBean, 'intProp', 42)
+        builder.applyProperty(cBean, 'simpleBean', new SimpleBean())
+        
+        assertTrue cBean.stringProp == 'hello world'
+        assertTrue cBean.booleanProp
+        assertTrue cBean.intProp == 42
+        assertTrue cBean.simpleBean instanceof SimpleBean
+    }
+
+    @Test
+    void testMapEntryAssignment() {
+        ReflectionBuilder builder = new ReflectionBuilder();
+
+        CompositeBean cBean = new CompositeBean();
+        cBean.simpleBeanMap = ['simpleBean1': new SimpleBean()]
+        
+        builder.applyProperty(cBean, 'simpleBeanMap[simpleBean2]', new SimpleBean())
+        
+        assertTrue cBean.simpleBeanMap['simpleBean2'] instanceof SimpleBean
+    }
+
+    @Test
+    void testArrayEntryAssignment() {
+        ReflectionBuilder builder = new ReflectionBuilder();
+
+        CompositeBean cBean = new CompositeBean();
+        cBean.compositeBeanArray = new CompositeBean[1];
+
+        builder.applyProperty(cBean, 'compositeBeanArray[0]', new CompositeBean())
+
+        assertTrue cBean.compositeBeanArray[0] instanceof CompositeBean
+    }
+
+    @Test
+    void testNestedPathAssignment() {
+        ReflectionBuilder builder = new ReflectionBuilder();
+
+        CompositeBean cbean1 = new CompositeBean('cbean1');
+        cbean1.compositeBeanMap = ['cbean2': new CompositeBean('cbean2')]
+        cbean1.compositeBeanMap['cbean2'].compositeBeanArray = new CompositeBean[2];
+        
+        builder.applyProperty(cbean1, "compositeBeanMap[cbean2].compositeBeanArray[0]", new CompositeBean('cbean3'))
+        builder.applyProperty(cbean1, "compositeBeanMap[cbean2].compositeBeanArray[0].simpleBean", new SimpleBean('sbean1'))
+
+        assertTrue cbean1.compositeBeanMap['cbean2'].compositeBeanArray[0].name == 'cbean3'
+        assertTrue cbean1.compositeBeanMap['cbean2'].compositeBeanArray[0].simpleBean.name == 'sbean1'
+    }
+
+    @Test
+    //asserts SHIRO-305: https://issues.apache.org/jira/browse/SHIRO-305
+    void testNestedMapAssignmentWithPeriodDelimitedKeys() {
+        def ini = new Ini()
+        ini.load('''
+            ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
+            ldapRealm.contextFactory.environment[java.naming.security.protocol] = ssl
+            ldapRealm.contextFactory.environment[com.sun.jndi.ldap.connect.pool] = true 
+            ldapRealm.contextFactory.environment[com.sun.jndi.ldap.connect.pool.protocol] = plain ssl 
+        ''')
+        def builder = new ReflectionBuilder()
+        def objects = builder.buildObjects(ini.getSections().iterator().next())
+        
+        assertFalse objects.isEmpty()
+        def ldapRealm = objects['ldapRealm'] as JndiLdapRealm
+        assertEquals 'ssl', ldapRealm.contextFactory.environment['java.naming.security.protocol']
+        assertEquals 'true', ldapRealm.contextFactory.environment['com.sun.jndi.ldap.connect.pool']
+        assertEquals 'plain ssl', ldapRealm.contextFactory.environment['com.sun.jndi.ldap.connect.pool.protocol']
+    }
+
+    @Test
+    void testSimpleConfig() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.stringProp", "blah");
+        defs.put("compositeBean.booleanProp", "true");
+        defs.put("compositeBean.intProp", "42");
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertEquals(compositeBean.getStringProp(), "blah");
+        assertTrue(compositeBean.isBooleanProp());
+        assertEquals(compositeBean.getIntProp(), 42);
+    }
+
+    @Test
+    void testWithConfiguredNullValue() {
+        Map<String,Object> defaults = new LinkedHashMap<String,Object>();
+        CompositeBean cBean = new CompositeBean();
+        cBean.setSimpleBean(new SimpleBean());
+        defaults.put("compositeBean", cBean);
+
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean.intProp", "42");
+        defs.put("compositeBean.booleanProp", "true");
+        defs.put("compositeBean.stringProp", "test");
+        defs.put("compositeBean.simpleBean", "null");
+
+        ReflectionBuilder builder = new ReflectionBuilder(defaults);
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertTrue(compositeBean.isBooleanProp());
+        assertEquals(compositeBean.getIntProp(), 42);
+        assertEquals("test", compositeBean.getStringProp());
+        assertNull(compositeBean.getSimpleBean());
+    }
+
+    @Test
+    void testWithConfiguredNullLiteralValue() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.intProp", "42");
+        defs.put("compositeBean.booleanProp", "true");
+        defs.put("compositeBean.stringProp", "\"null\"");
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertTrue(compositeBean.isBooleanProp());
+        assertEquals(compositeBean.getIntProp(), 42);
+        assertEquals("null", compositeBean.getStringProp());
+    }
+
+    @Test
+    void testWithConfiguredEmptyStringValue() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.intProp", "42");
+        defs.put("compositeBean.booleanProp", "true");
+        defs.put("compositeBean.stringProp", "\"\"");
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertTrue(compositeBean.isBooleanProp());
+        assertEquals(compositeBean.getIntProp(), 42);
+        assertEquals("", compositeBean.getStringProp());
+    }
+
+    @Test
+    void testWithConfiguredEmptyStringLiteralValue() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.intProp", "42");
+        defs.put("compositeBean.booleanProp", "true");
+        defs.put("compositeBean.stringProp", "\"\"\"\"");
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertTrue(compositeBean.isBooleanProp());
+        assertEquals(compositeBean.getIntProp(), 42);
+        assertEquals("\"\"", compositeBean.getStringProp());
+    }
+
+    @Test
+    void testSimpleConfigWithDollarSignStringValue() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.stringProp", '\\$500');
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertEquals(compositeBean.getStringProp(), '$500');
+    }
+
+    @Test
+    void testObjectReferenceConfig() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean.intProp", "101");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.stringProp", "blah");
+        defs.put("compositeBean.simpleBean", '$simpleBean');
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map beans = builder.buildObjects(defs);
+
+        CompositeBean compositeBean = (CompositeBean) beans.get("compositeBean");
+        assertNotNull(compositeBean);
+        assertEquals(compositeBean.getStringProp(), "blah");
+        SimpleBean simpleBean = (SimpleBean) beans.get("simpleBean");
+        assertNotNull(simpleBean);
+        assertNotNull(compositeBean.getSimpleBean());
+        assertEquals(simpleBean, compositeBean.getSimpleBean());
+        assertEquals(simpleBean.getIntProp(), 101);
+    }
+
+    @Test
+    void testObjectReferenceConfigWithTypeMismatch() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBean", "simpleBean");
+        ReflectionBuilder builder = new ReflectionBuilder();
+        try {
+            builder.buildObjects(defs);
+            "Should have encountered an " + ConfigurationException.class.name
+        } catch (ConfigurationException expected) {
+        }
+    }
+
+    @Test
+    void testObjectReferenceConfigWithInvalidReference() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBean", '$foo');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        try {
+            builder.buildObjects(defs);
+            fail "should have encountered an " + UnresolveableReferenceException.class.name
+        } catch (UnresolveableReferenceException expected) {
+        }
+    }
+
+    @Test
+    void testSetProperty() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBeanSet", '$simpleBean1, $simpleBean2, $simpleBean2');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        Set<SimpleBean> simpleBeans = cBean.getSimpleBeanSet();
+        assertNotNull(simpleBeans);
+        assertEquals(2, simpleBeans.size());
+    }
+
+    @Test
+    //SHIRO-423
+    void testSetPropertyWithReferencedSet() {
+        def set = [new SimpleBean('foo'), new SimpleBean('bar')] as Set
+
+        def defs = [
+            compositeBean: 'org.apache.shiro.config.CompositeBean',
+            'compositeBean.simpleBeanSet': '$set'
+        ]
+
+        ReflectionBuilder builder = new ReflectionBuilder(['set': set]);
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        Set<SimpleBean> simpleBeans = cBean.getSimpleBeanSet();
+        assertNotNull(simpleBeans);
+        assertSame set, simpleBeans
+        assertEquals(2, simpleBeans.size());
+        def i = simpleBeans.iterator()
+        assertEquals 'foo', i.next().name
+        assertEquals 'bar', i.next().name
+    }
+
+    @Test
+    void testListProperty() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBeanList", '$simpleBean1, $simpleBean2, $simpleBean2');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        List<SimpleBean> simpleBeans = cBean.getSimpleBeanList();
+        assertNotNull(simpleBeans);
+        assertEquals(3, simpleBeans.size());
+    }
+
+    @Test
+    //SHIRO-423
+    void testListPropertyWithReferencedList() {
+        List list = [new SimpleBean('foo'), new SimpleBean('bar')] as List
+
+        def defs = [
+            compositeBean: 'org.apache.shiro.config.CompositeBean',
+            'compositeBean.simpleBeanList': '$list'
+        ]
+
+        ReflectionBuilder builder = new ReflectionBuilder(['list': list]);
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        def simpleBeans = cBean.getSimpleBeanList();
+        assertNotNull(simpleBeans);
+        assertSame list, simpleBeans
+        assertEquals(2, simpleBeans.size());
+        assertEquals 'foo', simpleBeans[0].name
+        assertEquals 'bar', simpleBeans[1].name
+    }
+
+    @Test
+    void testCollectionProperty() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBeanCollection", '$simpleBean1, $simpleBean2, $simpleBean2');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        Collection<SimpleBean> simpleBeans = cBean.getSimpleBeanCollection();
+        assertNotNull(simpleBeans);
+        assertTrue(simpleBeans instanceof List);
+        assertEquals(3, simpleBeans.size());
+    }
+
+    @Test
+    //SHIRO-423
+    void testCollectionPropertyWithReferencedCollection() {
+        def c = [new SimpleBean('foo'), new SimpleBean('bar')]
+
+        def defs = [
+            compositeBean: 'org.apache.shiro.config.CompositeBean',
+            'compositeBean.simpleBeanCollection': '$collection'
+        ]
+
+        ReflectionBuilder builder = new ReflectionBuilder(['collection': c]);
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        def simpleBeans = cBean.getSimpleBeanCollection();
+        assertNotNull(simpleBeans);
+        assertSame c, simpleBeans
+        assertEquals(2, simpleBeans.size());
+        def i  = simpleBeans.iterator()
+        assertEquals 'foo', i.next().name
+        assertEquals 'bar', i.next().name
+    }
+
+    @Test
+    void testByteArrayHexProperty() {
+        String source = "Hello, world.";
+        byte[] bytes = CodecSupport.toBytes(source);
+        String hex = Hex.encodeToString(bytes);
+        String hexValue = "0x" + hex;
+
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean.byteArrayProp", hexValue);
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        SimpleBean bean = (SimpleBean) objects.get("simpleBean");
+        assertNotNull(bean);
+        byte[] beanBytes = bean.getByteArrayProp();
+        assertNotNull(beanBytes);
+        String reconstituted = CodecSupport.toString(beanBytes);
+        assertEquals(source, reconstituted);
+    }
+
+    @Test
+    void testByteArrayBase64Property() {
+        String source = "Hello, world.";
+        byte[] bytes = CodecSupport.toBytes(source);
+        String base64 = Base64.encodeToString(bytes);
+
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean.byteArrayProp", base64);
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        SimpleBean bean = (SimpleBean) objects.get("simpleBean");
+        byte[] beanBytes = bean.getByteArrayProp();
+        assertNotNull(beanBytes);
+        assertTrue(Arrays.equals(beanBytes, bytes));
+        String reconstituted = CodecSupport.toString(beanBytes);
+        assertEquals(reconstituted, source);
+    }
+
+    @Test
+    void testMapProperty() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBeanMap", 'simpleBean1:$simpleBean1, simpleBean2:$simpleBean2');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        Map map = cBean.getSimpleBeanMap();
+        assertNotNull(map);
+        assertEquals(2, map.size());
+        Object value = map.get("simpleBean1");
+        assertTrue(value instanceof SimpleBean);
+        value = map.get("simpleBean2");
+        assertTrue(value instanceof SimpleBean);
+    }
+
+    @Test
+    //SHIRO-423
+    void testMapPropertyWithReferencedMap() {
+        def map = ['foo': new SimpleBean('foo'), 'bar': new SimpleBean('bar')]
+
+        def defs = [
+            compositeBean: 'org.apache.shiro.config.CompositeBean',
+            'compositeBean.simpleBeanMap': '$map'
+        ]
+
+        ReflectionBuilder builder = new ReflectionBuilder(['map': map]);
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        def simpleBeansMap = cBean.getSimpleBeanMap();
+        assertNotNull(simpleBeansMap);
+        assertSame map, simpleBeansMap
+        assertEquals(2, simpleBeansMap.size());
+        assertEquals 'foo', simpleBeansMap['foo'].name
+        assertEquals 'bar', simpleBeansMap['bar'].name
+    }
+
+    @Test
+    void testNestedListProperty() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBean1", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean2", "org.apache.shiro.config.SimpleBean");
+        defs.put("simpleBean3", "org.apache.shiro.config.SimpleBean");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBean", '$simpleBean1');
+        defs.put("compositeBean.simpleBean.simpleBeans", '$simpleBean2, $simpleBean3');
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean cBean = (CompositeBean) objects.get("compositeBean");
+        assertNotNull(cBean);
+        SimpleBean nested = cBean.getSimpleBean();
+        assertNotNull(nested);
+        List<SimpleBean> children = nested.getSimpleBeans();
+        assertNotNull(children);
+        assertEquals(2, children.size());
+    }
+
+    @Test
+    //asserts SHIRO-413
+    void testInitializable() {
+        def defs = [
+                initializableBean: 'org.apache.shiro.config.InitializableBean'
+        ]
+        def builder = new ReflectionBuilder()
+        def objects = builder.buildObjects(defs)
+        def bean = objects.get('initializableBean') as InitializableBean
+        assertTrue bean.isInitialized()
+    }
+
+    @Test
+    void testFactoryInstantiation() {
+        Map<String, String> defs = new LinkedHashMap<String, String>();
+        defs.put("simpleBeanFactory", "org.apache.shiro.config.SimpleBeanFactory");
+        defs.put("simpleBeanFactory.factoryInt", "5");
+        defs.put("simpleBeanFactory.factoryString", "someString");
+        defs.put("compositeBean", "org.apache.shiro.config.CompositeBean");
+        defs.put("compositeBean.simpleBean", '$simpleBeanFactory');
+
+        ReflectionBuilder builder = new ReflectionBuilder();
+        Map objects = builder.buildObjects(defs);
+        assertFalse(CollectionUtils.isEmpty(objects));
+        CompositeBean compositeBean = (CompositeBean) objects.get("compositeBean");
+        SimpleBean bean = compositeBean.getSimpleBean();
+        assertNotNull(bean);
+        assertEquals(5, bean.getIntProp());
+        assertEquals("someString", bean.getStringProp());
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHashServiceTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHashServiceTest.groovy
new file mode 100644
index 0000000..bf08063
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHashServiceTest.groovy
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash
+
+import org.apache.shiro.crypto.RandomNumberGenerator
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+import org.apache.shiro.util.ByteSource
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link DefaultHashService} implementation.
+ *
+ * @since 1.2
+ */
+class DefaultHashServiceTest extends GroovyTestCase {
+
+    void testNullRequest() {
+        assertNull createService().computeHash(null)
+    }
+
+    void testDifferentAlgorithmName() {
+        def service = new DefaultHashService(hashAlgorithmName: 'MD5')
+        def hash = hash(service, "test")
+        assertEquals 'MD5', hash.algorithmName
+    }
+
+    void testDifferentIterations() {
+        def service = new DefaultHashService(hashIterations: 2)
+        def hash = hash(service, "test")
+        assertEquals 2, hash.iterations
+    }
+
+    void testDifferentRandomNumberGenerator() {
+
+        def ByteSource randomBytes = new SecureRandomNumberGenerator().nextBytes()
+        def rng = createMock(RandomNumberGenerator)
+        expect(rng.nextBytes()).andReturn randomBytes
+
+        replay rng
+
+        def service = new DefaultHashService(randomNumberGenerator: rng, generatePublicSalt: true)
+        hash(service, "test")
+
+        verify rng
+    }
+
+    /**
+     * If 'generatePublicSalt' is true, 2 hashes of the same input source should be different.
+     */
+    void testWithRandomlyGeneratedSalt() {
+        def service = new DefaultHashService(generatePublicSalt: true)
+        def first = hash(service, "password")
+        def second = hash(service, "password")
+        assertFalse first == second
+    }
+
+    void testRequestWithEmptySource() {
+        def source = ByteSource.Util.bytes((byte[])null)
+        def request = new HashRequest.Builder().setSource(source).build()
+        def service = createService()
+        assertNull service.computeHash(request)
+    }
+
+    /**
+     * Two different strings hashed with the same salt should result in two different
+     * hashes.
+     */
+    void testOnlyRandomSaltHash() {
+        HashService service = createService();
+        Hash first = hash(service, "password");
+        Hash second = hash(service, "password2", first.salt);
+        assertFalse first == second
+    }
+
+    /**
+     * If the same string is hashed twice and only base salt was supplied, hashed
+     * result should be different in each case.
+     */
+    void testBothSaltsRandomness() {
+        HashService service = createServiceWithPrivateSalt();
+        Hash first = hash(service, "password");
+        Hash second = hash(service, "password");
+        assertFalse first == second
+    }
+
+    /**
+     * If a string is hashed and only base salt was supplied, random salt is generated.
+     * Hash of the same string with generated random salt should return the
+     * same result.
+     */
+    void testBothSaltsReturn() {
+        HashService service = createServiceWithPrivateSalt();
+        Hash first = hash(service, "password");
+        Hash second = hash(service, "password", first.salt);
+        assertEquals first, second
+    }
+
+    /**
+     * Two different strings hashed with the same salt should result in two different
+     * hashes.
+     */
+    void testBothSaltsHash() {
+        HashService service = createServiceWithPrivateSalt();
+        Hash first = hash(service, "password");
+        Hash second = hash(service, "password2", first.salt);
+        assertFalse first == second
+    }
+
+    /**
+     * Hash result is different if the base salt is added.
+     */
+    public void testPrivateSaltChangesResult() {
+        HashService saltedService = createServiceWithPrivateSalt();
+        HashService service = createService();
+        Hash first = hashPredictable(saltedService, "password");
+        Hash second = hashPredictable(service, "password");
+        assertFalse first == second
+    }
+
+    protected Hash hash(HashService hashService, def source) {
+        return hashService.computeHash(new HashRequest.Builder().setSource(source).build());
+    }
+
+    protected Hash hash(HashService hashService, def source, def salt) {
+        return hashService.computeHash(new HashRequest.Builder().setSource(source).setSalt(salt).build());
+    }
+
+    private Hash hashPredictable(HashService hashService, def source) {
+        byte[] salt = new byte[20];
+        Arrays.fill(salt, (byte) 2);
+        return hashService.computeHash(new HashRequest.Builder().setSource(source).setSalt(salt).build());
+    }
+
+    private DefaultHashService createService() {
+        return new DefaultHashService();
+    }
+
+    private DefaultHashService createServiceWithPrivateSalt() {
+        DefaultHashService defaultHashService = new DefaultHashService();
+        defaultHashService.setPrivateSalt(new SecureRandomNumberGenerator().nextBytes());
+        return defaultHashService;
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/HashRequestBuilderTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/HashRequestBuilderTest.groovy
new file mode 100644
index 0000000..4c7c5ff
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/HashRequestBuilderTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+import org.apache.shiro.util.ByteSource
+
+/**
+ * Unit tests for the {@link HashRequest.Builder} implementation
+ *
+ * @since 1.2
+ */
+class HashRequestBuilderTest extends GroovyTestCase {
+
+    void testNullSource() {
+        try {
+            new HashRequest.Builder().build()
+            fail "NullPointerException should be thrown"
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    void testDefault() {
+        assertEquals 0, new HashRequest.Builder().setSource("test").build().iterations
+    }
+
+    void testConfig() {
+        ByteSource source = ByteSource.Util.bytes("test")
+        ByteSource salt = new SecureRandomNumberGenerator().nextBytes()
+        def request = new HashRequest.Builder()
+            .setSource(source)
+            .setSalt(salt)
+            .setIterations(2)
+            .setAlgorithmName('MD5').build()
+
+        assertNotNull request
+        assertEquals source, request.source
+        assertEquals salt, request.salt
+        assertEquals 2, request.iterations
+        assertEquals 'MD5', request.algorithmName
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Base64FormatTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Base64FormatTest.groovy
new file mode 100644
index 0000000..88f035e
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Base64FormatTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.hash.Hash
+import org.apache.shiro.crypto.hash.Sha1Hash
+
+/**
+ * Unit tests for the {@link Base64Format} implementation.
+ *
+ * @since 1.2
+ */
+class Base64FormatTest extends GroovyTestCase {
+
+    void testFormat() {
+        Hash hash = new Sha1Hash("hello");
+        Base64Format format = new Base64Format()
+        String base64 = format.format(hash)
+        assertEquals base64, hash.toBase64()
+    }
+
+    void testFormatWithNullArgument() {
+        Base64Format format = new Base64Format()
+        assertNull format.format(null)
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactoryTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactoryTest.groovy
new file mode 100644
index 0000000..e731a7a
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactoryTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.hash.Sha1Hash
+
+/**
+ * Unit tests for the {@link DefaultHashFormatFactory} implementation.
+ *
+ * @since 1.2
+ */
+class DefaultHashFormatFactoryTest extends GroovyTestCase {
+
+    void testDefaultInstance() {
+        def factory = new DefaultHashFormatFactory()
+        assertNotNull factory.formatClassNames
+        assertTrue factory.formatClassNames.isEmpty()
+        assertNotNull factory.searchPackages
+        assertTrue factory.formatClassNames.isEmpty()
+    }
+
+    void testNullArg() {
+        def factory = new DefaultHashFormatFactory()
+        assertNull factory.getInstance(null)
+    }
+
+    void testNotFound() {
+        def factory = new DefaultHashFormatFactory()
+        assertNull factory.getInstance('foo')
+    }
+
+    void testSetFormatClassNames() {
+        def classNames = ['hex': HexFormat.class.name]
+        def factory = new DefaultHashFormatFactory()
+        factory.formatClassNames = classNames
+        assertNotNull factory.formatClassNames
+        assertEquals 1, factory.formatClassNames.size()
+        assertEquals factory.formatClassNames['hex'], HexFormat.class.name
+    }
+
+    void testGetInstanceWithConfiguredFormatClassName() {
+        def classNames = ['anAlias': HexFormat.class.name]
+        def factory = new DefaultHashFormatFactory(formatClassNames: classNames)
+        def instance = factory.getInstance('anAlias')
+        assertNotNull instance
+        assertTrue instance instanceof HexFormat
+    }
+
+    void testGetInstanceWithMcfFormattedString() {
+        Shiro1CryptFormat format = new Shiro1CryptFormat()
+        def formatted = format.format(new Sha1Hash("test"))
+
+        def factory = new DefaultHashFormatFactory()
+
+        def instance = factory.getInstance(formatted)
+
+        assertNotNull instance
+        assertTrue instance instanceof Shiro1CryptFormat
+    }
+
+    void testAbsentFQCN() {
+        def factory = new DefaultHashFormatFactory()
+        def instance = factory.getInstance("com.foo.bar.some.random.MyHashFormat")
+        assertNull instance
+    }
+
+    void testPresentFQCN() {
+        def factory = new DefaultHashFormatFactory()
+        def instance = factory.getInstance(Shiro1CryptFormat.class.name)
+        assertNotNull instance
+        assertTrue instance instanceof Shiro1CryptFormat
+    }
+
+    void testMcfFormattedArgument() {
+        def factory = new DefaultHashFormatFactory()
+
+        def hash = new Sha1Hash("test")
+        def formatted = new Shiro1CryptFormat().format(hash)
+
+        def instance = factory.getInstance(formatted)
+
+        assertNotNull instance
+        assertTrue instance instanceof Shiro1CryptFormat
+    }
+
+    void testSearchPackages() {
+        def factory = new DefaultHashFormatFactory()
+        factory.searchPackages = ['org.apache.shiro.crypto.hash.format']
+
+        //find the test class 'ToStringHashFormat'
+        def instance = factory.getInstance('toString')
+
+        assertNotNull instance
+        assertTrue instance instanceof ToStringHashFormat
+    }
+
+    void testSearchPackagesWithoutMatch() {
+        def factory = new DefaultHashFormatFactory()
+        factory.searchPackages = ['com.foo']
+
+        assertNull factory.getInstance('bar')
+    }
+
+    void testWithInvalidHashFormatImplementation() {
+        def factory = new DefaultHashFormatFactory()
+        try {
+            factory.getInstance("java.lang.Integer")
+            fail "Call should have resulted in an IllegalArgumentException"
+        } catch (IllegalArgumentException expected) {
+        }
+
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/HexFormatTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/HexFormatTest.groovy
new file mode 100644
index 0000000..e8f3828
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/HexFormatTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.hash.Hash
+import org.apache.shiro.crypto.hash.Sha1Hash
+
+/**
+ * Unit tests for the {@link HexFormat} implementation.
+ *
+ * @since 1.2
+ */
+class HexFormatTest extends GroovyTestCase {
+
+    void testFormat() {
+        Hash hash = new Sha1Hash("hello");
+        HexFormat format = new HexFormat()
+        String hex = format.format(hash)
+        assertEquals hex, hash.toHex()
+    }
+
+    void testFormatWithNullArgument() {
+        HexFormat format = new HexFormat()
+        assertNull format.format(null)
+    }
+
+}
+
+
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ProvidedHashFormatTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ProvidedHashFormatTest.groovy
new file mode 100644
index 0000000..820760a
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ProvidedHashFormatTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+/**
+ * Unit tests for the {@link ProvidedHashFormat} implementation.
+ *
+ * @since 1.2
+ */
+class ProvidedHashFormatTest extends GroovyTestCase {
+
+    void testDefaults() {
+        def set = ProvidedHashFormat.values() as Set
+        assertEquals 3, set.size()
+        assertTrue set.contains(ProvidedHashFormat.HEX)
+        assertTrue set.contains(ProvidedHashFormat.BASE64)
+        assertTrue set.contains(ProvidedHashFormat.SHIRO1)
+    }
+
+    void testByIdWithNullArg() {
+        assertNull ProvidedHashFormat.byId(null)
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy
new file mode 100644
index 0000000..2228464
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+import org.apache.shiro.crypto.hash.SimpleHash
+
+/**
+ * Unit tests for the {@link Shiro1CryptFormat} implementation.
+ *
+ * @since 1.2
+ */
+class Shiro1CryptFormatTest extends GroovyTestCase {
+
+    void testGetId() {
+        assertEquals "shiro1", new Shiro1CryptFormat().getId()
+    }
+
+    void testFormatDefault() {
+        def format = new Shiro1CryptFormat();
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+        def salt = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, salt, iterations)
+
+        String formatted = format.format(hash);
+
+        String expected =
+            Shiro1CryptFormat.MCF_PREFIX + alg + '$' + iterations + '$' + salt.toBase64() + '$' + hash.toBase64()
+
+        assertNotNull formatted
+        assertEquals expected, formatted
+    }
+
+    void testFormatWithoutSalt() {
+        def format = new Shiro1CryptFormat();
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, null, iterations)
+
+        String formatted = format.format(hash);
+
+        String expected = Shiro1CryptFormat.MCF_PREFIX + alg + '$' + iterations + '$$' + hash.toBase64()
+
+        assertNotNull formatted
+        assertEquals expected, formatted
+    }
+
+    void testFormatWithNullArgument() {
+        def format = new Shiro1CryptFormat()
+        def result = format.format(null)
+        assertNull result
+    }
+
+    void testParseDefault() {
+        def format = new Shiro1CryptFormat();
+        def delim = Shiro1CryptFormat.TOKEN_DELIMITER
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+        def salt = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, salt, iterations)
+
+        String formatted = Shiro1CryptFormat.MCF_PREFIX +
+                alg + delim +
+                iterations + delim +
+                salt.toBase64() + delim +
+                hash.toBase64()
+
+        def parsedHash = format.parse(formatted)
+
+        assertEquals hash, parsedHash
+        assertEquals hash.algorithmName, parsedHash.algorithmName
+        assertEquals hash.iterations, parsedHash.iterations
+        assertEquals hash.salt, parsedHash.salt
+        assertTrue Arrays.equals(hash.bytes, parsedHash.bytes)
+    }
+
+    void testParseWithoutSalt() {
+        def format = new Shiro1CryptFormat();
+        def delim = Shiro1CryptFormat.TOKEN_DELIMITER
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, null, iterations)
+
+        String formatted = Shiro1CryptFormat.MCF_PREFIX +
+                alg + delim +
+                iterations + delim +
+                delim +
+                hash.toBase64()
+
+        def parsedHash = format.parse(formatted)
+
+        assertEquals hash, parsedHash
+        assertEquals hash.algorithmName, parsedHash.algorithmName
+        assertEquals hash.iterations, parsedHash.iterations
+        assertNull hash.salt
+        assertTrue Arrays.equals(hash.bytes, parsedHash.bytes)
+    }
+
+    void testParseWithNullArgument() {
+        def format = new Shiro1CryptFormat()
+        def result = format.parse(null)
+        assertNull result
+    }
+
+    void testParseWithInvalidId() {
+        def format = new Shiro1CryptFormat()
+        try {
+            format.parse('$foo$xxxxxxx')
+            fail("parse should have thrown an IllegalArgumentException")
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    void testParseWithNonNumericIterations() {
+        def format = new Shiro1CryptFormat();
+        def formatted = '$shiro1$SHA-512$N$foo$foo'
+
+        try {
+            format.parse(formatted)
+            fail("parse should have thrown an IllegalArgumentException")
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ToStringHashFormat.groovy b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ToStringHashFormat.groovy
new file mode 100644
index 0000000..6ff0fdb
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/crypto/hash/format/ToStringHashFormat.groovy
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.hash.Hash
+
+/**
+ * Simple {@code HashFormat} for testing that merely returns {@code hash.toString()}.
+ *
+ * @since 1.2
+ */
+class ToStringHashFormat implements HashFormat {
+
+    String format(Hash hash) {
+        return hash.toString()
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/mgt/DefaultSubjectDAOTest.groovy b/core/src/test/groovy/org/apache/shiro/mgt/DefaultSubjectDAOTest.groovy
new file mode 100644
index 0000000..1033ca0
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/mgt/DefaultSubjectDAOTest.groovy
@@ -0,0 +1,409 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt
+
+import org.apache.shiro.session.Session
+import org.apache.shiro.subject.PrincipalCollection
+import org.apache.shiro.subject.Subject
+import org.apache.shiro.subject.support.DefaultSubjectContext
+import static org.easymock.EasyMock.*
+import org.apache.shiro.subject.support.DelegatingSubject
+
+/**
+ * Unit tests for the {@link DefaultSubjectDAO} implementation.
+ *
+ * @since 1.2
+ */
+class DefaultSubjectDAOTest extends GroovyTestCase {
+
+    void testIsSessionStorageEnabledDefault() {
+        def dao = new DefaultSubjectDAO()
+        assertTrue dao.sessionStorageEvaluator instanceof DefaultSessionStorageEvaluator
+        assertTrue dao.isSessionStorageEnabled(null)
+    }
+
+    void testIsSessionStorageEnabledDefaultSubject() {
+        def dao = new DefaultSubjectDAO()
+
+        def subject = createStrictMock(Subject)
+
+        expect(subject.getSession(false)).andReturn null
+
+        replay subject
+
+        assertTrue dao.isSessionStorageEnabled(subject)
+
+        verify subject
+    }
+
+    void testCustomSessionStorageEvaluator() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createMock(Subject)
+        def evaluator = createStrictMock(SessionStorageEvaluator)
+        dao.sessionStorageEvaluator = evaluator
+
+        expect(evaluator.isSessionStorageEnabled(same(subject))).andReturn true
+
+        replay subject, evaluator
+
+        assertTrue dao.isSessionStorageEnabled(subject)
+
+        verify subject, evaluator
+    }
+
+    void testDeleteWithoutSession() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+
+        expect(subject.getSession(false)).andReturn null
+
+        replay subject
+
+        dao.delete(subject)
+
+        verify subject
+    }
+
+    void testDeleteWithSession() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn(session)
+        expect(session.removeAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY))).andReturn null
+        expect(session.removeAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn null
+
+        replay subject, session
+
+        dao.delete(subject)
+
+        verify subject, session
+    }
+
+    /**
+     * Ensures that when save is called and session storage is disabled, that the subject is never asked for its session.
+     */
+    void testSaveWhenSessionStorageIsDisabled() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+
+        expect(subject.getSession(false)).andReturn null
+
+        //turn off session storage:
+        ((DefaultSessionStorageEvaluator)dao.sessionStorageEvaluator).sessionStorageEnabled = false
+
+        replay subject
+
+        Subject saved = dao.save(subject)
+
+        assertSame saved, subject
+
+        verify subject
+    }
+
+    /**
+     * Tests the case when the save method is called but the Subject does not yet have any associated
+     * principals or authentication state or even a session.  In this case, the session should never be created.
+     */
+    void testSaveWithoutSessionOrPrincipalsOrAuthentication() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn null
+
+        expect(subject.isRunAs()).andReturn(false)
+        expect(subject.principals).andReturn null
+        expect(subject.getSession(false)).andReturn(null).anyTimes()
+        expect(subject.authenticated).andReturn false
+
+        replay subject, session
+
+        dao.save(subject)
+
+        verify subject, session
+    }
+
+    // BEGIN: mergePrincipals tests
+
+    /**
+     * SHIRO-380
+     */
+    void testMergePrincipalsWithDelegatingSubject() {
+
+        def sessionId = "sessionId"
+
+        def principals = createStrictMock(PrincipalCollection)
+        def runAsPrincipals = createStrictMock(PrincipalCollection)
+        def session = createStrictMock(Session)
+        def securityManager = createStrictMock(SecurityManager)
+
+        expect(session.getId()).andStubReturn sessionId
+        expect(session.getAttribute(eq(DelegatingSubject.RUN_AS_PRINCIPALS_SESSION_KEY))).andReturn(Arrays.asList(runAsPrincipals))
+        expect(principals.isEmpty()).andStubReturn false
+        expect(session.getAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn null
+        session.setAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY), same(principals));
+
+        replay principals, runAsPrincipals, session, securityManager
+
+        def subject = new DelegatingSubject(principals, true, "localhost", session, true, securityManager)
+        new DefaultSubjectDAO().mergePrincipals(subject)
+
+        verify principals, runAsPrincipals, session, securityManager
+    }
+
+    /**
+     * Tests the case when the Subject has principals but no session yet.  In this case, a session will be created
+     * and the session will be set with the principals.
+     */
+    void testMergePrincipalsWithSubjectPrincipalsButWithoutSession() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+        def principals = createStrictMock(PrincipalCollection)
+
+        expect(subject.runAs).andReturn false
+        expect(subject.principals).andReturn principals
+        expect(subject.getSession(false)).andReturn null //no session
+        expect(principals.isEmpty()).andReturn(false).anyTimes()
+        expect(subject.getSession()).andReturn session //new session created
+        session.setAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY), same(principals))
+
+        replay subject, session, principals
+
+        dao.mergePrincipals(subject)
+
+        verify subject, session, principals
+    }
+
+    /**
+     * Tests the case when the Subject has a Session but the subject does not yet have any associated
+     * principals and neither does the session.  In this case, the session will be accessed
+     * but never updated.
+     */
+    void testMergePrincipalsWithoutSubjectPrincipalsOrSessionPrincipals() {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.runAs).andReturn(false)
+        expect(subject.principals).andReturn null
+        expect(subject.getSession(false)).andReturn(session).anyTimes()
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn null
+
+        replay subject, session
+
+        dao.mergePrincipals(subject)
+
+        verify subject, session
+    }
+
+    /**
+     * Tests the case when the Subject has a Session but the subject does not yet have any associated
+     * principals but the session does.  In this case, the session will be accessed and the session-principals will
+     * be removed (to match the Subject's state).
+     */
+    void testMergePrincipalsWithoutSubjectPrincipalsButWithSessionPrincipals() {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+        def sessionPrincipals = createStrictMock(PrincipalCollection)
+
+        expect(subject.runAs).andReturn false
+        expect(subject.principals).andReturn null
+        expect(subject.getSession(false)).andReturn(session).anyTimes()
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn sessionPrincipals
+        expect(sessionPrincipals.isEmpty()).andReturn false
+        expect(session.removeAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn sessionPrincipals
+
+        replay subject, session, sessionPrincipals
+
+        dao.mergePrincipals(subject)
+
+        verify subject, session, sessionPrincipals
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject has associated principals, but the session does not
+     * yet reflect those principals.  In this case, the session will be accessed and the session will be set with the
+     * Subject's principals.
+     */
+    void testMergePrincipalsWithSubjectPrincipalsButWithoutSessionPrincipals() {
+        testMergePrincipalsWithSubjectPrincipalsButWithSessionPrincipals(null)
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject has associated principals, but the session reflects
+     * different principals.  In this case, the session will be accessed and the session will be set with the
+     * Subject's principals.
+     */
+    void testMergePrincipalsWithSubjectPrincipalsButWithDifferentSessionPrincipals() {
+        def sessionPrincipals = createStrictMock(PrincipalCollection)
+
+        replay sessionPrincipals
+
+        testMergePrincipalsWithSubjectPrincipalsButWithSessionPrincipals(sessionPrincipals)
+
+        verify sessionPrincipals
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject has associated principals, but the session does not
+     * yet reflect those principals.  In this case, the session will be accessed and the session will be set with the
+     * Subject's principals.
+     */
+    private void testMergePrincipalsWithSubjectPrincipalsButWithSessionPrincipals(PrincipalCollection sessionPrincipals) {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+        def subjectPrincipals = createStrictMock(PrincipalCollection)
+
+        expect(subject.runAs).andReturn false
+        expect(subject.principals).andReturn subjectPrincipals
+        expect(subject.getSession(false)).andReturn session
+        expect(subjectPrincipals.isEmpty()).andReturn false
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY))).andReturn sessionPrincipals
+        session.setAttribute(eq(DefaultSubjectContext.PRINCIPALS_SESSION_KEY), same(subjectPrincipals))
+
+        replay subject, session, subjectPrincipals
+
+        dao.mergePrincipals(subject)
+
+        verify subject, session, subjectPrincipals
+    }
+
+    // BEGIN: mergeAuthenticationState tests
+
+    /**
+     * Tests the case when the Subject is authenticated but doesn't yet have a session.  In this case, a
+     * session will be created and the session will be set with the authentication state.
+     */
+    void testMergeAuthcWithSubjectAuthcButWithoutSession() {
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn null //no session
+        expect(subject.authenticated).andReturn true
+        expect(subject.getSession()).andReturn session //new session created
+        session.setAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY), eq(Boolean.TRUE))
+
+        replay subject, session
+
+        dao.mergeAuthenticationState(subject)
+
+        verify subject, session
+    }
+
+    /**
+     * Tests the case when the Subject has a Session but the subject is not yet authenticated
+     * and the session doesn't have an attribute reflecting this.  In this case, the session will be accessed
+     * but never updated.
+     */
+    void testMergeAuthcWithoutSubjectAuthcOrSessionAuthc() {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn(session).anyTimes()
+        expect(subject.authenticated).andReturn false
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY))).andReturn null
+
+        replay subject, session
+
+        dao.mergeAuthenticationState(subject)
+
+        verify subject, session
+    }
+
+    /**
+     * Tests the case when the Subject has a Session but the subject is not yet authenticated but the session
+     * has authentication state.  In this case, the session will be accessed and the session authc state will
+     * be removed to match the Subject's state.
+     */
+    void testMergeAuthcWithoutSubjectAuthcButWithSessionAuthc() {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn(session).anyTimes()
+        expect(subject.authenticated).andReturn false
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY))).andReturn Boolean.TRUE
+        expect(session.removeAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY))).andReturn Boolean.TRUE
+
+        replay subject, session
+
+        dao.mergeAuthenticationState(subject)
+
+        verify subject, session
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject is authenticated, but the session does not
+     * yet reflect that authentication state.  In this case, the session will be accessed and the session will be set
+     * with the Subject's authentication state.
+     */
+    void testMergeAuthcWithSubjectAuthcButWithoutSessionAuthc() {
+        testMergeAuthcWithSubjectAuthcButWithSessionAuthc(null)
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject is authenticated, but the session reflects a
+     * different state.  In this case, the session will be accessed and the session will be set with the
+     * Subject's authentication state.
+     */
+    void testMergeAuthcWithSubjectAuthcButWithDifferentSessionAuthc() {
+        testMergeAuthcWithSubjectAuthcButWithSessionAuthc(Boolean.FALSE)
+    }
+
+    /**
+     * Tests the case when the Subject has a Session and the Subject has associated principals, but the session does not
+     * yet reflect those principals.  In this case, the session will be accessed and the session will be set with the
+     * Subject's principals.
+     */
+    private void testMergeAuthcWithSubjectAuthcButWithSessionAuthc(Boolean value) {
+
+        def dao = new DefaultSubjectDAO()
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn session
+        expect(subject.authenticated).andReturn true
+
+        expect(session.getAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY))).andReturn value
+        session.setAttribute(eq(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY), eq(Boolean.TRUE))
+
+        replay subject, session
+
+        dao.mergeAuthenticationState(subject)
+
+        verify subject, session
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmIntegrationTest.groovy b/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmIntegrationTest.groovy
new file mode 100644
index 0000000..9770cc4
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmIntegrationTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm
+
+import org.apache.shiro.authc.AuthenticationToken
+import org.apache.shiro.authc.UsernamePasswordToken
+import org.apache.shiro.config.Ini
+import org.apache.shiro.config.IniSecurityManagerFactory
+import org.apache.shiro.mgt.SecurityManager
+import org.apache.shiro.subject.Subject
+
+/**
+ * Integration tests for the AuthenticatingRealm implementation.
+ *
+ * @since 1.2.1
+ */
+class AuthenticatingRealmIntegrationTest extends GroovyTestCase {
+
+    void testShiro354() {
+
+        Ini ini = new Ini();
+        ini.load('''
+
+        [main]
+        realm = org.apache.shiro.realm.TestAuthenticatingRealm
+        securityManager.realms = $realm
+        cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
+        securityManager.cacheManager = $cacheManager
+        # if you comment this line out, the test will fail as expected:
+        realm.authenticationCachingEnabled = true
+
+        ''');
+
+        SecurityManager sm = new IniSecurityManagerFactory(ini).getInstance();
+
+        AuthenticationToken token = new UsernamePasswordToken("user1", "secret");
+
+        Subject subject = new Subject.Builder(sm).buildSubject();
+        subject.login(token);
+
+        Subject subject2 = new Subject.Builder(sm).buildSubject();
+        subject2.login(token);
+
+        //2 login calls for the same account, but the count on realm.doGetAuthenticationInfo should only be 1 due to caching:
+        assertEquals 1, sm.getRealms().iterator().next().authenticationInfoCount
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmTest.groovy b/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmTest.groovy
new file mode 100644
index 0000000..dde4f4d
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/realm/AuthenticatingRealmTest.groovy
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm
+
+import org.apache.shiro.authc.credential.CredentialsMatcher
+import org.apache.shiro.cache.Cache
+import org.apache.shiro.cache.CacheManager
+import org.apache.shiro.subject.PrincipalCollection
+import org.apache.shiro.authc.*
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link AuthenticatingRealm} implementation.
+ */
+class AuthenticatingRealmTest extends GroovyTestCase {
+
+    void testSetName() {
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+        def name = "foo"
+
+        realm.name = name
+        realm.init()
+
+        assertEquals name, realm.name
+        assertEquals name + AuthenticatingRealm.DEFAULT_AUTHORIZATION_CACHE_SUFFIX, realm.authenticationCacheName
+
+        realm.authenticationCacheName = "bar"
+
+        assertEquals name, realm.name
+        assertEquals "bar", realm.authenticationCacheName
+    }
+
+    void testSupports() {
+        def password = "foo"
+        def token = new UsernamePasswordToken("username", password);
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm();
+
+        assertTrue realm.supports(token)
+    }
+
+    void testSupportsWithCustomAuthenticationTokenClass() {
+
+        def token = createStrictMock(RememberMeAuthenticationToken)
+
+        replay token
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm();
+        realm.setAuthenticationTokenClass RememberMeAuthenticationToken
+
+        assertTrue realm.supports(token)
+
+        verify token
+    }
+
+    void testNewInstanceWithCacheManager() {
+        def cacheManager = createStrictMock(CacheManager)
+
+        replay cacheManager
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm(cacheManager)
+
+        assertSame cacheManager, realm.cacheManager
+
+        verify cacheManager
+    }
+
+    void testNewInstanceWithCredentialsMatcher() {
+        def matcher = createStrictMock(CredentialsMatcher)
+
+        replay matcher
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm(matcher)
+        assertSame matcher, realm.credentialsMatcher
+
+        verify matcher
+    }
+
+    void testSetCache() {
+        def cache = createStrictMock(Cache)
+
+        replay cache
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+        realm.authenticationCache = cache
+
+        assertSame cache, realm.authenticationCache
+
+        verify cache
+    }
+
+    void testGetAuthenticationInfo() {
+
+        def password = "foo"
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        expect(token.getCredentials()).andReturn(password).anyTimes();
+        expect(info.getCredentials()).andReturn(password).anyTimes();
+
+        replay token, info
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+        realm.info = info
+
+        def returnedInfo = realm.getAuthenticationInfo(token)
+
+        assertSame returnedInfo, info
+
+        verify token, info
+    }
+
+    void testGetAuthenticationInfoWithNullReturnValue() {
+
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        replay token, info
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+
+        def returnedInfo = realm.getAuthenticationInfo(token)
+
+        assertNull returnedInfo
+
+        verify token, info
+    }
+
+    void testAuthenticationCachingEnabledWithCacheMiss() {
+
+        def username = "foo"
+        def password = "bar"
+
+        def cacheManager = createStrictMock(CacheManager)
+        def cache = createStrictMock(Cache)
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        expect(cacheManager.getCache(isA(String))).andReturn cache
+        expect(token.getPrincipal()).andReturn(username).anyTimes()
+        expect(token.getCredentials()).andReturn(password).anyTimes()
+
+        expect(cache.get(eq(username))).andReturn null
+        expect(cache.put(eq(username), same(info))).andReturn null
+
+        expect(info.getCredentials()).andReturn(password)
+
+        replay cacheManager, cache, token, info
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+        realm.info = info
+
+        realm.cacheManager = cacheManager
+        realm.authenticationCachingEnabled = true
+
+        def returnedInfo = realm.getAuthenticationInfo(token)
+
+        assertSame info, returnedInfo
+
+        verify cacheManager, cache, token, info
+    }
+
+    void testAuthenticationCachingEnabledWithCacheHit() {
+
+        def username = "foo"
+        def password = "bar"
+
+        def cacheManager = createStrictMock(CacheManager)
+        def cache = createStrictMock(Cache)
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        expect(cacheManager.getCache(isA(String))).andReturn cache
+        expect(token.getPrincipal()).andReturn(username).anyTimes()
+        expect(token.getCredentials()).andReturn(password).anyTimes()
+
+        expect(cache.get(eq(username))).andReturn info
+
+        expect(info.getCredentials()).andReturn(password)
+
+        replay cacheManager, cache, token, info
+
+        AuthenticatingRealm realm = new NoLookupAuthenticatingRealm()
+        realm.cacheManager = cacheManager
+        realm.authenticationCachingEnabled = true
+
+        def returnedInfo = realm.getAuthenticationInfo(token)
+
+        assertSame info, returnedInfo
+
+        verify cacheManager, cache, token, info
+    }
+
+    void testLogoutWithAuthenticationCachingEnabled() {
+
+        def realmName = "testRealm"
+        def authcCacheName = realmName + AuthenticatingRealm.DEFAULT_AUTHORIZATION_CACHE_SUFFIX
+        def username = "foo"
+
+        def cacheManager = createStrictMock(CacheManager)
+        def cache = createStrictMock(Cache)
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+        def principals = createStrictMock(PrincipalCollection)
+        def realmPrincipals = [username]
+
+        expect(principals.isEmpty()).andReturn(false).anyTimes()
+        expect(principals.fromRealm(eq(realmName))).andReturn realmPrincipals
+
+        expect(cacheManager.getCache(eq(authcCacheName))).andReturn cache
+        expect(cache.remove(eq(username))).andReturn info
+
+        replay cacheManager, cache, token, info, principals
+
+        AuthenticatingRealm realm = new NoLookupAuthenticatingRealm()
+        realm.cacheManager = cacheManager
+        realm.authenticationCachingEnabled = true
+        realm.name = realmName
+
+        realm.onLogout(principals)
+
+        verify cacheManager, cache, token, info, principals
+    }
+
+    void testAssertCredentialsMatchWithNullCredentialsMatcher() {
+        AuthenticatingRealm realm = new TestAuthenticatingRealm();
+        realm.credentialsMatcher = null
+
+        try {
+            realm.assertCredentialsMatch(null, null)
+            fail("should have thrown an AuthenticationException")
+        } catch (AuthenticationException e) {
+            assertNotNull e.getMessage()
+            assertTrue e.getMessage().contains("A CredentialsMatcher must be configured")
+        }
+    }
+
+    void testAssertCredentialsMatchFailure() {
+
+        def matcher = createStrictMock(CredentialsMatcher)
+        def token = createStrictMock(AuthenticationToken)
+        def info = createStrictMock(AuthenticationInfo)
+
+        expect(matcher.doCredentialsMatch(same(token), same(info))).andReturn false
+
+        replay matcher, token, info
+
+        AuthenticatingRealm realm = new TestAuthenticatingRealm()
+        realm.credentialsMatcher = matcher
+        try {
+            realm.assertCredentialsMatch(token, info)
+            fail("IncorrectCredentialsException should have been thrown.");
+        } catch (IncorrectCredentialsException expected) {
+        }
+
+        verify matcher, token, info
+    }
+
+
+    private class TestAuthenticatingRealm extends AuthenticatingRealm {
+
+        def AuthenticationInfo info;
+
+        def TestAuthenticatingRealm() {
+            super()
+        }
+
+        def TestAuthenticatingRealm(CacheManager cacheManager) {
+            super(cacheManager)
+        }
+
+        def TestAuthenticatingRealm(CredentialsMatcher matcher) {
+            super(matcher)
+        }
+
+        def TestAuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
+            super(cacheManager, matcher)
+        }
+
+        @Override
+        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
+            return info;
+        }
+    }
+
+    private class NoLookupAuthenticatingRealm extends AuthenticatingRealm {
+        @Override
+        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
+            fail("This implementation does not allow lookups.");
+            return null;
+        }
+    }
+
+}
diff --git a/core/src/test/groovy/org/apache/shiro/realm/CachingRealmTest.groovy b/core/src/test/groovy/org/apache/shiro/realm/CachingRealmTest.groovy
new file mode 100644
index 0000000..3a9da85
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/realm/CachingRealmTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm
+
+import org.apache.shiro.authc.AuthenticationInfo
+import org.apache.shiro.authc.AuthenticationToken
+import org.apache.shiro.cache.Cache
+import org.apache.shiro.cache.CacheManager
+import org.apache.shiro.subject.PrincipalCollection
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link CachingRealm} implementation.
+ */
+class CachingRealmTest extends GroovyTestCase {
+
+    void testCachingEnabled() {
+
+        CachingRealm realm = new TestCachingRealm()
+
+        assertTrue realm.cachingEnabled
+        realm.cachingEnabled = false
+        assertFalse realm.cachingEnabled
+    }
+
+    void testSetName() {
+
+        CachingRealm realm = new TestCachingRealm()
+
+        assertTrue realm.name.contains(TestCachingRealm.class.getName())
+
+        realm.name = "testRealm"
+        assertEquals "testRealm", realm.name
+    }
+
+
+    void testNewInstanceWithCacheManager() {
+
+        def cacheManager = createStrictMock(CacheManager)
+
+        CachingRealm realm = new TestCachingRealm()
+        realm.cacheManager = cacheManager
+
+        assertNotNull realm.cacheManager
+        assertTrue realm.templateMethodCalled
+    }
+
+    void testOnLogout() {
+
+        def realmName = "testRealm"
+
+        def cacheManager = createStrictMock(CacheManager)
+        def cache = createStrictMock(Cache)
+        def principals = createStrictMock(PrincipalCollection)
+
+        expect(principals.isEmpty()).andReturn(false).anyTimes()
+
+        replay cacheManager, cache, principals
+
+        CachingRealm realm = new TestCachingRealm()
+
+        realm.cacheManager = cacheManager
+        realm.name = realmName
+
+        realm.onLogout(principals)
+
+        assertTrue realm.doClearCacheCalled
+
+        verify cacheManager, cache, principals
+    }
+
+    void testGetAvailablePrincipalWithRealmPrincipals() {
+
+        def realmName = "testRealm"
+        def username = "foo"
+
+        def principals = createStrictMock(PrincipalCollection)
+
+        expect(principals.isEmpty()).andReturn false
+        expect(principals.fromRealm(eq(realmName))).andReturn([username])
+
+        replay principals
+
+        CachingRealm realm = new TestCachingRealm()
+        realm.name = realmName
+
+        Object principal = realm.getAvailablePrincipal(principals)
+
+        assertEquals username, principal
+
+        verify principals
+    }
+
+    void testGetAvailablePrincipalWithoutRealmPrincipals() {
+
+        def realmName = "testRealm"
+        def username = "foo"
+
+        def principals = createStrictMock(PrincipalCollection)
+
+        expect(principals.isEmpty()).andReturn false
+        expect(principals.fromRealm(eq(realmName))).andReturn null
+        expect(principals.getPrimaryPrincipal()).andReturn username
+
+        replay principals
+
+        CachingRealm realm = new TestCachingRealm()
+        realm.name = realmName
+
+        Object principal = realm.getAvailablePrincipal(principals)
+
+        assertEquals username, principal
+
+        verify principals
+    }
+
+    private static final class TestCachingRealm extends CachingRealm {
+
+        def info;
+
+        boolean templateMethodCalled = false
+        boolean doClearCacheCalled = false
+
+        boolean supports(AuthenticationToken token) {
+            return true
+        }
+
+        AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
+            return info;
+        }
+
+        @Override
+        protected void afterCacheManagerSet() {
+            super.afterCacheManagerSet()
+            templateMethodCalled = true
+        }
+
+        @Override
+        protected void doClearCache(PrincipalCollection principals) {
+            super.doClearCache(principals)
+            doClearCacheCalled = true
+        }
+    }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/realm/TestAuthenticatingRealm.groovy b/core/src/test/groovy/org/apache/shiro/realm/TestAuthenticatingRealm.groovy
new file mode 100644
index 0000000..98b566c
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/realm/TestAuthenticatingRealm.groovy
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm
+
+import org.apache.shiro.authc.AuthenticationInfo
+import org.apache.shiro.authc.AuthenticationToken
+import org.apache.shiro.authc.SimpleAccount
+
+/**
+ * Used by the AuthenticatingRealmIntegrationTest.
+ *
+ * @since 1.2.1
+ */
+class TestAuthenticatingRealm extends AuthenticatingRealm {
+
+    int authenticationInfoCount = 0;
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
+        authenticationInfoCount++
+        return new SimpleAccount("user1", "secret", getName());
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/AtUnitTestBase.java b/core/src/test/java/org/apache/shiro/AtUnitTestBase.java
new file mode 100644
index 0000000..0dfb0b3
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/AtUnitTestBase.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro;
+
+/*import atunit.AtUnit;
+import atunit.Container;
+import atunit.MockFramework;
+import org.junit.runner.RunWith;*/
+
+/**
+ * Super class that simply provides boiler plate annotations for subclass tests.
+ *
+ * @since 0.9
+ */
+/*@RunWith(AtUnit.class)
+ at Container(Container.Option.SPRING)
+ at MockFramework(MockFramework.Option.EASYMOCK)*/
+public class AtUnitTestBase {
+}
diff --git a/core/src/test/java/org/apache/shiro/ExceptionTest.java b/core/src/test/java/org/apache/shiro/ExceptionTest.java
new file mode 100644
index 0000000..2fb76e3
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/ExceptionTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro;
+
+import junit.framework.TestCase;
+import org.apache.shiro.util.ClassUtils;
+import org.junit.Test;
+
+
+/**
+ */
+ at SuppressWarnings({"ThrowableInstanceNeverThrown"})
+public abstract class ExceptionTest extends TestCase {
+
+    protected abstract Class getExceptionClass();
+
+    @Test
+    public void testNoArgConstructor() {
+        ClassUtils.newInstance(getExceptionClass());
+    }
+
+    @Test
+    public void testMsgConstructor() throws Exception {
+        ClassUtils.newInstance(getExceptionClass(), "Msg");
+    }
+
+    @Test
+    public void testCauseConstructor() throws Exception {
+        ClassUtils.newInstance(getExceptionClass(), new Throwable());
+    }
+
+    @Test
+    public void testMsgCauseConstructor() {
+        ClassUtils.newInstance(getExceptionClass(), "Msg", new Throwable());
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/aop/AnnotationResolverTest.java b/core/src/test/java/org/apache/shiro/aop/AnnotationResolverTest.java
new file mode 100644
index 0000000..545f35d
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/aop/AnnotationResolverTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aop;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import java.lang.reflect.Method;
+
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresUser;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class AnnotationResolverTest {
+    @SuppressWarnings("unused")
+    @RequiresRoles("root")
+    private class MyFixture {
+	public void operateThis() {}
+        @RequiresUser()
+        public void operateThat() {}
+    }
+    
+    DefaultAnnotationResolver annotationResolver = new DefaultAnnotationResolver();
+
+    @Test
+    public void testAnnotationFoundFromClass() throws SecurityException, NoSuchMethodException {
+	MyFixture myFixture = new MyFixture();
+	MethodInvocation methodInvocation = createMock(MethodInvocation.class);
+	Method method = MyFixture.class.getDeclaredMethod("operateThis");
+        expect(methodInvocation.getMethod()).andReturn(method);
+        expect(methodInvocation.getThis()).andReturn(myFixture);
+        replay(methodInvocation);
+	assertNotNull(annotationResolver.getAnnotation(methodInvocation, RequiresRoles.class));
+    }
+    
+    @Test
+    public void testAnnotationFoundFromMethod() throws SecurityException, NoSuchMethodException {
+	MethodInvocation methodInvocation = createMock(MethodInvocation.class);
+	Method method = MyFixture.class.getDeclaredMethod("operateThat");
+        expect(methodInvocation.getMethod()).andReturn(method);
+        replay(methodInvocation);
+	assertNotNull(annotationResolver.getAnnotation(methodInvocation, RequiresUser.class));
+    }
+}
+
diff --git a/core/src/test/java/org/apache/shiro/authc/AbstractAuthenticatorTest.java b/core/src/test/java/org/apache/shiro/authc/AbstractAuthenticatorTest.java
new file mode 100644
index 0000000..f2350df
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/AbstractAuthenticatorTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+
+/**
+ * @since 0.1
+ */
+public class AbstractAuthenticatorTest {
+
+    AbstractAuthenticator abstractAuthenticator;
+    private final SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("user1", "secret", "realmName");
+
+    private AbstractAuthenticator createAuthcReturnNull() {
+        return new AbstractAuthenticator() {
+            protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
+                return null;
+            }
+        };
+    }
+
+    private AbstractAuthenticator createAuthcReturnValidAuthcInfo() {
+        return new AbstractAuthenticator() {
+            protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
+                return info;
+            }
+        };
+    }
+
+    private AuthenticationToken newToken() {
+        return new UsernamePasswordToken("user1", "secret");
+    }
+
+    @Before
+    public void setUp() {
+        abstractAuthenticator = createAuthcReturnValidAuthcInfo();
+    }
+
+    @Test
+    public void newAbstractAuthenticatorSecurityManagerConstructor() {
+        abstractAuthenticator = new AbstractAuthenticator() {
+            protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
+                return info;
+            }
+        };
+    }
+
+
+    /**
+     * Ensures that the authenticate() method proactively fails if a <tt>null</tt> AuthenticationToken is passed as an
+     * argument.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void authenticateWithNullArgument() {
+        abstractAuthenticator.authenticate(null);
+    }
+
+    /**
+     * Ensures that the authenticate() method throws an AuthenticationException if the subclass returns <tt>null</tt>
+     * as the return value to the doAuthenticate() method.
+     */
+    @Test(expected = AuthenticationException.class)
+    public void throwAuthenticationExceptionIfDoAuthenticateReturnsNull() {
+        abstractAuthenticator = createAuthcReturnNull();
+        abstractAuthenticator.authenticate(newToken());
+    }
+
+    /**
+     * Ensures a non-null <tt>Subject</tt> instance is returned from the authenticate() method after a valid
+     * authentication attempt (i.e. the subclass's doAuthenticate implementation returns a valid, non-null
+     * AuthenticationInfo object).
+     */
+    @Test
+    public void nonNullAuthenticationInfoAfterAuthenticate() {
+        AuthenticationInfo authcInfo = abstractAuthenticator.authenticate(newToken());
+        assertNotNull(authcInfo);
+    }
+
+    @Test
+    public void notifySuccessAfterDoAuthenticate() {
+        AuthenticationListener mockListener = createMock(AuthenticationListener.class);
+        abstractAuthenticator.getAuthenticationListeners().add(mockListener);
+        AuthenticationToken token = newToken();
+        mockListener.onSuccess(token, info);
+
+        replay(mockListener);
+        abstractAuthenticator.authenticate(token);
+        verify(mockListener);
+    }
+
+    @Test
+    public void notifyFailureAfterDoAuthenticateThrowsAuthenticationException() {
+        AuthenticationListener mockListener = createMock(AuthenticationListener.class);
+        AuthenticationToken token = newToken();
+
+        final AuthenticationException ae = new AuthenticationException("dummy exception to test notification");
+
+        abstractAuthenticator = new AbstractAuthenticator() {
+            protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
+                throw ae;
+            }
+        };
+        abstractAuthenticator.getAuthenticationListeners().add(mockListener);
+
+        mockListener.onFailure(token, ae);
+        replay(mockListener);
+
+        boolean exceptionThrown = false;
+        try {
+            abstractAuthenticator.authenticate(token);
+        } catch (AuthenticationException e) {
+            exceptionThrown = true;
+            assertEquals(e, ae);
+        }
+        verify(mockListener);
+
+        if (!exceptionThrown) {
+            fail("An AuthenticationException should have been thrown during the notifyFailure test case.");
+        }
+    }
+
+    @Test(expected = AuthenticationException.class)
+    public void notifyFailureAfterDoAuthenticateThrowsNonAuthenticationException() {
+        abstractAuthenticator = new AbstractAuthenticator() {
+            protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
+                throw new IllegalArgumentException("not an AuthenticationException subclass");
+            }
+        };
+        AuthenticationToken token = newToken();
+        abstractAuthenticator.authenticate(token);
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/ConcurrentAccessExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/ConcurrentAccessExceptionTest.java
new file mode 100644
index 0000000..28a45d9
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/ConcurrentAccessExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 29, 2008
+ * Time: 1:16:13 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class ConcurrentAccessExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return ConcurrentAccessException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/ExcessiveAttemptsExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/ExcessiveAttemptsExceptionTest.java
new file mode 100644
index 0000000..bfc5d9b
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/ExcessiveAttemptsExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 29, 2008
+ * Time: 1:35:54 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class ExcessiveAttemptsExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return ExcessiveAttemptsException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/ExpiredCredentialsExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/ExpiredCredentialsExceptionTest.java
new file mode 100644
index 0000000..0b2caa1
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/ExpiredCredentialsExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 29, 2008
+ * Time: 1:33:05 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class ExpiredCredentialsExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return ExpiredCredentialsException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/IncorrectCredentialsExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/IncorrectCredentialsExceptionTest.java
new file mode 100644
index 0000000..c35d8f6
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/IncorrectCredentialsExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 29, 2008
+ * Time: 1:34:02 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class IncorrectCredentialsExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return IncorrectCredentialsException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/LockedAccountExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/LockedAccountExceptionTest.java
new file mode 100644
index 0000000..fbfbaa4
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/LockedAccountExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 29, 2008
+ * Time: 1:35:02 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class LockedAccountExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return LockedAccountException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/SimpleAuthenticationInfoTest.java b/core/src/test/java/org/apache/shiro/authc/SimpleAuthenticationInfoTest.java
new file mode 100644
index 0000000..4790b32
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/SimpleAuthenticationInfoTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.shiro.subject.PrincipalCollection;
+import org.junit.Test;
+
+
+/**
+ * @since 0.9
+ */
+public class SimpleAuthenticationInfoTest {
+
+    @Test
+    public void testMergeWithEmptyInstances() {
+        SimpleAuthenticationInfo aggregate = new SimpleAuthenticationInfo();
+        SimpleAuthenticationInfo local = new SimpleAuthenticationInfo();
+        aggregate.merge(local);
+    }
+
+    /**
+     * Verifies fix for JSEC-122
+     */
+    @Test
+    public void testMergeWithAggregateNullCredentials() {
+        SimpleAuthenticationInfo aggregate = new SimpleAuthenticationInfo();
+        SimpleAuthenticationInfo local = new SimpleAuthenticationInfo("username", "password", "testRealm");
+        aggregate.merge(local);
+    }
+    
+    @SuppressWarnings("serial")
+    @Test
+    public void testMergeWithImmutablePrincipalCollection() {
+        SimpleAuthenticationInfo aggregate = new SimpleAuthenticationInfo();
+        // Make a quick test fixture that does *not* implement MutablePrincipalCollection 
+        PrincipalCollection principalCollection = new PrincipalCollection() {
+	    @SuppressWarnings("unchecked")
+	    public List asList() { return null;}
+	    @SuppressWarnings("unchecked")
+	    public Set asSet() {return null;}
+	    public <T> Collection<T> byType(Class<T> type) {return null;}
+	    @SuppressWarnings("unchecked")
+	    public Collection fromRealm(String realmName) {
+		Collection<Object> principals = new HashSet<Object>();
+		principals.add("testprincipal");
+		return principals;
+	    }
+	    public Object getPrimaryPrincipal() {return null;}
+	    public Set<String> getRealmNames() {
+		Set<String> realms = new HashSet<String>();
+		realms.add("testrealm");
+		return realms;
+	    }
+	    public boolean isEmpty() {return false;}
+	    public <T> T oneByType(Class<T> type) {return null;}
+	    @SuppressWarnings("unchecked")
+	    public Iterator iterator() {return null;}
+            
+        };
+        aggregate.setPrincipals(principalCollection);
+        SimpleAuthenticationInfo local = new SimpleAuthenticationInfo("username", "password", "testRealm");
+        aggregate.merge(local);
+        assertEquals(2, aggregate.getPrincipals().asList().size());
+    }
+    
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/UnknownAccountExceptionTest.java b/core/src/test/java/org/apache/shiro/authc/UnknownAccountExceptionTest.java
new file mode 100644
index 0000000..f00fede
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/UnknownAccountExceptionTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Mar 30, 2008
+ * Time: 2:55:33 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class UnknownAccountExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return UnknownAccountException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java
new file mode 100644
index 0000000..2507895
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import junit.framework.TestCase;
+import org.junit.Test;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.util.ClassUtils;
+
+/**
+ * @since Jun 10, 2008 4:47:09 PM
+ */
+public abstract class AbstractHashedCredentialsMatcherTest extends TestCase {
+
+    public abstract Class<? extends HashedCredentialsMatcher> getMatcherClass();
+
+    public abstract AbstractHash hash(Object credentials);
+
+    @Test
+    public void testBasic() {
+        CredentialsMatcher matcher = (CredentialsMatcher) ClassUtils.newInstance(getMatcherClass());
+        byte[] hashed = hash("password").getBytes();
+        AuthenticationInfo account = new SimpleAuthenticationInfo("username", hashed, "realmName");
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcherTest.java
new file mode 100644
index 0000000..74aa56f
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/AllowAllCredentialsMatcherTest.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+
+/**
+ * @since Jun 10, 2008 4:35:27 PM
+ */
+public class AllowAllCredentialsMatcherTest {
+
+    @Test
+    public void testBasic() {
+        assertTrue(new AllowAllCredentialsMatcher().doCredentialsMatch(null, null));
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java
new file mode 100644
index 0000000..caf93a0
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.Sha1Hash;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link org.apache.shiro.authc.credential.HashedCredentialsMatcher} class.
+ */
+public class HashedCredentialsMatcherTest {
+
+    /**
+     * Test new Shiro 1.1 functionality, where the salt is obtained from the stored account information, as it
+     * should be.  See <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a>
+     */
+    @Test
+    public void testSaltedAuthenticationInfo() {
+        //use SHA-1 hashing in this test:
+        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(Sha1Hash.ALGORITHM_NAME);
+
+        //simulate a user account with a SHA-1 hashed and salted password:
+        ByteSource salt = new SecureRandomNumberGenerator().nextBytes();
+        Object hashedPassword = new Sha1Hash("password", salt);
+        SimpleAuthenticationInfo account = new SimpleAuthenticationInfo("username", hashedPassword, salt, "realmName");
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+
+    /**
+     * Test backwards compatibility of unsalted credentials before
+     * <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a> edits.
+     */
+    @Test
+    public void testBackwardsCompatibleUnsaltedAuthenticationInfo() {
+        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(Sha1Hash.ALGORITHM_NAME);
+
+        //simulate an account with SHA-1 hashed password (no salt)
+        final String username = "username";
+        final String password = "password";
+        final Object hashedPassword = new Sha1Hash(password).getBytes();
+        AuthenticationInfo account = new AuthenticationInfo() {
+            public PrincipalCollection getPrincipals() {
+                return new SimplePrincipalCollection(username, "realmName");
+            }
+
+            public Object getCredentials() {
+                return hashedPassword;
+            }
+        };
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+
+    /**
+     * Test backwards compatibility of salted credentials before
+     * <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a> edits.
+     */
+    @Test
+    public void testBackwardsCompatibleSaltedAuthenticationInfo() {
+        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(Sha1Hash.ALGORITHM_NAME);
+        //enable this for Shiro 1.0 backwards compatibility:
+        matcher.setHashSalted(true);
+
+        //simulate an account with SHA-1 hashed password, using the username as the salt
+        //(BAD IDEA, but backwards-compatible):
+        final String username = "username";
+        final String password = "password";
+        final Object hashedPassword = new Sha1Hash(password, username).getBytes();
+        AuthenticationInfo account = new AuthenticationInfo() {
+            public PrincipalCollection getPrincipals() {
+                return new SimplePrincipalCollection(username, "realmName");
+            }
+
+            public Object getCredentials() {
+                return hashedPassword;
+            }
+        };
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java
new file mode 100644
index 0000000..5286a58
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Md2Hash;
+
+
+/**
+ * @since Jun 10, 2008 4:38:16 PM
+ */
+public class Md2CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Md2CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Md2Hash(credentials);
+    }
+}
+
+
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java
new file mode 100644
index 0000000..4c9d71d
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Md5Hash;
+
+
+/**
+ * @since Jun 10, 2008 4:59:36 PM
+ */
+public class Md5CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Md5CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Md5Hash(credentials);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java
new file mode 100644
index 0000000..29d6283
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Sha1Hash;
+
+
+/**
+ * @since Jun 10, 2008 5:00:30 PM
+ */
+public class Sha1CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Sha1CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Sha1Hash(credentials);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java
new file mode 100644
index 0000000..5e4c5cd
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Sha256Hash;
+
+
+/**
+ * @since Jun 10, 2008 5:01:00 PM
+ */
+public class Sha256CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Sha256CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Sha256Hash(credentials);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java
new file mode 100644
index 0000000..e3cc6ce
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Sha384Hash;
+
+
+/**
+ * @since Jun 10, 2008 5:02:27 PM
+ */
+public class Sha384CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Sha384CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Sha384Hash(credentials);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java b/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java
new file mode 100644
index 0000000..3da3eba
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.credential;
+
+import org.apache.shiro.crypto.hash.AbstractHash;
+import org.apache.shiro.crypto.hash.Sha512Hash;
+
+
+/**
+ * @since Jun 10, 2008 5:02:58 PM
+ */
+public class Sha512CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
+
+    public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
+        return Sha512CredentialsMatcher.class;
+    }
+
+    public AbstractHash hash(Object credentials) {
+        return new Sha512Hash(credentials);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authc/pam/AllSuccessfulStrategyTest.java b/core/src/test/java/org/apache/shiro/authc/pam/AllSuccessfulStrategyTest.java
new file mode 100644
index 0000000..6417b5a
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authc/pam/AllSuccessfulStrategyTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authc.pam;
+
+import static org.junit.Assert.assertNotNull;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.SimpleAccountRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+
+public class AllSuccessfulStrategyTest {
+
+    private AllSuccessfulStrategy strategy;
+
+    @Before
+    public void setUp() {
+        strategy = new AllSuccessfulStrategy();
+    }
+
+    @Test
+    public void beforeAllAttempts() {
+        AuthenticationInfo info = strategy.beforeAllAttempts(null, null);
+        assertNotNull(info);
+    }
+
+    @Test
+    public void beforeAttemptSupportingToken() {
+        new SimpleAccountRealm();
+    }
+
+    @Test(expected = UnsupportedTokenException.class)
+    public void beforeAttemptRealmDoesntSupportToken() {
+        Realm notSupportingRealm = new AuthorizingRealm() {
+
+            public boolean supports(AuthenticationToken token) {
+                return false;
+            }
+
+            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+                return null;
+            }
+
+            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
+                return null;
+            }
+
+        };
+
+        strategy.beforeAttempt(notSupportingRealm, null, null);
+    }
+
+
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/AuthorizationExceptionTest.java b/core/src/test/java/org/apache/shiro/authz/AuthorizationExceptionTest.java
new file mode 100644
index 0000000..bcc487a
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/AuthorizationExceptionTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * @since Jun 10, 2008 4:03:56 PM
+ */
+public class AuthorizationExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return AuthorizationException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/HostUnauthorizedExceptionTest.java b/core/src/test/java/org/apache/shiro/authz/HostUnauthorizedExceptionTest.java
new file mode 100644
index 0000000..06876e4
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/HostUnauthorizedExceptionTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * @since Jun 10, 2008 4:04:38 PM
+ */
+public class HostUnauthorizedExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return HostUnauthorizedException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java b/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java
new file mode 100644
index 0000000..edd046b
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import junit.framework.Assert;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.permission.RolePermissionResolver;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.junit.Test;
+
+public class ModularRealmAuthorizerTest
+{
+    
+    @Test
+    public void testSettingOfRolePermissionResolver()
+    {
+        Collection<Realm> realms = new ArrayList<Realm>();
+        
+        realms.add( new MockAuthorizingRealm() );
+        realms.add( new MockAuthorizingRealm() );
+        
+        // its null to start with
+        for ( Realm realm : realms )
+        {
+            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
+        }
+        
+        ModularRealmAuthorizer modRealmAuthz = new ModularRealmAuthorizer();
+        modRealmAuthz.setRealms( realms );
+        
+        // make sure they are still null
+        for ( Realm realm : realms )
+        {
+            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
+        }
+        
+        // now set the RolePermissionResolver
+        RolePermissionResolver rolePermissionResolver = new RolePermissionResolver()
+        {   
+            public Collection<Permission> resolvePermissionsInRole( String roleString )
+            {
+                return null;
+            }
+        };
+        modRealmAuthz.setRolePermissionResolver( rolePermissionResolver );
+     
+        // make sure they are set
+        for ( Realm realm : realms )
+        {
+            // check for same instance
+            Assert.assertTrue( ((AuthorizingRealm)realm).getRolePermissionResolver() == rolePermissionResolver );
+        }
+        
+        // add a new realm and make sure the RolePermissionResolver is set
+        MockAuthorizingRealm mockRealm = new MockAuthorizingRealm();
+        realms.add( mockRealm );
+        modRealmAuthz.setRealms( realms );
+        assertTrue( ((AuthorizingRealm) mockRealm).getRolePermissionResolver() == rolePermissionResolver );
+        
+        
+        // TODO: no way to unset them, not sure if that is a valid use case, but this is conistent with the PermissionResolver logic
+//        // now just to be sure, unset them
+//        modRealmAuthz.setRolePermissionResolver( null );
+//        for ( Realm realm : realms )
+//        {
+//            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
+//        }
+        
+        
+    }
+    
+    class MockAuthorizingRealm extends AuthorizingRealm
+    {
+
+        @Override
+        protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals )
+        {
+            return null;
+        }
+
+        @Override
+        protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token )
+            throws AuthenticationException
+        {
+            return null;
+        }
+        
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/UnauthenticatedExceptionTest.java b/core/src/test/java/org/apache/shiro/authz/UnauthenticatedExceptionTest.java
new file mode 100644
index 0000000..e47fb10
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/UnauthenticatedExceptionTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * @since Jun 10, 2008 4:06:15 PM
+ */
+public class UnauthenticatedExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return UnauthenticatedException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/UnauthorizedExceptionTest.java b/core/src/test/java/org/apache/shiro/authz/UnauthorizedExceptionTest.java
new file mode 100644
index 0000000..c45bb33
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/UnauthorizedExceptionTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ * @since Jun 10, 2008 4:06:45 PM
+ */
+public class UnauthorizedExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return UnauthorizedException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/PermissionAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/PermissionAnnotationHandlerTest.java
new file mode 100644
index 0000000..84c70c6
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/PermissionAnnotationHandlerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Test cases for the {@link PermissionAnnotationHandler} class.
+ */
+public class PermissionAnnotationHandlerTest extends SecurityManagerTestSupport {
+
+    //Added to satisfy SHIRO-146
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestSinglePermissionAssertion() throws Throwable {
+        PermissionAnnotationHandler handler = new PermissionAnnotationHandler();
+
+        Annotation requiresPermissionAnnotation = new RequiresPermissions() {
+            public String[] value() {
+                return new String[]{"test:test"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RequiresPermissions.class;
+            }
+
+	    public Logical logical() {
+		return Logical.AND;
+	    }
+        };
+
+        handler.assertAuthorized(requiresPermissionAnnotation);
+    }
+
+    //Added to satisfy SHIRO-146
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestMultiplePermissionAssertion() throws Throwable {
+        PermissionAnnotationHandler handler = new PermissionAnnotationHandler();
+
+        Annotation requiresPermissionAnnotation = new RequiresPermissions() {
+            public String[] value() {
+                return new String[]{"test:test", "test2:test2"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RequiresPermissions.class;
+            }
+            
+	    public Logical logical() {
+		return Logical.AND;
+	    }
+        };
+
+        handler.assertAuthorized(requiresPermissionAnnotation);
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/RoleAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/RoleAnnotationHandlerTest.java
new file mode 100644
index 0000000..d6f831d
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/RoleAnnotationHandlerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.aop;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Test cases for the {@link RoleAnnotationHandler} class.
+ */
+public class RoleAnnotationHandlerTest extends SecurityManagerTestSupport {
+    private Subject subject;
+
+    //Added to satisfy SHIRO-146
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestSingleRoleAssertion() throws Throwable {
+        RoleAnnotationHandler handler = new RoleAnnotationHandler();
+
+        Annotation requiresRolesAnnotation = new RequiresRoles() {
+            public String[] value() {
+                return new String[]{"blah"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RequiresRoles.class;
+            }
+	    public Logical logical() {
+		return Logical.AND;
+	    }
+        };
+
+        handler.assertAuthorized(requiresRolesAnnotation);
+    }
+
+    //Added to satisfy SHIRO-146
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestMultipleRolesAssertion() throws Throwable {
+        RoleAnnotationHandler handler = new RoleAnnotationHandler();
+
+        Annotation requiresRolesAnnotation = new RequiresRoles() {
+            public String[] value() {
+                return new String[]{"blah", "blah2"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RequiresRoles.class;
+            }
+	    public Logical logical() {
+		return Logical.AND;
+	    }
+        };
+
+        handler.assertAuthorized(requiresRolesAnnotation);
+    }
+    
+    @Test
+    public void testOneOfTheRolesRequired() throws Throwable {
+	subject = createMock(Subject.class);
+	expect(subject.hasRole("blah")).andReturn(true);
+	expect(subject.hasRole("blah2")).andReturn(false);
+        replay(subject);
+	RoleAnnotationHandler handler = new RoleAnnotationHandler() {
+            @Override
+	    protected Subject getSubject() {
+        	return subject;
+            }
+        };
+
+        Annotation requiresRolesAnnotation = new RequiresRoles() {
+            public String[] value() {
+                return new String[]{"blah", "blah2"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RequiresRoles.class;
+            }
+	    public Logical logical() {
+		return Logical.OR;
+	    }
+        };
+        handler.assertAuthorized(requiresRolesAnnotation);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/permission/AllPermissionTest.java b/core/src/test/java/org/apache/shiro/authz/permission/AllPermissionTest.java
new file mode 100644
index 0000000..044c0cc
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/permission/AllPermissionTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+
+/**
+ */
+public class AllPermissionTest {
+
+    @Test
+    public void testNullArgument() {
+        assertTrue(new AllPermission().implies(null));
+    }
+
+    @Test
+    public void testNonNullArgument() {
+        assertTrue(new AllPermission().implies(new AllPermission()));
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/permission/DomainPermissionTest.java b/core/src/test/java/org/apache/shiro/authz/permission/DomainPermissionTest.java
new file mode 100644
index 0000000..fad0929
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/permission/DomainPermissionTest.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+public class DomainPermissionTest {
+    @Test
+    public void testDefaultConstructor() {
+        DomainPermission p;
+        List<Set<String>> parts;
+        Set<String> set;
+        String entry;
+
+        // No arg constructor
+        p = new DomainPermission();
+
+        // Verify domain
+        assertTrue("domain".equals(p.getDomain()));
+
+        // Verify actions
+        set = p.getActions();
+        assertNull(set);
+
+        // Verify targets
+        set = p.getTargets();
+        assertNull(set);
+
+        // Verify parts
+        parts = p.getParts();
+        assertEquals("Number of parts", 1, parts.size());
+        set = parts.get(0);
+        assertEquals(1, set.size());
+        entry = set.iterator().next();
+        assertEquals("domain", entry);
+    }
+
+    @Test
+    public void testActionsConstructorWithSingleAction() {
+        DomainPermission p;
+        List<Set<String>> parts;
+        Set<String> set;
+        Iterator<String> iterator;
+        String entry;
+
+        // Actions constructor with a single action
+        p = new DomainPermission("action1");
+
+        // Verify domain
+        assertEquals("domain", p.getDomain());
+
+        // Verify actions
+        set = p.getActions();
+        assertNotNull(set);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+
+        // Verify targets
+        set = p.getTargets();
+        assertNull(set);
+
+        // Verify parts
+        parts = p.getParts();
+        assertEquals(2, parts.size());
+        set = parts.get(0);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("domain", entry);
+        set = parts.get(1);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+    }
+
+    @Test
+    public void testActionsConstructorWithMultipleActions() {
+        DomainPermission p;
+        List<Set<String>> parts;
+        Set<String> set;
+        Iterator<String> iterator;
+        String entry;
+
+        // Actions constructor with three actions
+        p = new DomainPermission("action1,action2,action3");
+
+        // Verify domain
+        assertEquals("domain", p.getDomain());
+
+        // Verify actions
+        set = p.getActions();
+        assertNotNull(set);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+        entry = iterator.next();
+        assertEquals("action2", entry);
+        entry = iterator.next();
+        assertEquals("action3", entry);
+
+        // Verify targets
+        set = p.getTargets();
+        assertNull(set);
+
+        // Verify parts
+        parts = p.getParts();
+        assertEquals(2, parts.size());
+        set = parts.get(0);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("domain", entry);
+        set = parts.get(1);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+        entry = iterator.next();
+        assertEquals("action2", entry);
+        entry = iterator.next();
+        assertEquals("action3", entry);
+    }
+
+    @Test
+    public void testActionsTargetsConstructorWithSingleActionAndTarget() {
+        DomainPermission p;
+        List<Set<String>> parts;
+        Set<String> set;
+        Iterator<String> iterator;
+        String entry;
+
+        // Actions and target constructor with a single action and target
+        p = new DomainPermission("action1", "target1");
+
+        // Verify domain
+        assertEquals("domain", p.getDomain());
+
+        // Verify actions
+        set = p.getActions();
+        assertNotNull(set);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+
+        // Verify targets
+        set = p.getTargets();
+        assertNotNull(set);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("target1", entry);
+
+        // Verify parts
+        parts = p.getParts();
+        assertEquals(3, parts.size());
+        set = parts.get(0);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("domain", entry);
+        set = parts.get(1);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+        set = parts.get(2);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("target1", entry);
+    }
+
+    @Test
+    public void testActionsTargetsConstructorWithMultipleActionsAndTargets() {
+        DomainPermission p;
+        List<Set<String>> parts;
+        Set<String> set;
+        Iterator<String> iterator;
+        String entry;
+
+        // Actions and target constructor with a single action and target
+        p = new DomainPermission("action1,action2,action3", "target1,target2,target3");
+
+        // Verify domain
+        assertEquals("domain", p.getDomain());
+
+        // Verify actions
+        set = p.getActions();
+        assertNotNull(set);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+        entry = iterator.next();
+        assertEquals("action2", entry);
+        entry = iterator.next();
+        assertEquals("action3", entry);
+
+        // Verify targets
+        set = p.getTargets();
+        assertNotNull(set);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("target1", entry);
+        entry = iterator.next();
+        assertEquals("target2", entry);
+        entry = iterator.next();
+        assertEquals("target3", entry);
+
+        // Verify parts
+        parts = p.getParts();
+        assertEquals(3, parts.size());
+        set = parts.get(0);
+        assertEquals(1, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("domain", entry);
+        set = parts.get(1);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("action1", entry);
+        entry = iterator.next();
+        assertEquals("action2", entry);
+        entry = iterator.next();
+        assertEquals("action3", entry);
+        set = parts.get(2);
+        assertEquals(3, set.size());
+        iterator = set.iterator();
+        entry = iterator.next();
+        assertEquals("target1", entry);
+        entry = iterator.next();
+        assertEquals("target2", entry);
+        entry = iterator.next();
+        assertEquals("target3", entry);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/permission/WildcardPermissionTest.java b/core/src/test/java/org/apache/shiro/authz/permission/WildcardPermissionTest.java
new file mode 100644
index 0000000..56cea74
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/permission/WildcardPermissionTest.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.authz.permission;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+
+/**
+ * @since 0.9
+ */
+public class WildcardPermissionTest {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNull() {
+        new WildcardPermission(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEmpty() {
+        new WildcardPermission("");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBlank() {
+        new WildcardPermission("   ");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOnlyDelimiters() {
+        new WildcardPermission("::,,::,:");
+    }
+
+    @Test
+    public void testNamed() {
+        WildcardPermission p1, p2;
+
+        // Case insensitive, same
+        p1 = new WildcardPermission("something");
+        p2 = new WildcardPermission("something");
+        assertTrue(p1.implies(p2));
+        assertTrue(p2.implies(p1));
+
+        // Case insensitive, different case
+        p1 = new WildcardPermission("something");
+        p2 = new WildcardPermission("SOMETHING");
+        assertTrue(p1.implies(p2));
+        assertTrue(p2.implies(p1));
+
+        // Case insensitive, different word
+        p1 = new WildcardPermission("something");
+        p2 = new WildcardPermission("else");
+        assertFalse(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+
+        // Case sensitive same
+        p1 = new WildcardPermission("BLAHBLAH", false);
+        p2 = new WildcardPermission("BLAHBLAH", false);
+        assertTrue(p1.implies(p2));
+        assertTrue(p2.implies(p1));
+
+        // Case sensitive, different case
+        p1 = new WildcardPermission("BLAHBLAH", false);
+        p2 = new WildcardPermission("bLAHBLAH", false);
+        assertTrue(p1.implies(p2));
+        assertTrue(p2.implies(p1));
+
+        // Case sensitive, different word
+        p1 = new WildcardPermission("BLAHBLAH", false);
+        p2 = new WildcardPermission("whatwhat", false);
+        assertFalse(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+
+    }
+
+    @Test
+    public void testLists() {
+        WildcardPermission p1, p2, p3;
+
+        p1 = new WildcardPermission("one,two");
+        p2 = new WildcardPermission("one");
+        assertTrue(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+
+        p1 = new WildcardPermission("one,two,three");
+        p2 = new WildcardPermission("one,three");
+        assertTrue(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+
+        p1 = new WildcardPermission("one,two:one,two,three");
+        p2 = new WildcardPermission("one:three");
+        p3 = new WildcardPermission("one:two,three");
+        assertTrue(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+        assertTrue(p1.implies(p3));
+        assertFalse(p2.implies(p3));
+        assertTrue(p3.implies(p2));
+
+        p1 = new WildcardPermission("one,two,three:one,two,three:one,two");
+        p2 = new WildcardPermission("one:three:two");
+        assertTrue(p1.implies(p2));
+        assertFalse(p2.implies(p1));
+
+        p1 = new WildcardPermission("one");
+        p2 = new WildcardPermission("one:two,three,four");
+        p3 = new WildcardPermission("one:two,three,four:five:six:seven");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p3));
+        assertFalse(p2.implies(p1));
+        assertFalse(p3.implies(p1));
+        assertTrue(p2.implies(p3));
+
+    }
+
+    @Test
+    public void testWildcards() {
+        WildcardPermission p1, p2, p3, p4, p5, p6, p7, p8;
+
+        p1 = new WildcardPermission("*");
+        p2 = new WildcardPermission("one");
+        p3 = new WildcardPermission("one:two");
+        p4 = new WildcardPermission("one,two:three,four");
+        p5 = new WildcardPermission("one,two:three,four,five:six:seven,eight");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p3));
+        assertTrue(p1.implies(p4));
+        assertTrue(p1.implies(p5));
+
+        p1 = new WildcardPermission("newsletter:*");
+        p2 = new WildcardPermission("newsletter:read");
+        p3 = new WildcardPermission("newsletter:read,write");
+        p4 = new WildcardPermission("newsletter:*");
+        p5 = new WildcardPermission("newsletter:*:*");
+        p6 = new WildcardPermission("newsletter:*:read");
+        p7 = new WildcardPermission("newsletter:write:*");
+        p8 = new WildcardPermission("newsletter:read,write:*");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p3));
+        assertTrue(p1.implies(p4));
+        assertTrue(p1.implies(p5));
+        assertTrue(p1.implies(p6));
+        assertTrue(p1.implies(p7));
+        assertTrue(p1.implies(p8));
+
+
+        p1 = new WildcardPermission("newsletter:*:*");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p3));
+        assertTrue(p1.implies(p4));
+        assertTrue(p1.implies(p5));
+        assertTrue(p1.implies(p6));
+        assertTrue(p1.implies(p7));
+        assertTrue(p1.implies(p8));
+
+        p1 = new WildcardPermission("newsletter:*:*:*");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p3));
+        assertTrue(p1.implies(p4));
+        assertTrue(p1.implies(p5));
+        assertTrue(p1.implies(p6));
+        assertTrue(p1.implies(p7));
+        assertTrue(p1.implies(p8));
+
+        p1 = new WildcardPermission("newsletter:*:read");
+        p2 = new WildcardPermission("newsletter:123:read");
+        p3 = new WildcardPermission("newsletter:123,456:read,write");
+        p4 = new WildcardPermission("newsletter:read");
+        p5 = new WildcardPermission("newsletter:read,write");
+        p6 = new WildcardPermission("newsletter:123:read:write");
+        assertTrue(p1.implies(p2));
+        assertFalse(p1.implies(p3));
+        assertFalse(p1.implies(p4));
+        assertFalse(p1.implies(p5));
+        assertTrue(p1.implies(p6));
+
+        p1 = new WildcardPermission("newsletter:*:read:*");
+        assertTrue(p1.implies(p2));
+        assertTrue(p1.implies(p6));
+
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorServiceTest.java b/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorServiceTest.java
new file mode 100644
index 0000000..7d6a025
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorServiceTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.concurrent;
+
+import org.apache.shiro.subject.support.SubjectRunnable;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import java.util.concurrent.*;
+
+import static org.easymock.EasyMock.*;
+
+/**
+ * Test cases for the {@link SubjectAwareExecutorService} implementation.
+ */
+public class SubjectAwareExecutorServiceTest extends SecurityManagerTestSupport {
+
+    @SuppressWarnings({"unchecked"})
+    @Test
+    public void testSubmitRunnable() {
+        ExecutorService mockExecutorService = createNiceMock(ExecutorService.class);
+        expect(mockExecutorService.submit(isA(SubjectRunnable.class))).andReturn(new DummyFuture());
+        replay(mockExecutorService);
+
+        final SubjectAwareExecutorService executor = new SubjectAwareExecutorService(mockExecutorService);
+
+        Runnable testRunnable = new Runnable() {
+            public void run() {
+                System.out.println("Hello World");
+            }
+        };
+
+        executor.submit(testRunnable);
+        verify(mockExecutorService);
+    }
+
+    private class DummyFuture<V> implements Future<V> {
+
+        public boolean cancel(boolean b) {
+            return false;
+        }
+
+        public boolean isCancelled() {
+            return false;
+        }
+
+        public boolean isDone() {
+            return true;
+        }
+
+        public V get() throws InterruptedException, ExecutionException {
+            return null;
+        }
+
+        public V get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
+            return null;
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorTest.java b/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorTest.java
new file mode 100644
index 0000000..29d0148
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/concurrent/SubjectAwareExecutorTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.concurrent;
+
+import org.apache.shiro.subject.support.SubjectRunnable;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import java.util.concurrent.Executor;
+
+import static org.easymock.EasyMock.*;
+
+/**
+ * Test cases for the {@link SubjectAwareExecutor} implementation.
+ *
+ * @since 1.0
+ */
+public class SubjectAwareExecutorTest extends SecurityManagerTestSupport {
+
+    @Test
+    public void testExecute() {
+        Executor targetMockExecutor = createNiceMock(Executor.class);
+        //* ensure the target Executor receives a SubjectRunnable instance that retains the subject identity:
+        //(this is what verifies the test is valid):
+        targetMockExecutor.execute(isA(SubjectRunnable.class));
+        replay(targetMockExecutor);
+
+        final SubjectAwareExecutor executor = new SubjectAwareExecutor(targetMockExecutor);
+
+        Runnable work = new Runnable() {
+            public void run() {
+                System.out.println("Hello World");
+            }
+        };
+        executor.execute(work);
+
+        verify(targetMockExecutor);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/CompositeBean.java b/core/src/test/java/org/apache/shiro/config/CompositeBean.java
new file mode 100644
index 0000000..6c1be50
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/CompositeBean.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @since Aug 5, 2008 10:17:37 AM
+ */
+ at SuppressWarnings({"UnusedDeclaration"})
+public class CompositeBean {
+    
+    private String name;
+
+    private String stringProp;
+    private boolean booleanProp;
+    private int intProp;
+    private SimpleBean simpleBean;
+
+    private Set<SimpleBean> simpleBeanSet;
+    private List<SimpleBean> simpleBeanList;
+    private Collection<SimpleBean> simpleBeanCollection;
+    private Map<String, SimpleBean> simpleBeanMap;
+    private Map<String, CompositeBean> compositeBeanMap;
+    private CompositeBean[] compositeBeanArray;
+
+    public CompositeBean() {
+    }
+    
+    public CompositeBean(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getStringProp() {
+        return stringProp;
+    }
+
+    public void setStringProp(String stringProp) {
+        this.stringProp = stringProp;
+    }
+
+    public boolean isBooleanProp() {
+        return booleanProp;
+    }
+
+    public void setBooleanProp(boolean booleanProp) {
+        this.booleanProp = booleanProp;
+    }
+
+    public int getIntProp() {
+        return intProp;
+    }
+
+    public void setIntProp(int intProp) {
+        this.intProp = intProp;
+    }
+
+    public SimpleBean getSimpleBean() {
+        return simpleBean;
+    }
+
+    public void setSimpleBean(SimpleBean simpleBean) {
+        this.simpleBean = simpleBean;
+    }
+
+    public Set<SimpleBean> getSimpleBeanSet() {
+        return simpleBeanSet;
+    }
+
+    public void setSimpleBeanSet(Set<SimpleBean> simpleBeanSet) {
+        this.simpleBeanSet = simpleBeanSet;
+    }
+
+    public List<SimpleBean> getSimpleBeanList() {
+        return simpleBeanList;
+    }
+
+    public void setSimpleBeanList(List<SimpleBean> simpleBeanList) {
+        this.simpleBeanList = simpleBeanList;
+    }
+
+    public Collection<SimpleBean> getSimpleBeanCollection() {
+        return simpleBeanCollection;
+    }
+
+    public void setSimpleBeanCollection(Collection<SimpleBean> simpleBeanCollection) {
+        this.simpleBeanCollection = simpleBeanCollection;
+    }
+
+    public Map<String, SimpleBean> getSimpleBeanMap() {
+        return simpleBeanMap;
+    }
+
+    public void setSimpleBeanMap(Map<String, SimpleBean> simpleBeanMap) {
+        this.simpleBeanMap = simpleBeanMap;
+    }
+
+    public Map<String, CompositeBean> getCompositeBeanMap() {
+        return compositeBeanMap;
+    }
+
+    public void setCompositeBeanMap(Map<String, CompositeBean> compositeBeanMap) {
+        this.compositeBeanMap = compositeBeanMap;
+    }
+
+    public CompositeBean[] getCompositeBeanArray() {
+        return compositeBeanArray;
+    }
+
+    public void setCompositeBeanArray(CompositeBean[] compositeBeanArray) {
+        this.compositeBeanArray = compositeBeanArray;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/HashMapCacheManager.java b/core/src/test/java/org/apache/shiro/config/HashMapCacheManager.java
new file mode 100644
index 0000000..c1c0895
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/HashMapCacheManager.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.cache.AbstractCacheManager;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.MapCache;
+
+import java.util.HashMap;
+
+/**
+ * Returns HashMap-backed cache instances for testing only.  NEVER use this in production, as it would cause
+ * memory leaks since HashMaps retain strong references.
+ *
+ * @since 1.0
+ */
+public class HashMapCacheManager<K, V> extends AbstractCacheManager {
+
+    @Override
+    protected Cache createCache(String name) throws CacheException {
+        return new HashMapCache<K, V>(name);
+    }
+
+    //This class is not strictly necessary - it exists to verify a test case only.
+
+    public class HashMapCache<K, V> extends MapCache<K, V> {
+        public HashMapCache(String name) {
+            super(name, new HashMap<K, V>());
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/IniTest.java b/core/src/test/java/org/apache/shiro/config/IniTest.java
new file mode 100644
index 0000000..36fd17c
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/IniTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.util.Scanner;
+
+/**
+ * Unit test for the {@link Ini} class.
+ *
+ * @since 1.0
+ */
+public class IniTest {
+
+    private static final String NL = "\n";
+
+    @Test
+    public void testNoSections() {
+        String test =
+                "prop1 = value1" + NL +
+                        "prop2 = value2";
+
+        Ini ini = new Ini();
+        ini.load(test);
+
+        assertNotNull(ini.getSections());
+        assertEquals(1, ini.getSections().size());
+
+        Ini.Section section = ini.getSections().iterator().next();
+        assertEquals(Ini.DEFAULT_SECTION_NAME, section.getName());
+        assertFalse(section.isEmpty());
+        assertEquals(2, section.size());
+        assertEquals("value1", section.get("prop1"));
+        assertEquals("value2", section.get("prop2"));
+    }
+
+    @Test
+    public void testIsContinued() {
+        //no slashes
+        String line = "prop = value ";
+        assertFalse(Ini.Section.isContinued(line));
+
+        //1 slash (odd number, but edge case):
+        line = "prop = value" + Ini.ESCAPE_TOKEN;
+        assertTrue(Ini.Section.isContinued(line));
+
+        //2 slashes = even number
+        line = "prop = value" + Ini.ESCAPE_TOKEN + Ini.ESCAPE_TOKEN;
+        assertFalse(Ini.Section.isContinued(line));
+
+        //3 slashes = odd number
+        line = "prop = value" + Ini.ESCAPE_TOKEN + Ini.ESCAPE_TOKEN + Ini.ESCAPE_TOKEN;
+        assertTrue(Ini.Section.isContinued(line));
+    }
+
+    @Test
+    public void testSplitKeyValue() {
+        String test = "Truth Beauty";
+        String[] kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth=Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth:Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth = Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth:  Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth  :Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth:Beauty        ";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "    Truth:Beauty    ";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+
+        test = "Truth        =Beauty";
+        kv = Ini.Section.splitKeyValue(test);
+        assertEquals("Truth", kv[0]);
+        assertEquals("Beauty", kv[1]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSplitKeyValueNoValue() {
+        String test = "  Truth  ";
+        Ini.Section.splitKeyValue(test);
+    }
+
+    @Test
+    public void testOneSection() {
+        String sectionName = "main";
+        String test = NL +
+                "" + NL +
+                "  " + NL +
+                "    #  comment1 " + NL +
+                " ; comment 2" + NL +
+                "[" + sectionName + "]" + NL +
+                "prop1 = value1" + NL +
+                "  " + NL +
+                "; comment " + NL +
+                "prop2   value2" + NL +
+                "prop3:value3" + NL +
+                "prop4 : value 4" + NL +
+                "prop5 some long \\" + NL +
+                "      value " + NL +
+                "# comment.";
+
+        Ini ini = new Ini();
+        ini.load(new Scanner(test));
+
+        assertNotNull(ini.getSections());
+        assertEquals(1, ini.getSections().size());
+        Ini.Section section = ini.getSection("main");
+        assertNotNull(section);
+        assertEquals(sectionName, section.getName());
+        assertFalse(section.isEmpty());
+        assertEquals(5, section.size());
+        assertEquals("value1", section.get("prop1"));
+        assertEquals("value2", section.get("prop2"));
+        assertEquals("value3", section.get("prop3"));
+        assertEquals("value 4", section.get("prop4"));
+        assertEquals("some long value", section.get("prop5"));
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/InitializableBean.java b/core/src/test/java/org/apache/shiro/config/InitializableBean.java
new file mode 100644
index 0000000..6046f79
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/InitializableBean.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.ShiroException;
+import org.apache.shiro.util.Initializable;
+
+/**
+ * @since 1.2.2
+ */
+public class InitializableBean implements Initializable {
+
+    private volatile boolean initialized = false;
+
+    public void init() throws ShiroException {
+        initialized = true;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/SimpleBean.java b/core/src/test/java/org/apache/shiro/config/SimpleBean.java
new file mode 100644
index 0000000..03a3094
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/SimpleBean.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import java.util.List;
+
+/**
+ * @since 1.0
+ */
+public class SimpleBean {
+    
+    private String name;
+
+    private String stringProp = null;
+    private int intProp;
+    private byte[] byteArrayProp = null;
+
+    private List<SimpleBean> simpleBeans;
+
+    public SimpleBean() {
+    }
+    
+    public SimpleBean(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getStringProp() {
+        return stringProp;
+    }
+
+    public void setStringProp(String stringProp) {
+        this.stringProp = stringProp;
+    }
+
+    public int getIntProp() {
+        return intProp;
+    }
+
+    public void setIntProp(int intProp) {
+        this.intProp = intProp;
+    }
+
+    public byte[] getByteArrayProp() {
+        return byteArrayProp;
+    }
+
+    public void setByteArrayProp(byte[] byteArrayProp) {
+        this.byteArrayProp = byteArrayProp;
+    }
+
+    public List<SimpleBean> getSimpleBeans() {
+        return simpleBeans;
+    }
+
+    public void setSimpleBeans(List<SimpleBean> simpleBeans) {
+        this.simpleBeans = simpleBeans;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/config/SimpleBeanFactory.java b/core/src/test/java/org/apache/shiro/config/SimpleBeanFactory.java
new file mode 100644
index 0000000..b295138
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/config/SimpleBeanFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.config;
+
+import org.apache.shiro.util.Factory;
+
+public class SimpleBeanFactory implements Factory<SimpleBean> {
+    private int factoryInt;
+    private String factoryString;
+
+    public SimpleBean getInstance() {
+        final SimpleBean simpleBean = new SimpleBean();
+        simpleBean.setIntProp(factoryInt);
+        simpleBean.setStringProp(factoryString);
+        return simpleBean;
+    }
+
+    public int getFactoryInt() {
+        return factoryInt;
+    }
+
+    public void setFactoryInt(int factoryInt) {
+        this.factoryInt = factoryInt;
+    }
+
+    public String getFactoryString() {
+        return factoryString;
+    }
+
+    public void setFactoryString(String factoryString) {
+        this.factoryString = factoryString;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/crypto/AesCipherServiceTest.java b/core/src/test/java/org/apache/shiro/crypto/AesCipherServiceTest.java
new file mode 100644
index 0000000..319755f
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/crypto/AesCipherServiceTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.codec.CodecSupport;
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * Test class for the AesCipherService class.
+ *
+ * @since 1.0
+ */
+public class AesCipherServiceTest {
+
+    private static final String[] PLAINTEXTS = new String[]{
+            "Hello, this is a test.",
+            "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
+    };
+
+    @Test
+    public void testBlockOperations() {
+        AesCipherService aes = new AesCipherService();
+
+        byte[] key = aes.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            ByteSource ciphertext = aes.encrypt(plaintext, key);
+            ByteSource decrypted = aes.decrypt(ciphertext.getBytes(), key);
+            assertTrue(Arrays.equals(plaintext, decrypted.getBytes()));
+        }
+    }
+
+    @Test
+    public void testStreamingOperations() {
+
+        AesCipherService cipher = new AesCipherService();
+        byte[] key = cipher.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            InputStream plainIn = new ByteArrayInputStream(plaintext);
+            ByteArrayOutputStream cipherOut = new ByteArrayOutputStream();
+            cipher.encrypt(plainIn, cipherOut, key);
+
+            byte[] ciphertext = cipherOut.toByteArray();
+            InputStream cipherIn = new ByteArrayInputStream(ciphertext);
+            ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
+            cipher.decrypt(cipherIn, plainOut, key);
+
+            byte[] decrypted = plainOut.toByteArray();
+            assertTrue(Arrays.equals(plaintext, decrypted));
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/crypto/BlowfishCipherServiceTest.java b/core/src/test/java/org/apache/shiro/crypto/BlowfishCipherServiceTest.java
new file mode 100644
index 0000000..fe176a4
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/crypto/BlowfishCipherServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.codec.CodecSupport;
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * Test cases for the {@link BlowfishCipherService} class.
+ *
+ * @since 1.0
+ */
+public class BlowfishCipherServiceTest {
+
+    private static final String[] PLAINTEXTS = new String[]{
+            "Hello, this is a test.",
+            "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
+    };
+
+    @Test
+    public void testBlockOperations() {
+        BlowfishCipherService blowfish = new BlowfishCipherService();
+
+        byte[] key = blowfish.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            ByteSource ciphertext = blowfish.encrypt(plaintext, key);
+            ByteSource decrypted = blowfish.decrypt(ciphertext.getBytes(), key);
+            assertTrue(Arrays.equals(plaintext, decrypted.getBytes()));
+        }
+    }
+
+    @Test
+    public void testStreamingOperations() {
+
+        BlowfishCipherService cipher = new BlowfishCipherService();
+        byte[] key = cipher.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            InputStream plainIn = new ByteArrayInputStream(plaintext);
+            ByteArrayOutputStream cipherOut = new ByteArrayOutputStream();
+            cipher.encrypt(plainIn, cipherOut, key);
+
+            byte[] ciphertext = cipherOut.toByteArray();
+            InputStream cipherIn = new ByteArrayInputStream(ciphertext);
+            ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
+            cipher.decrypt(cipherIn, plainOut, key);
+
+            byte[] decrypted = plainOut.toByteArray();
+            assertTrue(Arrays.equals(plaintext, decrypted));
+        }
+
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java b/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java
new file mode 100644
index 0000000..7c2ae6a
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class JcaCipherServiceTest {
+
+    @Test(expected = CryptoException.class)
+    public void testDecrypt() {
+        JcaCipherService cipherService = new JcaCipherService("AES") {
+        };
+        String ciphertext = "iv_helloword";
+        String key = "somekey";
+        cipherService.decrypt(ciphertext.getBytes(), key.getBytes());
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java b/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java
new file mode 100644
index 0000000..fb34fd8
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link SecureRandomNumberGenerator} class.
+ *
+ * @since 1.1
+ */
+public class SecureRandomNumberGeneratorTest {
+
+    @Test
+    public void testDefaultNextBytesSize() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        boolean negativeThrown = false;
+        boolean zeroThrown = false;
+        try {
+            rng.setDefaultNextBytesSize(-1);
+        } catch (IllegalArgumentException e) {
+            negativeThrown = true;
+        }
+
+        try {
+            rng.setDefaultNextBytesSize(0);
+        } catch (IllegalArgumentException e) {
+            zeroThrown = true;
+        }
+
+        assertTrue(negativeThrown);
+        assertTrue(zeroThrown);
+
+        ByteSource bs = rng.nextBytes();
+        assertNotNull(bs);
+        assertNotNull(bs.getBytes());
+        assertEquals(SecureRandomNumberGenerator.DEFAULT_NEXT_BYTES_SIZE, bs.getBytes().length);
+
+        rng.setDefaultNextBytesSize(64);
+        assertNotNull(bs);
+        bs = rng.nextBytes();
+        assertNotNull(bs.getBytes());
+        assertEquals(64, bs.getBytes().length);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testInvalidSecureRandomProperty() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        rng.setSecureRandom(null);
+    }
+
+    @Test
+    public void testNextBytesWithSize() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        boolean negativeThrown = false;
+        boolean zeroThrown = false;
+        try {
+            rng.nextBytes(-1);
+        } catch (IllegalArgumentException e) {
+            negativeThrown = true;
+        }
+
+        try {
+            rng.nextBytes(0);
+        } catch (IllegalArgumentException e) {
+            zeroThrown = true;
+        }
+
+        assertTrue(negativeThrown);
+        assertTrue(zeroThrown);
+
+        ByteSource bs = rng.nextBytes(8);
+        assertNotNull(bs);
+        assertNotNull(bs.getBytes());
+        assertEquals(8, bs.getBytes().length);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/io/SerializationExceptionTest.java b/core/src/test/java/org/apache/shiro/io/SerializationExceptionTest.java
new file mode 100644
index 0000000..8321caf
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/io/SerializationExceptionTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.io;
+
+import org.apache.shiro.ExceptionTest;
+
+
+/**
+ */
+public class SerializationExceptionTest extends ExceptionTest {
+
+    protected Class getExceptionClass() {
+        return SerializationException.class;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/jndi/JndiObjectFactoryTest.java b/core/src/test/java/org/apache/shiro/jndi/JndiObjectFactoryTest.java
new file mode 100644
index 0000000..cd55c70
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/jndi/JndiObjectFactoryTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.jndi;
+
+import org.junit.Test;
+
+import javax.naming.NamingException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * This test makes the assumption that {@link JndiLocator} is tested elsewhere and only makes an attempt to test the
+ * functionality added by {@link JndiObjectFactory}.
+ */
+public class JndiObjectFactoryTest {
+    @Test
+    public void testGetInstanceWithType() throws Exception {
+        final String name = "my/jndi/resource";
+        final String returnValue = "jndiString";
+        JndiObjectFactory<String> underTest = new JndiObjectFactory<String>() {
+            @Override
+            protected Object lookup(String jndiName, Class requiredType) throws NamingException {
+                assertEquals(name, jndiName);
+                assertEquals(String.class, requiredType);
+                return new String(returnValue);
+            }
+        };
+
+        underTest.setRequiredType(String.class);
+        underTest.setResourceName(name);
+
+        assertEquals(returnValue, underTest.getInstance());
+    }
+
+    @Test
+    public void testGetInstanceNoType() throws Exception {
+        final String name = "my/jndi/resource";
+        final String returnValue = "jndiString";
+        JndiObjectFactory<String> underTest = new JndiObjectFactory<String>() {
+            @Override
+            protected Object lookup(String jndiName) throws NamingException {
+                assertEquals(name, jndiName);
+                return new String(returnValue);
+            }
+        };
+
+        underTest.setResourceName(name);
+
+        assertEquals(returnValue, underTest.getInstance());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testJndiLookupFailsWithType() throws Exception {
+        final String name = "my/jndi/resource";
+        JndiObjectFactory<String> underTest = new JndiObjectFactory<String>() {
+            @Override
+            protected Object lookup(String jndiName, Class requiredType) throws NamingException {
+                throw new NamingException("No resource named " + jndiName);
+            }
+        };
+
+        underTest.setResourceName(name);
+        underTest.setRequiredType(String.class);
+
+        underTest.getInstance();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testJndiLookupFailsNoType() throws Exception {
+        final String name = "my/jndi/resource";
+        JndiObjectFactory<String> underTest = new JndiObjectFactory<String>() {
+            @Override
+            protected Object lookup(String jndiName) throws NamingException {
+                throw new NamingException("No resource named " + jndiName);
+            }
+        };
+
+        underTest.setResourceName(name);
+
+        underTest.getInstance();
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/mgt/AbstractRememberMeManagerTest.java b/core/src/test/java/org/apache/shiro/mgt/AbstractRememberMeManagerTest.java
new file mode 100644
index 0000000..c52a2b2
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/mgt/AbstractRememberMeManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test cases for the {@link AbstractRememberMeManager} implementation.
+ */
+public class AbstractRememberMeManagerTest {
+
+    /**
+     * Tests the {@link AbstractRememberMeManager#getRememberedPrincipals(SubjectContext)} method
+     * implementation when the internal
+     * {@link AbstractRememberMeManager#getRememberedSerializedIdentity(SubjectContext)} method
+     * returns null or empty bytes.
+     */
+    @Test
+    public void testGetRememberedPrincipalsWithEmptySerializedBytes() {
+        AbstractRememberMeManager rmm = new DummyRememberMeManager();
+        //Since the dummy's getRememberedSerializedIdentity implementation returns an empty byte
+        //array, we should be ok:
+        PrincipalCollection principals = rmm.getRememberedPrincipals(new DefaultSubjectContext());
+        assertNull(principals);
+
+        //try with a null return value too:
+        rmm = new DummyRememberMeManager() {
+            @Override
+            protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
+                return null;
+            }
+        };
+        principals = rmm.getRememberedPrincipals(new DefaultSubjectContext());
+        assertNull(principals);
+    }
+
+    private static class DummyRememberMeManager extends AbstractRememberMeManager {
+        public void forgetIdentity(SubjectContext subjectContext) {
+            //do nothing
+        }
+
+        @Override
+        protected void forgetIdentity(Subject subject) {
+        }
+
+        @Override
+        protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
+        }
+
+        @Override
+        protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
+            return new byte[0];
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java b/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java
new file mode 100644
index 0000000..7e316f9
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/mgt/AbstractSecurityManagerTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.ThreadContext;
+import org.apache.shiro.util.ThreadState;
+import org.junit.After;
+
+/**
+ * @since 1.0
+ */
+public abstract class AbstractSecurityManagerTest {
+
+    protected ThreadState threadState;
+
+    @After
+    public void tearDown() {
+        ThreadContext.remove();
+    }
+
+    protected Subject newSubject(SecurityManager securityManager) {
+        Subject subject = new Subject.Builder(securityManager).buildSubject();
+        threadState = new SubjectThreadState(subject);
+        threadState.bind();
+        return subject;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/mgt/DefaultSecurityManagerTest.java b/core/src/test/java/org/apache/shiro/mgt/DefaultSecurityManagerTest.java
new file mode 100644
index 0000000..cfa3c2b
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/mgt/DefaultSecurityManagerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.AbstractValidatingSessionManager;
+import org.apache.shiro.subject.Subject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * @since 0.2
+ */
+public class DefaultSecurityManagerTest extends AbstractSecurityManagerTest {
+
+    DefaultSecurityManager sm = null;
+
+    @Before
+    public void setup() {
+        sm = new DefaultSecurityManager();
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        section.put("guest", "guest, guest");
+        section.put("lonestarr", "vespa, goodguy");
+        sm.setRealm(new IniRealm(ini));
+        SecurityUtils.setSecurityManager(sm);
+    }
+
+    @After
+    public void tearDown() {
+        SecurityUtils.setSecurityManager(null);
+        sm.destroy();
+        super.tearDown();
+    }
+
+    @Test
+    public void testDefaultConfig() {
+        Subject subject = SecurityUtils.getSubject();
+
+        AuthenticationToken token = new UsernamePasswordToken("guest", "guest");
+        subject.login(token);
+        assertTrue(subject.isAuthenticated());
+        assertTrue("guest".equals(subject.getPrincipal()));
+        assertTrue(subject.hasRole("guest"));
+
+        Session session = subject.getSession();
+        session.setAttribute("key", "value");
+        assertEquals(session.getAttribute("key"), "value");
+
+        subject.logout();
+
+        assertNull(subject.getSession(false));
+        assertNull(subject.getPrincipal());
+        assertNull(subject.getPrincipals());
+    }
+
+    /**
+     * Test that validates functionality for issue
+     * <a href="https://issues.apache.org/jira/browse/JSEC-46">JSEC-46</a>
+     */
+    @Test
+    public void testAutoCreateSessionAfterInvalidation() {
+        Subject subject = SecurityUtils.getSubject();
+        Session session = subject.getSession();
+        Serializable origSessionId = session.getId();
+
+        String key = "foo";
+        String value1 = "bar";
+        session.setAttribute(key, value1);
+        assertEquals(value1, session.getAttribute(key));
+
+        //now test auto creation:
+        session.setTimeout(50);
+        try {
+            Thread.sleep(150);
+        } catch (InterruptedException e) {
+            //ignored
+        }
+        try {
+            session.setTimeout(AbstractValidatingSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
+            fail("Session should have expired.");
+        } catch (ExpiredSessionException expected) {
+        }
+    }
+
+    /**
+     * Test that validates functionality for issue
+     * <a href="https://issues.apache.org/jira/browse/JSEC-22">JSEC-22</a>
+     */
+    @Test
+    public void testSubjectReuseAfterLogout() {
+
+        Subject subject = SecurityUtils.getSubject();
+
+        AuthenticationToken token = new UsernamePasswordToken("guest", "guest");
+        subject.login(token);
+        assertTrue(subject.isAuthenticated());
+        assertTrue("guest".equals(subject.getPrincipal()));
+        assertTrue(subject.hasRole("guest"));
+
+        Session session = subject.getSession();
+        Serializable firstSessionId = session.getId();
+
+        session.setAttribute("key", "value");
+        assertEquals(session.getAttribute("key"), "value");
+
+        subject.logout();
+
+        assertNull(subject.getSession(false));
+        assertNull(subject.getPrincipal());
+        assertNull(subject.getPrincipals());
+
+        subject.login(new UsernamePasswordToken("lonestarr", "vespa"));
+        assertTrue(subject.isAuthenticated());
+        assertTrue("lonestarr".equals(subject.getPrincipal()));
+        assertTrue(subject.hasRole("goodguy"));
+
+        assertNotNull(subject.getSession());
+        assertFalse(firstSessionId.equals(subject.getSession().getId()));
+
+        subject.logout();
+
+        assertNull(subject.getSession(false));
+        assertNull(subject.getPrincipal());
+        assertNull(subject.getPrincipals());
+
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/mgt/VMSingletonDefaultSecurityManagerTest.java b/core/src/test/java/org/apache/shiro/mgt/VMSingletonDefaultSecurityManagerTest.java
new file mode 100644
index 0000000..0de2a65
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/mgt/VMSingletonDefaultSecurityManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.mgt;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @since May 8, 2008 12:26:23 AM
+ */
+public class VMSingletonDefaultSecurityManagerTest {
+
+    @Before
+    public void setUp() {
+        ThreadContext.remove();
+    }
+
+    @After
+    public void tearDown() {
+        ThreadContext.remove();
+    }
+
+    @Test
+    public void testVMSingleton() {
+        DefaultSecurityManager sm = new DefaultSecurityManager();
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        section.put("guest", "guest");
+        sm.setRealm(new IniRealm(ini));
+        SecurityUtils.setSecurityManager(sm);
+
+        try {
+            Subject subject = SecurityUtils.getSubject();
+
+            AuthenticationToken token = new UsernamePasswordToken("guest", "guest");
+            subject.login(token);
+            subject.getSession().setAttribute("key", "value");
+            assertTrue(subject.getSession().getAttribute("key").equals("value"));
+
+            subject = SecurityUtils.getSubject();
+
+            assertTrue(subject.isAuthenticated());
+            assertTrue(subject.getSession().getAttribute("key").equals("value"));
+        } finally {
+            sm.destroy();
+            //SHIRO-270:
+            SecurityUtils.setSecurityManager(null);
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/AuthorizingRealmTest.java b/core/src/test/java/org/apache/shiro/realm/AuthorizingRealmTest.java
new file mode 100644
index 0000000..13f447d
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/AuthorizingRealmTest.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.apache.shiro.authz.permission.RolePermissionResolver;
+import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.junit.After;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Principal;
+import java.util.*;
+
+
+/**
+ * Simple test case for AuthorizingRealm.
+ * <p/>
+ * TODO - this could/should be expaned to be more robust end to end test for the AuthorizingRealm
+ */
+public class AuthorizingRealmTest {
+
+    AuthorizingRealm realm;
+
+    private static final String USERNAME = "testuser";
+    private static final String PASSWORD = "password";
+    private static final int USER_ID = 12345;
+    private static final String ROLE = "admin";
+    private String localhost = "localhost";
+
+    @Before
+    public void setup() {
+        realm = new AllowAllRealm();
+
+    }
+
+    @After
+    public void tearDown() {
+        realm = null;
+    }
+
+    @Test
+    public void testDefaultConfig() {
+        AuthenticationInfo info = realm.getAuthenticationInfo(new UsernamePasswordToken(USERNAME, PASSWORD, localhost));
+
+        assertNotNull(info);
+        assertTrue(realm.hasRole(info.getPrincipals(), ROLE));
+
+        Object principal = info.getPrincipals().getPrimaryPrincipal();
+        assertTrue(principal instanceof UserIdPrincipal);
+
+        UsernamePrincipal usernamePrincipal = info.getPrincipals().oneByType(UsernamePrincipal.class);
+        assertTrue(usernamePrincipal.getUsername().equals(USERNAME));
+
+        UserIdPrincipal userIdPrincipal = info.getPrincipals().oneByType(UserIdPrincipal.class);
+        assertTrue(userIdPrincipal.getUserId() == USER_ID);
+
+        String stringPrincipal = info.getPrincipals().oneByType(String.class);
+        assertTrue(stringPrincipal.equals(USER_ID + USERNAME));
+    }
+
+    @Test
+    public void testCreateAccountOverride() {
+
+        AuthorizingRealm realm = new AllowAllRealm() {
+            @Override
+            protected AuthenticationInfo buildAuthenticationInfo(Object principal, Object credentials) {
+                String username = (String) principal;
+                UsernamePrincipal customPrincipal = new UsernamePrincipal(username);
+                return new SimpleAccount(customPrincipal, credentials, getName());
+            }
+        };
+
+        AuthenticationInfo info = realm.getAuthenticationInfo(new UsernamePasswordToken(USERNAME, PASSWORD, localhost));
+        assertNotNull(info);
+        assertTrue(realm.hasRole(info.getPrincipals(), ROLE));
+        Object principal = info.getPrincipals().getPrimaryPrincipal();
+        assertTrue(principal instanceof UsernamePrincipal);
+        assertEquals(USERNAME, ((UsernamePrincipal) principal).getUsername());
+
+
+    }
+
+    @Test
+    public void testNullAuthzInfo() {
+	AuthorizingRealm realm = new AuthorizingRealm() {
+            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+                return null;
+            }
+
+            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+                return null;
+            }
+        };
+
+        Principal principal = new UsernamePrincipal("blah");
+        PrincipalCollection pCollection = new SimplePrincipalCollection(principal, "nullAuthzRealm");
+        List<Permission> permList = new ArrayList<Permission>();
+        permList.add(new WildcardPermission("stringPerm1"));
+        permList.add(new WildcardPermission("stringPerm2"));
+        List<String> roleList = new ArrayList<String>();
+        roleList.add("role1");
+        roleList.add("role2");
+
+        boolean thrown = false;
+        try {
+            realm.checkPermission(pCollection, "stringPermission");
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        thrown = false;
+
+        try {
+            realm.checkPermission(pCollection, new WildcardPermission("stringPermission"));
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        thrown = false;
+
+        try {
+            realm.checkPermissions(pCollection, "stringPerm1", "stringPerm2");
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        thrown = false;
+
+        try {
+            realm.checkPermissions(pCollection, permList);
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        thrown = false;
+
+        try {
+            realm.checkRole(pCollection, "role1");
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+        thrown = false;
+
+        try {
+            realm.checkRoles(pCollection, roleList);
+        } catch (UnauthorizedException e) {
+            thrown = true;
+        }
+        assertTrue(thrown);
+
+        assertFalse(realm.hasAllRoles(pCollection, roleList));
+        assertFalse(realm.hasRole(pCollection, "role1"));
+        assertArrayEquals(new boolean[]{false, false}, realm.hasRoles(pCollection, roleList));
+        assertFalse(realm.isPermitted(pCollection, "perm1"));
+        assertFalse(realm.isPermitted(pCollection, new WildcardPermission("perm1")));
+        assertArrayEquals(new boolean[]{false, false}, realm.isPermitted(pCollection, "perm1", "perm2"));
+        assertArrayEquals(new boolean[]{false, false}, realm.isPermitted(pCollection, permList));
+        assertFalse(realm.isPermittedAll(pCollection, "perm1", "perm2"));
+        assertFalse(realm.isPermittedAll(pCollection, permList));
+    }
+    
+    @Test
+    public void testRealmWithRolePermissionResolver()
+    {   
+        Principal principal = new UsernamePrincipal("rolePermResolver");
+        PrincipalCollection pCollection = new SimplePrincipalCollection(principal, "testRealmWithRolePermissionResolver");
+        
+        AuthorizingRealm realm = new AllowAllRealm();
+        realm.setRolePermissionResolver( new RolePermissionResolver()
+        { 
+            public Collection<Permission> resolvePermissionsInRole( String roleString )
+            {
+                Collection<Permission> permissions = new HashSet<Permission>();
+                if( roleString.equals( ROLE ))
+                {
+                    permissions.add( new WildcardPermission( ROLE + ":perm1" ) );
+                    permissions.add( new WildcardPermission( ROLE + ":perm2" ) );
+                    permissions.add( new WildcardPermission( "other:*:foo" ) );
+                }
+                return permissions;
+            }
+        });
+        
+        assertTrue( realm.hasRole( pCollection, ROLE ) );
+        assertTrue( realm.isPermitted( pCollection, ROLE + ":perm1" ) );
+        assertTrue( realm.isPermitted( pCollection, ROLE + ":perm2" ) );
+        assertFalse( realm.isPermitted( pCollection, ROLE + ":perm3" ) );
+        assertTrue( realm.isPermitted( pCollection, "other:bar:foo" ) );
+    }
+
+    private void assertArrayEquals(boolean[] expected, boolean[] actual) {
+        if (expected.length != actual.length) {
+            fail("Expected array of length [" + expected.length + "] but received array of length [" + actual.length + "]");
+        }
+        for (int i = 0; i < expected.length; i++) {
+            if (expected[i] != actual[i]) {
+                fail("Expected index [" + i + "] to be [" + expected[i] + "] but was [" + actual[i] + "]");
+            }
+        }
+    }
+
+    public class AllowAllRealm extends AuthorizingRealm {
+
+        public AllowAllRealm() {
+            super();
+            setCredentialsMatcher(new AllowAllCredentialsMatcher());
+        }
+
+        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+            return buildAuthenticationInfo(token.getPrincipal(), token.getCredentials());
+        }
+
+        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+            Set<String> roles = new HashSet<String>();
+            roles.add(ROLE);
+            return new SimpleAuthorizationInfo(roles);
+        }
+
+        protected AuthenticationInfo buildAuthenticationInfo(Object principal, Object credentials) {
+            Collection<Object> principals = new ArrayList<Object>(3);
+            principals.add(new UserIdPrincipal(USER_ID));
+            principals.add(new UsernamePrincipal(USERNAME));
+            principals.add(USER_ID + USERNAME);
+            return new SimpleAuthenticationInfo(principals, PASSWORD, getName());
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/realm/UserIdPrincipal.java b/core/src/test/java/org/apache/shiro/realm/UserIdPrincipal.java
new file mode 100644
index 0000000..09d2f49
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/UserIdPrincipal.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+public class UserIdPrincipal implements Principal, Serializable {
+
+    private int userId;
+
+    public UserIdPrincipal(int userId) {
+        this.userId = userId;
+    }
+
+    public int getUserId() {
+        return userId;
+    }
+
+    public String getName() {
+        return String.valueOf(userId);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/realm/UsernamePrincipal.java b/core/src/test/java/org/apache/shiro/realm/UsernamePrincipal.java
new file mode 100644
index 0000000..f9535f6
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/UsernamePrincipal.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+public class UsernamePrincipal implements Principal, Serializable {
+
+    private String username;
+
+    public UsernamePrincipal(String username) {
+        this.username = username;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getName() {
+        return String.valueOf(username);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealmTest.java b/core/src/test/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealmTest.java
new file mode 100644
index 0000000..5a2ce0e
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/activedirectory/ActiveDirectoryRealmTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.activedirectory;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.realm.UserIdPrincipal;
+import org.apache.shiro.realm.UsernamePrincipal;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.naming.NamingException;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Simple test case for ActiveDirectoryRealm.
+ * <p/>
+ * todo:  While the original incarnation of this test case does not actually test the
+ * heart of ActiveDirectoryRealm (no meaningful implemenation of queryForLdapAccount, etc) it obviously should.
+ * This version was intended to mimic my current usage scenario in an effort to debug upgrade issues which were not related
+ * to LDAP connectivity.
+ *
+ */
+public class ActiveDirectoryRealmTest {
+
+    DefaultSecurityManager securityManager = null;
+    AuthorizingRealm realm;
+
+    private static final String USERNAME = "testuser";
+    private static final String PASSWORD = "password";
+    private static final int USER_ID = 12345;
+    private static final String ROLE = "admin";
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+        realm = new TestActiveDirectoryRealm();
+        securityManager = new DefaultSecurityManager(realm);
+        SecurityUtils.setSecurityManager(securityManager);
+    }
+
+    @After
+    public void tearDown() {
+        SecurityUtils.setSecurityManager(null);
+        securityManager.destroy();
+        ThreadContext.remove();
+    }
+
+    @Test
+    public void testDefaultConfig() {
+        String localhost = "localhost";
+        Subject subject = SecurityUtils.getSubject();
+        subject.login(new UsernamePasswordToken(USERNAME, PASSWORD, localhost));
+        assertTrue(subject.isAuthenticated());
+        assertTrue(subject.hasRole(ROLE));
+
+
+        UsernamePrincipal usernamePrincipal = subject.getPrincipals().oneByType(UsernamePrincipal.class);
+        assertTrue(usernamePrincipal.getUsername().equals(USERNAME));
+
+        UserIdPrincipal userIdPrincipal = subject.getPrincipals().oneByType(UserIdPrincipal.class);
+        assertTrue(userIdPrincipal.getUserId() == USER_ID);
+
+        assertTrue(realm.hasRole(subject.getPrincipals(), ROLE));
+
+        subject.logout();
+    }
+
+    public class TestActiveDirectoryRealm extends ActiveDirectoryRealm {
+
+        /*--------------------------------------------
+        |         C O N S T R U C T O R S           |
+            ============================================*/
+        CredentialsMatcher credentialsMatcher;
+
+        public TestActiveDirectoryRealm() {
+            super();
+
+
+            credentialsMatcher = new CredentialsMatcher() {
+                public boolean doCredentialsMatch(AuthenticationToken object, AuthenticationInfo object1) {
+                    return true;
+                }
+            };
+
+            setCredentialsMatcher(credentialsMatcher);
+        }
+
+
+        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+            SimpleAccount account = (SimpleAccount) super.doGetAuthenticationInfo(token);
+
+            if (account != null) {
+                SimplePrincipalCollection principals = new SimplePrincipalCollection();
+                principals.add(new UserIdPrincipal(USER_ID), getName());
+                principals.add(new UsernamePrincipal(USERNAME), getName());
+                account.setPrincipals(principals);
+            }
+
+            return account;
+
+        }
+
+        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+            Set<String> roles = new HashSet<String>();
+            roles.add(ROLE);
+            return new SimpleAuthorizationInfo(roles);
+        }
+
+        // override ldap query because i don't care about testing that piece in this case
+        protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
+            return new SimpleAccount(token.getPrincipal(), token.getCredentials(), getName());
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/realm/jdbc/JDBCRealmTest.java b/core/src/test/java/org/apache/shiro/realm/jdbc/JDBCRealmTest.java
new file mode 100644
index 0000000..bd356dc
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/jdbc/JDBCRealmTest.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.jdbc;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.IncorrectCredentialsException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.JdbcUtils;
+import org.apache.shiro.util.ThreadContext;
+import org.hsqldb.jdbc.jdbcDataSource;
+import org.junit.*;
+import org.junit.rules.TestName;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+
+
+/**
+ * Test case for JDBCRealm.
+ */
+public class JDBCRealmTest {
+
+    protected DefaultSecurityManager securityManager = null;
+    protected AuthorizingRealm realm;
+    protected final String username = "testUser";
+    protected final String plainTextPassword = "testPassword";
+    protected final String salt = username;  //Default impl of getSaltForUser returns username
+    protected final String testRole = "testRole";
+    protected final String testPermissionString = "testDomain:testTarget:testAction";
+    
+    // Maps keyed on test method name so setup/teardown can manage per test resources
+    protected HashMap<String, JdbcRealm> realmMap = new HashMap<String, JdbcRealm>();
+    protected HashMap<String, DataSource> dsMap = new HashMap<String, DataSource>();
+
+    @Rule 
+    public TestName name = new TestName();
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+        Ini config = new Ini();
+        config.setSectionProperty("main", "myRealm", "org.apache.shiro.realm.jdbc.JdbcRealm");
+        config.setSectionProperty("main", "myRealmCredentialsMatcher", "org.apache.shiro.authc.credential.Sha256CredentialsMatcher");
+        config.setSectionProperty("main", "myRealm.credentialsMatcher", "$myRealmCredentialsMatcher");
+        config.setSectionProperty("main", "securityManager.sessionManager.sessionValidationSchedulerEnabled", "false");
+        
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(config);
+        securityManager = (DefaultSecurityManager) factory.createInstance();
+        SecurityUtils.setSecurityManager(securityManager);
+        
+        // Create a database and realm for the test
+        createRealm(name.getMethodName()); 
+    }
+
+    @After
+    public void tearDown() {
+        final String testName = name.getMethodName();
+        shutDown(testName);
+        SecurityUtils.setSecurityManager(null);
+        securityManager.destroy();
+        ThreadContext.remove();
+    }
+    
+    @Test
+    public void testUnSaltedSuccess() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        currentUser.logout();
+    }
+    
+    @Test
+    public void testUnSaltedWrongPassword() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
+        try {
+            currentUser.login(token);
+        } catch (IncorrectCredentialsException ex) {
+            // Expected
+        }
+    }
+    
+    @Test
+    public void testUnSaltedMultipleRows() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        Connection conn = dsMap.get(testMethodName).getConnection();
+        Statement sql = conn.createStatement();
+        sql.executeUpdate("insert into users values ('" + username + "', 'dupe')");
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
+        try {
+            currentUser.login(token);
+        } catch (AuthenticationException ex) {
+            // Expected
+        }
+    }
+    
+    @Test
+    public void testSaltColumnSuccess() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createSaltColumnSchema(testMethodName);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        currentUser.logout();
+    }
+    
+    @Test
+    public void testSaltColumnWrongPassword() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createSaltColumnSchema(testMethodName);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
+        try {
+            currentUser.login(token);
+        } catch (IncorrectCredentialsException ex) {
+            // Expected
+        }
+    }
+    
+    @Test
+    public void testExternalSuccess() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, true);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.EXTERNAL);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        currentUser.logout();
+    }
+    
+    @Test
+    public void testExternalWrongPassword() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, true);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.EXTERNAL);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
+        try {
+            currentUser.login(token);
+        } catch (IncorrectCredentialsException ex) {
+            // Expected
+        }
+    }
+    
+    @Test
+    public void testRolePresent() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        Assert.assertTrue(currentUser.hasRole(testRole));
+    }
+    
+    @Test
+    public void testRoleNotPresent() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        Assert.assertFalse(currentUser.hasRole("Game Overall Director"));
+    }
+    
+    @Test
+    public void testPermissionPresent() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        realm.setPermissionsLookupEnabled(true);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        Assert.assertTrue(currentUser.isPermitted(testPermissionString));
+    }
+    
+    @Test
+    public void testPermissionNotPresent() throws Exception {
+        String testMethodName = name.getMethodName();
+        JdbcRealm realm = realmMap.get(testMethodName);
+        createDefaultSchema(testMethodName, false);
+        realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
+        realm.setPermissionsLookupEnabled(true);
+        
+        Subject.Builder builder = new Subject.Builder(securityManager);
+        Subject currentUser = builder.buildSubject();
+        UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
+        currentUser.login(token);
+        Assert.assertFalse(currentUser.isPermitted("testDomain:testTarget:specialAction"));
+    }
+    
+    /**
+     * Creates a realm for a test method and puts it in the realMap.
+     */
+    protected void createRealm(String testMethodName) {
+        JdbcRealm realm = (JdbcRealm) securityManager.getRealms().iterator().next();
+        realmMap.put(testMethodName, realm);
+    }
+    
+    /**
+     * Shuts down the database and removes the realm from the realm map.
+     */
+    protected void shutDown(String testName) {
+        Connection conn = null;
+        Statement sql = null;
+        DataSource ds = dsMap.get(testName);
+        try {
+            Connection c = ds.getConnection();
+            Statement s = c.createStatement();
+            s.executeUpdate("SHUTDOWN");
+        } catch (SQLException ex) {
+            // ignore
+        } finally {
+            JdbcUtils.closeStatement(sql);
+            JdbcUtils.closeConnection(conn);
+            dsMap.remove(testName);
+            realmMap.remove(testName);
+        }
+    }
+    
+    /**
+     * Creates a test database with the default (no separate salt column) schema, salting with
+     * username if salted is true. Sets the DataSource of the realm associated with the test
+     * to a DataSource connected to the database.  (To prevent concurrency problems when tests
+     * are executed in multithreaded mode, each test method gets its own database.)
+     */
+    protected void createDefaultSchema(String testName, boolean salted) {
+        jdbcDataSource ds = new jdbcDataSource();
+        ds.setDatabase("jdbc:hsqldb:mem:" + name);
+        ds.setUser("SA");
+        ds.setPassword("");
+        Connection conn = null;
+        Statement sql = null;
+        try {
+            conn = ds.getConnection();
+            sql = conn.createStatement();
+            sql.executeUpdate("create table users (username varchar(20), password varchar(20))");
+            Sha256Hash sha256Hash = salted ? new Sha256Hash(plainTextPassword, salt) :
+                new Sha256Hash(plainTextPassword);
+            String password = sha256Hash.toHex();
+            sql.executeUpdate("insert into users values ('" + username + "', '" + password + "')");
+        } catch (SQLException ex) {
+            Assert.fail("Exception creating test database");
+        } finally {
+            JdbcUtils.closeStatement(sql);
+            JdbcUtils.closeConnection(conn);
+        }
+        createRolesAndPermissions(ds);
+        realmMap.get(testName).setDataSource(ds);
+        dsMap.put(testName, ds);
+    }
+    
+    /**
+     * Creates a test database with a separate salt column in the users table. Sets the
+     * DataSource of the realm associated with the test to a DataSource connected to the database.
+     */
+    protected void createSaltColumnSchema(String testName) {
+        jdbcDataSource ds = new jdbcDataSource();
+        ds.setDatabase("jdbc:hsqldb:mem:" + name);
+        ds.setUser("SA");
+        ds.setPassword("");
+        Connection conn = null;
+        Statement sql = null;
+        try {
+            conn = ds.getConnection();
+            sql = conn.createStatement();
+            sql.executeUpdate(
+                    "create table users (username varchar(20), password varchar(20), password_salt varchar(20))");
+            Sha256Hash sha256Hash = new Sha256Hash(plainTextPassword, salt);
+            String password = sha256Hash.toHex();
+            sql.executeUpdate("insert into users values ('" + username + "', '" + password + "', '" + salt + "')");
+        } catch (SQLException ex) {
+            Assert.fail("Exception creating test database");
+        } finally {
+            JdbcUtils.closeStatement(sql);
+            JdbcUtils.closeConnection(conn);
+        }
+        createRolesAndPermissions(ds);
+        realmMap.get(testName).setDataSource(ds);
+        dsMap.put(testName, ds);
+    }
+    
+    /**
+     * Creates and adds test data to user_role and roles_permissions tables.
+     */
+    protected void createRolesAndPermissions(DataSource ds) {
+        Connection conn = null;;
+        Statement sql = null;
+        try {
+            conn = ds.getConnection();
+            sql = conn.createStatement();
+            sql.executeUpdate("create table user_roles (username varchar(20), role_name varchar(20))");
+            sql.executeUpdate("insert into user_roles values ('" + username + "', '" + testRole + "')");
+            sql.executeUpdate("create table roles_permissions (role_name varchar(20), permission varchar(40))");
+            sql.executeUpdate(
+                    "insert into roles_permissions values ('" + testRole + "', '" + testPermissionString + "')");
+        } catch (SQLException ex) {
+            Assert.fail("Exception adding test role and permission");
+        } finally {
+            JdbcUtils.closeStatement(sql);
+            JdbcUtils.closeConnection(conn);
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapContextFactoryTest.java b/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapContextFactoryTest.java
new file mode 100644
index 0000000..cc5a930
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapContextFactoryTest.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.UUID;
+
+import static junit.framework.Assert.*;
+import static org.easymock.EasyMock.*;
+
+/**
+ * Tests for the {@link JndiLdapContextFactory} class.
+ *
+ * @since 1.1
+ */
+public class JndiLdapContextFactoryTest {
+
+    private JndiLdapContextFactory factory;
+
+    @Before
+    public void setUp() {
+        factory = new JndiLdapContextFactory() {
+            //Fake a JNDI environment for the tests:
+            @Override
+            protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+                return createNiceMock(LdapContext.class);
+            }
+        };
+    }
+
+    /**
+     * This is the only test that does not fake the JNDI environment.  It is provided for 100% test coverage.
+     *
+     * @throws NamingException thrown because the host is always broken.
+     */
+    @Test(expected = NamingException.class)
+    public void testGetLdapContext() throws NamingException {
+        factory = new JndiLdapContextFactory();
+        //garbage URL to test that the context is being created, but fails:
+        String brokenHost = UUID.randomUUID().toString();
+        factory.setUrl("ldap://" + brokenHost + ":389");
+        factory.getLdapContext((Object) "foo", "bar");
+    }
+
+    @Test
+    public void testAuthenticationMechanism() {
+        String mech = "MD5-DIGEST";
+        factory.setAuthenticationMechanism(mech);
+        assertEquals(mech, factory.getAuthenticationMechanism());
+    }
+
+    @Test
+    public void testReferral() {
+        String referral = "throw";
+        factory.setReferral(referral);
+        assertEquals(referral, factory.getReferral());
+    }
+
+    @Test
+    public void testGetContextFactoryClassName() {
+        assertEquals(JndiLdapContextFactory.DEFAULT_CONTEXT_FACTORY_CLASS_NAME, factory.getContextFactoryClassName());
+    }
+
+    @Test
+    public void testSetEnvironmentPropertyNull() {
+        factory.setAuthenticationMechanism("MD5-DIGEST");
+        factory.setAuthenticationMechanism(null);
+        assertNull(factory.getAuthenticationMechanism());
+    }
+
+    @Test
+    public void testCustomEnvironment() {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("foo", "bar");
+        factory.setEnvironment(map);
+        assertEquals("bar", factory.getEnvironment().get("foo"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetLdapContextWithoutUrl() throws NamingException {
+        factory.getLdapContext((Object) "foo", "bar");
+    }
+
+    @Test
+    public void testGetLdapContextDefault() throws NamingException {
+        factory = new JndiLdapContextFactory() {
+            @Override
+            protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+                assertEquals("ldap://localhost:389", env.get(Context.PROVIDER_URL));
+                assertEquals("foo", env.get(Context.SECURITY_PRINCIPAL));
+                assertEquals("bar", env.get(Context.SECURITY_CREDENTIALS));
+                assertEquals("simple", env.get(Context.SECURITY_AUTHENTICATION));
+                assertNull(env.get(SUN_CONNECTION_POOLING_PROPERTY));
+                return createNiceMock(LdapContext.class);
+            }
+        };
+
+        factory.setUrl("ldap://localhost:389");
+        factory.getLdapContext((Object) "foo", "bar");
+    }
+
+    @SuppressWarnings({"deprecation"})
+    @Test
+    public void testGetLdapContextStringArguments() throws NamingException {
+        factory = new JndiLdapContextFactory() {
+            @Override
+            protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+                assertEquals("ldap://localhost:389", env.get(Context.PROVIDER_URL));
+                assertEquals("foo", env.get(Context.SECURITY_PRINCIPAL));
+                assertEquals("bar", env.get(Context.SECURITY_CREDENTIALS));
+                assertEquals("simple", env.get(Context.SECURITY_AUTHENTICATION));
+                assertNull(env.get(SUN_CONNECTION_POOLING_PROPERTY));
+                return createNiceMock(LdapContext.class);
+            }
+        };
+
+        factory.setUrl("ldap://localhost:389");
+        factory.getLdapContext("foo", "bar");
+    }
+
+    @Test
+    public void testGetSystemLdapContext() throws NamingException {
+        factory = new JndiLdapContextFactory() {
+            @Override
+            protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+                assertEquals("ldap://localhost:389", env.get(Context.PROVIDER_URL));
+                assertEquals("foo", env.get(Context.SECURITY_PRINCIPAL));
+                assertEquals("bar", env.get(Context.SECURITY_CREDENTIALS));
+                assertEquals("simple", env.get(Context.SECURITY_AUTHENTICATION));
+                assertNotNull(env.get(SUN_CONNECTION_POOLING_PROPERTY));
+                return createNiceMock(LdapContext.class);
+            }
+        };
+
+        factory.setSystemUsername("foo");
+        factory.setSystemPassword("bar");
+        factory.setUrl("ldap://localhost:389");
+        factory.getSystemLdapContext();
+    }
+
+    @Test
+    public void testGetSystemLdapContextPoolingDisabled() throws NamingException {
+        factory = new JndiLdapContextFactory() {
+            @Override
+            protected LdapContext createLdapContext(Hashtable env) throws NamingException {
+                assertEquals("ldap://localhost:389", env.get(Context.PROVIDER_URL));
+                assertEquals("foo", env.get(Context.SECURITY_PRINCIPAL));
+                assertEquals("bar", env.get(Context.SECURITY_CREDENTIALS));
+                assertEquals("simple", env.get(Context.SECURITY_AUTHENTICATION));
+                assertNull(env.get(SUN_CONNECTION_POOLING_PROPERTY));
+                return createNiceMock(LdapContext.class);
+            }
+        };
+
+        factory.setSystemUsername("foo");
+        factory.setSystemPassword("bar");
+        factory.setPoolingEnabled(false);
+        factory.setUrl("ldap://localhost:389");
+        factory.getSystemLdapContext();
+    }
+
+
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapRealmTest.java b/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapRealmTest.java
new file mode 100644
index 0000000..3d809a9
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/ldap/JndiLdapRealmTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.ldap;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+import java.util.UUID;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link JndiLdapRealm} class.
+ *
+ * @since 1.1
+ */
+ at SuppressWarnings({"ThrowableInstanceNeverThrown"})
+public class JndiLdapRealmTest {
+
+    private JndiLdapRealm realm;
+
+    @Before
+    public void setUp() {
+        realm = new JndiLdapRealm();
+    }
+
+    @Test
+    public void testDefaultInstance() {
+        assertTrue(realm.getCredentialsMatcher() instanceof AllowAllCredentialsMatcher);
+        assertEquals(AuthenticationToken.class, realm.getAuthenticationTokenClass());
+        assertTrue(realm.getContextFactory() instanceof JndiLdapContextFactory);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testSetUserDnTemplateNull() {
+        realm.setUserDnTemplate(null);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testSetUserDnTemplateEmpty() {
+        realm.setUserDnTemplate("  ");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testSetUserDnTemplateWithoutToken() {
+        realm.setUserDnTemplate("uid=,ou=users,dc=mycompany,dc=com");
+    }
+
+    @Test
+    public void testUserDnTemplate() {
+        String template = "uid={0},ou=users,dc=mycompany,dc=com";
+        realm.setUserDnTemplate(template);
+        assertEquals(template, realm.getUserDnTemplate());
+    }
+
+    @Test
+    public void testUserDnTemplateSubstitution() throws NamingException {
+        realm.setUserDnTemplate("uid={0},ou=users,dc=mycompany,dc=com");
+        LdapContextFactory factory = createMock(LdapContextFactory.class);
+        realm.setContextFactory(factory);
+
+        Object expectedPrincipal = "uid=jsmith,ou=users,dc=mycompany,dc=com";
+
+        expect(factory.getLdapContext(eq(expectedPrincipal), isA(Object.class))).andReturn(createNiceMock(LdapContext.class));
+        replay(factory);
+
+        realm.getAuthenticationInfo(new UsernamePasswordToken("jsmith", "secret") );
+        verify(factory);
+    }
+
+    @Test(expected= AuthenticationException.class)
+    public void testGetAuthenticationInfoNamingAuthenticationException() throws NamingException {
+        realm.setUserDnTemplate("uid={0},ou=users,dc=mycompany,dc=com");
+        LdapContextFactory factory = createMock(LdapContextFactory.class);
+        realm.setContextFactory(factory);
+
+        expect(factory.getLdapContext(isA(Object.class), isA(Object.class)))
+                .andThrow(new javax.naming.AuthenticationException("LDAP Authentication failed."));
+        replay(factory);
+
+        realm.getAuthenticationInfo(new UsernamePasswordToken("jsmith", "secret") );
+    }
+
+    @Test(expected= AuthenticationException.class)
+    public void testGetAuthenticationInfoNamingException() throws NamingException {
+        realm.setUserDnTemplate("uid={0},ou=users,dc=mycompany,dc=com");
+        LdapContextFactory factory = createMock(LdapContextFactory.class);
+        realm.setContextFactory(factory);
+
+        expect(factory.getLdapContext(isA(Object.class), isA(Object.class)))
+                .andThrow(new NamingException("Communication error."));
+        replay(factory);
+
+        realm.getAuthenticationInfo(new UsernamePasswordToken("jsmith", "secret") );
+    }
+
+    /**
+     * This test simulates that if a non-String principal (i.e. not a username) is passed as the LDAP principal, that
+     * it is not altered into a User DN and is passed as-is.  This will allow principals to be things like X.509
+     * certificates as well instead of only strings.
+     *
+     * @throws NamingException not thrown
+     */
+    @Test
+    public void testGetAuthenticationInfoNonSimpleToken() throws NamingException {
+        realm.setUserDnTemplate("uid={0},ou=users,dc=mycompany,dc=com");
+        LdapContextFactory factory = createMock(LdapContextFactory.class);
+        realm.setContextFactory(factory);
+
+        final UUID userId = UUID.randomUUID();
+
+        //ensure the userId is passed as-is:
+        expect(factory.getLdapContext(eq(userId), isA(Object.class))).andReturn(createNiceMock(LdapContext.class));
+        replay(factory);
+
+        realm.getAuthenticationInfo(new AuthenticationToken() {
+            public Object getPrincipal() {
+                return userId;
+            }
+
+            public Object getCredentials() {
+                return "secret";
+            }
+        });
+        verify(factory);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testGetUserDnNullArgument() {
+        realm.getUserDn(null);
+    }
+
+    @Test
+    public void testGetUserDnWithOutPrefixAndSuffix() {
+        realm = new JndiLdapRealm() {
+            @Override
+            protected String getUserDnPrefix() {
+                return null;
+            }
+
+            @Override
+            protected String getUserDnSuffix() {
+                return null;
+            }
+        };
+        String principal = "foo";
+        String userDn = realm.getUserDn(principal);
+        assertEquals(principal, userDn);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/text/IniRealmTest.java b/core/src/test/java/org/apache/shiro/realm/text/IniRealmTest.java
new file mode 100644
index 0000000..78cca77
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/text/IniRealmTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.text;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+/**
+ * Unit tests for the {@link IniRealm} class.
+ *
+ * @since 1.0
+ */
+public class IniRealmTest {
+
+    @Test
+    public void testNullIni() {
+        IniRealm realm = new IniRealm((Ini) null);
+    }
+
+    @Test
+    public void testEmptyIni() {
+        new IniRealm(new Ini());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInitWithoutIniResource() {
+        new IniRealm().init();
+    }
+
+    @Test
+    public void testIniFile() {
+        IniRealm realm = new IniRealm();
+        realm.setResourcePath("classpath:org/apache/shiro/realm/text/IniRealmTest.simple.ini");
+        realm.init();
+        assertTrue(realm.roleExists("admin"));
+        UsernamePasswordToken token = new UsernamePasswordToken("user1", "user1");
+        AuthenticationInfo info = realm.getAuthenticationInfo(token);
+        assertNotNull(info);
+        assertTrue(realm.hasRole(info.getPrincipals(), "admin"));
+    }
+
+    @Test
+    public void testIniFileWithoutUsers() {
+        IniRealm realm = new IniRealm();
+        realm.setResourcePath("classpath:org/apache/shiro/realm/text/IniRealmTest.noUsers.ini");
+        realm.init();
+        assertTrue(realm.roleExists("admin"));
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/realm/text/TextConfigurationRealmTest.java b/core/src/test/java/org/apache/shiro/realm/text/TextConfigurationRealmTest.java
new file mode 100644
index 0000000..4b0b8c2
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/realm/text/TextConfigurationRealmTest.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.realm.text;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.junit.Test;
+
+import java.text.ParseException;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+public class TextConfigurationRealmTest {
+
+    private TestRealm realm;
+
+    private void setRoles() {
+        StringBuilder roleDefinitions = new StringBuilder()
+                .append("role1 = role1_permission1\n")
+                .append("role2 = role2_persission1, role2_permission2\n");
+        realm.setRoleDefinitions(roleDefinitions.toString());
+    }
+
+    private void setUsers() {
+        StringBuilder userDefinitions = new StringBuilder();
+        for (int i = 1; i < 3; i++) {
+            userDefinitions.append(String.format("user%1$d = user%1$d_password, role1, role2%n", i));
+        }
+        realm.setUserDefinitions(userDefinitions.toString());
+    }
+
+    private void setUpForReadConfigurationTest() {
+        realm = new TestRealm() {
+            /*
+             * Demonstrates that a lock can't be obtained on the realm by a read thread until after
+             * the lock is released.
+             */
+            public void test(Thread runnable) throws InterruptedException {
+                // Obtain the realm's locks
+                USERS_LOCK.writeLock().lock();
+                try {
+                    ROLES_LOCK.writeLock().lock();
+                    try {
+                        // Any read threads attempting to obtain the realms lock will block
+                        runnable.start();
+                        Thread.sleep(500);
+                        // Process role and user definitions
+                        realm.onInit();
+
+                    } finally {
+                        ROLES_LOCK.writeLock().unlock();
+                    }
+                } finally {
+                    USERS_LOCK.writeLock().unlock();
+                }
+            }
+        };
+        setRoles();
+        setUsers();
+    }
+
+    /*
+     * Executes a test that attempts to read to read from a realm before it is loaded.
+     */
+    private void executeTest(Runnable runnable) throws InterruptedException {
+        TestThread testThread = new TestThread(runnable);
+        Thread testTask = new Thread(testThread);
+        realm.test(testTask);
+        testTask.join(500);
+        // Check whether any assertion error was thrown by the read thread
+        testThread.test();
+    }
+
+    /*
+     * Tests that roles and account can't be tested while the realm is being loaded. 
+     */
+    @Test
+    public void testRoleAndUserAccount() throws InterruptedException {
+        setUpForReadConfigurationTest();
+        executeTest(new Runnable() {
+            public void run() {
+                assertTrue("role not found when it was expected", realm.roleExists("role1"));
+                assertTrue("user not found when it was expected", realm.accountExists("user1"));
+            }
+        });
+    }
+
+    /*
+     * Tests that roles can't be read while the realm is being loaded. 
+     */
+    @Test
+    public void testHasRole() throws InterruptedException {
+        setUpForReadConfigurationTest();
+        executeTest(new Runnable() {
+            public void run() {
+                PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
+                assertTrue("principal doesn't have role when it should",
+                        realm.hasRole(principalCollection, "role2"));
+                assertTrue("principal doesn't have all roles when it should",
+                        realm.hasAllRoles(principalCollection, Arrays.asList(new String[]{"role1", "role2"})));
+            }
+        });
+    }
+
+    /*
+     * Tests that roles can't be checked while the realm is being loaded. 
+     */
+    @Test
+    public void testCheckRole() throws InterruptedException {
+        setUpForReadConfigurationTest();
+        executeTest(new Runnable() {
+            public void run() {
+                PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
+                try {
+                    realm.checkRoles(principalCollection, new String[]{"role1", "role2"});
+                } catch (AuthorizationException ae) {
+                    fail("principal doesn't have all roles when it should");
+                }
+            }
+        });
+    }
+
+    /*
+     * Tests that a principal's permissions can't be checked while the realm is being loaded. 
+     */
+    @Test
+    public void testCheckPermission() throws InterruptedException {
+        setUpForReadConfigurationTest();
+        executeTest(new Runnable() {
+            public void run() {
+                PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
+                try {
+                    realm.checkPermission(principalCollection, "role1_permission1");
+                    realm.checkPermissions(principalCollection, new String[]{"role1_permission1", "role2_permission2"});
+                } catch (AuthorizationException ae) {
+                    fail("principal doesn't have permission when it should");
+                }
+            }
+        });
+    }
+
+    /*
+     * Tests that a principal's permissions can't be checked while the realm is being loaded. 
+     */
+    @Test
+    public void testIsPermitted() throws InterruptedException {
+        setUpForReadConfigurationTest();
+        executeTest(new Runnable() {
+            public void run() {
+                PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
+                assertTrue("permission not permitted when it should be", realm.isPermitted(principalCollection, "role1_permission1"));
+                assertTrue("permission not permitted when it should be",
+                        realm.isPermittedAll(principalCollection, new String[]{"role1_permission1", "role2_permission2"}));
+            }
+        });
+    }
+
+    /*
+     * Test that role definitions cannot be updated when a read thread holds the realm's lock.
+     */
+    @Test
+    public void testProcessRoleDefinitions() throws InterruptedException {
+        realm = new TestRealm() {
+            public void test(Thread runnable) throws InterruptedException {
+                // While the realm's lock is held by this thread role definitions cannot be processed
+                // Obtain the realm's locks
+                ROLES_LOCK.writeLock().lock();
+                try {
+                    runnable.start();
+                    Thread.sleep(500);
+                    // No role until lock is released and role definitions are processed
+                    assertFalse("role exists when it shouldn't", realm.roleExists("role1"));
+                } finally {
+                    ROLES_LOCK.writeLock().unlock();
+                }
+            }
+        };
+        // A thread to process new role definitions
+        TestThread testThread = new TestThread(new Runnable() {
+            public void run() {
+                try {
+                    realm.processRoleDefinitions();
+                } catch (ParseException e) {
+                    fail("Unable to parse role definitions");
+                }
+            }
+        });
+        setRoles();
+        Thread testTask = new Thread(testThread);
+        realm.test(testTask);
+        testTask.join(500);
+        assertTrue("role doesn't exist when it should", realm.roleExists("role1"));
+        testThread.test();
+    }
+
+    /*
+     * Test that user definitions cannot be updated when a read thread holds the realm's lock.
+     */
+    @Test
+    public void testProcessUserDefinitions() throws InterruptedException {
+        realm = new TestRealm() {
+            public void test(Thread runnable) throws InterruptedException {
+                // While the realm's lock is held by this thread user definitions cannot be processed
+                // Obtain the realm's locks
+                USERS_LOCK.writeLock().lock();
+                try {
+                    runnable.start();
+                    Thread.sleep(500);
+                    // No account until lock is released and user definitions are processed
+                    assertFalse("account exists when it shouldn't", realm.accountExists("user1"));
+                } finally {
+                    USERS_LOCK.writeLock().unlock();
+                }
+            }
+        };
+        TestThread testThread = new TestThread(new Runnable() {
+            public void run() {
+                try {
+                    realm.processUserDefinitions();
+                } catch (ParseException e) {
+                    fail("Unable to parse user definitions");
+                }
+            }
+        });
+        setUsers();
+        Thread testTask = new Thread(testThread);
+        realm.test(testTask);
+        testTask.join(500);
+        assertTrue("account doesn't exist when it should", realm.accountExists("user1"));
+        testThread.test();
+    }
+
+    /*
+     * A Class that captures a thread's assertion error.
+     */
+    private class TestThread implements Runnable {
+        private Runnable test;
+        private volatile AssertionError ae;
+
+        public TestThread(Runnable test) {
+            this.test = test;
+        }
+
+        public void run() {
+            try {
+                test.run();
+            } catch (AssertionError ae) {
+                this.ae = ae;
+            }
+        }
+
+        public void test() {
+            if (ae != null)
+                throw ae;
+        }
+    }
+
+    /*
+     * Provides an additional method that has access to the realm's lock for mutual exclusion.
+     */
+    private abstract class TestRealm extends TextConfigurationRealm {
+        abstract public void test(Thread runnable) throws InterruptedException;
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManagerTest.java b/core/src/test/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManagerTest.java
new file mode 100644
index 0000000..0b1eceb
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionListener;
+import org.apache.shiro.session.SessionListenerAdapter;
+import org.apache.shiro.session.UnknownSessionException;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for the {@link org.apache.shiro.session.mgt.AbstractValidatingSessionManager} class.
+ */
+public class AbstractValidatingSessionManagerTest {
+
+    /**
+     * Tests that both SessionListeners are called and that invalid sessions are deleted by default.
+     * Verifies <a href="https://issues.apache.org/jira/browse/SHIRO-199">SHIRO-199</a>.
+     */
+    @Test
+    public void testValidateSessions() {
+
+        final SimpleSession validSession = new SimpleSession();
+        validSession.setId(1);
+        final SimpleSession invalidSession = new SimpleSession();
+        //set to a time in the past:
+        Calendar cal = Calendar.getInstance();
+        Long expiredTimeout = AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT + 1;
+        cal.add(Calendar.MILLISECOND, -(expiredTimeout.intValue()));
+        Date past = cal.getTime();
+        invalidSession.setStartTimestamp(past);
+        invalidSession.setLastAccessTime(past);
+        invalidSession.setId(2);
+
+        final AtomicInteger expirationCount = new AtomicInteger();
+
+        SessionListener sessionListener = new SessionListenerAdapter() {
+            @Override
+            public void onExpiration(Session session) {
+                expirationCount.incrementAndGet();
+            }
+        };
+
+        AbstractValidatingSessionManager sessionManager = new AbstractValidatingSessionManager() {
+            @Override
+            protected Session retrieveSession(SessionKey key) throws UnknownSessionException {
+                throw new UnsupportedOperationException("Should not be called in this test.");
+            }
+
+            @Override
+            protected Session doCreateSession(SessionContext initData) throws AuthorizationException {
+                throw new UnsupportedOperationException("Should not be called in this test.");
+            }
+
+            @Override
+            protected Collection<Session> getActiveSessions() {
+                Collection<Session> sessions = new ArrayList<Session>(2);
+                sessions.add(validSession);
+                sessions.add(invalidSession);
+                return sessions;
+            }
+        };
+
+        sessionManager.setSessionListeners(Arrays.asList(sessionListener));
+        sessionManager.validateSessions();
+
+        assertEquals(1, expirationCount.intValue());
+    }
+
+
+    /**
+     * Tests that no memory leak exists on invalid sessions: expired or stopped
+     * Verifies <a href="https://issues.apache.org/jira/browse/SHIRO-399">SHIRO-399</a>.
+     */
+    @Test
+    public void testNoMemoryLeakOnInvalidSessions() throws Exception {
+        SessionListener sessionListener = new SessionListener() {
+            public void onStart(Session session) {
+                session.setAttribute("I love", "Romania");
+            }
+
+            public void onStop(Session session) {
+                tryToCleanSession(session);
+            }
+
+            public void onExpiration(Session session) {
+                tryToCleanSession(session);
+            }
+
+            private void tryToCleanSession(Session session) {
+                Collection<Object> keys = session.getAttributeKeys();
+                for (Object key : keys) {
+                    session.removeAttribute(key);
+                }
+            }
+        };
+
+        DefaultSessionManager sessionManager = new DefaultSessionManager();
+        sessionManager.setSessionListeners(Arrays.asList(sessionListener));
+
+        Session session = sessionManager.start(null);
+        assertEquals(1, sessionManager.getActiveSessions().size());
+
+        session.setTimeout(0L);
+        //last access timestamp needs to be older than the current timestamp when validating, so ensure a delay:
+        Thread.sleep(1);
+
+        sessionManager.validateSessions();
+
+        assertEquals(0, sessionManager.getActiveSessions().size());
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java b/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
new file mode 100644
index 0000000..9723146
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/session/mgt/DefaultSessionManagerTest.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.*;
+import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.apache.shiro.util.ThreadContext;
+import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.UUID;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit test for the {@link DefaultSessionManager DefaultSessionManager} implementation.
+ */
+public class DefaultSessionManagerTest {
+
+    DefaultSessionManager sm = null;
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+        sm = new DefaultSessionManager();
+    }
+
+    @After
+    public void tearDown() {
+        sm.destroy();
+        ThreadContext.remove();
+    }
+
+    public void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Test
+    public void testGlobalTimeout() {
+        long timeout = 1000;
+        sm.setGlobalSessionTimeout(timeout);
+        Session session = sm.start(null);
+        assertNotNull(session);
+        assertNotNull(session.getId());
+        assertEquals(session.getTimeout(), timeout);
+    }
+
+    @Test
+    public void testSessionListenerStartNotification() {
+        final boolean[] started = new boolean[1];
+        SessionListener listener = new SessionListenerAdapter() {
+            public void onStart(Session session) {
+                started[0] = true;
+            }
+        };
+        sm.getSessionListeners().add(listener);
+        sm.start(null);
+        assertTrue(started[0]);
+    }
+
+    @Test
+    public void testSessionListenerStopNotification() {
+        final boolean[] stopped = new boolean[1];
+        SessionListener listener = new SessionListenerAdapter() {
+            public void onStop(Session session) {
+                stopped[0] = true;
+            }
+        };
+        sm.getSessionListeners().add(listener);
+        Session session = sm.start(null);
+        sm.stop(new DefaultSessionKey(session.getId()));
+        assertTrue(stopped[0]);
+    }
+
+    //asserts fix for SHIRO-388:
+    //Ensures that a session attribute can be accessed in the listener without
+    //causing a stack overflow exception.
+    @Test
+    public void testSessionListenerStopNotificationWithReadAttribute() {
+        final boolean[] stopped = new boolean[1];
+        final String[] value = new String[1];
+        SessionListener listener = new SessionListenerAdapter() {
+            public void onStop(Session session) {
+                stopped[0] = true;
+                value[0] = (String)session.getAttribute("foo");
+            }
+        };
+        sm.getSessionListeners().add(listener);
+        Session session = sm.start(null);
+        session.setAttribute("foo", "bar");
+
+        sm.stop(new DefaultSessionKey(session.getId()));
+
+        assertTrue(stopped[0]);
+        assertEquals("bar", value[0]);
+    }
+
+    @Test
+    public void testSessionListenerExpiredNotification() {
+        final boolean[] expired = new boolean[1];
+        SessionListener listener = new SessionListenerAdapter() {
+            public void onExpiration(Session session) {
+                expired[0] = true;
+            }
+        };
+        sm.getSessionListeners().add(listener);
+        sm.setGlobalSessionTimeout(100);
+        Session session = sm.start(null);
+        sleep(150);
+        try {
+            sm.checkValid(new DefaultSessionKey(session.getId()));
+            fail("check should have thrown an exception.");
+        } catch (InvalidSessionException expected) {
+            //do nothing - expected.
+        }
+        assertTrue(expired[0]);
+    }
+
+    @Test
+    public void testSessionDeleteOnExpiration() {
+        sm.setGlobalSessionTimeout(100);
+
+        SessionDAO sessionDAO = createMock(SessionDAO.class);
+        sm.setSessionDAO(sessionDAO);
+
+        String sessionId1 = UUID.randomUUID().toString();
+        final SimpleSession session1 = new SimpleSession();
+        session1.setId(sessionId1);
+
+        final Session[] activeSession = new SimpleSession[]{session1};
+        sm.setSessionFactory(new SessionFactory() {
+            public Session createSession(SessionContext initData) {
+                return activeSession[0];
+            }
+        });
+
+        expect(sessionDAO.create(eq(session1))).andReturn(sessionId1);
+        sessionDAO.update(eq(session1));
+        expectLastCall().anyTimes();
+        replay(sessionDAO);
+        Session session = sm.start(null);
+        assertNotNull(session);
+        verify(sessionDAO);
+        reset(sessionDAO);
+
+        expect(sessionDAO.readSession(sessionId1)).andReturn(session1).anyTimes();
+        sessionDAO.update(eq(session1));
+        replay(sessionDAO);
+        sm.setTimeout(new DefaultSessionKey(sessionId1), 1);
+        verify(sessionDAO);
+        reset(sessionDAO);
+
+        sleep(20);
+
+        expect(sessionDAO.readSession(sessionId1)).andReturn(session1);
+        sessionDAO.update(eq(session1)); //update's the stop timestamp
+        sessionDAO.delete(session1);
+        replay(sessionDAO);
+
+        //Try to access the same session, but it should throw an UnknownSessionException due to timeout:
+        try {
+            sm.getTimeout(new DefaultSessionKey(sessionId1));
+            fail("Session with id [" + sessionId1 + "] should have expired due to timeout.");
+        } catch (ExpiredSessionException expected) {
+            //expected
+        }
+
+        verify(sessionDAO); //verify that the delete call was actually made on the DAO
+    }
+
+    public static <T extends Session> T eqSessionTimeout(long timeout) {
+        EasyMock.reportMatcher(new SessionTimeoutMatcher(timeout));
+        return null;
+    }
+
+    private static class SessionTimeoutMatcher implements IArgumentMatcher {
+
+        private final long timeout;
+
+        public SessionTimeoutMatcher(long timeout) {
+            this.timeout = timeout;
+        }
+
+        public void appendTo(StringBuffer buffer) {
+            buffer.append("eqSession(timeout=").append(this.timeout).append(")");
+        }
+
+        public boolean matches(Object o) {
+            return o instanceof Session && ((Session) o).getTimeout() == this.timeout;
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java b/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
new file mode 100644
index 0000000..b28c4d4
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/session/mgt/DelegatingSessionTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit test for the {@link DelegatingSession} class.
+ */
+public class DelegatingSessionTest {
+
+    DelegatingSession session = null;
+    DefaultSessionManager sm = null;
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+        sm = new DefaultSessionManager();
+        this.session = new DelegatingSession(sm, new DefaultSessionKey(sm.start(null).getId()));
+    }
+
+    @After
+    public void tearDown() {
+        sm.destroy();
+        ThreadContext.remove();
+    }
+
+    public void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Test
+    public void testTimeout() {
+        Serializable origId = session.getId();
+        assertEquals(session.getTimeout(), AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
+        session.touch();
+        session.setTimeout(100);
+        assertEquals(100, session.getTimeout());
+        sleep(150);
+        try {
+            session.getTimeout();
+            fail("Session should have expired.");
+        } catch (ExpiredSessionException expected) {
+        }
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/session/mgt/SimpleSessionTest.java b/core/src/test/java/org/apache/shiro/session/mgt/SimpleSessionTest.java
new file mode 100644
index 0000000..db73448
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/session/mgt/SimpleSessionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt;
+
+import org.junit.Test;
+
+import java.io.*;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SimpleSessionTest {
+
+    @Test
+    public void testDefaultSerialization() throws Exception {
+        SimpleSession session = new SimpleSession();
+
+        long timeout = session.getTimeout();
+        Date start = session.getStartTimestamp();
+        Date lastAccess = session.getLastAccessTime();
+
+        SimpleSession deserialized = serializeAndDeserialize(session);
+
+        assertEquals(timeout, deserialized.getTimeout());
+        assertEquals(start, deserialized.getStartTimestamp());
+        assertEquals(lastAccess, deserialized.getLastAccessTime());
+    }
+
+    @Test
+    public void serializeHost() throws IOException, ClassNotFoundException {
+        SimpleSession session = new SimpleSession("localhost");
+        assertEquals("localhost", serializeAndDeserialize(session).getHost());
+    }
+
+    @Test
+    public void serializeExpired() throws IOException, ClassNotFoundException {
+        SimpleSession session = new SimpleSession();
+        session.setExpired(true);
+        assertTrue(serializeAndDeserialize(session).isExpired());
+    }
+
+    private SimpleSession serializeAndDeserialize(SimpleSession session) throws IOException, ClassNotFoundException {
+        ByteArrayOutputStream serialized = new ByteArrayOutputStream();
+        ObjectOutputStream serializer = new ObjectOutputStream(serialized);
+        serializer.writeObject(session);
+        serializer.close();
+        return (SimpleSession) new ObjectInputStream(new ByteArrayInputStream(serialized.toByteArray())).readObject();
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java
new file mode 100644
index 0000000..d1acc72
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.subject;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.junit.Assert.*;
+
+
+/**
+ * @since Aug 1, 2008 2:11:17 PM
+ */
+public class DelegatingSubjectTest {
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+    }
+
+    @After
+    public void tearDown() {
+        ThreadContext.remove();
+    }
+
+    @Test
+    public void testSessionStopThenStart() {
+        String key = "testKey";
+        String value = "testValue";
+        DefaultSecurityManager sm = new DefaultSecurityManager();
+
+        DelegatingSubject subject = new DelegatingSubject(sm);
+
+        Session session = subject.getSession();
+        session.setAttribute(key, value);
+        assertTrue(session.getAttribute(key).equals(value));
+        Serializable firstSessionId = session.getId();
+        assertNotNull(firstSessionId);
+
+        session.stop();
+
+        session = subject.getSession();
+        assertNotNull(session);
+        assertNull(session.getAttribute(key));
+        Serializable secondSessionId = session.getId();
+        assertNotNull(secondSessionId);
+        assertFalse(firstSessionId.equals(secondSessionId));
+
+        subject.logout();
+
+        sm.destroy();
+    }
+
+    @Test
+    public void testExecuteCallable() {
+
+        String username = "jsmith";
+
+        SecurityManager securityManager = createNiceMock(SecurityManager.class);
+        PrincipalCollection identity = new SimplePrincipalCollection(username, "testRealm");
+        final Subject sourceSubject = new DelegatingSubject(identity, true, null, null, securityManager);
+
+        assertNull(ThreadContext.getSubject());
+        assertNull(ThreadContext.getSecurityManager());
+
+        Callable<String> callable = new Callable<String>() {
+            public String call() throws Exception {
+                Subject callingSubject = SecurityUtils.getSubject();
+                assertNotNull(callingSubject);
+                assertNotNull(SecurityUtils.getSecurityManager());
+                assertEquals(callingSubject, sourceSubject);
+                return "Hello " + callingSubject.getPrincipal();
+            }
+        };
+        String response = sourceSubject.execute(callable);
+
+        assertNotNull(response);
+        assertEquals("Hello " + username, response);
+
+        assertNull(ThreadContext.getSubject());
+        assertNull(ThreadContext.getSecurityManager());
+    }
+
+    @Test
+    public void testExecuteRunnable() {
+
+        String username = "jsmith";
+
+        SecurityManager securityManager = createNiceMock(SecurityManager.class);
+        PrincipalCollection identity = new SimplePrincipalCollection(username, "testRealm");
+        final Subject sourceSubject = new DelegatingSubject(identity, true, null, null, securityManager);
+
+        assertNull(ThreadContext.getSubject());
+        assertNull(ThreadContext.getSecurityManager());
+
+        Runnable runnable = new Runnable() {
+            public void run() {
+                Subject callingSubject = SecurityUtils.getSubject();
+                assertNotNull(callingSubject);
+                assertNotNull(SecurityUtils.getSecurityManager());
+                assertEquals(callingSubject, sourceSubject);
+            }
+        };
+        sourceSubject.execute(runnable);
+
+        assertNull(ThreadContext.getSubject());
+        assertNull(ThreadContext.getSecurityManager());
+    }
+
+    @Test
+    public void testRunAs() {
+
+        Ini ini = new Ini();
+        Ini.Section users = ini.addSection("users");
+        users.put("user1", "user1,role1");
+        users.put("user2", "user2,role2");
+        users.put("user3", "user3,role3");
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini);
+        SecurityManager sm = factory.getInstance();
+
+        //login as user1
+        Subject subject = new Subject.Builder(sm).buildSubject();
+        subject.login(new UsernamePasswordToken("user1", "user1"));
+
+        assertFalse(subject.isRunAs());
+        assertEquals("user1", subject.getPrincipal());
+        assertTrue(subject.hasRole("role1"));
+        assertFalse(subject.hasRole("role2"));
+        assertFalse(subject.hasRole("role3"));
+        assertNull(subject.getPreviousPrincipals()); //no previous principals since we haven't called runAs yet
+
+        //runAs user2:
+        subject.runAs(new SimplePrincipalCollection("user2", IniSecurityManagerFactory.INI_REALM_NAME));
+        assertTrue(subject.isRunAs());
+        assertEquals("user2", subject.getPrincipal());
+        assertTrue(subject.hasRole("role2"));
+        assertFalse(subject.hasRole("role1"));
+        assertFalse(subject.hasRole("role3"));
+
+        //assert we still have the previous (user1) principals:
+        PrincipalCollection previous = subject.getPreviousPrincipals();
+        assertFalse(CollectionUtils.isEmpty(previous));
+        assertTrue(previous.getPrimaryPrincipal().equals("user1"));
+
+        //test the stack functionality:  While as user2, run as user3:
+        subject.runAs(new SimplePrincipalCollection("user3", IniSecurityManagerFactory.INI_REALM_NAME));
+        assertTrue(subject.isRunAs());
+        assertEquals("user3", subject.getPrincipal());
+        assertTrue(subject.hasRole("role3"));
+        assertFalse(subject.hasRole("role1"));
+        assertFalse(subject.hasRole("role2"));
+
+        //assert we still have the previous (user2) principals in the stack:
+        previous = subject.getPreviousPrincipals();
+        assertFalse(CollectionUtils.isEmpty(previous));
+        assertTrue(previous.getPrimaryPrincipal().equals("user2"));
+
+        //drop down to user2:
+        subject.releaseRunAs();
+
+        //assert still run as:
+        assertTrue(subject.isRunAs());
+        assertEquals("user2", subject.getPrincipal());
+        assertTrue(subject.hasRole("role2"));
+        assertFalse(subject.hasRole("role1"));
+        assertFalse(subject.hasRole("role3"));
+
+        //assert we still have the previous (user1) principals:
+        previous = subject.getPreviousPrincipals();
+        assertFalse(CollectionUtils.isEmpty(previous));
+        assertTrue(previous.getPrimaryPrincipal().equals("user1"));
+
+        //drop down to original user1:
+        subject.releaseRunAs();
+
+        //assert we're no longer runAs:
+        assertFalse(subject.isRunAs());
+        assertEquals("user1", subject.getPrincipal());
+        assertTrue(subject.hasRole("role1"));
+        assertFalse(subject.hasRole("role2"));
+        assertFalse(subject.hasRole("role3"));
+        assertNull(subject.getPreviousPrincipals()); //no previous principals in orig state
+
+        subject.logout();
+
+        LifecycleUtils.destroy(sm);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java b/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java
new file mode 100644
index 0000000..f58f4df
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/test/AbstractShiroTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.UnavailableSecurityManagerException;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.ThreadState;
+import org.junit.AfterClass;
+
+/**
+ * Abstract test case showing how to use Shiro in testing environments.
+ *
+ * @since 1.2
+ */
+public abstract class AbstractShiroTest {
+
+    private static ThreadState subjectThreadState;
+
+    public AbstractShiroTest() {
+    }
+
+    /**
+     * Allows subclasses to set the currently executing {@link Subject} instance.
+     *
+     * @param subject the Subject instance
+     */
+    protected void setSubject(Subject subject) {
+        clearSubject();
+        subjectThreadState = createThreadState(subject);
+        subjectThreadState.bind();
+    }
+
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    protected ThreadState createThreadState(Subject subject) {
+        return new SubjectThreadState(subject);
+    }
+
+    /**
+     * Clears Shiro's thread state, ensuring the thread remains clean for future test execution.
+     */
+    protected void clearSubject() {
+        doClearSubject();
+    }
+
+    private static void doClearSubject() {
+        if (subjectThreadState != null) {
+            subjectThreadState.clear();
+            subjectThreadState = null;
+        }
+    }
+
+    protected static void setSecurityManager(SecurityManager securityManager) {
+        SecurityUtils.setSecurityManager(securityManager);
+    }
+
+    protected static SecurityManager getSecurityManager() {
+        return SecurityUtils.getSecurityManager();
+    }
+
+    @AfterClass
+    public static void tearDownShiro() {
+        doClearSubject();
+        try {
+            SecurityManager securityManager = getSecurityManager();
+            LifecycleUtils.destroy(securityManager);
+        } catch (UnavailableSecurityManagerException e) {
+            //we don't care about this when cleaning up the test environment
+            //(for example, maybe the subclass is a unit test and it didn't
+            // need a SecurityManager instance because it was using only mock Subject instances)
+        }
+        setSecurityManager(null);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/test/ExampleShiroIntegrationTest.java b/core/src/test/java/org/apache/shiro/test/ExampleShiroIntegrationTest.java
new file mode 100644
index 0000000..17f94e7
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/test/ExampleShiroIntegrationTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.Factory;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Simple example test class to be used to show how one might write Shiro-compatible unit tests.
+ *
+ * @since 1.2
+ */
+public class ExampleShiroIntegrationTest extends AbstractShiroTest {
+
+    @BeforeClass
+    public static void beforeClass() {
+        //0.  Build and set the SecurityManager used to build Subject instances used in your tests
+        //    This typically only needs to be done once per class if your shiro.ini doesn't change,
+        //    otherwise, you'll need to do this logic in each test that is different
+        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:test.shiro.ini");
+        setSecurityManager(factory.getInstance());
+    }
+
+    @Test
+    public void testSimple() {
+        //1.  Build the Subject instance for the test to run:
+        Subject subjectUnderTest = new Subject.Builder(getSecurityManager()).buildSubject();
+
+        //2. Bind the subject to the current thread:
+        setSubject(subjectUnderTest);
+
+        //perform test logic here.  Any call to
+        //SecurityUtils.getSubject() directly (or nested in the
+        //call stack) will work properly.
+    }
+
+    @After
+    public void tearDownSubject() {
+        //3. Unbind the subject from the current thread:
+        clearSubject();
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/test/ExampleShiroUnitTest.java b/core/src/test/java/org/apache/shiro/test/ExampleShiroUnitTest.java
new file mode 100644
index 0000000..d9dd473
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/test/ExampleShiroUnitTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import org.apache.shiro.subject.Subject;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+
+/**
+ * Simple example test class showing how one may perform unit tests for code that requires Shiro APIs.
+ *
+ * @since 1.2
+ */
+public class ExampleShiroUnitTest extends AbstractShiroTest {
+
+    @Test
+    public void testSimple() {
+        //1.  Create a mock Subject instance for the test to run
+        //    (for example, as an authenticated Subject):
+        Subject subjectUnderTest = createNiceMock(Subject.class);
+
+        expect(subjectUnderTest.isAuthenticated()).andReturn(true);
+
+        //2. Bind the subject to the current thread:
+        setSubject(subjectUnderTest);
+
+        //perform test logic here.  Any call to
+        //SecurityUtils.getSubject() directly (or nested in the
+        //call stack) will work properly.
+    }
+
+    @After
+    public void tearDownSubject() {
+        //3. Unbind the subject from the current thread:
+        clearSubject();
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/test/SecurityManagerTestSupport.java b/core/src/test/java/org/apache/shiro/test/SecurityManagerTestSupport.java
new file mode 100644
index 0000000..3cc47bd
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/test/SecurityManagerTestSupport.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Utility methods for use by Shiro test case subclasses.  You can use these methods as examples for your own
+ * test cases, but you SHOULD NOT use any ThreadContext API calls in your actual application code.
+ * The utility methods here make heavy assumptions about Shiro's implementation details, and your
+ * application code should definitely not.
+ * <p/>
+ * See the <a href="http://cwiki.apache.org/confluence/display/SHIRO/Subject">wiki Subject documentation</a>
+ * for proper application practices using Subject instances with threads.
+ */
+public class SecurityManagerTestSupport {
+
+    protected static SecurityManager createTestSecurityManager() {
+        Ini ini = new Ini();
+        ini.setSectionProperty("users", "test", "test");
+        return new DefaultSecurityManager(new IniRealm(ini));
+    }
+
+    protected void destroy(SecurityManager sm) {
+        LifecycleUtils.destroy(sm);
+    }
+
+    protected SecurityManager createAndBindTestSecurityManager() {
+        SecurityManager sm = createTestSecurityManager();
+        ThreadContext.bind(sm);
+        return sm;
+    }
+
+    protected Subject createAndBindTestSubject() {
+        SecurityManager sm = ThreadContext.getSecurityManager();
+        if (sm == null) {
+            createAndBindTestSecurityManager();
+        }
+        return SecurityUtils.getSubject();
+    }
+
+    @Before
+    public void setup() {
+        createAndBindTestSubject();
+    }
+
+    @After
+    public void teardown() {
+        ThreadContext.remove();
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/util/RegExPatternMatcherTest.java b/core/src/test/java/org/apache/shiro/util/RegExPatternMatcherTest.java
new file mode 100644
index 0000000..5246ba6
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/util/RegExPatternMatcherTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.regex.Pattern;
+
+/**
+ * Unit tests for the {@link RegExPatternMatcher}.
+ *
+ * @since 1.0
+ */
+public class RegExPatternMatcherTest {
+
+    @Test
+    public void testSimplePattern() {
+        PatternMatcher pm = new RegExPatternMatcher();
+        String pattern = "a*b";
+        String test = "aaaaaaab";
+        //not necessary for the test, but Idea performs auto validation when it sees this:
+        Pattern.compile(pattern);
+        assertTrue(pm.matches(pattern, test));
+    }
+
+}
diff --git a/core/src/test/java/org/apache/shiro/util/StringUtilsTest.java b/core/src/test/java/org/apache/shiro/util/StringUtilsTest.java
new file mode 100644
index 0000000..99873a5
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/util/StringUtilsTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+
+/**
+ * @since 0.9
+ */
+public class StringUtilsTest {
+
+    @Test
+    public void splitWithNullInput() {
+        String line = null;
+        String[] split = StringUtils.split(line);
+        assertNull(split);
+    }
+
+    @Test
+    public void splitWithCommas() {
+        String line = "shall,we,play,a,game?";
+        String[] split = StringUtils.split(line);
+        assertNotNull(split);
+        assertTrue(split.length == 5);
+        assertEquals("shall", split[0]);
+        assertEquals("we", split[1]);
+        assertEquals("play", split[2]);
+        assertEquals("a", split[3]);
+        assertEquals("game?", split[4]);
+    }
+
+    @Test
+    public void splitWithCommasAndSpaces() {
+        String line = "shall,we ,    play, a,game?";
+        String[] split = StringUtils.split(line);
+        assertNotNull(split);
+        assertTrue(split.length == 5);
+        assertEquals("shall", split[0]);
+        assertEquals("we", split[1]);
+        assertEquals("play", split[2]);
+        assertEquals("a", split[3]);
+        assertEquals("game?", split[4]);
+    }
+
+    @Test
+    public void splitWithQuotedCommasAndSpaces() {
+        String line = "shall, \"we, play\", a, game?";
+        String[] split = StringUtils.split(line);
+        assertNotNull(split);
+        assertTrue(split.length == 4);
+        assertEquals("shall", split[0]);
+        assertEquals("we, play", split[1]);
+        assertEquals("a", split[2]);
+        assertEquals("game?", split[3]);
+    }
+
+    @Test
+    public void splitWithQuotedCommasAndSpacesAndDifferentQuoteChars() {
+        String line = "authc, test[blah], test[1,2,3], test[]";
+        String[] split = StringUtils.split(line, ',', '[', ']', false, true);
+        assertNotNull(split);
+        assertTrue(split.length == 4);
+        assertEquals("authc", split[0]);
+        assertEquals("testblah", split[1]);
+        assertEquals("test1,2,3", split[2]);
+        assertEquals("test", split[3]);
+    }
+
+    @Test
+    public void splitWithQuotedCommasAndSpacesAndDifferentQuoteCharsWhileRetainingQuotes() {
+        String line = "authc, test[blah], test[1,2,3], test[]";
+        String[] split = StringUtils.split(line, ',', '[', ']', true, true);
+        assertNotNull(split);
+        assertTrue(split.length == 4);
+        assertEquals("authc", split[0]);
+        assertEquals("test[blah]", split[1]);
+        assertEquals("test[1,2,3]", split[2]);
+        assertEquals("test[]", split[3]);
+    }
+
+    @Test
+    public void splitTestWithQuotedCommas() {
+        String line = "authc, test[blah], test[\"1,2,3\"], test[]";
+        String[] split = StringUtils.split(line);
+        assertNotNull(split);
+        assertTrue(split.length == 4);
+        assertEquals("authc", split[0]);
+        assertEquals("test[blah]", split[1]);
+        assertEquals("test[1,2,3]", split[2]);
+        assertEquals("test[]", split[3]);
+    }
+
+    @Test
+    public void splitWithQuotedCommasAndSpacesAndEscapedQuotes() {
+        String line = "shall, \"\"\"we, play\", a, \"\"\"game?";
+        String[] split = StringUtils.split(line);
+        assertNotNull(split);
+        assertTrue(split.length == 4);
+        assertEquals("shall", split[0]);
+        assertEquals("\"we, play", split[1]);
+        assertEquals("a", split[2]);
+        assertEquals("\"game?", split[3]);
+    }
+
+}
diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..acf1e2a
--- /dev/null
+++ b/core/src/test/resources/log4j.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+log4j.rootLogger=TRACE, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# Pattern to output: date priority [category] - message
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
+log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
+
+# Spring logging level is WARN
+log4j.logger.org.springframework=WARN
+
+# General Apache libraries is WARN
+log4j.logger.org.apache=WARN
+
+log4j.logger.net.sf.ehcache=WARN
+
+log4j.logger.org.apache.shiro=TRACE
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
\ No newline at end of file
diff --git a/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.ini b/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.ini
new file mode 100644
index 0000000..28657e6
--- /dev/null
+++ b/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.ini
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[users]
+user1 = user1, admin
+
+[roles]
+admin = *
\ No newline at end of file
diff --git a/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.propsRealm.properties b/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.propsRealm.properties
new file mode 100644
index 0000000..9170a27
--- /dev/null
+++ b/core/src/test/resources/org/apache/shiro/config/IniSecurityManagerFactoryTest.propsRealm.properties
@@ -0,0 +1,30 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# ------------------------------
+# Users and their assigned roles
+# ------------------------------
+# user 'root' with password 'secret' and the 'root' role
+user.root = secret,root
+
+# -------------------------------
+# Roles with assigned permissions
+# -------------------------------
+# 'root' role has all permissions, indicated by the wildcard '*'
+role.root = *
diff --git a/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.noUsers.ini b/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.noUsers.ini
new file mode 100644
index 0000000..69e3acb
--- /dev/null
+++ b/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.noUsers.ini
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[roles]
+admin = *
diff --git a/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.simple.ini b/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.simple.ini
new file mode 100644
index 0000000..d3c81aa
--- /dev/null
+++ b/core/src/test/resources/org/apache/shiro/realm/text/IniRealmTest.simple.ini
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[users]
+user1 = user1, admin
+
+[roles]
+admin = *
+
diff --git a/core/src/test/resources/test.shiro.ini b/core/src/test/resources/test.shiro.ini
new file mode 100644
index 0000000..40a3bba
--- /dev/null
+++ b/core/src/test/resources/test.shiro.ini
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[users]
+jsmith = jsmith,role1
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..e8738dd
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,1018 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- pick up Apache distributionManagement for releasing (snapshots, releases, etc): -->
+    <parent>
+        <groupId>org.apache</groupId>
+        <artifactId>apache</artifactId>
+        <version>7</version>
+    </parent>
+
+    <groupId>org.apache.shiro</groupId>
+    <artifactId>shiro-root</artifactId>
+    <packaging>pom</packaging>
+    <version>1.2.2</version>
+
+    <name>Apache Shiro</name>
+    <url>http://shiro.apache.org/</url>
+    <description>
+        Apache Shiro is a powerful and flexible open-source security framework that cleanly handles
+        authentication, authorization, enterprise session management, single sign-on and cryptography services.
+    </description>
+    <inceptionYear>2004</inceptionYear>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/shiro/tags/shiro-root-1.2.2</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/shiro/tags/shiro-root-1.2.2</developerConnection>
+        <url>http://svn.apache.org/repos/asf/shiro/tags/shiro-root-1.2.2</url>
+    </scm>
+    <issueManagement>
+        <system>Jira</system>
+        <url>http://issues.apache.org/jira/browse/SHIRO</url>
+    </issueManagement>
+    <ciManagement>
+        <system>Hudson</system>
+        <url>http://hudson.zones.apache.org/hudson/view/Shiro/</url>
+    </ciManagement>
+
+    <distributionManagement>
+        <site>
+            <id>shiro.website</id>
+            <name>Apache Shiro Site</name>
+            <url>scp://people.apache.org/www/shiro.apache.org/static/latest</url>
+        </site>
+    </distributionManagement>
+
+    <properties>
+
+        <!-- Replaced by the build number plugin at build time: -->
+        <buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
+
+        <!-- non-dependency-based properties: -->
+        <shiro.osgi.importRange>[1.2, 2)</shiro.osgi.importRange>
+
+        <!-- Compile 3rd party dependencies: -->
+        <!-- Don't change this version without also changing the shiro-aspect and shiro-features
+             modules' OSGi metadata: -->
+        <aspectj.version>1.6.12</aspectj.version>
+        <commons.cli.version>1.2</commons.cli.version>
+        <commons.codec.version>1.4</commons.codec.version>
+        <crowd.version>1.5.2</crowd.version>
+        <!-- Don't change this version without also changing the shiro-ehcache and shiro-features
+             modules' OSGi metadata: -->
+        <ehcache.version>2.5.0</ehcache.version>
+        <hsqldb.version>1.8.0.7</hsqldb.version>
+        <jdk.version>1.5</jdk.version>
+        <jetty.version>6.1.26</jetty.version>
+        <!-- Don't change this version without also changing the shiro-quartz and shiro-features
+             modules' OSGi metadata: -->
+        <quartz.version>1.6.1</quartz.version>
+        <slf4j.version>1.6.4</slf4j.version>
+        <spring.version>3.1.0.RELEASE</spring.version>
+        <guice.version>3.0</guice.version>
+        <guava.version>r09</guava.version>
+
+        <!-- Test 3rd-party dependencies: -->
+        <easymock.version>3.1</easymock.version>
+        <gmaven.version>1.3</gmaven.version>
+        <groovy.version>1.8.5</groovy.version>
+        <junit.version>4.8.2</junit.version>
+
+    </properties>
+
+    <mailingLists>
+        <mailingList>
+            <name>Apache Shiro Users Mailing List</name>
+            <subscribe>user-subscribe at shiro.apache.org</subscribe>
+            <unsubscribe>user-unsubscribe at shiro.apache.org</unsubscribe>
+            <post>user at shiro.apache.org</post>
+            <!--archive/-->
+            <!--otherArchives-->
+        </mailingList>
+        <mailingList>
+            <name>Apache Shiro Developers Mailing List</name>
+            <subscribe>dev-subscribe at shiro.apache.org</subscribe>
+            <unsubscribe>dev-unsubscribe at shiro.apache.org</unsubscribe>
+            <post>dev at shiro.apache.org</post>
+            <!--archive/-->
+            <!--otherArchives-->
+        </mailingList>
+    </mailingLists>
+
+
+    <developers>
+        <developer>
+            <id>aditzel</id>
+            <name>Allan Ditzel</name>
+            <email>aditzel at apache.org</email>
+            <url>http://www.allanditzel.com</url>
+            <organization>Apache Software Foundation</organization>
+            <timezone>-5</timezone>
+        </developer>
+        <developer>
+            <id>jhaile</id>
+            <name>Jeremy Haile</name>
+            <email>jhaile at apache.org</email>
+            <url>http://www.jeremyhaile.com</url>
+            <organization>Mobilization Labs</organization>
+            <organizationUrl>http://www.mobilizationlabs.com</organizationUrl>            
+            <timezone>-5</timezone>
+        </developer>
+        <developer>
+            <id>lhazlewood</id>
+            <name>Les Hazlewood</name>
+            <email>lhazlewood at apache.org</email>
+            <url>http://www.leshazlewood.com</url>
+            <organization>Katasoft</organization>
+            <organizationUrl>http://www.katasoft.com</organizationUrl>
+            <timezone>-8</timezone>
+        </developer>
+        <developer>
+            <id>kaosko</id>
+            <name>Kalle Korhonen</name>
+            <email>kaosko at apache.org</email>
+            <url>http://tynamo.org</url>
+            <organization>Apache Software Foundation</organization>
+            <timezone>-8</timezone>
+        </developer>
+        <developer>
+            <id>pledbrook</id>
+            <name>Peter Ledbrook</name>
+            <email>p.ledbrook at cacoethes.co.uk</email>
+            <url>http://www.cacoethes.co.uk/blog/</url>
+            <organization>SpringSource</organization>
+            <organizationUrl>http://www.springsource.com/</organizationUrl>
+            <timezone>0</timezone>
+        </developer>
+        <developer>
+            <id>tveil</id>
+            <name>Tim Veil</name>
+            <email>tveil at apache.org</email>
+        </developer>
+        <developer>
+            <id>bdemers</id>
+            <name>Brian Demers</name>
+            <email>bdemers at apache.org</email>
+            <url>http://about.me/brian.demers</url>
+            <organization>Sonatype</organization>
+            <organizationUrl>http://www.sonatype.com/</organizationUrl>
+            <timezone>-5</timezone>
+        </developer>
+        <developer>
+            <id>jbunting</id>
+            <name>Jared Bunting</name>
+            <email>jbunting at apache.org</email>
+            <organization>Apache Software Foundation</organization>
+            <timezone>-6</timezone>
+        </developer>
+    </developers>
+
+    <modules>
+        <module>core</module>
+        <module>web</module>
+        <module>support</module>
+        <module>samples</module>
+        <module>tools</module>
+        <module>all</module>
+    </modules>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.felix</groupId>
+                    <artifactId>maven-bundle-plugin</artifactId>
+                    <version>2.1.0</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-gpg-plugin</artifactId>
+                    <version>1.1</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-site-plugin</artifactId>
+                    <version>3.1</version>
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.apache.maven.wagon</groupId>
+                            <artifactId>wagon-ssh</artifactId>
+                            <version>2.2</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.rat</groupId>
+                    <artifactId>apache-rat-plugin</artifactId>
+                    <version>0.8</version>
+                    <configuration>
+                    		<!-- note that this configuration needs to be maintain both in pluginManagement and reporting sections -->
+                        <excludes>
+                            <exclude>**/.externalToolBuilders/*</exclude>
+                            <exclude>**/infinitest.filters</exclude>
+                            <!-- Apparently some test in samples/spring-client generates velocity log - would better to reconfigure to output to target/ -->
+                            <exclude>velocity.log</exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>versions-maven-plugin</artifactId>
+                    <version>1.2</version>
+                </plugin>
+                <!-- Allow writing tests in Groovy: -->
+                <plugin>
+                    <groupId>org.codehaus.gmaven</groupId>
+                    <artifactId>gmaven-plugin</artifactId>
+                    <version>${gmaven.version}</version>
+                    <configuration>
+                        <providerSelection>1.7</providerSelection>
+                        <source>src/main/groovy</source>
+                    </configuration>
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.codehaus.gmaven.runtime</groupId>
+                            <artifactId>gmaven-runtime-1.7</artifactId>
+                            <version>${gmaven.version}</version>
+                            <exclusions>
+                                <exclusion>
+                                    <groupId>org.codehaus.groovy</groupId>
+                                    <artifactId>groovy-all</artifactId>
+                                </exclusion>
+                            </exclusions>
+                        </dependency>
+                        <dependency>
+                            <groupId>org.codehaus.groovy</groupId>
+                            <artifactId>groovy-all</artifactId>
+                            <version>${groovy.version}</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+                <plugin>
+                	<groupId>org.eclipse.m2e</groupId>
+                	<artifactId>lifecycle-mapping</artifactId>
+                	<version>1.0.0</version>
+                	<configuration>
+                		<lifecycleMappingMetadata>
+                			<pluginExecutions>
+                				<pluginExecution>
+                					<pluginExecutionFilter>
+                						<groupId>
+                							org.codehaus.mojo
+                						</groupId>
+                						<artifactId>
+                							aspectj-maven-plugin
+                						</artifactId>
+                						<versionRange>
+                							[1.3,)
+                						</versionRange>
+                						<goals>
+                							<goal>test-compile</goal>
+                							<goal>compile</goal>
+                						</goals>
+                					</pluginExecutionFilter>
+                					<action>
+                						<ignore />
+                					</action>
+                				</pluginExecution>
+                				<pluginExecution>
+                					<pluginExecutionFilter>
+                						<groupId>
+                							org.apache.maven.plugins
+                						</groupId>
+                						<artifactId>
+                							maven-antrun-plugin
+                						</artifactId>
+                						<versionRange>
+                							[1.3,)
+                						</versionRange>
+                						<goals>
+                							<goal>run</goal>
+                						</goals>
+                					</pluginExecutionFilter>
+                					<action>
+                						<ignore />
+                					</action>
+                				</pluginExecution>
+                				<pluginExecution>
+                					<pluginExecutionFilter>
+                						<groupId>
+                							org.codehaus.mojo
+                						</groupId>
+                						<artifactId>
+                							dependency-maven-plugin
+                						</artifactId>
+                						<versionRange>
+                							[1.0,)
+                						</versionRange>
+                						<goals>
+                							<goal>unpack</goal>
+                						</goals>
+                					</pluginExecutionFilter>
+                					<action>
+                						<ignore />
+                					</action>
+                				</pluginExecution>
+                			</pluginExecutions>
+                		</lifecycleMappingMetadata>
+                	</configuration>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>build-helper-maven-plugin</artifactId>
+                    <version>1.7</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>dependency-maven-plugin</artifactId>
+                    <version>1.0</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.0.2</version>
+                <configuration>
+                    <source>${jdk.version}</source>
+                    <target>${jdk.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.12</version>
+                <configuration>
+                    <printSummary>true</printSummary>
+                </configuration>
+            </plugin>
+            <!-- Allow Groovy tests to run: -->
+            <plugin>
+                <groupId>org.codehaus.gmaven</groupId>
+                <artifactId>gmaven-plugin</artifactId>
+                <version>${gmaven.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>testCompile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <!-- Add SVN Revision To A JAR Manifest
+                     - http://maven.apache.org/plugin-developers/cookbook/add-svn-revision-to-manifest.html
+                -->
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>buildnumber-maven-plugin</artifactId>
+                <version>1.0-beta-4</version>
+                <executions>
+                    <execution>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>create</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <doCheck>false</doCheck>
+                    <doUpdate>false</doUpdate>
+                    <revisionOnScmFailure>${project.version}</revisionOnScmFailure>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.3.1</version>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                        </manifest>
+                        <manifestEntries>
+                            <SCM-Revision>${buildNumber}</SCM-Revision>
+                            <SCM-url>${project.scm.url}</SCM-url>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <configuration>
+                    <!-- "install" needed because we have interrelated dependencies between the modules and we are releasing them all the same - especially consider shiro-all -->
+                    <preparationGoals>clean apache-rat:check install</preparationGoals>
+                    <autoVersionSubmodules>true</autoVersionSubmodules>
+                    <!-- This configuration copied from apache:apache:7 parent pom -->
+                    <useReleaseProfile>false</useReleaseProfile>
+                    <goals>deploy site-deploy</goals>
+                    <arguments>-Pdocs,apache-release</arguments>
+                    <mavenExecutorId>forked-path</mavenExecutorId>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- Automatically inherited dependencies.  The only ones that should be in here
+             are test dependencies.  Actual compile or runtime dependencies should be
+             explicitly declared in a child module, referencing the dependency defined
+             in this file's <dependencyManagement> section. -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <version>${easymock.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- Writing tests in groovy is fast!: -->
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-all</artifactId>
+            <version>${groovy.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Intra project dependencies -->
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-core</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-web</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-ehcache</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-quartz</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-spring</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-guice</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-aspectj</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-all</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro.samples</groupId>
+                <artifactId>samples-spring-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <!-- Intra project test dependencies: -->
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-core</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+                <scope>test</scope>
+            </dependency>
+
+            <!-- 3rd party dependencies -->
+            <dependency>
+                <!-- used for the 'hasher' command line tool: -->
+                <groupId>commons-cli</groupId>
+                <artifactId>commons-cli</artifactId>
+                <version>${commons.cli.version}</version>
+            </dependency>
+            <dependency>
+                <!-- runtime dependency for the shiro-cas module: -->
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>${commons.codec.version}</version>
+                <scope>runtime</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.aspectj</groupId>
+                <artifactId>aspectjrt</artifactId>
+                <version>${aspectj.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.aspectj</groupId>
+                <artifactId>aspectjweaver</artifactId>
+                <version>${aspectj.version}</version>
+            </dependency>
+            <dependency>
+                <!-- Used for Atlassian Crowd Realm - not required for the framework: -->
+                <groupId>com.atlassian.crowd</groupId>
+                <artifactId>crowd-integration-client</artifactId>
+                <version>${crowd.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-simple</artifactId>
+                <version>${slf4j.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-log4j12</artifactId>
+                <version>${slf4j.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <!-- Required in the sample apps only for 3rd-party libraries that expect to call
+                     the commons logging APIs -->
+                <groupId>org.slf4j</groupId>
+                <artifactId>jcl-over-slf4j</artifactId>
+                <version>${slf4j.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>commons-beanutils</groupId>
+                <artifactId>commons-beanutils</artifactId>
+                <version>1.8.3</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>hsqldb</groupId>
+                <artifactId>hsqldb</artifactId>
+                <version>${hsqldb.version}</version>
+                <scope>runtime</scope>
+                <optional>true</optional>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet.jsp</groupId>
+                <artifactId>jsp-api</artifactId>
+                <version>2.1</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>jstl</artifactId>
+                <version>1.1.2</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+                <version>2.5</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
+                <groupId>log4j</groupId>
+                <artifactId>log4j</artifactId>
+                <version>1.2.16</version>
+                <scope>test</scope>
+                <exclusions>
+                    <exclusion>
+                        <groupId>javax.mail</groupId>
+                        <artifactId>mail</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>javax.jms</groupId>
+                        <artifactId>jms</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>com.sun.jdmk</groupId>
+                        <artifactId>jmxtools</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>com.sun.jmx</groupId>
+                        <artifactId>jmxri</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.codehaus.groovy</groupId>
+                <artifactId>groovy-all</artifactId>
+                <version>${groovy.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>net.sf.ehcache</groupId>
+                <artifactId>ehcache-core</artifactId>
+                <version>${ehcache.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <!-- Used for sample applications only - not required for the framework: -->
+                <groupId>org.hibernate</groupId>
+                <artifactId>hibernate</artifactId>
+                <version>3.2.6.ga</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>ant</groupId>
+                        <artifactId>ant</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <!--suppress MavenModelInspection -->
+                        <groupId>ant-launcher</groupId>
+                        <!--suppress MavenModelInspection -->
+                        <artifactId>ant-launcher</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>c3p0</groupId>
+                        <artifactId>c3p0</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <!--suppress MavenModelInspection -->
+                        <groupId>javax.security</groupId>
+                        <!--suppress MavenModelInspection -->
+                        <artifactId>jacc</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>javax.servlet</groupId>
+                        <artifactId>servlet-api</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>jboss</groupId>
+                        <!--suppress MavenModelInspection -->
+                        <artifactId>jboss-cache</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>net.sf.ehcache</groupId>
+                        <artifactId>ehcache</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>asm</groupId>
+                        <!--suppress MavenModelInspection -->
+                        <artifactId>asm-attrs</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>javax.transaction</groupId>
+                        <artifactId>jta</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <!-- Used to support Hibernate's JTA runtime dependency in the sample application(s) only.
+                 Not required for Shiro -->
+            <dependency>
+                <groupId>org.apache.geronimo.specs</groupId>
+                <artifactId>geronimo-jta_1.1_spec</artifactId>
+                <version>1.1.1</version>
+                <scope>runtime</scope>
+                <optional>true</optional>
+            </dependency>
+            <dependency>
+                <groupId>org.hibernate</groupId>
+                <artifactId>hibernate-annotations</artifactId>
+                <version>3.2.1.ga</version>
+                <optional>true</optional>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-context</artifactId>
+                <version>${spring.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-jdbc</artifactId>
+                <version>${spring.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-orm</artifactId>
+                <version>${spring.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-webmvc</artifactId>
+                <version>${spring.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-test</artifactId>
+                <version>${spring.version}</version>
+                <scope>test</scope>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.google.inject</groupId>
+                <artifactId>guice</artifactId>
+                <version>${guice.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.google.inject.extensions</groupId>
+                <artifactId>guice-multibindings</artifactId>
+                <version>${guice.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.google.inject.extensions</groupId>
+                <artifactId>guice-servlet</artifactId>
+                <version>${guice.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opensymphony.quartz</groupId>
+                <artifactId>quartz</artifactId>
+                <version>${quartz.version}</version>
+                <optional>true</optional>
+                <exclusions>
+                    <exclusion>
+                        <groupId>commons-logging</groupId>
+                        <artifactId>commons-logging</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    
+    <!-- Note that reporting may fail with lower settings than something like: MAVEN_OPTS="-X512m -XX:MaxPermSize=128m" -->
+    <reporting>
+        <plugins>
+            <plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.7</version>
+                <configuration>
+                    <source>${jdk.version}</source>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <linksource>true</linksource>
+                    <links>
+                        <link>http://java.sun.com/javase/6/docs/api/</link>
+                        <link>http://java.sun.com/javaee/5/docs/api/</link>
+                        <link>http://www.slf4j.org/api/</link>
+                        <link>http://static.springframework.org/spring/docs/2.5.x/api/</link>
+                        <link>http://junit.org/junit/javadoc/4.4/</link>
+                        <link>http://easymock.org/api/easymock/2.4</link>
+                        <link>http://www.quartz-scheduler.org/docs/api/1.8.0/</link>
+                    </links>
+                    <!-- Don't include the sample apps - they're not part of Shiro's API: -->
+                    <excludePackageNames>org.apache.shiro.samples.*</excludePackageNames>
+                    <top><![CDATA[
+  <!-- Begin Google Analytics code -->
+  <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-11551827-1']);
+      _gaq.push(['_trackPageview']);
+
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+  </script>
+  <!-- End Google Analytics code -->
+                    ]]></top>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <id>javadoc-aggregate</id>
+                        <reports>
+                            <report>aggregate</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <formats>
+                        <format>xml</format>
+                        <format>html</format>
+                    </formats>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-jxr-plugin</artifactId>
+                <version>2.1</version>
+                <configuration>
+                    <aggregate>true</aggregate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <version>2.5</version>
+                <configuration>
+                    <sourceEncoding>utf-8</sourceEncoding>
+                    <targetJdk>1.5</targetJdk>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <version>2.2</version>
+                <!-- Disable, just to make it go faster -->
+                <configuration>
+                    <dependencyLocationsEnabled>false</dependencyLocationsEnabled>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <version>0.8</version>
+                <configuration>
+                		<!-- note that this configuration needs to be maintain both in pluginManagement and reporting sections -->
+                    <excludes>
+                        <exclude>**/.externalToolBuilders/*</exclude>
+                        <exclude>**/infinitest.filters</exclude>
+                        <!-- Apparently some test in samples/spring-client generates velocity log - would better to reconfigure to output to target/ -->
+                        <exclude>velocity.log</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-report-plugin</artifactId>
+                <version>2.5</version>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>taglist-maven-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <tagListOptions>
+                        <tagClasses>
+                            <tagClass>
+                                <displayName>Todo Work</displayName>
+                                <tags>
+                                    <tag>
+                                        <matchString>todo</matchString>
+                                        <matchType>ignoreCase</matchType>
+                                    </tag>
+                                    <tag>
+                                        <matchString>FIXME</matchString>
+                                        <matchType>exact</matchType>
+                                    </tag>
+                                </tags>
+                            </tagClass>
+                            <tagClass>
+                                <displayName>Deprecated</displayName>
+                                <tags>
+                                    <tag>
+                                        <matchString>@Deprecated</matchString>
+                                        <matchType>exact</matchType>
+                                    </tag>
+                                </tags>
+                            </tagClass>
+                        </tagClasses>
+                    </tagListOptions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jdepend-maven-plugin</artifactId>
+                <version>2.0-beta-2</version>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>dashboard-maven-plugin</artifactId>
+                <version>1.0.0-beta-1</version>
+            </plugin>
+        </plugins>
+    </reporting>
+
+    <profiles>
+        <profile>
+            <id>docs</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <version>2.7</version>
+                        <executions>
+                            <execution>
+                                <id>attach-api-docs</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <inherited>true</inherited>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <version>2.0.4</version>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <inherited>true</inherited>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>apache-release</id>
+            <distributionManagement>
+                <site>
+                    <id>shiro.website</id>
+                    <name>Apache Shiro Site</name>
+                    <url>scp://people.apache.org/www/shiro.apache.org/static/${project.version}</url>
+                </site>
+            </distributionManagement>
+        </profile>
+    </profiles>
+</project>
diff --git a/samples/aspectj/pom.xml b/samples/aspectj/pom.xml
new file mode 100644
index 0000000..9689896
--- /dev/null
+++ b/samples/aspectj/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	~ Licensed to the Apache Software Foundation (ASF) under one
+	~ or more contributor license agreements.  See the NOTICE file
+	~ distributed with this work for additional information
+	~ regarding copyright ownership.  The ASF licenses this file
+	~ to you under the Apache License, Version 2.0 (the
+	~ "License"); you may not use this file except in compliance
+	~ with the License.  You may obtain a copy of the License at
+	~
+	~     http://www.apache.org/licenses/LICENSE-2.0
+	~
+	~ Unless required by applicable law or agreed to in writing,
+	~ software distributed under the License is distributed on an
+	~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+	~ KIND, either express or implied.  See the License for the
+	~ specific language governing permissions and limitations
+	~ under the License.
+	-->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+	<parent>
+		<groupId>org.apache.shiro.samples</groupId>
+		<artifactId>shiro-samples</artifactId>
+		<version>1.2.2</version>
+	        <relativePath>../pom.xml</relativePath> 
+        </parent>
+
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>samples-aspectj</artifactId>
+	<name>Apache Shiro :: Samples :: AspectJ</name>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>aspectj-maven-plugin</artifactId>
+				<version>1.4</version>
+				<configuration>
+					<source>1.5</source>
+					<target>1.5</target>
+					<showWeaveInfo>true</showWeaveInfo>
+					<aspectLibraries>
+						<aspectLibrary>
+							<groupId>org.apache.shiro</groupId>
+							<artifactId>shiro-aspectj</artifactId>
+						</aspectLibrary>
+					</aspectLibraries>
+				</configuration>
+				<executions>
+					<execution>
+						<id>aspectj-compile</id>
+						<goals>
+							<goal>compile</goal>
+							<goal>test-compile</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.shiro</groupId>
+			<artifactId>shiro-aspectj</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.4</version>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+		</dependency>
+	</dependencies>
+
+</project>
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/Account.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/Account.java
new file mode 100644
index 0000000..096d6b5
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/Account.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class Account {
+
+    private static long _SEQUENCE;
+
+    private long _id;
+
+    private String _ownerName;
+
+    private volatile boolean _isActive;
+
+    private double _balance;
+
+    private final List<AccountTransaction> _transactions;
+
+    private String _createdBy;
+
+    private Date _creationDate;
+
+    public Account(String anOwnerName) {
+        _id = ++_SEQUENCE;
+        _ownerName = anOwnerName;
+        _isActive = true;
+        _balance = 0.0d;
+        _transactions = new ArrayList<AccountTransaction>();
+        _createdBy = "unknown";
+        _creationDate = new Date();
+    }
+
+    /**
+     * Returns the id attribute.
+     *
+     * @return The id value.
+     */
+    public long getId() {
+        return _id;
+    }
+
+    /**
+     * Returns the ownerName attribute.
+     *
+     * @return The ownerName value.
+     */
+    public String getOwnerName() {
+        return _ownerName;
+    }
+
+    /**
+     * Returns the isActive attribute.
+     *
+     * @return The isActive value.
+     */
+    public boolean isActive() {
+        return _isActive;
+    }
+
+    /**
+     * Changes the value of the attributes isActive.
+     *
+     * @param aIsActive The new value of the isActive attribute.
+     */
+    public void setActive(boolean aIsActive) {
+        _isActive = aIsActive;
+    }
+
+    /**
+     * Changes the value of the attributes ownerName.
+     *
+     * @param aOwnerName The new value of the ownerName attribute.
+     */
+    public void setOwnerName(String aOwnerName) {
+        _ownerName = aOwnerName;
+    }
+
+    /**
+     * Returns the balance attribute.
+     *
+     * @return The balance value.
+     */
+    public double getBalance() {
+        return _balance;
+    }
+
+    /**
+     * Returns the transactions attribute.
+     *
+     * @return The transactions value.
+     */
+    public List<AccountTransaction> getTransactions() {
+        return _transactions;
+    }
+
+    protected void applyTransaction(AccountTransaction aTransaction) throws NotEnoughFundsException, InactiveAccountException {
+        if (!_isActive) {
+            throw new InactiveAccountException("Unable to apply " + aTransaction.getType() + " of amount " + aTransaction.getAmount() + " to account " + _id);
+        }
+
+        synchronized (_transactions) {
+            if (AccountTransaction.TransactionType.DEPOSIT == aTransaction.getType()) {
+                _transactions.add(aTransaction);
+                _balance += aTransaction.getAmount();
+
+            } else if (AccountTransaction.TransactionType.WITHDRAWAL == aTransaction.getType()) {
+                if (_balance < aTransaction.getAmount()) {
+                    throw new NotEnoughFundsException("Unable to withdraw " + aTransaction.getAmount() + "$ from account " + _id + " - current balance is " + _balance);
+                }
+                _transactions.add(aTransaction);
+                _balance -= aTransaction.getAmount();
+
+            } else {
+                throw new IllegalArgumentException("The transaction passed in has an invalid type: " + aTransaction.getType());
+            }
+        }
+    }
+
+    /**
+     * Changes the value of the attributes createdBy.
+     *
+     * @param aCreatedBy The new value of the createdBy attribute.
+     */
+    protected void setCreatedBy(String aCreatedBy) {
+        _createdBy = aCreatedBy;
+    }
+
+    /**
+     * Returns the createdBy attribute.
+     *
+     * @return The createdBy value.
+     */
+    public String getCreatedBy() {
+        return _createdBy;
+    }
+
+    /**
+     * Returns the creationDate attribute.
+     *
+     * @return The creationDate value.
+     */
+    public Date getCreationDate() {
+        return _creationDate;
+    }
+
+    /* (non-Javadoc)
+    * @see java.lang.Object#toString()
+    */
+
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).
+                append("id", _id).
+                append("ownerName", _ownerName).
+                append("isActive", _isActive).
+                append("balance", _balance).
+                append("tx.count", _transactions.size()).
+                append("createdBy", _createdBy).
+                append("creationDate", new Timestamp(_creationDate.getTime())).
+                toString();
+    }
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountNotFoundException.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountNotFoundException.java
new file mode 100644
index 0000000..4050c82
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountNotFoundException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+public class AccountNotFoundException extends BankServiceException {
+
+    public AccountNotFoundException(String aMessage) {
+        super(aMessage);
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountTransaction.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountTransaction.java
new file mode 100644
index 0000000..2298519
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/AccountTransaction.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+public class AccountTransaction {
+
+    private static long _SEQUENCE;
+
+    public enum TransactionType {
+        DEPOSIT,
+        WITHDRAWAL
+    }
+
+    private long _id;
+
+    private TransactionType _type;
+
+    private long _accountId;
+
+    private double _amount;
+
+    private String _createdBy;
+    private Date _creationDate;
+
+    public static AccountTransaction createDepositTx(long anAccountId, double anAmount) {
+        return new AccountTransaction(TransactionType.DEPOSIT, anAccountId, anAmount);
+    }
+
+    public static AccountTransaction createWithdrawalTx(long anAccountId, double anAmount) {
+        return new AccountTransaction(TransactionType.WITHDRAWAL, anAccountId, anAmount);
+    }
+
+    private AccountTransaction(TransactionType aType, long anAccountId, double anAmount) {
+        _id = ++_SEQUENCE;
+        _type = aType;
+        _accountId = anAccountId;
+        _amount = anAmount;
+        _createdBy = "unknown";
+        _creationDate = new Date();
+    }
+
+    /**
+     * Returns the id attribute.
+     *
+     * @return The id value.
+     */
+    public long getId() {
+        return _id;
+    }
+
+    /**
+     * Returns the type attribute.
+     *
+     * @return The type value.
+     */
+    public TransactionType getType() {
+        return _type;
+    }
+
+    /**
+     * Returns the accountId attribute.
+     *
+     * @return The accountId value.
+     */
+    public long getAccountId() {
+        return _accountId;
+    }
+
+    /**
+     * Returns the amount attribute.
+     *
+     * @return The amount value.
+     */
+    public double getAmount() {
+        return _amount;
+    }
+
+    /**
+     * Changes the value of the attributes createdBy.
+     *
+     * @param aCreatedBy The new value of the createdBy attribute.
+     */
+    protected void setCreatedBy(String aCreatedBy) {
+        _createdBy = aCreatedBy;
+    }
+
+    /**
+     * Returns the createdBy attribute.
+     *
+     * @return The createdBy value.
+     */
+    public String getCreatedBy() {
+        return _createdBy;
+    }
+
+    /**
+     * Returns the creationDate attribute.
+     *
+     * @return The creationDate value.
+     */
+    public Date getCreationDate() {
+        return _creationDate;
+    }
+
+    /* (non-Javadoc)
+    * @see java.lang.Object#toString()
+    */
+
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).
+                append("id", _id).
+                append("type", _type).
+                append("accountId", _accountId).
+                append("amount", _amount).
+                append("createdBy", _createdBy).
+                append("creationDate", new Timestamp(_creationDate.getTime())).
+                toString();
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServerRunner.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServerRunner.java
new file mode 100644
index 0000000..162d340
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServerRunner.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+public class BankServerRunner {
+
+    private SecureBankService _bankService;
+
+    public synchronized void start() throws Exception {
+        if (_bankService == null) {
+            _bankService = new SecureBankService();
+            _bankService.start();
+        }
+    }
+
+    public synchronized void stop() {
+        if (_bankService != null) {
+            try {
+                _bankService.dispose();
+            } finally {
+                _bankService = null;
+            }
+        }
+    }
+
+    public BankService getBankService() {
+        return _bankService;
+    }
+
+    public static void main(String[] args) {
+        try {
+            BankServerRunner server = new BankServerRunner();
+            server.start();
+
+            server.stop();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankService.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankService.java
new file mode 100644
index 0000000..9dd7c9d
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankService.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+import java.util.Date;
+
+public interface BankService {
+
+    public long[] searchAccountIdsByOwner(String anOwnerName);
+
+    public long createNewAccount(String anOwnerName);
+
+    public double getBalanceOf(long anAccountId) throws AccountNotFoundException;
+
+    public String getOwnerOf(long anAccountId) throws AccountNotFoundException;
+
+    public double depositInto(long anAccountId, double anAmount) throws AccountNotFoundException, InactiveAccountException;
+
+    public double withdrawFrom(long anAccountId, double anAmount) throws AccountNotFoundException, NotEnoughFundsException, InactiveAccountException;
+
+    public TxLog[] getTxHistoryFor(long anAccountId) throws AccountNotFoundException;
+
+    public double closeAccount(long anAccountId) throws AccountNotFoundException, InactiveAccountException;
+
+    public boolean isAccountActive(long anAccountId) throws AccountNotFoundException;
+
+    public static class TxLog {
+        private Date _creationDate;
+        private double _amount;
+        private String _madeBy;
+
+        public TxLog(Date aCreationDate, double aAmount, String aMadeBy) {
+            super();
+            _creationDate = aCreationDate;
+            _amount = aAmount;
+            _madeBy = aMadeBy;
+        }
+
+        /**
+         * Returns the creationDate attribute.
+         *
+         * @return The creationDate value.
+         */
+        public Date getCreationDate() {
+            return _creationDate;
+        }
+
+        /**
+         * Returns the amount attribute.
+         *
+         * @return The amount value.
+         */
+        public double getAmount() {
+            return _amount;
+        }
+
+        /**
+         * Returns the madeBy attribute.
+         *
+         * @return The madeBy value.
+         */
+        public String getMadeBy() {
+            return _madeBy;
+        }
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServiceException.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServiceException.java
new file mode 100644
index 0000000..6a6539e
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/BankServiceException.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+public class BankServiceException extends Exception {
+
+    public BankServiceException(String aMessage) {
+        super(aMessage);
+    }
+
+    public BankServiceException(String aMessage, Throwable aCause) {
+        super(aMessage, aCause);
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/InactiveAccountException.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/InactiveAccountException.java
new file mode 100644
index 0000000..5e7b85a
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/InactiveAccountException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+public class InactiveAccountException extends BankServiceException {
+
+    public InactiveAccountException(String aMessage) {
+        super(aMessage);
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/NotEnoughFundsException.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/NotEnoughFundsException.java
new file mode 100644
index 0000000..866944d
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/NotEnoughFundsException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+public class NotEnoughFundsException extends BankServiceException {
+
+    public NotEnoughFundsException(String aMessage) {
+        super(aMessage);
+    }
+
+}
diff --git a/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/SecureBankService.java b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/SecureBankService.java
new file mode 100644
index 0000000..5d71f51
--- /dev/null
+++ b/samples/aspectj/src/main/java/org/apache/shiro/samples/aspectj/bank/SecureBankService.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.samples.aspectj.bank.AccountTransaction.TransactionType;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SecureBankService implements BankService {
+
+    private static final Logger log = LoggerFactory.getLogger(SecureBankService.class);
+    private volatile boolean _isRunning;
+    private final List<Account> _accounts;
+    private Map<Long, Account> _accountsById;
+
+    /**
+     * Creates a new {@link SecureBankService} instance.
+     */
+    public SecureBankService() {
+        _accounts = new ArrayList<Account>();
+        _accountsById = new HashMap<Long, Account>();
+    }
+
+    /**
+     * Starts this service
+     */
+    public void start() throws Exception {
+        _isRunning = true;
+        log.info("Bank service started");
+    }
+
+    /**
+     * Stop this service
+     */
+    public void dispose() {
+        log.info("Stopping bank service...");
+        _isRunning = false;
+
+        synchronized (_accounts) {
+            _accountsById.clear();
+            _accounts.clear();
+        }
+
+        log.info("Bank service stopped");
+    }
+
+    /**
+     * Internal utility method that validate the internal state of this service.
+     */
+    protected void assertServiceState() {
+        if (!_isRunning) {
+            throw new IllegalStateException("This bank service is not running");
+        }
+    }
+
+    public int getAccountCount() {
+        return _accounts.size();
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#createNewAccount(java.lang.String)
+    */
+
+    @RequiresPermissions("bankAccount:create")
+    public long createNewAccount(String anOwnerName) {
+        assertServiceState();
+        log.info("Creating new account for " + anOwnerName);
+
+        synchronized (_accounts) {
+            Account account = new Account(anOwnerName);
+            account.setCreatedBy(getCurrentUsername());
+            _accounts.add(account);
+            _accountsById.put(account.getId(), account);
+
+            log.debug("Created new account: " + account);
+            return account.getId();
+        }
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#searchAccountIdsByOwner(java.lang.String)
+    */
+
+    public long[] searchAccountIdsByOwner(String anOwnerName) {
+        assertServiceState();
+        log.info("Searching existing accounts for " + anOwnerName);
+
+        ArrayList<Account> matchAccounts = new ArrayList<Account>();
+        synchronized (_accounts) {
+            for (Account a : _accounts) {
+                if (a.getOwnerName().toLowerCase().contains(anOwnerName.toLowerCase())) {
+                    matchAccounts.add(a);
+                }
+            }
+        }
+
+        long[] accountIds = new long[matchAccounts.size()];
+        int index = 0;
+        for (Account a : matchAccounts) {
+            accountIds[index++] = a.getId();
+        }
+
+        log.debug("Found " + accountIds.length + " account(s) matching the name " + anOwnerName);
+        return accountIds;
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#getOwnerOf(long)
+    */
+
+    @RequiresPermissions("bankAccount:read")
+    public String getOwnerOf(long anAccountId) throws AccountNotFoundException {
+        assertServiceState();
+        log.info("Getting owner of account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+        return a.getOwnerName();
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#getBalanceOf(long)
+    */
+
+    @RequiresPermissions("bankAccount:read")
+    public double getBalanceOf(long anAccountId) throws AccountNotFoundException {
+        assertServiceState();
+        log.info("Getting balance of account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+        return a.getBalance();
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#depositInto(long, double)
+    */
+
+    @RequiresPermissions("bankAccount:operate")
+    public double depositInto(long anAccountId, double anAmount) throws AccountNotFoundException, InactiveAccountException {
+        assertServiceState();
+        log.info("Making deposit of " + anAmount + " into account " + anAccountId);
+
+        try {
+            Account a = safellyRetrieveAccountForId(anAccountId);
+            AccountTransaction tx = AccountTransaction.createDepositTx(anAccountId, anAmount);
+            tx.setCreatedBy(getCurrentUsername());
+            log.debug("Created a new transaction " + tx);
+
+            a.applyTransaction(tx);
+            log.debug("New balance of account " + a.getId() + " after deposit is " + a.getBalance());
+
+            return a.getBalance();
+
+        } catch (NotEnoughFundsException nefe) {
+            throw new IllegalStateException("Should never happen", nefe);
+        }
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#withdrawFrom(long, double)
+    */
+
+    @RequiresPermissions("bankAccount:operate")
+    public double withdrawFrom(long anAccountId, double anAmount) throws AccountNotFoundException, NotEnoughFundsException, InactiveAccountException {
+        assertServiceState();
+        log.info("Making withdrawal of " + anAmount + " from account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+        AccountTransaction tx = AccountTransaction.createWithdrawalTx(anAccountId, anAmount);
+        tx.setCreatedBy(getCurrentUsername());
+        log.debug("Created a new transaction " + tx);
+
+        a.applyTransaction(tx);
+        log.debug("New balance of account " + a.getId() + " after withdrawal is " + a.getBalance());
+
+        return a.getBalance();
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#getTxHistoryFor(long)
+    */
+
+    @RequiresPermissions("bankAccount:read")
+    public TxLog[] getTxHistoryFor(long anAccountId) throws AccountNotFoundException {
+        assertServiceState();
+        log.info("Getting transactions of account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+
+        TxLog[] txs = new TxLog[a.getTransactions().size()];
+        int index = 0;
+        for (AccountTransaction tx : a.getTransactions()) {
+            log.debug("Retrieved transaction " + tx);
+
+            if (TransactionType.DEPOSIT == tx.getType()) {
+                txs[index++] = new TxLog(tx.getCreationDate(), tx.getAmount(), tx.getCreatedBy());
+            } else {
+                txs[index++] = new TxLog(tx.getCreationDate(), -1.0d * tx.getAmount(), tx.getCreatedBy());
+            }
+        }
+
+        return txs;
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#closeAccount(long)
+    */
+
+    @RequiresPermissions("bankAccount:close")
+    public double closeAccount(long anAccountId) throws AccountNotFoundException, InactiveAccountException {
+        assertServiceState();
+        log.info("Closing account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+        if (!a.isActive()) {
+            throw new InactiveAccountException("The account " + anAccountId + " is already closed");
+        }
+
+        try {
+            AccountTransaction tx = AccountTransaction.createWithdrawalTx(a.getId(), a.getBalance());
+            tx.setCreatedBy(getCurrentUsername());
+            log.debug("Created a new transaction " + tx);
+            a.applyTransaction(tx);
+            a.setActive(false);
+
+            log.debug("Account " + a.getId() + " is now closed and an amount of " + tx.getAmount() + " is given to the owner");
+            return tx.getAmount();
+
+        } catch (NotEnoughFundsException nefe) {
+            throw new IllegalStateException("Should never happen", nefe);
+        }
+    }
+
+    /* (non-Javadoc)
+    * @see com.connectif.trilogy.root.security.BankService#isAccountActive(long)
+    */
+
+    @RequiresPermissions("bankAccount:read")
+    public boolean isAccountActive(long anAccountId) throws AccountNotFoundException {
+        assertServiceState();
+        log.info("Getting active status of account " + anAccountId);
+
+        Account a = safellyRetrieveAccountForId(anAccountId);
+        return a.isActive();
+    }
+
+
+    /**
+     * Internal method that safelly (concurrency-wise) retrieves an account from the id passed in.
+     *
+     * @param anAccountId The identifier of the account to retrieve.
+     * @return The account instance retrieved.
+     * @throws AccountNotFoundException If no account is found for the provided identifier.
+     */
+    protected Account safellyRetrieveAccountForId(long anAccountId) throws AccountNotFoundException {
+        Account account = null;
+        synchronized (_accounts) {
+            account = _accountsById.get(anAccountId);
+        }
+
+        if (account == null) {
+            throw new AccountNotFoundException("No account found for the id " + anAccountId);
+        }
+
+        log.info("Retrieved account " + account);
+        return account;
+    }
+
+    /**
+     * Internal utility method to retrieve the username of the current authenticated user.
+     *
+     * @return The name.
+     */
+    protected String getCurrentUsername() {
+        Subject subject = SecurityUtils.getSubject();
+        if (subject == null || subject.getPrincipal() == null || !subject.isAuthenticated()) {
+            throw new IllegalStateException("Unable to retrieve the current authenticated subject");
+        }
+        return SecurityUtils.getSubject().getPrincipal().toString();
+    }
+}
diff --git a/samples/aspectj/src/test/java/org/apache/shiro/samples/aspectj/bank/SecureBankServiceTest.java b/samples/aspectj/src/test/java/org/apache/shiro/samples/aspectj/bank/SecureBankServiceTest.java
new file mode 100644
index 0000000..a03067e
--- /dev/null
+++ b/samples/aspectj/src/test/java/org/apache/shiro/samples/aspectj/bank/SecureBankServiceTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.aspectj.bank;
+
+import junit.framework.Assert;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.Factory;
+import org.junit.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SecureBankServiceTest {
+
+    private static Logger logger = LoggerFactory.getLogger(SecureBankServiceTest.class);
+    private static SecureBankService service;
+    private static int testCounter;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiroBankServiceTest.ini");
+        SecurityManager securityManager = factory.getInstance();
+        SecurityUtils.setSecurityManager(securityManager);
+
+        service = new SecureBankService();
+        service.start();
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (service != null) {
+            service.dispose();
+        }
+    }
+
+    private Subject _subject;
+
+    @Before
+    public void setUp() throws Exception {
+        logger.info("\n\n#########################\n### STARTING TEST CASE " + (++testCounter) + "\n");
+        Thread.sleep(50);
+    }
+
+    @After
+    public void tearDown() {
+        if (_subject != null) {
+            _subject.logout();
+        }
+    }
+
+    protected void logoutCurrentSubject() {
+        if (_subject != null) {
+            _subject.logout();
+        }
+    }
+
+    protected void loginAsUser() {
+        if (_subject == null) {
+            _subject = SecurityUtils.getSubject();
+        }
+
+        // use dan to run as a normal user (which cannot close an account)
+        _subject.login(new UsernamePasswordToken("dan", "123"));
+    }
+
+    protected void loginAsSuperviser() {
+        if (_subject == null) {
+            _subject = SecurityUtils.getSubject();
+        }
+
+        // use sally to run as a superviser (which cannot operate an account)
+        _subject.login(new UsernamePasswordToken("sally", "1234"));
+    }
+
+    @Test
+    public void testCreateAccount() throws Exception {
+        loginAsUser();
+        createAndValidateAccountFor("Bob Smith");
+    }
+
+    @Test
+    public void testDepositInto_singleTx() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Joe Smith");
+        makeDepositAndValidateAccount(accountId, 250.00d, "Joe Smith");
+    }
+
+    @Test
+    public void testDepositInto_multiTxs() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Everett Smith");
+        makeDepositAndValidateAccount(accountId, 50.00d, "Everett Smith");
+        makeDepositAndValidateAccount(accountId, 300.00d, "Everett Smith");
+        makeDepositAndValidateAccount(accountId, 85.00d, "Everett Smith");
+        assertAccount("Everett Smith", true, 435.00d, 3, accountId);
+    }
+
+    @Test(expected = NotEnoughFundsException.class)
+    public void testWithdrawFrom_emptyAccount() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Wally Smith");
+        service.withdrawFrom(accountId, 100.00d);
+    }
+
+    @Test(expected = NotEnoughFundsException.class)
+    public void testWithdrawFrom_notEnoughFunds() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Frank Smith");
+        makeDepositAndValidateAccount(accountId, 50.00d, "Frank Smith");
+        service.withdrawFrom(accountId, 100.00d);
+    }
+
+    @Test
+    public void testWithdrawFrom_singleTx() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Al Smith");
+        makeDepositAndValidateAccount(accountId, 500.00d, "Al Smith");
+        makeWithdrawalAndValidateAccount(accountId, 100.00d, "Al Smith");
+        assertAccount("Al Smith", true, 400.00d, 2, accountId);
+    }
+
+    @Test
+    public void testWithdrawFrom_manyTxs() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Zoe Smith");
+        makeDepositAndValidateAccount(accountId, 500.00d, "Zoe Smith");
+        makeWithdrawalAndValidateAccount(accountId, 100.00d, "Zoe Smith");
+        makeWithdrawalAndValidateAccount(accountId, 75.00d, "Zoe Smith");
+        makeWithdrawalAndValidateAccount(accountId, 125.00d, "Zoe Smith");
+        assertAccount("Zoe Smith", true, 200.00d, 4, accountId);
+    }
+
+    @Test
+    public void testWithdrawFrom_upToZero() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Zoe Smith");
+        makeDepositAndValidateAccount(accountId, 500.00d, "Zoe Smith");
+        makeWithdrawalAndValidateAccount(accountId, 500.00d, "Zoe Smith");
+        assertAccount("Zoe Smith", true, 0.00d, 2, accountId);
+    }
+
+    @Test
+    public void testCloseAccount_zeroBalance() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Chris Smith");
+
+        logoutCurrentSubject();
+        loginAsSuperviser();
+        double closingBalance = service.closeAccount(accountId);
+        Assert.assertEquals(0.00d, closingBalance);
+        assertAccount("Chris Smith", false, 0.00d, 1, accountId);
+    }
+
+    @Test
+    public void testCloseAccount_withBalance() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Gerry Smith");
+        makeDepositAndValidateAccount(accountId, 385.00d, "Gerry Smith");
+
+        logoutCurrentSubject();
+        loginAsSuperviser();
+        double closingBalance = service.closeAccount(accountId);
+        Assert.assertEquals(385.00d, closingBalance);
+        assertAccount("Gerry Smith", false, 0.00d, 2, accountId);
+    }
+
+    @Test(expected = InactiveAccountException.class)
+    public void testCloseAccount_alreadyClosed() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Chris Smith");
+
+        logoutCurrentSubject();
+        loginAsSuperviser();
+        double closingBalance = service.closeAccount(accountId);
+        Assert.assertEquals(0.00d, closingBalance);
+        assertAccount("Chris Smith", false, 0.00d, 1, accountId);
+        service.closeAccount(accountId);
+    }
+
+    @Test(expected = UnauthorizedException.class)
+    public void testCloseAccount_unauthorizedAttempt() throws Exception {
+        loginAsUser();
+        long accountId = createAndValidateAccountFor("Chris Smith");
+        service.closeAccount(accountId);
+    }
+
+    protected long createAndValidateAccountFor(String anOwner) throws Exception {
+        long createdId = service.createNewAccount(anOwner);
+        assertAccount(anOwner, true, 0.0d, 0, createdId);
+        return createdId;
+    }
+
+    protected double makeDepositAndValidateAccount(long anAccountId, double anAmount, String eOwnerName) throws Exception {
+        double previousBalance = service.getBalanceOf(anAccountId);
+        int previousTxCount = service.getTxHistoryFor(anAccountId).length;
+        double newBalance = service.depositInto(anAccountId, anAmount);
+        Assert.assertEquals(previousBalance + anAmount, newBalance);
+        assertAccount(eOwnerName, true, newBalance, 1 + previousTxCount, anAccountId);
+        return newBalance;
+    }
+
+    protected double makeWithdrawalAndValidateAccount(long anAccountId, double anAmount, String eOwnerName) throws Exception {
+        double previousBalance = service.getBalanceOf(anAccountId);
+        int previousTxCount = service.getTxHistoryFor(anAccountId).length;
+        double newBalance = service.withdrawFrom(anAccountId, anAmount);
+        Assert.assertEquals(previousBalance - anAmount, newBalance);
+        assertAccount(eOwnerName, true, newBalance, 1 + previousTxCount, anAccountId);
+        return newBalance;
+    }
+
+
+    public static void assertAccount(String eOwnerName, boolean eIsActive, double eBalance, int eTxLogCount, long actualAccountId) throws Exception {
+        Assert.assertEquals(eOwnerName, service.getOwnerOf(actualAccountId));
+        Assert.assertEquals(eIsActive, service.isAccountActive(actualAccountId));
+        Assert.assertEquals(eBalance, service.getBalanceOf(actualAccountId));
+        Assert.assertEquals(eTxLogCount, service.getTxHistoryFor(actualAccountId).length);
+    }
+}
diff --git a/samples/aspectj/src/test/resources/META-INF/aop.xml b/samples/aspectj/src/test/resources/META-INF/aop.xml
new file mode 100644
index 0000000..1cc0a0a
--- /dev/null
+++ b/samples/aspectj/src/test/resources/META-INF/aop.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<aspectj>
+
+    <aspects>
+        <!-- declare shiro aspects to the weaver -->
+        <aspect name="org.apache.shiro.aspectj.ShiroAnnotationAuthorizingAspect"/>
+    </aspects>
+
+    <weaver options="-verbose -showWeaveInfo">
+        <!-- Weave types that are within the org.apache.shiro.sample package
+             and all sub-packages. This limits the scope of the weaver and
+             reduces the overhead -->
+        <include within="org.apache.shiro.samples.aspectj.bank..*"/>
+    </weaver>
+</aspectj>
diff --git a/samples/aspectj/src/test/resources/log4j.properties b/samples/aspectj/src/test/resources/log4j.properties
new file mode 100644
index 0000000..157e11e
--- /dev/null
+++ b/samples/aspectj/src/test/resources/log4j.properties
@@ -0,0 +1,28 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootCategory=info, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=[%p] %c{2} %m%n
+
+log4j.category.org.apache.shiro.aspectj=TRACE
\ No newline at end of file
diff --git a/samples/aspectj/src/test/resources/shiroBankServiceTest.ini b/samples/aspectj/src/test/resources/shiroBankServiceTest.ini
new file mode 100644
index 0000000..cf14bc2
--- /dev/null
+++ b/samples/aspectj/src/test/resources/shiroBankServiceTest.ini
@@ -0,0 +1,42 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# -----------------------------------------------------------------------------
+# Users and their assigned roles
+#
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[users]
+root = secret, admin
+sally = 1234, superviser
+dan = 123, user
+
+
+# -----------------------------------------------------------------------------
+# Roles with assigned permissions
+# 
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[roles]
+admin = bankAccount:*
+superviser = bankAccount:create, bankAccount:read, bankAccount:close
+user = bankAccount:create, bankAccount:read, bankAccount:operate
diff --git a/samples/pom.xml b/samples/pom.xml
new file mode 100644
index 0000000..c2975fb
--- /dev/null
+++ b/samples/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.shiro.samples</groupId>
+    <artifactId>shiro-samples</artifactId>
+    <name>Apache Shiro :: Samples</name>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>aspectj</module>
+        <module>quickstart</module>
+        <module>web</module>
+        <module>spring-client</module>
+        <module>spring</module>
+        <module>spring-hibernate</module>
+    </modules>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-site-plugin</artifactId>
+                <inherited>true</inherited>
+                <configuration>
+                <!-- Turn off this plugin - it often fails with the example projects:
+                     http://shiro-developer.582600.n2.nabble.com/Failed-releases-and-Apache-Rat-tp7577605.html -->
+                    <skip>true</skip>
+                    <skipDeploy>true</skipDeploy>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+</project>
+
diff --git a/samples/quickstart/pom.xml b/samples/quickstart/pom.xml
new file mode 100644
index 0000000..0018bf1
--- /dev/null
+++ b/samples/quickstart/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-quickstart</artifactId>
+    <name>Apache Shiro :: Samples :: Quick Start</name>
+    <packaging>jar</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <classpathScope>test</classpathScope>
+                    <mainClass>Quickstart</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/samples/quickstart/src/main/java/Quickstart.java b/samples/quickstart/src/main/java/Quickstart.java
new file mode 100644
index 0000000..95f60ea
--- /dev/null
+++ b/samples/quickstart/src/main/java/Quickstart.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.*;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Simple Quickstart application showing how to use Shiro's API.
+ *
+ * @since 0.9 RC2
+ */
+public class Quickstart {
+
+    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
+
+
+    public static void main(String[] args) {
+
+        // The easiest way to create a Shiro SecurityManager with configured
+        // realms, users, roles and permissions is to use the simple INI config.
+        // We'll do that by using a factory that can ingest a .ini file and
+        // return a SecurityManager instance:
+
+        // Use the shiro.ini file at the root of the classpath
+        // (file: and url: prefixes load from files and urls respectively):
+        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
+        SecurityManager securityManager = factory.getInstance();
+
+        // for this simple example quickstart, make the SecurityManager
+        // accessible as a JVM singleton.  Most applications wouldn't do this
+        // and instead rely on their container configuration or web.xml for
+        // webapps.  That is outside the scope of this simple quickstart, so
+        // we'll just do the bare minimum so you can continue to get a feel
+        // for things.
+        SecurityUtils.setSecurityManager(securityManager);
+
+        // Now that a simple Shiro environment is set up, let's see what you can do:
+
+        // get the currently executing user:
+        Subject currentUser = SecurityUtils.getSubject();
+
+        // Do some stuff with a Session (no need for a web or EJB container!!!)
+        Session session = currentUser.getSession();
+        session.setAttribute("someKey", "aValue");
+        String value = (String) session.getAttribute("someKey");
+        if (value.equals("aValue")) {
+            log.info("Retrieved the correct value! [" + value + "]");
+        }
+
+        // let's login the current user so we can check against roles and permissions:
+        if (!currentUser.isAuthenticated()) {
+            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
+            token.setRememberMe(true);
+            try {
+                currentUser.login(token);
+            } catch (UnknownAccountException uae) {
+                log.info("There is no user with username of " + token.getPrincipal());
+            } catch (IncorrectCredentialsException ice) {
+                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
+            } catch (LockedAccountException lae) {
+                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
+                        "Please contact your administrator to unlock it.");
+            }
+            // ... catch more exceptions here (maybe custom ones specific to your application?
+            catch (AuthenticationException ae) {
+                //unexpected condition?  error?
+            }
+        }
+
+        //say who they are:
+        //print their identifying principal (in this case, a username):
+        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
+
+        //test a role:
+        if (currentUser.hasRole("schwartz")) {
+            log.info("May the Schwartz be with you!");
+        } else {
+            log.info("Hello, mere mortal.");
+        }
+
+        //test a typed permission (not instance-level)
+        if (currentUser.isPermitted("lightsaber:weild")) {
+            log.info("You may use a lightsaber ring.  Use it wisely.");
+        } else {
+            log.info("Sorry, lightsaber rings are for schwartz masters only.");
+        }
+
+        //a (very powerful) Instance Level permission:
+        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
+            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
+                    "Here are the keys - have fun!");
+        } else {
+            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
+        }
+
+        //all done - log out!
+        currentUser.logout();
+
+        System.exit(0);
+    }
+}
diff --git a/samples/quickstart/src/main/resources/log4j.properties b/samples/quickstart/src/main/resources/log4j.properties
new file mode 100644
index 0000000..779033d
--- /dev/null
+++ b/samples/quickstart/src/main/resources/log4j.properties
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# General Apache libraries
+log4j.logger.org.apache=WARN
+
+# Spring
+log4j.logger.org.springframework=WARN
+
+# Default Shiro logging
+log4j.logger.org.apache.shiro=TRACE
+
+# Disable verbose logging
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
+log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
diff --git a/samples/quickstart/src/main/resources/shiro.ini b/samples/quickstart/src/main/resources/shiro.ini
new file mode 100644
index 0000000..adca38c
--- /dev/null
+++ b/samples/quickstart/src/main/resources/shiro.ini
@@ -0,0 +1,58 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# =============================================================================
+# Quickstart INI Realm configuration
+#
+# For those that might not understand the references in this file, the
+# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Users and their assigned roles
+#
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[users]
+# user 'root' with password 'secret' and the 'admin' role
+root = secret, admin
+# user 'guest' with the password 'guest' and the 'guest' role
+guest = guest, guest
+# user 'presidentskroob' with password '12345' ("That's the same combination on
+# my luggage!!!" ;)), and role 'president'
+presidentskroob = 12345, president
+# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
+darkhelmet = ludicrousspeed, darklord, schwartz
+# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
+lonestarr = vespa, goodguy, schwartz
+
+# -----------------------------------------------------------------------------
+# Roles with assigned permissions
+# 
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[roles]
+# 'admin' role has all permissions, indicated by the wildcard '*'
+admin = *
+# The 'schwartz' role can do anything (*) with any lightsaber:
+schwartz = lightsaber:*
+# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
+# license plate 'eagle5' (instance specific id)
+goodguy = winnebago:drive:eagle5
diff --git a/samples/spring-client/pom.xml b/samples/spring-client/pom.xml
new file mode 100644
index 0000000..d400457
--- /dev/null
+++ b/samples/spring-client/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency	-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-spring-client</artifactId>
+    <name>Apache Shiro :: Samples :: Spring Client</name>
+    <description>A webstart application used to demonstrate Apache Shiro session and security management.</description>
+    <packaging>jar</packaging>
+
+    <properties>
+        <shiro.session.id>${sessionId}</shiro.session.id>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-beanutils</groupId>
+                    <artifactId>commons-beanutils</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>webstart-maven-plugin</artifactId>
+                <version>1.0-beta-3</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jnlp-inline</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!--	JNLP generation	-->
+                    <jnlp>
+                        <!--	default	values -->
+                        <!--inputTemplateResourcePath>${project.basedir}</inputTemplateResourcePath-->
+                        <!--inputTemplate>src/main/jnlp/template.vm</inputTemplate--> <!--	relative to	inputTemplateResourcePath	-->
+                        <outputFile>shiro.jnlp.jsp</outputFile>
+                        <!--	defaults to	launch.jnlp	-->
+                        <mainClass>org.apache.shiro.samples.spring.ui.WebStartDriver</mainClass>
+                    </jnlp>
+
+                    <sign>
+                        <keystore>jsecurity-sample.jks</keystore>
+                        <storepass>jsecurity</storepass>
+                        <alias>jsecurity</alias>
+                        <verify>false</verify>
+                    </sign>
+                    <!-- BUILDING PROCESS -->
+                    <pack200>true</pack200>
+                    <verbose>false</verbose>
+
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/SampleManager.java b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/SampleManager.java
new file mode 100644
index 0000000..3919b54
--- /dev/null
+++ b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/SampleManager.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+
+
+/**
+ * Business manager interface used for sample application.
+ *
+ * @since 0.1
+ */
+public interface SampleManager {
+
+    /**
+     * Returns the value stored in the user's session.
+     *
+     * @return the value.
+     */
+    String getValue();
+
+
+    /**
+     * Sets a value to be stored in the user's session.
+     *
+     * @param newValue the new value to store in the user's session.
+     */
+    void setValue(String newValue);
+
+    /**
+     * Method that requires <tt>role1</tt> in order to be invoked.
+     */
+    @RequiresRoles("role1")
+    void secureMethod1();
+
+    /**
+     * Method that requires <tt>role2</tt> in order to be invoked.
+     */
+    @RequiresRoles("role2")
+    void secureMethod2();
+
+    /**
+     * Method that requires <tt>permission1</tt> in order to be invoked.
+     */
+    @RequiresPermissions("permission2")
+    void secureMethod3();
+}
diff --git a/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartDriver.java b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartDriver.java
new file mode 100644
index 0000000..bfe257f
--- /dev/null
+++ b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartDriver.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.ui;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+/**
+ * Driver class used to launch the web start application by loading a
+ * Spring application context.  Once the Spring application context is
+ * loaded, the initialization of the {@link WebStartView} does the rest.
+ *
+ * @since 0.1
+ */
+public class WebStartDriver {
+    public static String LAUNCH_SESSION_ID;
+
+    public static void main(String[] args) {
+        // Store the session id given as argument to a static property to make it available for Spring context 
+        if (args.length > 0) LAUNCH_SESSION_ID = args[0];
+        new ClassPathXmlApplicationContext("webstart.spring.xml");
+    }
+}
diff --git a/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartView.java b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartView.java
new file mode 100644
index 0000000..ff62fbf
--- /dev/null
+++ b/samples/spring-client/src/main/java/org/apache/shiro/samples/spring/ui/WebStartView.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.ui;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.*;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.ClassPathResource;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.samples.spring.SampleManager;
+
+
+/**
+ * Simple web start application that helps to demo single sign-on and
+ * remoting authorization using Shiro.  The injected <tt>SampleManager</tt>
+ * is hosted by the Spring sample web application and remotely invoked
+ * when the buttons in this view are clicked.
+ *
+ * @since 0.1
+ */
+public class WebStartView implements ActionListener, InitializingBean {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private SampleManager sampleManager;
+    private JTextField valueField;
+    private JButton saveButton;
+    private JButton refreshButton;
+    private JButton secureMethod1Button;
+    private JButton secureMethod2Button;
+    private JButton secureMethod3Button;
+    private JFrame frame;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public void setSampleManager(SampleManager sampleManager) {
+        this.sampleManager = sampleManager;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+    public void afterPropertiesSet() throws Exception {
+        ClassPathResource resource = new ClassPathResource("logo.png");
+        ImageIcon icon = new ImageIcon(resource.getURL());
+        JLabel logo = new JLabel(icon);
+
+        valueField = new JTextField(20);
+        updateValueLabel();
+
+        saveButton = new JButton("Save Value");
+        saveButton.addActionListener(this);
+
+        refreshButton = new JButton("Refresh Value");
+        refreshButton.addActionListener(this);
+
+        JPanel valuePanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+        valuePanel.add(valueField);
+        valuePanel.add(saveButton);
+        valuePanel.add(refreshButton);
+
+        secureMethod1Button = new JButton("Method #1");
+        secureMethod1Button.addActionListener(this);
+
+        secureMethod2Button = new JButton("Method #2");
+        secureMethod2Button.addActionListener(this);
+
+        secureMethod3Button = new JButton("Method #3");
+        secureMethod3Button.addActionListener(this);
+
+        JPanel methodPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+        methodPanel.add(secureMethod1Button);
+        methodPanel.add(secureMethod2Button);
+        methodPanel.add(secureMethod3Button);
+
+        frame = new JFrame("Apache Shiro Sample Application");
+        frame.setSize(500, 200);
+
+        Container panel = frame.getContentPane();
+        panel.setLayout(new BorderLayout());
+        panel.add(logo, BorderLayout.NORTH);
+        panel.add(valuePanel, BorderLayout.CENTER);
+        panel.add(methodPanel, BorderLayout.SOUTH);
+
+        frame.setVisible(true);
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                System.exit(0);
+            }
+        });
+    }
+
+    private void updateValueLabel() {
+        valueField.setText(sampleManager.getValue());
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        try {
+
+            if (e.getSource() == saveButton) {
+                sampleManager.setValue(valueField.getText());
+
+            } else if (e.getSource() == refreshButton) {
+                updateValueLabel();
+
+            } else if (e.getSource() == secureMethod1Button) {
+                sampleManager.secureMethod1();
+                JOptionPane.showMessageDialog(frame, "Method #1 successfully called.", "Success", JOptionPane.INFORMATION_MESSAGE);
+
+            } else if (e.getSource() == secureMethod2Button) {
+                sampleManager.secureMethod2();
+                JOptionPane.showMessageDialog(frame, "Method #2 successfully called.", "Success", JOptionPane.INFORMATION_MESSAGE);
+            } else if (e.getSource() == secureMethod3Button) {
+                sampleManager.secureMethod3();
+                JOptionPane.showMessageDialog(frame, "Method #3 successfully called.", "Success", JOptionPane.INFORMATION_MESSAGE);
+
+            } else {
+                throw new RuntimeException("Unexpected action event from source: " + e.getSource());
+            }
+
+        } catch (AuthorizationException ae) {
+            JOptionPane.showMessageDialog(frame, "Unauthorized to perform action: " + ae.getMessage(), "Unauthorized", JOptionPane.WARNING_MESSAGE);
+        }
+    }
+}
diff --git a/samples/spring-client/src/main/jnlp/resources/jsecurity-sample.jks b/samples/spring-client/src/main/jnlp/resources/jsecurity-sample.jks
new file mode 100644
index 0000000..eb2ff9b
Binary files /dev/null and b/samples/spring-client/src/main/jnlp/resources/jsecurity-sample.jks differ
diff --git a/samples/spring-client/src/main/jnlp/template.vm b/samples/spring-client/src/main/jnlp/template.vm
new file mode 100644
index 0000000..fdda48e
--- /dev/null
+++ b/samples/spring-client/src/main/jnlp/template.vm
@@ -0,0 +1,53 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ page contentType="application/x-java-jnlp-file" %>
+
+<jnlp spec="$jnlpspec" codebase="${codebaseUrl}">
+	<information>
+	<title>$project.Name</title>
+	<vendor>$project.Organization.Name</vendor>
+	<homepage href="$project.Url"/>
+	<description>$project.Description</description>
+	<icon kind="splash" href="logo.png"/>
+	
+	#if($offlineAllowed)
+	<offline-allowed/>
+	#end
+	  
+	</information>
+	
+	#if($allPermissions)
+	
+	<security>
+		<all-permissions/>
+	</security>
+	#end
+	  
+	<resources>
+	<j2se version="$j2seVersion"/>
+			$dependencies
+		<%-- reading custom system properties requires more permissions than available in sandbox mode,
+		thus we need to sign the jars (although we are using an argument instead of a property to set this)
+		<property name="shiro.session.id" value="${sessionId}"/>
+		--%>
+	</resources>
+	<application-desc main-class="$mainClass">
+		<argument>${sessionId}</argument>
+	</application-desc> 	
+</jnlp>
\ No newline at end of file
diff --git a/samples/spring-client/src/main/resources/logo.png b/samples/spring-client/src/main/resources/logo.png
new file mode 100644
index 0000000..901d6ec
Binary files /dev/null and b/samples/spring-client/src/main/resources/logo.png differ
diff --git a/samples/spring-client/src/main/resources/webstart.spring.xml b/samples/spring-client/src/main/resources/webstart.spring.xml
new file mode 100644
index 0000000..431acc8
--- /dev/null
+++ b/samples/spring-client/src/main/resources/webstart.spring.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--
+  - Application context for Shiro WebStart sample application
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
+
+  <bean id="webStartView"
+        class="org.apache.shiro.samples.spring.ui.WebStartView">
+    <property name="sampleManager" ref="sampleManager"/>
+  </bean>
+
+  <bean id="sampleManager"
+        class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
+    <property name="serviceUrl" value="http://localhost:8080/shiro-samples-spring/remoting/sampleManager"/>
+    <property name="serviceInterface" value="org.apache.shiro.samples.spring.SampleManager"/>
+    <property name="remoteInvocationFactory" ref="secureRemoteInvocationFactory"/>
+  </bean>
+
+  <bean id="secureRemoteInvocationFactory"
+    class="org.apache.shiro.spring.remoting.SecureRemoteInvocationFactory">
+    <constructor-arg index="0">
+      <util:constant static-field="org.apache.shiro.samples.spring.ui.WebStartDriver.LAUNCH_SESSION_ID"/>
+    </constructor-arg>
+  </bean>
+
+</beans>
diff --git a/samples/spring-hibernate/pom.xml b/samples/spring-hibernate/pom.xml
new file mode 100644
index 0000000..881cd63
--- /dev/null
+++ b/samples/spring-hibernate/pom.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-sprhib</artifactId>
+    <name>Apache Shiro :: Samples :: Spring-Hibernate</name>
+    <packaging>war</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <!-- <configuration>
+                    <contextPath>/shirosprhib</contextPath>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9080</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
+                        <filename>./target/yyyy_mm_dd.request.log</filename>
+                        <retainDays>90</retainDays>
+                        <append>true</append>
+                        <extended>false</extended>
+                        <logTimeZone>GMT</logTimeZone>
+                    </requestLog>
+                    <systemProperties>
+                        <systemProperty>
+                            <name>org.apache.commons.logging.Log</name>
+                            <value>org.apache.commons.logging.impl.SimpleLog</value>
+                        </systemProperty>
+                        <systemProperty>
+                            <name>java.util.logging.config.file</name>
+                            <value>./target/test-classes/logging.properties</value>
+                        </systemProperty>
+                    </systemProperties>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>hsqldb</groupId>
+                        <artifactId>hsqldb</artifactId>
+                        <version>${hsqldb.version}</version>
+                    </dependency>
+                </dependencies> -->
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate</artifactId>
+            <scope>compile</scope>
+            <optional>false</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-annotations</artifactId>
+            <scope>compile</scope>
+            <optional>false</optional>
+        </dependency>
+        <!-- needed by Hibernate at runtime: -->
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-jta_1.1_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache-core</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-orm</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/BootstrapDataPopulator.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/BootstrapDataPopulator.java
new file mode 100644
index 0000000..877db00
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/BootstrapDataPopulator.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.dao;
+
+import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+
+/**
+ * Loads sample data for the sample app since it's an in-memory database.
+ */
+ at Component
+public class BootstrapDataPopulator implements InitializingBean {
+
+    private DataSource dataSource;
+    @SuppressWarnings({"FieldCanBeLocal"})
+    private SessionFactory sessionFactory;
+
+    @Autowired
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    // Session factory is only injected to ensure it is initialized before this runs
+    @Autowired
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        //because we're using an in-memory hsqldb for the sample app, a new one will be created each time the
+        //app starts, so insert the sample admin user at startup:
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
+
+        jdbcTemplate.execute("insert into roles values (1, 'user', 'The default role given to all users.')");
+        jdbcTemplate.execute("insert into roles values (2, 'admin', 'The administrator role only given to site admins')");
+        jdbcTemplate.execute("insert into roles_permissions values (2, 'user:*')");
+        jdbcTemplate.execute("insert into users(id,username,email,password) values (1, 'admin', 'sample at shiro.apache.org', '" + new Sha256Hash("admin").toHex() + "')");
+        jdbcTemplate.execute("insert into users_roles values (1, 2)");
+
+
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateDao.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateDao.java
new file mode 100644
index 0000000..3a91f7d
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateDao.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.dao;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.orm.hibernate3.SessionFactoryUtils;
+
+/**
+ * Convenience superclass for DAOs that contains annotations for injecting the session factory
+ * and accessing the session.
+ */
+public abstract class HibernateDao {
+
+    private SessionFactory sessionFactory;
+
+    @Autowired
+    public void setSessionFactory(SessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public Session getSession() {
+        return SessionFactoryUtils.getSession(this.sessionFactory, true);
+    }    
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateUserDAO.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateUserDAO.java
new file mode 100644
index 0000000..b46a4c6
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/HibernateUserDAO.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.dao;
+
+import org.apache.shiro.samples.sprhib.model.User;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+ at Repository("userDAO")
+ at SuppressWarnings("unchecked")
+public class HibernateUserDAO extends HibernateDao implements UserDAO {
+
+    public User getUser(Long userId) {
+        return (User) getSession().get(User.class, userId);
+    }
+
+    public User findUser(String username) {
+        Assert.hasText(username);
+        String query = "from User u where u.username = :username";
+        return (User) getSession().createQuery(query).setString("username", username).uniqueResult();
+    }
+
+    public void createUser(User user) {
+        getSession().save( user );
+    }
+
+    public List<User> getAllUsers() {
+        return getSession().createQuery("from User order by username").list();
+    }
+
+    public void deleteUser(Long userId) {
+        User user = getUser(userId);
+        if( user != null ) {
+            getSession().delete(user);
+        }
+    }
+
+    public void updateUser(User user) {
+        getSession().update(user);
+    }
+
+}
+
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/UserDAO.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/UserDAO.java
new file mode 100644
index 0000000..db580c5
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/dao/UserDAO.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.dao;
+
+import org.apache.shiro.samples.sprhib.model.User;
+
+import java.util.List;
+
+/**
+ * Data Access Object for User related operations.
+ */
+public interface UserDAO {
+
+    User getUser(Long userId);
+
+    User findUser(String username);
+
+    void createUser(User user);
+
+    List<User> getAllUsers();
+
+    void deleteUser(Long userId);
+
+    void updateUser(User user);
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/Role.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/Role.java
new file mode 100644
index 0000000..9115239
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/Role.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.model;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.CollectionOfElements;
+import org.hibernate.annotations.Index;
+
+import javax.persistence.*;
+import java.util.Set;
+
+/**
+ * Model object that represents a security role.
+ */
+ at Entity
+ at Table(name="roles")
+ at Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
+public class Role {
+
+    private Long id;
+
+    private String name;
+
+    private String description;
+
+    private Set<String> permissions;
+
+    protected Role() {
+    }
+
+    public Role(String name) {
+        this.name = name;
+    }
+
+
+    @Id
+    @GeneratedValue
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    @Basic(optional=false)
+    @Column(length=100)
+    @Index(name="idx_roles_name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Basic(optional=false)
+    @Column(length=255)
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @CollectionOfElements
+    @JoinTable(name="roles_permissions")
+    @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions) {
+        this.permissions = permissions;
+    }
+
+}
+
+
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/User.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/User.java
new file mode 100644
index 0000000..6545a02
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/model/User.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.model;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.Index;
+
+import javax.persistence.*;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple class that represents any User domain entity in any application.
+ *
+ * <p>Because this class performs its own Realm and Permission checks, and these can happen frequently enough in a
+ * production application, it is highly recommended that the internal User {@link #getRoles} collection be cached
+ * in a 2nd-level cache when using JPA and/or Hibernate.  The hibernate xml configuration for this sample application
+ * does in fact do this for your reference (see User.hbm.xml - the 'roles' declaration).</p>
+ */
+ at Entity
+ at Table(name="users")
+ at Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
+public class User  {
+
+    private Long id;
+    private String username;
+    private String email;
+    private String password;
+    private Set<Role> roles = new HashSet<Role>();
+
+
+    @Id
+    @GeneratedValue
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the username associated with this user account;
+     *
+     * @return the username associated with this user account;
+     */
+    @Basic(optional=false)
+    @Column(length=100)
+    @Index(name="idx_users_username")
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Basic(optional=false)
+    @Index(name="idx_users_email")
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    /**
+     * Returns the password for this user.
+     *
+     * @return this user's password
+     */
+    @Basic(optional=false)
+    @Column(length=255)
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+
+    @ManyToMany
+    @JoinTable(name="users_roles")
+    @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
+    public Set<Role> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(Set<Role> roles) {
+        this.roles = roles;
+    }
+
+}
+
+
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/security/SampleRealm.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/security/SampleRealm.java
new file mode 100644
index 0000000..a4584ed
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/security/SampleRealm.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.security;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authc.credential.Sha256CredentialsMatcher;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.samples.sprhib.dao.UserDAO;
+import org.apache.shiro.samples.sprhib.model.Role;
+import org.apache.shiro.samples.sprhib.model.User;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * The Spring/Hibernate sample application's one and only configured Apache Shiro Realm.
+ *
+ * <p>Because a Realm is really just a security-specific DAO, we could have just made Hibernate calls directly
+ * in the implementation and named it a 'HibernateRealm' or something similar.</p>
+ *
+ * <p>But we've decided to make the calls to the database using a UserDAO, since a DAO would be used in other areas
+ * of a 'real' application in addition to here. We felt it better to use that same DAO to show code re-use.</p>
+ */
+ at Component
+public class SampleRealm extends AuthorizingRealm {
+
+    protected UserDAO userDAO = null;
+
+    public SampleRealm() {
+        setName("SampleRealm"); //This name must match the name in the User class's getPrincipals() method
+        setCredentialsMatcher(new Sha256CredentialsMatcher());
+    }
+
+    @Autowired
+    public void setUserDAO(UserDAO userDAO) {
+        this.userDAO = userDAO;
+    }
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
+        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
+        User user = userDAO.findUser(token.getUsername());
+        if( user != null ) {
+            return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), getName());
+        } else {
+            return null;
+        }
+    }
+
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        Long userId = (Long) principals.fromRealm(getName()).iterator().next();
+        User user = userDAO.getUser(userId);
+        if( user != null ) {
+            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
+            for( Role role : user.getRoles() ) {
+                info.addRole(role.getName());
+                info.addStringPermissions( role.getPermissions() );
+            }
+            return info;
+        } else {
+            return null;
+        }
+    }
+
+}
+
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/DefaultUserService.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/DefaultUserService.java
new file mode 100644
index 0000000..8edc1d7
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/DefaultUserService.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.service;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.apache.shiro.samples.sprhib.dao.UserDAO;
+import org.apache.shiro.samples.sprhib.model.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * Default implementation of the {@link UserService} interface.  This service implements
+ * operations related to User data.
+ */
+ at Transactional
+ at Service("userService")
+public class DefaultUserService implements UserService {
+
+    private UserDAO userDAO;
+
+    @Autowired
+    public void setUserDAO(UserDAO userDAO) {
+        this.userDAO = userDAO;
+    }
+
+    public User getCurrentUser() {
+        final Long currentUserId = (Long) SecurityUtils.getSubject().getPrincipal();
+        if( currentUserId != null ) {
+            return getUser(currentUserId);
+        } else {
+            return null;
+        }
+    }
+
+    public void createUser(String username, String email, String password) {
+        User user = new User();
+        user.setUsername(username);
+        user.setEmail(email);
+        user.setPassword( new Sha256Hash(password).toHex() );
+        userDAO.createUser( user );
+    }
+
+    public List<User> getAllUsers() {
+        return userDAO.getAllUsers();
+    }
+
+    public User getUser(Long userId) {
+        return userDAO.getUser(userId);
+    }
+
+    public void deleteUser(Long userId) {
+        userDAO.deleteUser( userId );
+    }
+
+    public void updateUser(User user) {
+        userDAO.updateUser( user );
+    }
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/UserService.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/UserService.java
new file mode 100644
index 0000000..d294b9b
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/service/UserService.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.service;
+
+import org.apache.shiro.samples.sprhib.model.User;
+
+import java.util.List;
+
+/**
+ * A service interface for accessing and modifying user data in the system.
+ */
+public interface UserService {
+
+    User getCurrentUser();
+
+    void createUser(String username, String email, String password);
+
+    List<User> getAllUsers();
+
+    User getUser(Long userId);
+
+    void deleteUser(Long userId);
+
+    void updateUser(User user);
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/CurrentUserInterceptor.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/CurrentUserInterceptor.java
new file mode 100644
index 0000000..77ee936
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/CurrentUserInterceptor.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.samples.sprhib.model.User;
+import org.apache.shiro.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A Spring MVC interceptor that adds the currentUser into the request as a request attribute
+ * before the JSP is rendered.  This operation is assumed to be fast because the User should be
+ * cached in the Hibernate second-level cache.
+ */
+ at Component
+public class CurrentUserInterceptor extends HandlerInterceptorAdapter {
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
+        // Add the current user into the request
+        User currentUser = userService.getCurrentUser();
+        if( currentUser != null ) {
+            httpServletRequest.setAttribute( "currentUser", currentUser );
+        }
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserCommand.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserCommand.java
new file mode 100644
index 0000000..7369d54
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.apache.shiro.samples.sprhib.model.User;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Command binding object for editing a user.
+ */
+public class EditUserCommand {
+
+    private Long userId;
+    private String username;
+    private String email;
+    private String password;
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void updateUser(User user) {
+        Assert.isTrue( userId.equals( user.getId() ), "User ID of command must match the user being updated." );
+        user.setUsername( getUsername() );
+        user.setEmail( getEmail() );
+        if( StringUtils.hasText(getPassword()) ) {
+            user.setPassword( new Sha256Hash(getPassword()).toHex() );
+        }
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserValidator.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserValidator.java
new file mode 100644
index 0000000..09d4583
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/EditUserValidator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+import java.util.regex.Pattern;
+
+/**
+ * Validator when editing a user.
+ */
+public class EditUserValidator implements Validator {
+
+    private static final String SIMPLE_EMAIL_REGEX = "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
+
+    public boolean supports(Class aClass) {
+        return EditUserCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        EditUserCommand command = (EditUserCommand)o;
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "error.email.empty", "Please specify an email address.");
+        if( StringUtils.hasText( command.getEmail() ) && !Pattern.matches( SIMPLE_EMAIL_REGEX, command.getEmail().toUpperCase() ) ) {
+            errors.rejectValue( "email", "error.email.invalid", "Please enter a valid email address." );
+        }
+    }
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/HomeController.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/HomeController.java
new file mode 100644
index 0000000..5af4a4a
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/HomeController.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * Web controller used when loading the home page.
+ */
+ at Controller
+public class HomeController {
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping("/home")
+    public void viewHome(Model model) {
+        model.addAttribute( "users", userService.getAllUsers() );
+    }
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginCommand.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginCommand.java
new file mode 100644
index 0000000..a0170d6
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginCommand.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+/**
+ * Command binding object for logging in.
+ */
+public class LoginCommand {
+
+    private String username;
+
+    private String password;
+
+    private boolean rememberMe;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public boolean isRememberMe() {
+        return rememberMe;
+    }
+
+    public void setRememberMe(boolean rememberMe) {
+        this.rememberMe = rememberMe;
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginValidator.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginValidator.java
new file mode 100644
index 0000000..cc7b432
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/LoginValidator.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+/**
+ * Validator for login.
+ */
+public class LoginValidator implements Validator {
+    public boolean supports(Class aClass) {
+        return LoginCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "error.password.empty", "Please specify a password.");
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/ManageUsersController.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/ManageUsersController.java
new file mode 100644
index 0000000..0c40b34
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/ManageUsersController.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.samples.sprhib.model.User;
+import org.apache.shiro.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.Assert;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Web MVC controller that handles operations related to managing users, such as editing them and deleting them. 
+ */
+ at Controller
+public class ManageUsersController {
+
+    private EditUserValidator editUserValidator = new EditUserValidator();
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping("/manageUsers")
+    @RequiresPermissions("user:manage")
+    public void manageUsers(Model model) {
+        model.addAttribute("users", userService.getAllUsers());
+    }
+
+    @RequestMapping(value="/editUser",method= RequestMethod.GET)
+    @RequiresPermissions("user:edit")
+    public String showEditUserForm(Model model, @RequestParam Long userId, @ModelAttribute EditUserCommand command) {
+
+        User user = userService.getUser( userId );
+        command.setUserId(userId);
+        command.setUsername(user.getUsername());
+        command.setEmail(user.getEmail());
+        return "editUser";
+    }
+
+    @RequestMapping(value="/editUser",method= RequestMethod.POST)
+    @RequiresPermissions("user:edit")
+    public String editUser(Model model, @RequestParam Long userId, @ModelAttribute EditUserCommand command, BindingResult errors) {
+        editUserValidator.validate( command, errors );
+
+        if( errors.hasErrors() ) {
+            return "editUser";
+        }
+
+        User user = userService.getUser( userId );
+        command.updateUser( user );
+
+        userService.updateUser( user );
+
+        return "redirect:/s/manageUsers";
+    }
+
+    @RequestMapping("/deleteUser")
+    @RequiresPermissions("user:delete")
+    public String deleteUser(@RequestParam Long userId) {
+        Assert.isTrue( userId != 1, "Cannot delete admin user" );
+        userService.deleteUser( userId );
+        return "redirect:/s/manageUsers";
+    }
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SecurityController.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SecurityController.java
new file mode 100644
index 0000000..d091118
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SecurityController.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Web MVC controller that handles security-related web requests, such as login and logout.
+ */
+ at Controller
+public class SecurityController {
+
+    private LoginValidator loginValidator = new LoginValidator();
+
+    @RequestMapping(value="/login",method= RequestMethod.GET)
+    public String showLoginForm(Model model, @ModelAttribute LoginCommand command ) {
+        return "login";
+    }
+
+    @RequestMapping(value="/login",method= RequestMethod.POST)
+    public String login(Model model, @ModelAttribute LoginCommand command, BindingResult errors) {
+        loginValidator.validate(command, errors);
+
+        if( errors.hasErrors() ) {
+            return showLoginForm(model, command);
+        }
+
+        UsernamePasswordToken token = new UsernamePasswordToken(command.getUsername(), command.getPassword(), command.isRememberMe());
+        try {
+            SecurityUtils.getSubject().login(token);
+        } catch (AuthenticationException e) {
+            errors.reject( "error.login.generic", "Invalid username or password.  Please try again." );
+        }
+
+        if( errors.hasErrors() ) {
+            return showLoginForm(model, command);
+        } else {
+            return "redirect:/s/home";
+        }
+    }
+
+    @RequestMapping("/logout")
+    public String logout() {
+        SecurityUtils.getSubject().logout();
+        return "redirect:/";
+    }
+
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupCommand.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupCommand.java
new file mode 100644
index 0000000..c0638be
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupCommand.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+/**
+ * Command binding object for signing up for a new account. 
+ */
+public class SignupCommand {
+
+    private String username;
+
+    private String email;
+
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupController.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupController.java
new file mode 100644
index 0000000..1500b0c
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupController.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.samples.sprhib.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Web MVC controller that handles signup requests.
+ */
+ at Controller
+public class SignupController {
+
+    private SignupValidator signupValidator = new SignupValidator();
+
+    private UserService userService;
+
+    @Autowired
+    public void setUserService(UserService userService) {
+        this.userService = userService;
+    }
+
+    @RequestMapping(value="/signup",method= RequestMethod.GET)
+    public String showSignupForm(Model model, @ModelAttribute SignupCommand command) {
+        return "signup";
+    }
+
+    @RequestMapping(value="/signup",method= RequestMethod.POST)
+    public String showSignupForm(Model model, @ModelAttribute SignupCommand command, BindingResult errors) {
+        signupValidator.validate(command, errors);
+
+        if( errors.hasErrors() ) {
+            return showSignupForm(model, command);
+        }
+
+        // Create the user
+        userService.createUser( command.getUsername(), command.getEmail(), command.getPassword() );
+
+        // Login the newly created user
+        SecurityUtils.getSubject().login(new UsernamePasswordToken(command.getUsername(), command.getPassword()));
+
+        return "redirect:/s/home";
+    }
+
+}
diff --git a/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupValidator.java b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupValidator.java
new file mode 100644
index 0000000..26859ff
--- /dev/null
+++ b/samples/spring-hibernate/src/main/java/org/apache/shiro/samples/sprhib/web/SignupValidator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.sprhib.web;
+
+import org.apache.shiro.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+import java.util.regex.Pattern;
+
+/**
+ * Validator for the signup form.
+ */
+public class SignupValidator implements Validator {
+
+    private static final String SIMPLE_EMAIL_REGEX = "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}";
+
+    public boolean supports(Class aClass) {
+        return SignupCommand.class.isAssignableFrom(aClass);
+    }
+
+    public void validate(Object o, Errors errors) {
+        SignupCommand command = (SignupCommand)o;
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "error.username.empty", "Please specify a username.");
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "error.email.empty", "Please specify an email address.");
+        if( StringUtils.hasText( command.getEmail() ) && !Pattern.matches( SIMPLE_EMAIL_REGEX, command.getEmail().toUpperCase() ) ) {
+            errors.rejectValue( "email", "error.email.invalid", "Please enter a valid email address." );
+        }
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "error.password.empty", "Please specify a password.");
+    }
+}
diff --git a/samples/spring-hibernate/src/main/resources/ehcache.xml b/samples/spring-hibernate/src/main/resources/ehcache.xml
new file mode 100644
index 0000000..6e1cf55
--- /dev/null
+++ b/samples/spring-hibernate/src/main/resources/ehcache.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<ehcache>
+
+    <diskStore path="java.io.tmpdir/shiro-sprhib-ehcache"/>
+
+
+    <defaultCache
+        maxElementsInMemory="1000"
+        eternal="false"
+        timeToLiveSeconds="600"
+        overflowToDisk="true"
+        diskPersistent="false"
+        />
+
+    <!--=================================================================
+        Hibernate Object Caches
+        =================================================================-->
+
+    <cache name="org.apache.shiro.samples.sprhib.model.Role"
+        maxElementsInMemory="100"
+        timeToLiveSeconds="0"
+        overflowToDisk="true"/>
+
+    <cache name="org.apache.shiro.samples.sprhib.model.Role.permissions"
+        maxElementsInMemory="100"
+        timeToLiveSeconds="0"
+        overflowToDisk="true"/>
+
+    <cache name="org.apache.shiro.samples.sprhib.model.User"
+        maxElementsInMemory="1000"
+        timeToLiveSeconds="3600"
+        overflowToDisk="true"/>
+
+    <cache name="org.apache.shiro.samples.sprhib.model.User.roles"
+        maxElementsInMemory="1000"
+        timeToLiveSeconds="3600"
+        overflowToDisk="true"/>
+
+</ehcache>
\ No newline at end of file
diff --git a/samples/spring-hibernate/src/main/resources/hibernate.cfg.xml b/samples/spring-hibernate/src/main/resources/hibernate.cfg.xml
new file mode 100644
index 0000000..975b36e
--- /dev/null
+++ b/samples/spring-hibernate/src/main/resources/hibernate.cfg.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!DOCTYPE hibernate-configuration PUBLIC
+  "-//Hibernate/Hibernate Configuration DTD//EN"
+  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+
+<!-- This file is largely provided for IDE/tool support, and is NOT used at runtime -->
+
+<hibernate-configuration>
+  <session-factory>
+    <!-- DB schema will be updated if needed -->
+    <property name="hbm2ddl.auto">update</property>
+
+    <mapping class="org.apache.shiro.samples.sprhib.model.Role"/>
+    <mapping class="org.apache.shiro.samples.sprhib.model.User"/>
+
+  </session-factory>
+</hibernate-configuration>
\ No newline at end of file
diff --git a/samples/spring-hibernate/src/main/resources/log4j.properties b/samples/spring-hibernate/src/main/resources/log4j.properties
new file mode 100644
index 0000000..75afa85
--- /dev/null
+++ b/samples/spring-hibernate/src/main/resources/log4j.properties
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# General Apache libraries
+log4j.logger.org.apache=WARN
+
+# Spring
+log4j.logger.org.springframework=WARN
+
+# Hibernate
+log4j.logger.org.hibernate=WARN
+
+# Default Shiro logging
+log4j.logger.org.apache.shiro=TRACE
+
+# Disable verbose logging
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
+log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/applicationContext.xml b/samples/spring-hibernate/src/main/webapp/WEB-INF/applicationContext.xml
new file mode 100644
index 0000000..f852ec3
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/applicationContext.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xmlns:tx="http://www.springframework.org/schema/tx"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
+       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
+       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
+       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+    <!-- Enable annotation configuration -->
+    <context:annotation-config/>
+
+    <!-- Scan sample packages for Spring annotations -->
+    <context:component-scan base-package="org.apache.shiro.samples.sprhib.dao"/>
+    <context:component-scan base-package="org.apache.shiro.samples.sprhib.security"/>
+    <context:component-scan base-package="org.apache.shiro.samples.sprhib.service"/>
+
+    <!-- Spring AOP auto-proxy creation (required to support Shiro annotations) -->
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
+
+
+    <!-- Sample RDBMS data source that would exist in any application.  Sample is just using an in-memory HSQLDB
+         instance.  Change to your application's settings for a real app. -->
+    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
+        <property name="url" value="jdbc:hsqldb:mem:shiro-spring-hibernate"/>
+        <property name="username" value="sa"/>
+    </bean>
+
+    <!-- Hibernate SessionFactory -->
+    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
+        <property name="dataSource" ref="dataSource"/>
+        <!-- Because we're using an in-memory database for demo purposes (which is lost every time the app
+             shuts down), we have to ensure that the HSQLDB DDL is run each time the app starts.  The
+             DDL is auto-generated based on the *.hbm.xml mapping definitions below. -->
+        <property name="schemaUpdate" value="true"/>
+        <!-- Scan packages for JPA annotations -->
+        <property name="packagesToScan" value="org.apache.shiro.samples.sprhib.model"/>
+        <property name="hibernateProperties">
+            <props>
+                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
+                <prop key="hibernate.jdbc.fetch_size">100</prop>
+                <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</prop>
+            </props>
+        </property>
+        <property name="eventListeners">
+            <map>
+                <entry key="merge">
+                    <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
+                </entry>
+            </map>
+        </property>
+    </bean>
+
+    <!-- Transaction support beans -->
+    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
+        <property name="sessionFactory" ref="sessionFactory"/>
+    </bean>
+
+    <tx:annotation-driven/>
+
+
+    <!-- =========================================================
+         Shiro Components
+         ========================================================= -->
+
+    <!-- Shiro's main business-tier object for web-enabled applications
+         (use org.apache.shiro.web.mgt.DefaultWebSecurityManager instead when there is no web environment)-->
+    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
+        <!-- Single realm app (realm configured next, below).  If you have multiple realms, use the 'realms'
+      property instead. -->
+        <property name="realm" ref="sampleRealm"/>
+        <!-- Uncomment this next property if you want heterogenous session access or clusterable/distributable
+             sessions.  The default value is 'http' which uses the Servlet container's HttpSession as the underlying
+             Session implementation.
+        <property name="sessionMode" value="native"/> -->
+    </bean>
+
+    <!-- Post processor that automatically invokes init() and destroy() methods -->
+    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
+
+    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
+         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
+         to wire things with more control as well utilize nice Spring things such as
+         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
+    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
+        <property name="securityManager" ref="securityManager"/>
+        <property name="loginUrl" value="/s/login"/>
+        <property name="successUrl" value="/s/home"/>
+        <property name="unauthorizedUrl" value="/unauthorized"/>
+        <!-- The 'filters' property is usually not necessary unless performing an override, which we
+             want to do here (make authc point to a PassthruAuthenticationFilter instead of the
+             default FormAuthenticationFilter: -->
+        <property name="filters">
+            <util:map>
+                <entry key="authc">
+                    <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
+                </entry>
+            </util:map>
+        </property>
+        <property name="filterChainDefinitions">
+            <value>
+                /s/signup = anon
+                /s/manageUsers = perms[user:manage]
+                /s/** = authc
+            </value>
+        </property>
+    </bean>
+
+</beans>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/editUser.jsp b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/editUser.jsp
new file mode 100644
index 0000000..ad4255d
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/editUser.jsp
@@ -0,0 +1,52 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+    <div id="box">
+        <div class="title">Apache Shiro Sample App - Edit User</div>
+
+        <div class="content">
+            <form:form modelAttribute="editUserCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Email:</div><form:input path="email"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><input type="button" onclick="document.location.href='<c:url value="/s/manageUsers"/>'" value="Cancel"/> <input type="submit" value="Save Changes"/></div>
+            </form:form>
+
+            <p>Only edit the password field if you want to change the user's password.  Otherwise leave password blank.</p>
+            
+        </div>
+
+    </div>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/home.jsp b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/home.jsp
new file mode 100644
index 0000000..b3f59da
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/home.jsp
@@ -0,0 +1,55 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
+
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+
+<div id="bigbox">
+    <div class="title clearfix"><div style="float: left">Apache Shiro Sample App - Home</div><div class="info" >Logged in as ${currentUser.username} (<a href="<c:url value="/s/logout"/>">Logout</a>)</div></div>
+
+
+    <div class="content">
+
+        <p>Users in the system:</p>
+        <ul>
+            <c:forEach var="user" items="${users}">
+                <li>${user.username} - ${user.email}</li>
+            </c:forEach>
+        </ul>
+
+        <p>
+        <shiro:hasPermission name="user:manage">
+            Since you are logged in as the admin user, you can <a href="<c:url value="/s/manageUsers"/>">manage site users</a>.
+        </shiro:hasPermission>
+        <shiro:lacksPermission name="user:manage">
+            Since you are not logged in as the admin user, you can't manage site users.
+        </shiro:lacksPermission>
+        </p>
+    </div>
+
+</div>
+
+</body>
+</html>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/login.jsp b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/login.jsp
new file mode 100644
index 0000000..7c16c17
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/login.jsp
@@ -0,0 +1,56 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/> 
+</head>
+<body>
+    <div id="box">
+        <div class="title">Apache Shiro Sample App - Login</div>
+
+        <div class="content">
+            <form:form modelAttribute="loginCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><form:checkbox path="rememberMe"/> Remember Me</div>
+                <div><input type="submit" value="Login"/></div>
+            </form:form>
+
+            <div>Don't have an account? <a href="<c:url value="/s/signup"/>">Sign up</a></div>
+        </div>
+    </div>
+
+    <p>
+        Users created through the signup form have the role "user".  You can also log in as admin/admin, which has the
+        "admin" role.
+    </p>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/manageUsers.jsp b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/manageUsers.jsp
new file mode 100644
index 0000000..ebee253
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/manageUsers.jsp
@@ -0,0 +1,57 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
+
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+
+<div id="bigbox">
+    <div class="title clearfix"><div style="float: left">Apache Shiro Sample App - Manage Users</div><div class="info" >Logged in as ${currentUser.username} (<a href="<c:url value="/s/logout"/>">Logout</a>)</div></div>
+
+
+    <div class="content">
+
+        <table id="manageUsers">
+            <tr>
+                <th>Username</th>
+                <th>Email</th>
+                <th>Actions</th>
+            </tr>
+            <c:forEach var="user" items="${users}">
+            <tr>
+                <td>${user.username}</td>
+                <td>${user.email}</td>
+                <td><a href="<c:url value="/s/editUser?userId=${user.id}"/>">Edit</a><c:if test="${user.id ne 1}"> | <a href="<c:url value="/s/deleteUser?userId=${user.id}"/>">Delete</a></c:if>
+                </td>
+            </tr>
+            </c:forEach>
+        </table>
+
+        <p>Return to <a href="<c:url value="/s/home"/>">Home</a></p>
+    </div>
+
+</div>
+
+</body>
+</html>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/signup.jsp b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/signup.jsp
new file mode 100644
index 0000000..c131ebe
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/jsp/signup.jsp
@@ -0,0 +1,49 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+    <div id="box">
+        <div class="title">Apache Shiro Sample App - Signup</div>
+
+        <div class="content">
+            <form:form modelAttribute="signupCommand">
+
+                <form:errors path="*" element="div" cssClass="errors"/>
+
+                <div><div class="form-label">Username:</div><form:input path="username"/></div>
+                <div><div class="form-label">Email:</div><form:input path="email"/></div>
+                <div><div class="form-label">Password:</div><form:password path="password"/></div>
+                <div><input type="button" onclick="document.location.href='<c:url value="/s/login"/>'" value="Cancel"/> <input type="submit" value="Signup"/></div>
+            </form:form>
+        </div>
+    </div>
+
+    <script type="text/javascript">
+        document.getElementById('username').focus();
+    </script>
+
+</body>
+</html>
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/sprhib-servlet.xml b/samples/spring-hibernate/src/main/webapp/WEB-INF/sprhib-servlet.xml
new file mode 100644
index 0000000..6d9d353
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/sprhib-servlet.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+        http://www.springframework.org/schema/beans
+        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+        http://www.springframework.org/schema/context
+        http://www.springframework.org/schema/context/spring-context-2.5.xsd">
+
+    <!-- Enable annotation component scanning and autowiring of web package -->
+    <context:annotation-config/>
+    <context:component-scan base-package="org.apache.shiro.samples.sprhib.web"/>
+
+    <!-- Required for security annotations to work in this servlet -->
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
+    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
+
+
+    <!-- Enable annotation-based controllers using @Controller annotations -->
+    <bean id="annotationUrlMapping"
+          class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
+        <property name="interceptors" ref="currentUserInterceptor"/>
+    </bean>
+
+    <bean id="annotationMethodHandlerAdapter"
+          class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
+
+    <!-- All views are JSPs loaded from /WEB-INF/jsp -->
+    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
+        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
+        <property name="prefix" value="/WEB-INF/jsp/"/>
+        <property name="suffix" value=".jsp"/>
+    </bean>
+
+</beans>
\ No newline at end of file
diff --git a/samples/spring-hibernate/src/main/webapp/WEB-INF/web.xml b/samples/spring-hibernate/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..12992bf
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5">
+
+    <!-- ===================================================================
+ -  Context parameters
+ -  =================================================================== -->
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>
+            /WEB-INF/applicationContext.xml
+        </param-value>
+    </context-param>
+
+    <!--
+    - Key of the system property that should specify the root directory of this
+    - web app. Applied by WebAppRootListener or Log4jConfigListener.
+    -->
+    <context-param>
+        <param-name>webAppRootKey</param-name>
+        <param-value>shiro-spring-hibernate-sample.webapp.root</param-value>
+    </context-param>
+
+    <!-- ===================================================================
+ -  Servlet listeners
+ -  =================================================================== -->
+    <listener>
+        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+    </listener>
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+    <!-- ===================================================================
+ -  Filters
+ -  =================================================================== -->
+    <filter>
+        <filter-name>openSessionInViewFilter</filter-name>
+        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
+    </filter>
+
+    <!-- Shiro Filter is defined in the spring application context: -->
+    <filter>
+        <filter-name>shiroFilter</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>openSessionInViewFilter</filter-name>
+        <url-pattern>/s/*</url-pattern>
+    </filter-mapping>
+
+    <filter-mapping>
+        <filter-name>shiroFilter</filter-name>
+        <url-pattern>/s/*</url-pattern>
+    </filter-mapping>
+
+    <!-- ===================================================================
+ -  Servlets
+ -  =================================================================== -->
+    <servlet>
+        <servlet-name>sprhib</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>sprhib</servlet-name>
+        <url-pattern>/s/*</url-pattern>
+    </servlet-mapping>
+
+
+    <!-- ===================================================================
+     -  Welcome file list
+     -  =================================================================== -->
+   <welcome-file-list>
+       <welcome-file>index.jsp</welcome-file>
+   </welcome-file-list>
+
+    <error-page>
+        <error-code>401</error-code>
+        <location>/unauthorized.jsp</location>
+    </error-page>
+
+</web-app>
diff --git a/samples/spring-hibernate/src/main/webapp/index.jsp b/samples/spring-hibernate/src/main/webapp/index.jsp
new file mode 100644
index 0000000..56e49ec
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/index.jsp
@@ -0,0 +1,22 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<%-- Redirect to index page --%>
+<c:redirect url="/s/home"/>
\ No newline at end of file
diff --git a/samples/spring-hibernate/src/main/webapp/styles/sample.css b/samples/spring-hibernate/src/main/webapp/styles/sample.css
new file mode 100644
index 0000000..adff6d7
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/styles/sample.css
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+body { background: #f6f6f6}
+body, td, th { font-family:"Lucida Grande","Lucida Sans Unicode",Arial,Verdana,sans-serif; color: #333; font-size: 12px; }
+#box { width: 500px; }
+#bigbox { width: 940px; }
+#bigbox, #box { margin: 30px; padding: 0; border: thin black solid; background: #fff}
+#bigbox .title, #box .title { display: block; margin: 0 0 5px 0; padding: 5px 5px 5px 15px; background: #ddd; font-size: 18px}
+#bigbox .title .info { float: right; padding-right: 10px; font-size: 12px}
+
+.errors { color: red; padding: 10px 0 10px 0; }
+.form-label { float: left; width: 100px;}
+
+.content { padding: 10px;}
+.content div { padding: 5px 0 5px 0 }
+
+#manageUsers { width: 800px }
+#manageUsers th { text-align: left }
+
+a:active, a:visited, a:link { color: #6666DD; text-decoration: none; }
+a:hover { color: #6666FF; text-decoration: underline; }
+
+.clearfix:after {content:".";display:block;height:0;clear:both;visibility:hidden;}
+.clearfix {display:inline-block;}
+* html .clearfix {height:1%;}
+.clearfix {display:block;}
\ No newline at end of file
diff --git a/samples/spring-hibernate/src/main/webapp/unauthorized.jsp b/samples/spring-hibernate/src/main/webapp/unauthorized.jsp
new file mode 100644
index 0000000..a67cadd
--- /dev/null
+++ b/samples/spring-hibernate/src/main/webapp/unauthorized.jsp
@@ -0,0 +1,29 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<html>
+<head>
+    <title>Apache Shiro Spring-Hibernate Sample Application</title>
+    <link rel="stylesheet" type="text/css" href="<c:url value="/styles/sample.css"/>"/>
+</head>
+<body>
+<h3>Unauthorized</h3>
+<p>You are not authorized to access the requested page.  Please return to the <a href="<c:url value="/s/home"/>">homepage</a>.</p>
+</body>
+</html>
diff --git a/samples/spring/pom.xml b/samples/spring/pom.xml
new file mode 100644
index 0000000..2baf295
--- /dev/null
+++ b/samples/spring/pom.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency	-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-spring</artifactId>
+    <name>Apache Shiro :: Samples :: Spring</name>
+    <packaging>war</packaging>
+    <description>
+    	Spring-based web application sample demonstrating Shiro's capabilities. Uses samples-spring module
+    	as the web start application. To launch the webstart application successfully, you need to run 
+    	"mvn jetty:run-exploded", or otherwise make sure the webstart application is available through
+    	web application root context (see the dependency:unpack configure below)
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- Note	that you need	to run mvn jetty:run-exploded	to test	the	webstart application -->
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <configuration>
+                    <contextPath>/shiro-samples-spring</contextPath>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>8080</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
+                        <filename>./target/yyyy_mm_dd.request.log</filename>
+                        <retainDays>90</retainDays>
+                        <append>true</append>
+                        <extended>false</extended>
+                        <logTimeZone>GMT</logTimeZone>
+                    </requestLog>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>hsqldb</groupId>
+                        <artifactId>hsqldb</artifactId>
+                        <version>${hsqldb.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>dependency-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <artifactItems>
+                        <artifactItem>
+                            <groupId>org.apache.shiro.samples</groupId>
+                            <artifactId>samples-spring-client</artifactId>
+                            <version>${project.version}</version>
+                            <type>zip</type>
+                        </artifactItem>
+                    </artifactItems>
+                    <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>replace-jnlp-file</id>
+                        <phase>process-resources</phase>
+                        <configuration>
+                            <tasks>
+                                <!-- move would be more appropriate but it would fail on repetitive executions of
+                                     jetty:run for example, leaving the original in place doesn't hurt -->
+                                <copy file="${project.build.directory}/${project.build.finalName}/shiro.jnlp.jsp" todir="${project.build.directory}/${project.build.finalName}/WEB-INF/resources" />
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro.samples</groupId>
+            <artifactId>samples-spring-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-ehcache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>hsqldb</groupId>
+            <artifactId>hsqldb</artifactId>
+            <version>${hsqldb.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/BootstrapDataPopulator.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/BootstrapDataPopulator.java
new file mode 100644
index 0000000..7e50f0a
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/BootstrapDataPopulator.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.crypto.hash.Sha256Hash;
+
+/**
+ * A data populator that creates a set of security tables and test data that can be used by the
+ * Shiro Spring sample application to demonstrate the use of the {@link org.apache.shiro.realm.jdbc.JdbcRealm}
+ * The tables created by this class follow the default table and column names that {@link org.apache.shiro.realm.jdbc.JdbcRealm} uses.
+ *
+ */
+public class BootstrapDataPopulator implements InitializingBean {
+
+    private static final String CREATE_TABLES = "create table users (\n" +
+            "    username varchar(255) primary key,\n" +
+            "    password varchar(255) not null\n" +
+            ");\n" +
+            "\n" +
+            "create table roles (\n" +
+            "    role_name varchar(255) primary key\n" +
+            ");\n" +
+            "\n" +
+            "create table user_roles (\n" +
+            "    username varchar(255) not null,\n" +
+            "    role_name varchar(255) not null,\n" +
+            "    constraint user_roles_uq unique ( username, role_name )\n" +
+            ");\n" +
+            "\n" +
+            "create table roles_permissions (\n" +
+            "    role_name varchar(255) not null,\n" +
+            "    permission varchar(255) not null,\n" +
+            "    primary key (role_name, permission)\n" +
+            ");";
+
+    private static final Logger log = LoggerFactory.getLogger(BootstrapDataPopulator.class);
+
+    protected DataSource dataSource = null;
+
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        //because we're using an in-memory hsqldb for the sample app, a new one will be created each time the
+        //app starts, so create the tables and insert the 2 sample users on bootstrap:
+
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
+        jdbcTemplate.execute(CREATE_TABLES);
+
+        //password is 'user1' SHA hashed and base64 encoded:
+        //The first argument to the hash constructor is the actual value to be hased.  The 2nd is the
+        //salt.  In this simple demo scenario, the username and the password are the same, but to clarify the
+        //distinction, you would see this in practice:
+        //new Sha256Hash( <password>, <cryptographically strong randomly generated salt> (not the username!) )
+        String query = "insert into users values ('user1', '" + new Sha256Hash("user1", "user1").toBase64() + "' )";
+        jdbcTemplate.execute(query);
+        log.debug("Created user1.");
+
+        //password is 'user2' SHA hashed and base64 encoded:
+        query = "insert into users values ( 'user2', '"  + new Sha256Hash("user2", "user2").toBase64() + "' )";
+        jdbcTemplate.execute(query);
+        log.debug("Created user2.");
+
+        query = "insert into roles values ( 'role1' )";
+        jdbcTemplate.execute(query);
+        log.debug("Created role1");
+
+        query = "insert into roles values ( 'role2' )";
+        jdbcTemplate.execute(query);
+        log.debug("Created role2");
+
+        query = "insert into roles_permissions values ( 'role1', 'permission1')";
+        jdbcTemplate.execute(query);
+        log.debug("Created permission 1 for role 1");
+
+        query = "insert into roles_permissions values ( 'role1', 'permission2')";
+        jdbcTemplate.execute(query);
+        log.debug("Created permission 2 for role 1");
+
+        query = "insert into roles_permissions values ( 'role2', 'permission1')";
+        jdbcTemplate.execute(query);
+        log.debug("Created permission 1 for role 2");
+
+        query = "insert into user_roles values ( 'user1', 'role1' )";
+        jdbcTemplate.execute(query);
+        query = "insert into user_roles values ( 'user1', 'role2' )";
+        jdbcTemplate.execute(query);
+        log.debug("Assigned user1 roles role1 and role2");
+
+        query = "insert into user_roles values ( 'user2', 'role2' )";
+        jdbcTemplate.execute(query);
+        log.debug("Assigned user2 role role2");
+    }
+
+}
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/DefaultSampleManager.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/DefaultSampleManager.java
new file mode 100644
index 0000000..8321ad1
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/DefaultSampleManager.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+
+
+/**
+ * Default implementation of the {@link SampleManager} interface that stores
+ * and retrieves a value from the user's session.
+ *
+ * @since 0.1
+ */
+public class DefaultSampleManager implements SampleManager {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * Key used to store the value in the user's session.
+     */
+    private static final String VALUE_KEY = "sample_value";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(DefaultSampleManager.class);
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    public String getValue() {
+        String value = null;
+
+        Subject subject = SecurityUtils.getSubject();
+        Session session = subject.getSession(false);
+        if (session != null) {
+            value = (String) session.getAttribute(VALUE_KEY);
+            if (log.isDebugEnabled()) {
+                log.debug("retrieving session key [" + VALUE_KEY + "] with value [" + value + "] on session with id [" + session.getId() + "]");
+            }
+        }
+
+        return value;
+    }
+
+    public void setValue(String newValue) {
+        Subject subject = SecurityUtils.getSubject();
+        Session session = subject.getSession();
+
+        if (log.isDebugEnabled()) {
+            log.debug("saving session key [" + VALUE_KEY + "] with value [" + newValue + "] on session with id [" + session.getId() + "]");
+        }
+
+        session.setAttribute(VALUE_KEY, newValue);
+    }
+
+    public void secureMethod1() {
+        if (log.isInfoEnabled()) {
+            log.info("Secure method 1 called...");
+        }
+    }
+
+    public void secureMethod2() {
+        if (log.isInfoEnabled()) {
+            log.info("Secure method 2 called...");
+        }
+    }
+
+    public void secureMethod3() {
+        if (log.isInfoEnabled()) {
+            log.info("Secure method 3 called...");
+        }
+    }
+}
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/realm/SaltAwareJdbcRealm.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/realm/SaltAwareJdbcRealm.java
new file mode 100644
index 0000000..56448d0
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/realm/SaltAwareJdbcRealm.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.realm;
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.JdbcUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Realm that exists to support salted credentials.  The JdbcRealm implementation needs to be updated in a future
+ * Shiro release to handle this.
+ */
+public class SaltAwareJdbcRealm extends JdbcRealm {
+
+    private static final Logger log = LoggerFactory.getLogger(SaltAwareJdbcRealm.class);
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        String username = upToken.getUsername();
+
+        // Null username is invalid
+        if (username == null) {
+            throw new AccountException("Null usernames are not allowed by this realm.");
+        }
+
+        Connection conn = null;
+        AuthenticationInfo info = null;
+        try {
+            conn = dataSource.getConnection();
+
+            String password = getPasswordForUser(conn, username);
+
+            if (password == null) {
+                throw new UnknownAccountException("No account found for user [" + username + "]");
+            }
+
+            SimpleAuthenticationInfo saInfo = new SimpleAuthenticationInfo(username, password, getName());
+            /**
+             * This (very bad) example uses the username as the salt in this sample app.  DON'T DO THIS IN A REAL APP!
+             *
+             * Salts should not be based on anything that a user could enter (attackers can exploit this).  Instead
+             * they should ideally be cryptographically-strong randomly generated numbers.
+             */
+            saInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
+
+            info = saInfo;
+
+        } catch (SQLException e) {
+            final String message = "There was a SQL error while authenticating user [" + username + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+
+            // Rethrow any SQL errors as an authentication exception
+            throw new AuthenticationException(message, e);
+        } finally {
+            JdbcUtils.closeConnection(conn);
+        }
+
+        return info;
+    }
+
+    private String getPasswordForUser(Connection conn, String username) throws SQLException {
+
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        String password = null;
+        try {
+            ps = conn.prepareStatement(authenticationQuery);
+            ps.setString(1, username);
+
+            // Execute query
+            rs = ps.executeQuery();
+
+            // Loop over results - although we are only expecting one result, since usernames should be unique
+            boolean foundResult = false;
+            while (rs.next()) {
+
+                // Check to ensure only one row is processed
+                if (foundResult) {
+                    throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
+                }
+
+                password = rs.getString(1);
+
+                foundResult = true;
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+
+        return password;
+    }
+
+}
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/IndexController.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/IndexController.java
new file mode 100644
index 0000000..8e87e46
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/IndexController.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.samples.spring.SampleManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.springframework.validation.BindException;
+import org.springframework.validation.Errors;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.SimpleFormController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Spring MVC controller responsible for rendering the Shiro Spring sample
+ * application index page.
+ *
+ * @since 0.1
+ */
+public class IndexController extends SimpleFormController {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+
+    private SampleManager sampleManager;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public void setSampleManager(SampleManager sampleManager) {
+        this.sampleManager = sampleManager;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    protected Object formBackingObject(HttpServletRequest request) throws Exception {
+        SessionValueCommand command = (SessionValueCommand) createCommand();
+
+        command.setValue(sampleManager.getValue());
+        return command;
+    }
+
+    protected Map<String, Object> referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
+        Subject subject = SecurityUtils.getSubject();
+        boolean hasRole1 = subject.hasRole("role1");
+        boolean hasRole2 = subject.hasRole("role2");
+
+        Map<String, Object> refData = new HashMap<String, Object>();
+        refData.put("hasRole1", hasRole1);
+        refData.put("hasRole2", hasRole2);
+
+        Session session = subject.getSession();
+        Map<Object, Object> sessionAttributes = new LinkedHashMap<Object, Object>();
+        for (Object key : session.getAttributeKeys()) {
+            sessionAttributes.put(key, session.getAttribute(key));
+        }
+        refData.put("sessionAttributes", sessionAttributes);
+
+        refData.put("subjectSession", subject.getSession());
+        return refData;
+    }
+
+    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object obj, BindException errors) throws Exception {
+        SessionValueCommand command = (SessionValueCommand) obj;
+
+        sampleManager.setValue(command.getValue());
+
+        return showForm(request, response, errors);
+    }
+
+}
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/JnlpController.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/JnlpController.java
new file mode 100644
index 0000000..09a7fc2
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/JnlpController.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.AbstractController;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * Controller used to dynamically build a JNLP file used to launch the Shiro
+ * Spring WebStart sample application.
+ *
+ * @since 0.1
+ */
+public class JnlpController extends AbstractController {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private String jnlpView;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public void setJnlpView(String jnlpView) {
+        this.jnlpView = jnlpView;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+        Subject subject = SecurityUtils.getSubject();
+        Session session = null;
+
+        if (subject != null) {
+            session = subject.getSession();
+        }
+        if (session == null) {
+            String msg = "Expected a non-null Shiro session.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("http://");
+        sb.append(request.getServerName());
+        if (request.getServerPort() != 80) {
+            sb.append(":");
+            sb.append(request.getServerPort());
+        }
+        sb.append(request.getContextPath());
+
+        // prevent JNLP caching by setting response headers
+        response.setHeader("cache-control", "no-cache");
+        response.setHeader("pragma", "no-cache");
+
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("codebaseUrl", sb.toString());
+        model.put("sessionId", session.getId());
+        return new ModelAndView(jnlpView, model);
+    }
+}
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginCommand.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginCommand.java
new file mode 100644
index 0000000..e818a16
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginCommand.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+/**
+ * Command object that parameters are bound to when logging into the sample
+ * application.
+ *
+ * @since 0.1
+ */
+public class LoginCommand {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private String username;
+    private String password;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public String getUsername() {
+        return username;
+    }
+
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+
+    public String getPassword() {
+        return password;
+    }
+
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+}
\ No newline at end of file
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginController.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginController.java
new file mode 100644
index 0000000..381802d
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LoginController.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.validation.BindException;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.SimpleFormController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Spring MVC controller responsible for authenticating the user.
+ *
+ * @since 0.1
+ */
+public class LoginController extends SimpleFormController {
+
+    private static transient final Logger log = LoggerFactory.getLogger(LoginController.class);
+
+    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object cmd, BindException errors) throws Exception {
+
+        LoginCommand command = (LoginCommand) cmd;
+
+        UsernamePasswordToken token = new UsernamePasswordToken(command.getUsername(), command.getPassword());
+
+        try {
+            SecurityUtils.getSubject().login(token);
+        } catch (AuthenticationException e) {
+            log.debug("Error authenticating.", e);
+            errors.reject("error.invalidLogin", "The username or password was not correct.");
+        }
+
+        if (errors.hasErrors()) {
+            return showForm(request, response, errors);
+        } else {
+            return new ModelAndView(getSuccessView());
+        }
+    }
+}
\ No newline at end of file
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LogoutController.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LogoutController.java
new file mode 100644
index 0000000..b4dce7b
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/LogoutController.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+import org.apache.shiro.SecurityUtils;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.AbstractController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Controller responsible for logging out the current user by invoking
+ * {@link org.apache.shiro.subject.Subject#logout()}
+ *
+ * @since 0.1
+ */
+public class LogoutController extends AbstractController {
+
+    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        SecurityUtils.getSubject().logout();
+        return new ModelAndView("redirect:login");
+    }
+}
\ No newline at end of file
diff --git a/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/SessionValueCommand.java b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/SessionValueCommand.java
new file mode 100644
index 0000000..4191833
--- /dev/null
+++ b/samples/spring/src/main/java/org/apache/shiro/samples/spring/web/SessionValueCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.samples.spring.web;
+
+/**
+ * Command object used to bind parameters when submitting a value to be
+ * stored in the user's session from the index page.
+ *
+ * @since 0.1
+ */
+public class SessionValueCommand {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private String value;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/samples/spring/src/main/resources/ehcache.xml b/samples/spring/src/main/resources/ehcache.xml
new file mode 100644
index 0000000..eb3504d
--- /dev/null
+++ b/samples/spring/src/main/resources/ehcache.xml
@@ -0,0 +1,98 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!-- EhCache XML configuration file used for Shiro spring sample application -->
+<ehcache>
+
+    <!-- Sets the path to the directory where cache .data files are created.
+
+If the path is a Java System Property it is replaced by
+its value in the running VM.
+
+The following properties are translated:
+user.home - User's home directory
+user.dir - User's current working directory
+java.io.tmpdir - Default temp file path -->
+    <diskStore path="java.io.tmpdir/shiro-spring-sample"/>
+
+
+    <!--Default Cache configuration. These will applied to caches programmatically created through
+    the CacheManager.
+
+    The following attributes are required:
+
+    maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
+    eternal                        - Sets whether elements are eternal. If eternal,  timeouts are ignored and the
+                                     element is never expired.
+    overflowToDisk                 - Sets whether elements can overflow to disk when the in-memory cache
+                                     has reached the maxInMemory limit.
+
+    The following attributes are optional:
+    timeToIdleSeconds              - Sets the time to idle for an element before it expires.
+                                     i.e. The maximum amount of time between accesses before an element expires
+                                     Is only used if the element is not eternal.
+                                     Optional attribute. A value of 0 means that an Element can idle for infinity.
+                                     The default value is 0.
+    timeToLiveSeconds              - Sets the time to live for an element before it expires.
+                                     i.e. The maximum time between creation time and when an element expires.
+                                     Is only used if the element is not eternal.
+                                     Optional attribute. A value of 0 means that and Element can live for infinity.
+                                     The default value is 0.
+    diskPersistent                 - Whether the disk store persists between restarts of the Virtual Machine.
+                                     The default value is false.
+    diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+                                     is 120 seconds.
+    memoryStoreEvictionPolicy      - Policy would be enforced upon reaching the maxElementsInMemory limit. Default
+                                     policy is Least Recently Used (specified as LRU). Other policies available -
+                                     First In First Out (specified as FIFO) and Less Frequently Used
+                                     (specified as LFU)
+    -->
+
+    <defaultCache
+            maxElementsInMemory="10000"
+            eternal="false"
+            timeToIdleSeconds="120"
+            timeToLiveSeconds="120"
+            overflowToDisk="false"
+            diskPersistent="false"
+            diskExpiryThreadIntervalSeconds="120"
+            />
+
+    <!-- We want eternal="true" (with no timeToIdle or timeToLive settings) because Shiro manages session
+expirations explicitly.  If we set it to false and then set corresponding timeToIdle and timeToLive properties,
+ehcache would evict sessions without Shiro's knowledge, which would cause many problems
+(e.g. "My Shiro session timeout is 30 minutes - why isn't a session available after 2 minutes?"
+Answer - ehcache expired it due to the timeToIdle property set to 120 seconds.)
+
+diskPersistent=true since we want an enterprise session management feature - ability to use sessions after
+even after a JVM restart.  -->
+    <cache name="shiro-activeSessionCache"
+           maxElementsInMemory="10000"
+           eternal="true"
+           overflowToDisk="true"
+           diskPersistent="true"
+           diskExpiryThreadIntervalSeconds="600"/>
+
+    <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
+           maxElementsInMemory="100"
+           eternal="false"
+           timeToLiveSeconds="600"
+           overflowToDisk="false"/>
+
+</ehcache>
diff --git a/samples/spring/src/main/resources/jsecurity-sample.jks b/samples/spring/src/main/resources/jsecurity-sample.jks
new file mode 100644
index 0000000..eb2ff9b
Binary files /dev/null and b/samples/spring/src/main/resources/jsecurity-sample.jks differ
diff --git a/samples/spring/src/main/resources/log4j.properties b/samples/spring/src/main/resources/log4j.properties
new file mode 100644
index 0000000..779033d
--- /dev/null
+++ b/samples/spring/src/main/resources/log4j.properties
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# General Apache libraries
+log4j.logger.org.apache=WARN
+
+# Spring
+log4j.logger.org.springframework=WARN
+
+# Default Shiro logging
+log4j.logger.org.apache.shiro=TRACE
+
+# Disable verbose logging
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
+log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
diff --git a/samples/spring/src/main/webapp/WEB-INF/applicationContext.xml b/samples/spring/src/main/webapp/WEB-INF/applicationContext.xml
new file mode 100644
index 0000000..c3c116a
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/applicationContext.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+    <!-- Sample RDBMS data source that would exist in any application - not Shiro related. -->
+    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
+        <property name="url" value="jdbc:hsqldb:mem:shiro-spring"/>
+        <property name="username" value="sa"/>
+    </bean>
+    <!-- Populates the sample database with sample users and roles. -->
+    <bean id="bootstrapDataPopulator" class="org.apache.shiro.samples.spring.BootstrapDataPopulator">
+        <property name="dataSource" ref="dataSource"/>
+    </bean>
+
+    <!-- Simulated business-tier "Manager", not Shiro related, just an example -->
+    <bean id="sampleManager" class="org.apache.shiro.samples.spring.DefaultSampleManager"/>
+
+    <!-- =========================================================
+         Shiro Core Components - Not Spring Specific
+         ========================================================= -->
+    <!-- Shiro's main business-tier object for web-enabled applications
+         (use DefaultSecurityManager instead when there is no web environment)-->
+    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
+        <property name="cacheManager" ref="cacheManager"/>
+        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
+        <property name="sessionMode" value="native"/>
+        <property name="realm" ref="jdbcRealm"/>
+    </bean>
+
+    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
+         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
+    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
+        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
+             will be creaed with a default config:
+             <property name="cacheManager" ref="ehCacheManager"/> -->
+        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
+             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
+             will be used.:
+        <property name="cacheManagerConfigFile" value="classpath:some/path/to/ehcache.xml"/> -->
+    </bean>
+
+    <!-- Used by the SecurityManager to access security data (users, roles, etc).
+         Many other realm implementations can be used too (PropertiesRealm,
+         LdapRealm, etc. -->
+    <bean id="jdbcRealm" class="org.apache.shiro.samples.spring.realm.SaltAwareJdbcRealm">
+        <property name="name" value="jdbcRealm"/>
+        <property name="dataSource" ref="dataSource"/>
+        <property name="credentialsMatcher">
+            <!-- The 'bootstrapDataPopulator' Sha256 hashes the password
+                 (using the username as the salt) then base64 encodes it: -->
+            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
+                <property name="hashAlgorithmName" value="SHA-256"/>
+                <!-- true means hex encoded, false means base64 encoded -->
+                <property name="storedCredentialsHexEncoded" value="false"/>
+            </bean>
+        </property>
+    </bean>
+
+    <!-- =========================================================
+         Shiro Spring-specific integration
+         ========================================================= -->
+    <!-- Post processor that automatically invokes init() and destroy() methods
+         for Spring-configured Shiro objects so you don't have to
+         1) specify an init-method and destroy-method attributes for every bean
+            definition and
+         2) even know which Shiro objects require these methods to be
+            called. -->
+    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
+
+    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
+         the lifecycleBeanProcessor has run: -->
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
+          depends-on="lifecycleBeanPostProcessor"/>
+    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
+        <property name="securityManager" ref="securityManager"/>
+    </bean>
+
+    <!-- Secure Spring remoting:  Ensure any Spring Remoting method invocations can be associated
+         with a Subject for security checks. -->
+    <bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
+        <property name="securityManager" ref="securityManager"/>
+    </bean>
+
+    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
+         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
+         to wire things with more control as well utilize nice Spring things such as
+         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
+    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
+        <property name="securityManager" ref="securityManager"/>
+        <property name="loginUrl" value="/s/login"/>
+        <property name="successUrl" value="/s/index"/>
+        <property name="unauthorizedUrl" value="/s/unauthorized"/>
+        <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean
+             defined will be automatically acquired and available via its beanName in chain
+             definitions, but you can perform overrides or parent/child consolidated configuration
+             here if you like: -->
+        <!-- <property name="filters">
+            <util:map>
+                <entry key="aName" value-ref="someFilterPojo"/>
+            </util:map>
+        </property> -->
+        <property name="filterChainDefinitions">
+            <value>
+                /favicon.ico = anon
+                /logo.png = anon
+                /shiro.css = anon
+                /s/login = anon
+                # allow WebStart to pull the jars for the swing app:
+                /*.jar = anon
+                # everything else requires authentication:
+                /** = authc
+            </value>
+        </property>
+    </bean>
+
+</beans>
diff --git a/samples/spring/src/main/webapp/WEB-INF/remoting-servlet.xml b/samples/spring/src/main/webapp/WEB-INF/remoting-servlet.xml
new file mode 100644
index 0000000..ac08b71
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/remoting-servlet.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!--
+  - Application context definition for "remoting" DispatcherServlet.
+  -->
+<beans>
+
+    <bean name="/sampleManager" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
+        <property name="service" ref="sampleManager"/>
+        <property name="serviceInterface" value="org.apache.shiro.samples.spring.SampleManager"/>
+        <property name="remoteInvocationExecutor" ref="secureRemoteInvocationExecutor"/>
+    </bean>
+
+</beans>
diff --git a/samples/spring/src/main/webapp/WEB-INF/resources/include.jsp b/samples/spring/src/main/webapp/WEB-INF/resources/include.jsp
new file mode 100644
index 0000000..f65884f
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/resources/include.jsp
@@ -0,0 +1,24 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ page session="false" %>
+
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
\ No newline at end of file
diff --git a/samples/spring/src/main/webapp/WEB-INF/resources/login.jsp b/samples/spring/src/main/webapp/WEB-INF/resources/login.jsp
new file mode 100644
index 0000000..c1cc7cc
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/resources/login.jsp
@@ -0,0 +1,51 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ include file="include.jsp" %>
+
+<html>
+
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/shiro.css"/>"/>
+</head>
+
+<body onload="document.forms[0].elements[0].focus();">
+
+<div id="contentBox">
+
+    <h1>Shiro Login</h1>
+
+    <p>
+        <span style="color: red;">
+            <spring:bind path="command.*">
+                ${status.errorMessage}
+            </spring:bind>
+        </span>
+    </p>
+
+    <form action="login" method="POST">
+        Username: <input id="username" name="username" type="text"/><br/><br/>
+        Password: <input name="password" type="password"/><br/><br/>
+        <input type="submit" value="Login"/>
+    </form>
+
+    <p>Try logging in with username/passwords: user1/user1 and user2/user2.</p>
+</div>
+</body>
+
+</html>
diff --git a/samples/spring/src/main/webapp/WEB-INF/resources/sampleIndex.jsp b/samples/spring/src/main/webapp/WEB-INF/resources/sampleIndex.jsp
new file mode 100644
index 0000000..f81a6ec
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/resources/sampleIndex.jsp
@@ -0,0 +1,85 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ include file="include.jsp" %>
+
+<html>
+
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/shiro.css"/>"/>
+</head>
+
+<body>
+
+<div id="contentBox">
+    <img src="<c:url value="/logo.png"/>" style="margin-top:20px; border:0"/><br/>
+
+    <h2>You have successfully logged in as <shiro:principal/>.</h2>
+
+    Session ID: ${subjectSession.id}
+
+    <h3>Session Attribute Keys</h3>
+    <table border="1">
+        <tr>
+            <th>Key</th>
+            <th>Value</th>
+        </tr>
+        <c:forEach items="${sessionAttributes}" var="entry">
+            <tr>
+                <td>${entry.key}</td>
+                <td>${entry.value}</td>
+            </tr>
+        </c:forEach>
+    </table>
+
+    <p style="font-weight: bold;">
+        <shiro:hasRole name="role1">You have role 1.<br/></shiro:hasRole>
+        <shiro:lacksRole name="role1">You do not have role 1.<br/></shiro:lacksRole>
+        <shiro:hasRole name="role2">You have role 2.<br/></shiro:hasRole>
+        <shiro:lacksRole name="role2">You do not have role 2.<br/></shiro:lacksRole>
+    </p>
+
+    <p style="font-weight: bold;">
+        <shiro:hasPermission name="permission1">You have permission 1.<br/></shiro:hasPermission>
+        <shiro:lacksPermission name="permission1">You do not have permission 1.<br/></shiro:lacksPermission>
+        <shiro:hasPermission name="permission2">You have permission 2.<br/></shiro:hasPermission>
+        <shiro:lacksPermission name="permission2">You do not have permission 2.<br/></shiro:lacksPermission>
+    </p>
+
+
+    <form action="<c:url value="/s/index"/>" method="POST">
+        Enter value here to store in session: <input type="text" name="value" value="${command.value}" size="30"/>
+        <input type="submit" value="Save"/>
+        <button type="button" onclick="document.location.href='<c:url value="/s/index"/>';">Refresh</button>
+    </form>
+
+
+    <p>
+        Click <a href="<c:url value="/s/shiro.jnlp?sessionId=${subjectSession.id}"/>">here</a> to launch webstart
+        application. (Need to be running <span style="font-weight:bold">mvn jetty:run-exploded</span> to have webstart
+        app
+        resources available through the webapp context)
+    </p>
+
+
+    <p>
+        Click <a href="<c:url value="/s/logout"/>">here</a> to logout.
+    </p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring/src/main/webapp/WEB-INF/sample-servlet.xml b/samples/spring/src/main/webapp/WEB-INF/sample-servlet.xml
new file mode 100644
index 0000000..055487f
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/sample-servlet.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!--
+  - Application context definition for "sample" DispatcherServlet.
+  -->
+
+<beans>
+
+    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+        <property name="mappings">
+            <props>
+                <prop key="index">indexController</prop>
+                <prop key="shiro.jnlp">jnlpController</prop>
+                <prop key="login">loginController</prop>
+                <prop key="logout">logoutController</prop>
+                <prop key="unauthorized">loginController</prop>
+            </props>
+        </property>
+    </bean>
+
+    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+        <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
+        <property name="prefix" value="/WEB-INF/resources/"/>
+        <property name="suffix" value=".jsp"/>
+    </bean>
+
+    <!-- =========================================================
+         Spring controllers
+         ========================================================= -->
+    <bean name="loginController" class="org.apache.shiro.samples.spring.web.LoginController">
+        <property name="commandClass" value="org.apache.shiro.samples.spring.web.LoginCommand"/>
+        <property name="formView" value="login"/>
+        <property name="successView" value="redirect:/s/index"/>
+    </bean>
+
+    <bean name="logoutController" class="org.apache.shiro.samples.spring.web.LogoutController"/>
+
+    <bean id="jnlpController" class="org.apache.shiro.samples.spring.web.JnlpController">
+        <property name="jnlpView" value="shiro.jnlp"/>
+    </bean>
+
+    <bean id="indexController" class="org.apache.shiro.samples.spring.web.IndexController">
+        <property name="commandClass" value="org.apache.shiro.samples.spring.web.SessionValueCommand"/>
+        <property name="formView" value="sampleIndex"/>
+        <property name="successView" value="sampleIndex"/>
+        <property name="sampleManager" ref="sampleManager"/>
+    </bean>
+
+    <bean name="urlController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
+
+</beans>
diff --git a/samples/spring/src/main/webapp/WEB-INF/web.xml b/samples/spring/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..0e52b6c
--- /dev/null
+++ b/samples/spring/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5">
+
+    <!-- ==================================================================
+         Context parameters
+         ================================================================== -->
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>/WEB-INF/applicationContext.xml</param-value>
+    </context-param>
+
+    <!--
+    - Key of the system property that should specify the root directory of this
+    - web app. Applied by WebAppRootListener or Log4jConfigListener.
+    -->
+    <context-param>
+        <param-name>webAppRootKey</param-name>
+        <param-value>spring-sample.webapp.root</param-value>
+    </context-param>
+
+    <!-- ==================================================================
+         Servlet listeners
+         ================================================================== -->
+    <listener>
+        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
+    </listener>
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+    <!-- ==================================================================
+         Filters
+         ================================================================== -->
+    <!-- Shiro Filter is defined in the spring application context: -->
+    <filter>
+        <filter-name>shiroFilter</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+        <init-param>
+            <param-name>targetFilterLifecycle</param-name>
+            <param-value>true</param-value>
+        </init-param>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>shiroFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <!-- ==================================================================
+         Servlets
+         ================================================================== -->
+    <servlet>
+        <servlet-name>sample</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>sample</servlet-name>
+        <url-pattern>/s/*</url-pattern>
+    </servlet-mapping>
+
+    <servlet>
+        <servlet-name>remoting</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>remoting</servlet-name>
+        <url-pattern>/remoting/*</url-pattern>
+    </servlet-mapping>
+
+    <!-- ==================================================================
+         Welcome file list
+         ================================================================== -->
+    <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+
+</web-app>
diff --git a/samples/spring/src/main/webapp/index.jsp b/samples/spring/src/main/webapp/index.jsp
new file mode 100644
index 0000000..152cdc4
--- /dev/null
+++ b/samples/spring/src/main/webapp/index.jsp
@@ -0,0 +1,29 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ page session="false" %>
+<%@ include file="/WEB-INF/resources/include.jsp" %>
+<html>
+<head>
+    <meta http-equiv="Refresh" content="0; url=<c:url value="/s/login"/>">
+</head>
+<body>
+Please wait...
+</body>
+
+</html>
\ No newline at end of file
diff --git a/samples/spring/src/main/webapp/logo.png b/samples/spring/src/main/webapp/logo.png
new file mode 100644
index 0000000..901d6ec
Binary files /dev/null and b/samples/spring/src/main/webapp/logo.png differ
diff --git a/samples/spring/src/main/webapp/shiro.css b/samples/spring/src/main/webapp/shiro.css
new file mode 100644
index 0000000..4bb9bdf
--- /dev/null
+++ b/samples/spring/src/main/webapp/shiro.css
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+body {
+    margin: 1px;
+    padding: 1px;
+    background: #fff;
+    font: 12px 'Lucida Grande', Geneva, Verdana, Arial, sans-serif;
+    color: #000;
+}
+
+table, td {
+    font: 12px 'Lucida Grande', Geneva, Verdana, Arial, sans-serif;
+    color: #000;
+}
+
+h1 {
+    font: 24px;
+}
+
+img {
+    border: thin black solid;
+}
+
+#contentBox {
+    text-align: center;
+    width: 50%;
+    margin: auto;
+    margin-top: 50px;
+    color: black;
+    background: #eee;
+    border: thick #ccc solid;
+}
\ No newline at end of file
diff --git a/samples/standalone/src/main/java/MyRealm.java b/samples/standalone/src/main/java/MyRealm.java
new file mode 100644
index 0000000..3a5f75e
--- /dev/null
+++ b/samples/standalone/src/main/java/MyRealm.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ */
+public class MyRealm extends AuthorizingRealm {
+
+    public MyRealm() {
+    }
+
+    /**
+     * Simulates a call to an underlying data store - in a 'real' application, this call would communicate with
+     * an underlying data store via an EIS API (JDBC, JPA, Hibernate, etc).
+     * <p/>
+     * Note that when implementing your own realm, there is no need to check against a password (or other credentials)
+     * in this method. The {@link org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm} superclass will do
+     * that automatically via the use of a configured
+     * {@link org.apache.shiro.authc.credential.CredentialsMatcher CredentialsMatcher} (see this example's corresponding
+     * {@code shiro.ini} file to see a configured credentials matcher).
+     * <p/>
+     * All that is required is that the account information include directly the credentials found in the EIS.
+     *
+     * @param username the username for the account data to retrieve
+     * @return the Account information corresponding to the specified username:
+     */
+    protected SimpleAccount getAccount(String username) {
+        //just create a dummy.  A real app would construct one based on EIS access.
+        SimpleAccount account = new SimpleAccount(username, "sha256EncodedPasswordFromDatabase", getName());
+        //simulate some roles and permissions:
+        account.addRole("user");
+        account.addRole("admin");
+        //most applications would assign permissions to Roles instead of users directly because this is much more
+        //flexible (it is easier to configure roles and then change role-to-user assignments than it is to maintain
+        // permissions for each user).
+        // But these next lines assign permissions directly to this trivial account object just for simulation's sake:
+        account.addStringPermission("blogEntry:edit"); //this user is allowed to 'edit' _any_ blogEntry
+        //fine-grained instance level permission:
+        account.addStringPermission("printer:print:laserjet2000"); //allowed to 'print' to the 'printer' identified
+        //by the id 'laserjet2000'
+
+        return account;
+    }
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        //we can safely cast to a UsernamePasswordToken here, because this class 'supports' UsernamePasswordToken
+        //objects.  See the Realm.supports() method if your application will use a different type of token.
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        return getAccount(upToken.getUsername());
+    }
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        //get the principal this realm cares about:
+        String username = (String) getAvailablePrincipal(principals);
+
+        //call the underlying EIS for the account data:
+        return getAccount(username);
+    }
+}
diff --git a/samples/standalone/src/main/java/Standalone.java b/samples/standalone/src/main/java/Standalone.java
new file mode 100644
index 0000000..7f8994f
--- /dev/null
+++ b/samples/standalone/src/main/java/Standalone.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.config.IniConfiguration;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @since Aug 28, 2008 5:46:16 PM
+ */
+public class Standalone {
+
+    public static void main(String[] args) {
+
+        IniConfiguration config = new IniConfiguration();
+        //the following call will automatically use shiro.ini at the root of the classpath:
+        config.init();
+
+        //This is for Standalone (single-VM) applications that don't use a configuration container (Spring, JBoss, etc)
+        //See its JavaDoc for our feelings on this.
+        SecurityUtils.setSecurityManager(config.getSecurityManager());
+
+        //Now you are ready to access the Subject, as shown in the Quickstart:
+        Subject currentUser = SecurityUtils.getSubject();
+
+        //anything else you want to do with the Subject (see the Quickstart for examples).
+
+        currentUser.logout();
+
+        System.exit(0);
+    }
+}
diff --git a/samples/standalone/src/main/resources/log4j.properties b/samples/standalone/src/main/resources/log4j.properties
new file mode 100644
index 0000000..ef36028
--- /dev/null
+++ b/samples/standalone/src/main/resources/log4j.properties
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=INFO, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# General Apache libraries
+log4j.logger.org.apache=WARN
+
+# Spring
+log4j.logger.org.springframework=WARN
+
+# Default Shiro logging
+log4j.logger.org.apache.shiro=TRACE
+
+# Disable verbose logging
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
+log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
+
diff --git a/samples/standalone/src/main/resources/shiro.ini b/samples/standalone/src/main/resources/shiro.ini
new file mode 100644
index 0000000..cdbfe4e
--- /dev/null
+++ b/samples/standalone/src/main/resources/shiro.ini
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+[main]
+# Any realms here will automatically be added to the default created securityManager.  No need to define
+# a securityManager here unless you want to override the default. If you want to override the default, you would
+# do it by uncommenting this line and specifying the fully qualified class name of your SecurityManager implementation:
+# securityManager = my.domain.package.MySecurityManager
+
+# define the realm(s) we want to use for our application.  If you have more than one realm, the order in which they
+# are defined is the order in which they will be consulted during the authentication process.
+# This simple example uses only a single realm, but you could add more for more complicated requirements.
+
+# We'll use credentials hashing, since that keeps the users' credentials (passwords, private keys, etc) safe:
+myRealmCredentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
+
+# now define the realm, and specify that it use the above credentials matcher:
+myRealm = MyRealm
+myRealm.credentialsMatcher = $myRealmCredentialsMatcher
\ No newline at end of file
diff --git a/samples/web/pom.xml b/samples/web/pom.xml
new file mode 100644
index 0000000..e9046cb
--- /dev/null
+++ b/samples/web/pom.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-web</artifactId>
+    <name>Apache Shiro :: Samples :: Web</name>
+    <packaging>war</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <forkMode>never</forkMode>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <configuration>
+                    <contextPath>/</contextPath>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9080</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
+                        <filename>./target/yyyy_mm_dd.request.log</filename>
+                        <retainDays>90</retainDays>
+                        <append>true</append>
+                        <extended>false</extended>
+                        <logTimeZone>GMT</logTimeZone>
+                    </requestLog>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.htmlunit</groupId>
+            <artifactId>htmlunit</artifactId>
+            <version>2.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>${jetty.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jsp-2.1-jetty</artifactId>
+            <version>${jetty.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/samples/web/src/main/resources/log4j.properties b/samples/web/src/main/resources/log4j.properties
new file mode 100644
index 0000000..002a8d7
--- /dev/null
+++ b/samples/web/src/main/resources/log4j.properties
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# This file is used to format all logging output
+log4j.rootLogger=TRACE, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c]: %m%n
+
+# =============================================================================
+# 3rd Party Libraries
+# OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
+# =============================================================================
+# ehcache caching manager:
+log4j.logger.net.sf.ehcache=WARN
+
+# Most all Apache libs:
+log4j.logger.org.apache=WARN
+
+# Quartz Enterprise Scheular (java 'cron' utility)
+log4j.logger.org.quartz=WARN
+
+# =============================================================================
+# Apache Shiro
+# =============================================================================
+# Shiro security framework
+log4j.logger.org.apache.shiro=TRACE
+#log4j.logger.org.apache.shiro.realm.text.PropertiesRealm=INFO
+#log4j.logger.org.apache.shiro.cache.ehcache.EhCache=INFO
+#log4j.logger.org.apache.shiro.io=INFO
+#log4j.logger.org.apache.shiro.web.servlet=INFO
+log4j.logger.org.apache.shiro.util.ThreadContext=INFO
diff --git a/samples/web/src/main/webapp/WEB-INF/shiro.ini b/samples/web/src/main/webapp/WEB-INF/shiro.ini
new file mode 100644
index 0000000..5034565
--- /dev/null
+++ b/samples/web/src/main/webapp/WEB-INF/shiro.ini
@@ -0,0 +1,48 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# INI configuration is very powerful and flexible, while still remaining succinct.
+# Please http://shiro.apache.org/configuration.html and
+# http://shiro.apache.org/web.html for more.
+
+[main]
+shiro.loginUrl = /login.jsp
+
+[users]
+# format: username = password, role1, role2, ..., roleN
+root = secret,admin
+guest = guest,guest
+presidentskroob = 12345,president
+darkhelmet = ludicrousspeed,darklord,schwartz
+lonestarr = vespa,goodguy,schwartz
+
+[roles]
+# format: roleName = permission1, permission2, ..., permissionN
+admin = *
+schwartz = lightsaber:*
+goodguy = winnebago:drive:eagle5
+
+[urls]
+# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
+# the 'authc' filter must still be specified for it so it can process that url's
+# login submissions. It is 'smart' enough to allow those requests through as specified by the
+# shiro.loginUrl above.
+/login.jsp = authc
+/logout = logout
+/account/** = authc
+/remoting/** = authc, roles[b2bClient], perms["remote:invoke:lan,wan"]
\ No newline at end of file
diff --git a/samples/web/src/main/webapp/WEB-INF/web.xml b/samples/web/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..8571065
--- /dev/null
+++ b/samples/web/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5">
+
+    <listener>
+        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+    </listener>
+
+    <filter>
+        <filter-name>ShiroFilter</filter-name>
+        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>ShiroFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+
+</web-app>
diff --git a/samples/web/src/main/webapp/account/index.jsp b/samples/web/src/main/webapp/account/index.jsp
new file mode 100644
index 0000000..4f6c9d8
--- /dev/null
+++ b/samples/web/src/main/webapp/account/index.jsp
@@ -0,0 +1,36 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ include file="../include.jsp" %>
+
+<html>
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/style.css"/>"/>
+</head>
+<body>
+
+<h2>Users only</h2>
+
+<p>You are currently logged in.</p>
+
+<p><a href="<c:url value="/home.jsp"/>">Return to the home page.</a></p>
+
+<p><a href="<c:url value="/logout"/>">Log out.</a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/samples/web/src/main/webapp/home.jsp b/samples/web/src/main/webapp/home.jsp
new file mode 100644
index 0000000..61dee25
--- /dev/null
+++ b/samples/web/src/main/webapp/home.jsp
@@ -0,0 +1,69 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ include file="include.jsp" %>
+
+<html>
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/style.css"/>"/>
+    <title>Apache Shiro Quickstart</title>
+</head>
+<body>
+
+<h1>Apache Shiro Quickstart</h1>
+
+<p>Hi <shiro:guest>Guest</shiro:guest><shiro:user><shiro:principal/></shiro:user>!
+    ( <shiro:user><a href="<c:url value="/logout"/>">Log out</a></shiro:user>
+    <shiro:guest><a href="<c:url value="/login.jsp"/>">Log in</a> (sample accounts provided)</shiro:guest> )
+</p>
+
+<p>Welcome to the Apache Shiro Quickstart sample application.
+    This page represents the home page of any web application.</p>
+
+<shiro:user><p>Visit your <a href="<c:url value="/account"/>">account page</a>.</p></shiro:user>
+<shiro:guest><p>If you want to access the user-only <a href="<c:url value="/account"/>">account page</a>,
+    you will need to log-in first.</p></shiro:guest>
+
+<h2>Roles</h2>
+
+<p>To show some taglibs, here are the roles you have and don't have. Log out and log back in under different user
+    accounts to see different roles.</p>
+
+<h3>Roles you have</h3>
+
+<p>
+    <shiro:hasRole name="admin">admin<br/></shiro:hasRole>
+    <shiro:hasRole name="president">president<br/></shiro:hasRole>
+    <shiro:hasRole name="darklord">darklord<br/></shiro:hasRole>
+    <shiro:hasRole name="goodguy">goodguy<br/></shiro:hasRole>
+    <shiro:hasRole name="schwartz">schwartz<br/></shiro:hasRole>
+</p>
+
+<h3>Roles you DON'T have</h3>
+
+<p>
+    <shiro:lacksRole name="admin">admin<br/></shiro:lacksRole>
+    <shiro:lacksRole name="president">president<br/></shiro:lacksRole>
+    <shiro:lacksRole name="darklord">darklord<br/></shiro:lacksRole>
+    <shiro:lacksRole name="goodguy">goodguy<br/></shiro:lacksRole>
+    <shiro:lacksRole name="schwartz">schwartz<br/></shiro:lacksRole>
+</p>
+
+
+</body>
+</html>
diff --git a/samples/web/src/main/webapp/include.jsp b/samples/web/src/main/webapp/include.jsp
new file mode 100644
index 0000000..fddd239
--- /dev/null
+++ b/samples/web/src/main/webapp/include.jsp
@@ -0,0 +1,22 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ page import="org.apache.shiro.SecurityUtils" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
\ No newline at end of file
diff --git a/samples/web/src/main/webapp/index.jsp b/samples/web/src/main/webapp/index.jsp
new file mode 100644
index 0000000..9dedd19
--- /dev/null
+++ b/samples/web/src/main/webapp/index.jsp
@@ -0,0 +1,21 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+
+<%-- Forward the user to the home page --%>
+<jsp:forward page="home.jsp"/>
\ No newline at end of file
diff --git a/samples/web/src/main/webapp/login.jsp b/samples/web/src/main/webapp/login.jsp
new file mode 100644
index 0000000..c80ee54
--- /dev/null
+++ b/samples/web/src/main/webapp/login.jsp
@@ -0,0 +1,110 @@
+<%--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  --%>
+<%@ include file="include.jsp" %>
+
+<html>
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/style.css"/>"/>
+</head>
+<body>
+
+<h2>Please Log in</h2>
+
+<shiro:guest>
+    <p>Here are a few sample accounts to play with in the default text-based Realm (used for this
+        demo and test installs only). Do you remember the movie these names came from? ;)</p>
+
+
+    <style type="text/css">
+        table.sample {
+            border-width: 1px;
+            border-style: outset;
+            border-color: blue;
+            border-collapse: separate;
+            background-color: rgb(255, 255, 240);
+        }
+
+        table.sample th {
+            border-width: 1px;
+            padding: 1px;
+            border-style: none;
+            border-color: blue;
+            background-color: rgb(255, 255, 240);
+        }
+
+        table.sample td {
+            border-width: 1px;
+            padding: 1px;
+            border-style: none;
+            border-color: blue;
+            background-color: rgb(255, 255, 240);
+        }
+    </style>
+
+
+    <table class="sample">
+        <thead>
+        <tr>
+            <th>Username</th>
+            <th>Password</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr>
+            <td>root</td>
+            <td>secret</td>
+        </tr>
+        <tr>
+            <td>presidentskroob</td>
+            <td>12345</td>
+        </tr>
+        <tr>
+            <td>darkhelmet</td>
+            <td>ludicrousspeed</td>
+        </tr>
+        <tr>
+            <td>lonestarr</td>
+            <td>vespa</td>
+        </tr>
+        </tbody>
+    </table>
+    <br/><br/>
+</shiro:guest>
+
+<form name="loginform" action="" method="post">
+    <table align="left" border="0" cellspacing="0" cellpadding="3">
+        <tr>
+            <td>Username:</td>
+            <td><input type="text" name="username" maxlength="30"></td>
+        </tr>
+        <tr>
+            <td>Password:</td>
+            <td><input type="password" name="password" maxlength="30"></td>
+        </tr>
+        <tr>
+            <td colspan="2" align="left"><input type="checkbox" name="rememberMe"><font size="2">Remember Me</font></td>
+        </tr>
+        <tr>
+            <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
+        </tr>
+    </table>
+</form>
+
+</body>
+</html>
diff --git a/samples/web/src/main/webapp/style.css b/samples/web/src/main/webapp/style.css
new file mode 100644
index 0000000..1308e3f
--- /dev/null
+++ b/samples/web/src/main/webapp/style.css
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+body {
+    margin: 15px 0 0 15px;
+    padding: 1px; /*background: #2370cf;*/
+    font: 12px 'Lucida Grande', Geneva, Verdana, Arial, sans-serif;
+    color: #000;
+}
+
+table, td {
+    font: 12px 'Lucida Grande', Geneva, Verdana, Arial, sans-serif;
+    color: #000;
+}
+
+h1 {
+    font: 24px;
+}
+
+img {
+    border: thin black solid;
+}
+
+#contentBox {
+    text-align: center;
+    width: 50%;
+    margin: auto;
+    margin-top: 50px;
+    color: black;
+    background: #eee;
+    border: thick black solid;
+}
\ No newline at end of file
diff --git a/samples/web/src/test/java/org/apache/shiro/test/AbstractContainerTest.java b/samples/web/src/test/java/org/apache/shiro/test/AbstractContainerTest.java
new file mode 100644
index 0000000..6a0c6e8
--- /dev/null
+++ b/samples/web/src/test/java/org/apache/shiro/test/AbstractContainerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.nio.SelectChannelConnector;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+import java.net.BindException;
+
+public abstract class AbstractContainerTest {
+    public static final int MAX_PORT = 9200;
+
+    protected static PauseableServer server;
+
+    private static int port = 9180;
+
+    protected final WebClient webClient = new WebClient();
+
+    @BeforeClass
+    public static void startContainer() throws Exception {
+        while (server == null && port < MAX_PORT) {
+            try {
+                server = createAndStartServer(port);
+            } catch (BindException e) {
+                System.err.printf("Unable to listen on port %d.  Trying next port.", port);
+                port++;
+            }
+        }
+        assertTrue(server.isStarted());
+    }
+
+    private static PauseableServer createAndStartServer(final int port) throws Exception {
+        PauseableServer server = new PauseableServer();
+        Connector connector = new SelectChannelConnector();
+        connector.setPort(port);
+        server.setConnectors(new Connector[]{connector});
+        server.setHandler(new WebAppContext("src/main/webapp", "/"));
+        server.start();
+        return server;
+    }
+
+    protected static String getBaseUri() {
+        return "http://localhost:" + port + "/";
+    }
+
+    @Before
+    public void beforeTest() {
+        webClient.setThrowExceptionOnFailingStatusCode(true);
+    }
+
+    public void pauseServer(boolean paused) {
+        if (server != null) server.pause(paused);
+    }
+
+    public static class PauseableServer extends Server {
+        public synchronized void pause(boolean paused) {
+            try {
+                if (paused) for (Connector connector : getConnectors())
+                    connector.stop();
+                else for (Connector connector : getConnectors())
+                    connector.start();
+            } catch (Exception e) {
+            }
+        }
+    }
+}
diff --git a/samples/web/src/test/java/org/apache/shiro/test/ContainerIntegrationTest.java b/samples/web/src/test/java/org/apache/shiro/test/ContainerIntegrationTest.java
new file mode 100644
index 0000000..7123cc4
--- /dev/null
+++ b/samples/web/src/test/java/org/apache/shiro/test/ContainerIntegrationTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.test;
+
+import com.gargoylesoftware.htmlunit.ElementNotFoundException;
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.WebAssert;
+import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+ at Ignore
+public class ContainerIntegrationTest extends AbstractContainerTest {
+
+    @Before
+    public void logOut() throws IOException {
+        // Make sure we are logged out
+        final HtmlPage homePage = webClient.getPage(getBaseUri());
+        try {
+            homePage.getAnchorByHref("/logout").click();
+        }
+        catch (ElementNotFoundException e) {
+            //Ignore
+        }
+    }
+
+    @Test
+    public void logIn() throws FailingHttpStatusCodeException, MalformedURLException, IOException, InterruptedException {
+
+        HtmlPage page = webClient.getPage(getBaseUri() + "login.jsp");
+        HtmlForm form = page.getFormByName("loginform");
+        form.<HtmlInput>getInputByName("username").setValueAttribute("root");
+        form.<HtmlInput>getInputByName("password").setValueAttribute("secret");
+        page = form.<HtmlInput>getInputByName("submit").click();
+        // This'll throw an expection if not logged in
+        page.getAnchorByHref("/logout");
+    }
+
+    @Test
+    public void logInAndRememberMe() throws Exception {
+        HtmlPage page = webClient.getPage(getBaseUri() + "login.jsp");
+        HtmlForm form = page.getFormByName("loginform");
+        form.<HtmlInput>getInputByName("username").setValueAttribute("root");
+        form.<HtmlInput>getInputByName("password").setValueAttribute("secret");
+        HtmlCheckBoxInput checkbox = form.getInputByName("rememberMe");
+        checkbox.setChecked(true);
+        page = form.<HtmlInput>getInputByName("submit").click();
+        server.stop();
+        server.start();
+        page = webClient.getPage(getBaseUri());
+        // page.getAnchorByHref("/logout");
+        WebAssert.assertLinkPresentWithText(page, "Log out");
+        page = page.getAnchorByHref("/account").click();
+        // login page should be shown again - user remembered but not authenticated
+        WebAssert.assertFormPresent(page, "loginform");
+    }
+
+}
diff --git a/shiro.doap.rdf b/shiro.doap.rdf
new file mode 100644
index 0000000..9b79ffb
--- /dev/null
+++ b/shiro.doap.rdf
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl"?>
+<rdf:RDF xml:lang="en"
+         xmlns="http://usefulinc.com/ns/doap#"
+         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:asfext="http://projects.apache.org/ns/asfext#"
+         xmlns:foaf="http://xmlns.com/foaf/0.1/">
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+  <Project rdf:about="http://shiro.apache.org">
+    <created>2010-12-11</created>
+    <license rdf:resource="http://usefulinc.com/doap/licenses/asl20" />
+    <name>Apache Shiro</name>
+    <homepage rdf:resource="http://shiro.apache.org" />
+    <asfext:pmc rdf:resource="http://shiro.apache.org" />
+    <shortdesc>Apache Shiro is an easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management.</shortdesc>
+    <description>Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.</description>
+    <bug-database rdf:resource="http://shiro.apache.org/issues.html" />
+    <mailing-list rdf:resource="http://shiro.apache.org/mailing-lists.html" />
+    <download-page rdf:resource="http://shiro.apache.org/download.html" />
+    <programming-language>Java</programming-language>
+    <category rdf:resource="http://projects.apache.org/category/library" />
+	<category rdf:resource="http://projects.apache.org/category/web-framework"/>
+	<release>
+      <Version>
+        <name>Apache Shiro 1.0.0-incubating</name>
+        <created>2010-06-01</created>
+        <revision>1.0.0-incubating</revision>
+      </Version>
+    </release>
+    <release>
+      <Version>
+        <name>Apache Shiro 1.1.0</name>
+        <created>2010-11-01</created>
+        <revision>1.1.0</revision>
+      </Version>
+    </release>
+    <repository>
+      <SVNRepository>
+        <location rdf:resource="http://svn.apache.org/repos/asf/shiro"/>
+        <browse rdf:resource="http://svn.apache.org/repos/asf/shiro"/>
+      </SVNRepository>
+    </repository>
+    <maintainer>
+      <foaf:Person>
+        <foaf:name>Les Hazlewood</foaf:name>
+          <foaf:mbox rdf:resource="mailto:lhazlewood at apache.org"/>
+      </foaf:Person>
+    </maintainer>
+  </Project>
+</rdf:RDF>
\ No newline at end of file
diff --git a/support/aspectj/pom.xml b/support/aspectj/pom.xml
new file mode 100644
index 0000000..70f38d1
--- /dev/null
+++ b/support/aspectj/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-aspectj</artifactId>
+    <name>Apache Shiro :: Support :: AspectJ</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>aspectj-maven-plugin</artifactId>
+                <version>1.4</version>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                    <showWeaveInfo>true</showWeaveInfo>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>aspectj-compile</id>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>test-compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.aspectj</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.aspectj*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.aspectj*;version="[1.6.0, 2.0.0)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/aspectj/src/main/aspect/org/apache/shiro/aspectj/ShiroAnnotationAuthorizingAspect.java b/support/aspectj/src/main/aspect/org/apache/shiro/aspectj/ShiroAnnotationAuthorizingAspect.java
new file mode 100644
index 0000000..053dd15
--- /dev/null
+++ b/support/aspectj/src/main/aspect/org/apache/shiro/aspectj/ShiroAnnotationAuthorizingAspect.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+
+/**
+ * Aspect that adds a before advice for each invocation of an annotated method.
+ *
+ */
+ at Aspect()
+public class ShiroAnnotationAuthorizingAspect {
+
+    private static final String pointCupExpression =
+            "execution(@org.apache.shiro.authz.annotation.RequiresAuthentication * *(..)) || " +
+                    "execution(@org.apache.shiro.authz.annotation.RequiresGuest * *(..)) || " +
+                    "execution(@org.apache.shiro.authz.annotation.RequiresPermissions * *(..)) || " +
+                    "execution(@org.apache.shiro.authz.annotation.RequiresRoles * *(..)) || " +
+                    "execution(@org.apache.shiro.authz.annotation.RequiresUser * *(..))";
+
+    @Pointcut(pointCupExpression)
+    public void anyShiroAnnotatedMethod(){}
+
+    @Pointcut(pointCupExpression)
+    void anyShiroAnnotatedMethodCall(JoinPoint thisJoinPoint) {
+    }
+
+    private AspectjAnnotationsAuthorizingMethodInterceptor interceptor =
+            new AspectjAnnotationsAuthorizingMethodInterceptor();
+
+    @Before("anyShiroAnnotatedMethodCall(thisJoinPoint)")
+    public void executeAnnotatedMethod(JoinPoint thisJoinPoint) throws Throwable {
+        interceptor.performBeforeInterception(thisJoinPoint);
+    }
+}
diff --git a/support/aspectj/src/main/java/org/apache/shiro/aspectj/AspectjAnnotationsAuthorizingMethodInterceptor.java b/support/aspectj/src/main/java/org/apache/shiro/aspectj/AspectjAnnotationsAuthorizingMethodInterceptor.java
new file mode 100644
index 0000000..8837731
--- /dev/null
+++ b/support/aspectj/src/main/java/org/apache/shiro/aspectj/AspectjAnnotationsAuthorizingMethodInterceptor.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.aop.AnnotationsAuthorizingMethodInterceptor;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+
+/**
+ * Extends the annotations authorizing method interceptor class hierarchie to adapt
+ * an aspectj {@link JoinPoint} into a {@link MethodInvocation} amd to perform the
+ * authorization of method invocations.
+ *
+ * @since 1.0
+ */
+public class AspectjAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor {
+    /**
+     * This class's private log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(AspectjAnnotationsAuthorizingMethodInterceptor.class);
+
+    /**
+     * Performs the method interception of the before advice at the specified joint point.
+     *
+     * @param aJoinPoint The joint point to intercept.
+     * @throws Throwable If an error occurs berforming the method invocation.
+     */
+    protected void performBeforeInterception(JoinPoint aJoinPoint) throws Throwable {
+        if (log.isTraceEnabled()) log.trace("#### Invoking a method decorated with a Shiro annotation" +
+                "\n\tkind       : " + aJoinPoint.getKind() +
+                "\n\tjoinPoint  : " + aJoinPoint +
+                "\n\tannotations: " + Arrays.toString(((MethodSignature) aJoinPoint.getSignature()).getMethod().getAnnotations()) +
+                "\n\ttarget     : " + aJoinPoint.getTarget()
+        );
+
+        // 1. Adapt the join point into a method invocation
+        BeforeAdviceMethodInvocationAdapter mi = BeforeAdviceMethodInvocationAdapter.createFrom(aJoinPoint);
+
+        // 2. Delegate the authorization of the method call to the super class
+        super.invoke(mi);
+    }
+}
diff --git a/support/aspectj/src/main/java/org/apache/shiro/aspectj/BeforeAdviceMethodInvocationAdapter.java b/support/aspectj/src/main/java/org/apache/shiro/aspectj/BeforeAdviceMethodInvocationAdapter.java
new file mode 100644
index 0000000..f9d1de8
--- /dev/null
+++ b/support/aspectj/src/main/java/org/apache/shiro/aspectj/BeforeAdviceMethodInvocationAdapter.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.apache.shiro.aop.MethodInvocation;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.reflect.AdviceSignature;
+import org.aspectj.lang.reflect.MethodSignature;
+
+import java.lang.reflect.Method;
+
+/**
+ * Helper class that adapts an AspectJ {@link JoinPoint JoinPoint}.
+ *
+ * @since 1.0
+ */
+public class BeforeAdviceMethodInvocationAdapter implements MethodInvocation {
+
+    private Object _object;
+    private Method _method;
+    private Object[] _arguments;
+
+    /**
+     * Factory method that creates a new {@link BeforeAdviceMethodInvocationAdapter} instance
+     * using the AspectJ {@link JoinPoint} provided. If the joint point passed in is not
+     * a method joint point, this method throws an {@link IllegalArgumentException}.
+     *
+     * @param aJoinPoint The AspectJ {@link JoinPoint} to use to adapt the advice.
+     * @return The created instance.
+     * @throws IllegalArgumentException If the join point passed in does not involve a method call.
+     */
+    public static BeforeAdviceMethodInvocationAdapter createFrom(JoinPoint aJoinPoint) {
+        if (aJoinPoint.getSignature() instanceof MethodSignature) {
+            return new BeforeAdviceMethodInvocationAdapter(aJoinPoint.getThis(),
+                    ((MethodSignature) aJoinPoint.getSignature()).getMethod(),
+                    aJoinPoint.getArgs());
+
+        } else if (aJoinPoint.getSignature() instanceof AdviceSignature) {
+            return new BeforeAdviceMethodInvocationAdapter(aJoinPoint.getThis(),
+                    ((AdviceSignature) aJoinPoint.getSignature()).getAdvice(),
+                    aJoinPoint.getArgs());
+
+        } else {
+            throw new IllegalArgumentException("The joint point signature is invalid: expected a MethodSignature or an AdviceSignature but was " + aJoinPoint.getSignature());
+        }
+    }
+
+    /**
+     * Creates a new {@link BeforeAdviceMethodInvocationAdapter} instance.
+     *
+     * @param aMethod       The method to invoke.
+     * @param someArguments The arguments of the method invocation.
+     */
+    public BeforeAdviceMethodInvocationAdapter(Object anObject, Method aMethod, Object[] someArguments) {
+        _object = anObject;
+        _method = aMethod;
+        _arguments = someArguments;
+    }
+
+    /* (non-Javadoc)
+    * @see org.apache.shiro.aop.MethodInvocation#getArguments()
+    */
+
+    public Object[] getArguments() {
+        return _arguments;
+    }
+
+    /* (non-Javadoc)
+    * @see org.apache.shiro.aop.MethodInvocation#getMethod()
+    */
+
+    public Method getMethod() {
+        return _method;
+    }
+
+    /* (non-Javadoc)
+    * @see org.apache.shiro.aop.MethodInvocation#proceed()
+    */
+
+    public Object proceed() throws Throwable {
+        // Do nothing since this adapts a before advice
+        return null;
+    }
+
+    /**
+     * @since 1.0
+     */
+    public Object getThis() {
+        return _object;
+    }
+}
diff --git a/support/aspectj/src/main/java/org/apache/shiro/aspectj/package-info.java b/support/aspectj/src/main/java/org/apache/shiro/aspectj/package-info.java
new file mode 100644
index 0000000..9ef994e
--- /dev/null
+++ b/support/aspectj/src/main/java/org/apache/shiro/aspectj/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * <a href="http://www.eclipse.org/aspectj/">AspectJ</a> support for enabling
+ * dynamic authorization using Shiro's annotations.
+ */
+package org.apache.shiro.aspectj;
+
+
+
diff --git a/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyService.java b/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyService.java
new file mode 100644
index 0000000..f6eb916
--- /dev/null
+++ b/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyService.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+
+/**
+ * Basic service for test purposes.
+ *
+ */
+public interface DummyService {
+
+    public void log(String aMessage);
+
+    public void anonymous();
+
+    public void guest();
+
+    public void peek();
+
+    public void retrieve();
+
+    public void change();
+}
diff --git a/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyServiceTest.java b/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyServiceTest.java
new file mode 100644
index 0000000..1585f38
--- /dev/null
+++ b/support/aspectj/src/test/java/org/apache/shiro/aspectj/DummyServiceTest.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.Factory;
+import org.junit.*;
+
+/**
+ */
+public class DummyServiceTest {
+
+    private static DummyService SECURED_SERVICE;
+    private static DummyService RESTRICTED_SERVICE;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Logger log = Logger.getLogger(AspectjAnnotationsAuthorizingMethodInterceptor.class);
+        log.addAppender(new ConsoleAppender(new SimpleLayout(), ConsoleAppender.SYSTEM_OUT));
+        log.setLevel(Level.TRACE);
+
+        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiroDummyServiceTest.ini");
+        SecurityManager securityManager = factory.getInstance();
+        SecurityUtils.setSecurityManager(securityManager);
+
+        SECURED_SERVICE = new SecuredDummyService();
+        RESTRICTED_SERVICE = new RestrictedDummyService();
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        //don't corrupt other test cases since this is static memory:
+        SecurityUtils.setSecurityManager(null);
+    }
+
+    private Subject subject;
+
+    @Before
+    public void setUp() throws Exception {
+        subject = SecurityUtils.getSubject();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        subject.logout();
+    }
+
+    private void loginAsUser() {
+        subject.login(new UsernamePasswordToken("joe", "bob"));
+    }
+
+    private void loginAsAdmin() {
+        subject.login(new UsernamePasswordToken("root", "secret"));
+    }
+
+    // TEST ANONYMOUS
+    @Test
+    public void testAnonymous_asAnonymous() throws Exception {
+        SECURED_SERVICE.anonymous();
+    }
+
+    @Test
+    public void testAnonymous_asUser() throws Exception {
+        loginAsUser();
+        SECURED_SERVICE.anonymous();
+    }
+
+    @Test
+    public void testAnonymous_asAdmin() throws Exception {
+        loginAsAdmin();
+        SECURED_SERVICE.anonymous();
+    }
+
+    // TEST GUEST
+    @Test
+    public void testGuest_asAnonymous() throws Exception {
+        SECURED_SERVICE.guest();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuest_asUser() throws Exception {
+        loginAsUser();
+        SECURED_SERVICE.guest();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuest_asAdmin() throws Exception {
+        loginAsAdmin();
+        SECURED_SERVICE.guest();
+    }
+
+    // TEST PEEK
+    @Test(expected = UnauthenticatedException.class)
+    public void testPeek_asAnonymous() throws Exception {
+        SECURED_SERVICE.peek();
+    }
+
+    @Test
+    public void testPeek_asUser() throws Exception {
+        loginAsUser();
+        SECURED_SERVICE.peek();
+    }
+
+    @Test
+    public void testPeek_asAdmin() throws Exception {
+        loginAsAdmin();
+        SECURED_SERVICE.peek();
+    }
+
+    // TEST RETRIEVE
+    @Test(expected = UnauthenticatedException.class)
+    //UnauthenticatedException per SHIRO-146
+    public void testRetrieve_asAnonymous() throws Exception {
+        SECURED_SERVICE.retrieve();
+    }
+
+    @Test
+    public void testRetrieve_asUser() throws Exception {
+        loginAsUser();
+        SECURED_SERVICE.retrieve();
+    }
+
+    @Test
+    public void testRetrieve_asAdmin() throws Exception {
+        loginAsAdmin();
+        SECURED_SERVICE.retrieve();
+    }
+
+    // TEST CHANGE
+    @Test(expected = UnauthenticatedException.class)
+    //UnauthenticatedException per SHIRO-146
+    public void testChange_asAnonymous() throws Exception {
+        SECURED_SERVICE.change();
+    }
+
+    @Test(expected = UnauthorizedException.class)
+    public void testChange_asUser() throws Exception {
+        loginAsUser();
+        SECURED_SERVICE.change();
+    }
+
+    @Test
+    public void testChange_asAdmin() throws Exception {
+        loginAsAdmin();
+        SECURED_SERVICE.change();
+    }
+
+    // TEST RETRIEVE RESTRICTED
+    @Test(expected = UnauthenticatedException.class)
+    //UnauthenticatedException per SHIRO-146
+    public void testRetrieveRestricted_asAnonymous() throws Exception {
+        RESTRICTED_SERVICE.retrieve();
+    }
+
+    @Test(expected = UnauthorizedException.class)
+    public void testRetrieveRestricted_asUser() throws Exception {
+        loginAsUser();
+        RESTRICTED_SERVICE.retrieve();
+    }
+
+    @Test
+    public void testRetrieveRestricted_asAdmin() throws Exception {
+        loginAsAdmin();
+        RESTRICTED_SERVICE.retrieve();
+    }
+
+}
diff --git a/support/aspectj/src/test/java/org/apache/shiro/aspectj/RestrictedDummyService.java b/support/aspectj/src/test/java/org/apache/shiro/aspectj/RestrictedDummyService.java
new file mode 100644
index 0000000..889f469
--- /dev/null
+++ b/support/aspectj/src/test/java/org/apache/shiro/aspectj/RestrictedDummyService.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+
+/**
+ * Extends the secure dummy service and makes it some access more restrictive.
+ *
+ */
+public class RestrictedDummyService extends SecuredDummyService {
+
+    @RequiresPermissions("dummy:admin")
+    public void retrieve() {
+        log("retrieve *RESTRICTED*");
+        super.retrieve();
+    }
+
+
+}
diff --git a/support/aspectj/src/test/java/org/apache/shiro/aspectj/SecuredDummyService.java b/support/aspectj/src/test/java/org/apache/shiro/aspectj/SecuredDummyService.java
new file mode 100644
index 0000000..06bde9d
--- /dev/null
+++ b/support/aspectj/src/test/java/org/apache/shiro/aspectj/SecuredDummyService.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.aspectj;
+
+import org.apache.shiro.authz.annotation.RequiresAuthentication;
+import org.apache.shiro.authz.annotation.RequiresGuest;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresUser;
+
+import java.sql.Timestamp;
+
+/**
+ * Secured implementation of te dummy service that requires some permissions to execute.
+ *
+ */
+public class SecuredDummyService implements DummyService {
+
+    @RequiresAuthentication
+    @RequiresPermissions("dummy:admin")
+    public void change() {
+        retrieve();
+        log("change");
+        peek();
+    }
+
+    public void anonymous() {
+        log("anonymous");
+    }
+
+    @RequiresGuest
+    public void guest() {
+        log("guest");
+    }
+
+    @RequiresUser
+    public void peek() {
+        log("peek");
+    }
+
+    @RequiresPermissions("dummy:user")
+    public void retrieve() {
+        log("retrieve");
+    }
+
+    public void log(String aMessage) {
+        if (aMessage != null) {
+            System.out.println(new Timestamp(System.currentTimeMillis()).toString() + " [" + Thread.currentThread() + "] * LOG * " + aMessage);
+        } else {
+            System.out.println("\n\n");
+        }
+    }
+
+}
diff --git a/support/aspectj/src/test/resources/META-INF/aop.xml b/support/aspectj/src/test/resources/META-INF/aop.xml
new file mode 100644
index 0000000..f179aba
--- /dev/null
+++ b/support/aspectj/src/test/resources/META-INF/aop.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<aspectj>
+    <aspects>
+        <aspect name="org.apache.shiro.aspectj.ShiroAnnotationAuthorizingAspect"/>
+    </aspects>
+
+    <weaver options="-verbose">
+        <include within="org.apache.shiro.aspectj.*"/>
+    </weaver>
+
+</aspectj>
diff --git a/support/aspectj/src/test/resources/shiroDummyServiceTest.ini b/support/aspectj/src/test/resources/shiroDummyServiceTest.ini
new file mode 100644
index 0000000..c06fc8d
--- /dev/null
+++ b/support/aspectj/src/test/resources/shiroDummyServiceTest.ini
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# -----------------------------------------------------------------------------
+# Users and their assigned roles
+#
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[users]
+root = secret, admin
+joe = bob, user
+
+# -----------------------------------------------------------------------------
+# Roles with assigned permissions
+# 
+# Each line conforms to the format defined in the
+# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
+# -----------------------------------------------------------------------------
+[roles]
+admin = dummy:*
+user = dummy:user
diff --git a/support/cas/pom.xml b/support/cas/pom.xml
new file mode 100644
index 0000000..830b75a
--- /dev/null
+++ b/support/cas/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-cas</artifactId>
+    <name>Apache Shiro :: Support :: CAS</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jasig.cas.client</groupId>
+            <artifactId>cas-client-core</artifactId>
+            <version>3.2.1</version>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml</artifactId>
+            <version>1.1</version>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>org.apache.santuario</groupId>
+            <artifactId>xmlsec</artifactId>
+            <version>1.4.3</version>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.cas</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.cas*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.jasig.cas.client*;version="[3.2, 4)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
new file mode 100644
index 0000000..e73c6f7
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+
+/**
+ * @since 1.2
+ */
+public class CasAuthenticationException extends AuthenticationException {
+
+    public CasAuthenticationException() {
+        super();
+    }
+
+    public CasAuthenticationException(String message) {
+        super(message);
+    }
+
+    public CasAuthenticationException(Throwable cause) {
+        super(cause);
+    }
+
+    public CasAuthenticationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
new file mode 100644
index 0000000..04beb26
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * This filter validates the CAS service ticket to authenticate the user.  It must be configured on the URL recognized
+ * by the CAS server.  For example, in {@code shiro.ini}:
+ * <pre>
+ * [main]
+ * casFilter = org.apache.shiro.cas.CasFilter
+ * ...
+ *
+ * [urls]
+ * /shiro-cas = casFilter
+ * ...
+ * </pre>
+ * (example : http://host:port/mycontextpath/shiro-cas)
+ *
+ * @since 1.2
+ */
+public class CasFilter extends AuthenticatingFilter {
+    
+    private static Logger logger = LoggerFactory.getLogger(CasFilter.class);
+    
+    // the name of the parameter service ticket in url
+    private static final String TICKET_PARAMETER = "ticket";
+    
+    // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
+    private String failureUrl;
+    
+    /**
+     * The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
+     * the filter must be configured).
+     * 
+     * @param request the incoming request
+     * @param response the outgoing response
+     * @throws Exception if there is an error processing the request.
+     */
+    @Override
+    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        String ticket = httpRequest.getParameter(TICKET_PARAMETER);
+        return new CasToken(ticket);
+    }
+    
+    /**
+     * Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
+     * with this token.
+     * 
+     * @param request the incoming request
+     * @param response the outgoing response
+     * @throws Exception if there is an error processing the request.
+     */
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        return executeLogin(request, response);
+    }
+    
+    /**
+     * Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
+     * 
+     * @param request the incoming request
+     * @param response the outgoing response
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
+     * @return <code>false</code>
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        return false;
+    }
+    
+    /**
+     * If login has been successful, redirect user to the original protected url.
+     * 
+     * @param token the token representing the current authentication
+     * @param subject the current authenticated subjet
+     * @param request the incoming request
+     * @param response the outgoing response
+     * @throws Exception if there is an error processing the request.
+     */
+    @Override
+    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
+                                     ServletResponse response) throws Exception {
+        issueSuccessRedirect(request, response);
+        return false;
+    }
+    
+    /**
+     * If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
+     * authenticated, in which case redirect to the default success url.
+     * 
+     * @param token the token representing the current authentication
+     * @param ae the current authentication exception
+     * @param request the incoming request
+     * @param response the outgoing response
+     */
+    @Override
+    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
+                                     ServletResponse response) {
+        // is user authenticated or in remember me mode ?
+        Subject subject = getSubject(request, response);
+        if (subject.isAuthenticated() || subject.isRemembered()) {
+            try {
+                issueSuccessRedirect(request, response);
+            } catch (Exception e) {
+                logger.error("Cannot redirect to the default success url", e);
+            }
+        } else {
+            try {
+                WebUtils.issueRedirect(request, response, failureUrl);
+            } catch (IOException e) {
+                logger.error("Cannot redirect to failure url : {}", failureUrl, e);
+            }
+        }
+        return false;
+    }
+    
+    public void setFailureUrl(String failureUrl) {
+        this.failureUrl = failureUrl;
+    }
+}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
new file mode 100644
index 0000000..7083784
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+import org.jasig.cas.client.validation.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
+ * <p/>
+ * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially 
+ * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
+ * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
+ * <p/>
+ * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a
+ * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator}
+ * will be used for ticket validation.  You can alternatively set
+ * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on
+ * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS
+ * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according
+ * to the attributes previously retrieved).
+ *
+ * @since 1.2
+ */
+public class CasRealm extends AuthorizingRealm {
+
+    // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
+    public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
+    public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
+    
+    private static Logger log = LoggerFactory.getLogger(CasRealm.class);
+    
+    // this is the url of the CAS server (example : http://host:port/cas)
+    private String casServerUrlPrefix;
+    
+    // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas)
+    private String casService;
+    
+    /* CAS protocol to use for ticket validation : CAS (default) or SAML :
+       - CAS protocol can be used with CAS server version < 3.1 : in this case, no user attributes can be retrieved from the CAS ticket validation response (except if there are some customizations on CAS server side)
+       - SAML protocol can be used with CAS server version >= 3.1 : in this case, user attributes can be extracted from the CAS ticket validation response
+    */
+    private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL;
+    
+    // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
+    private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME;
+    
+    // this class from the CAS client is used to validate a service ticket on CAS server
+    private TicketValidator ticketValidator;
+    
+    // default roles to applied to authenticated user
+    private String defaultRoles;
+    
+    // default permissions to applied to authenticated user
+    private String defaultPermissions;
+    
+    // names of attributes containing roles
+    private String roleAttributeNames;
+    
+    // names of attributes containing permissions
+    private String permissionAttributeNames;
+    
+    public CasRealm() {
+        setAuthenticationTokenClass(CasToken.class);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        ensureTicketValidator();
+    }
+
+    protected TicketValidator ensureTicketValidator() {
+        if (this.ticketValidator == null) {
+            this.ticketValidator = createTicketValidator();
+        }
+        return this.ticketValidator;
+    }
+    
+    protected TicketValidator createTicketValidator() {
+        String urlPrefix = getCasServerUrlPrefix();
+        if ("saml".equalsIgnoreCase(getValidationProtocol())) {
+            return new Saml11TicketValidator(urlPrefix);
+        }
+        return new Cas20ServiceTicketValidator(urlPrefix);
+    }
+    
+    /**
+     * Authenticates a user and retrieves its information.
+     * 
+     * @param token the authentication token
+     * @throws AuthenticationException if there is an error during authentication.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        CasToken casToken = (CasToken) token;
+        if (token == null) {
+            return null;
+        }
+        
+        String ticket = (String)casToken.getCredentials();
+        if (!StringUtils.hasText(ticket)) {
+            return null;
+        }
+        
+        TicketValidator ticketValidator = ensureTicketValidator();
+
+        try {
+            // contact CAS server to validate service ticket
+            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
+            // get principal, user id and attributes
+            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
+            String userId = casPrincipal.getName();
+            log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
+                    ticket, getCasServerUrlPrefix(), userId
+            });
+
+            Map<String, Object> attributes = casPrincipal.getAttributes();
+            // refresh authentication token (user id + remember me)
+            casToken.setUserId(userId);
+            String rememberMeAttributeName = getRememberMeAttributeName();
+            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
+            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
+            if (isRemembered) {
+                casToken.setRememberMe(true);
+            }
+            // create simple authentication info
+            List<Object> principals = CollectionUtils.asList(userId, attributes);
+            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
+            return new SimpleAuthenticationInfo(principalCollection, ticket);
+        } catch (TicketValidationException e) { 
+            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
+        }
+    }
+    
+    /**
+     * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
+     * 
+     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
+     * @return the AuthorizationInfo associated with this principals.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        // retrieve user information
+        SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
+        List<Object> listPrincipals = principalCollection.asList();
+        Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
+        // create simple authorization info
+        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+        // add default roles
+        addRoles(simpleAuthorizationInfo, split(defaultRoles));
+        // add default permissions
+        addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
+        // get roles from attributes
+        List<String> attributeNames = split(roleAttributeNames);
+        for (String attributeName : attributeNames) {
+            String value = attributes.get(attributeName);
+            addRoles(simpleAuthorizationInfo, split(value));
+        }
+        // get permissions from attributes
+        attributeNames = split(permissionAttributeNames);
+        for (String attributeName : attributeNames) {
+            String value = attributes.get(attributeName);
+            addPermissions(simpleAuthorizationInfo, split(value));
+        }
+        return simpleAuthorizationInfo;
+    }
+    
+    /**
+     * Split a string into a list of not empty and trimmed strings, delimiter is a comma.
+     * 
+     * @param s the input string
+     * @return the list of not empty and trimmed strings
+     */
+    private List<String> split(String s) {
+        List<String> list = new ArrayList<String>();
+        String[] elements = StringUtils.split(s, ',');
+        if (elements != null && elements.length > 0) {
+            for (String element : elements) {
+                if (StringUtils.hasText(element)) {
+                    list.add(element.trim());
+                }
+            }
+        }
+        return list;
+    }
+    
+    /**
+     * Add roles to the simple authorization info.
+     * 
+     * @param simpleAuthorizationInfo
+     * @param roles the list of roles to add
+     */
+    private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) {
+        for (String role : roles) {
+            simpleAuthorizationInfo.addRole(role);
+        }
+    }
+    
+    /**
+     * Add permissions to the simple authorization info.
+     * 
+     * @param simpleAuthorizationInfo
+     * @param permissions the list of permissions to add
+     */
+    private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) {
+        for (String permission : permissions) {
+            simpleAuthorizationInfo.addStringPermission(permission);
+        }
+    }
+
+    public String getCasServerUrlPrefix() {
+        return casServerUrlPrefix;
+    }
+
+    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
+        this.casServerUrlPrefix = casServerUrlPrefix;
+    }
+
+    public String getCasService() {
+        return casService;
+    }
+
+    public void setCasService(String casService) {
+        this.casService = casService;
+    }
+
+    public String getValidationProtocol() {
+        return validationProtocol;
+    }
+
+    public void setValidationProtocol(String validationProtocol) {
+        this.validationProtocol = validationProtocol;
+    }
+
+    public String getRememberMeAttributeName() {
+        return rememberMeAttributeName;
+    }
+
+    public void setRememberMeAttributeName(String rememberMeAttributeName) {
+        this.rememberMeAttributeName = rememberMeAttributeName;
+    }
+
+    public String getDefaultRoles() {
+        return defaultRoles;
+    }
+
+    public void setDefaultRoles(String defaultRoles) {
+        this.defaultRoles = defaultRoles;
+    }
+
+    public String getDefaultPermissions() {
+        return defaultPermissions;
+    }
+
+    public void setDefaultPermissions(String defaultPermissions) {
+        this.defaultPermissions = defaultPermissions;
+    }
+
+    public String getRoleAttributeNames() {
+        return roleAttributeNames;
+    }
+
+    public void setRoleAttributeNames(String roleAttributeNames) {
+        this.roleAttributeNames = roleAttributeNames;
+    }
+
+    public String getPermissionAttributeNames() {
+        return permissionAttributeNames;
+    }
+
+    public void setPermissionAttributeNames(String permissionAttributeNames) {
+        this.permissionAttributeNames = permissionAttributeNames;
+    }
+}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
new file mode 100644
index 0000000..f89001c
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
+
+/**
+ * {@link org.apache.shiro.mgt.SubjectFactory Subject} implementation to be used in CAS-enabled applications.
+ *
+ * @since 1.2
+ */
+public class CasSubjectFactory extends DefaultWebSubjectFactory {
+
+    @Override
+    public Subject createSubject(SubjectContext context) {
+
+        //the authenticated flag is only set by the SecurityManager after a successful authentication attempt.
+        boolean authenticated = context.isAuthenticated();
+
+        //although the SecurityManager 'sees' the submission as a successful authentication, in reality, the
+        //login might have been just a CAS rememberMe login.  If so, set the authenticated flag appropriately:
+        if (authenticated) {
+
+            AuthenticationToken token = context.getAuthenticationToken();
+
+            if (token != null && token instanceof CasToken) {
+                CasToken casToken = (CasToken) token;
+                // set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
+                if (casToken.isRememberMe()) {
+                    context.setAuthenticated(false);
+                }
+            }
+        }
+
+        return super.createSubject(context);
+    }
+}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
new file mode 100644
index 0000000..9b1c22d
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.RememberMeAuthenticationToken;
+
+/**
+ * This class represents a token for a CAS authentication (service ticket + user id + remember me).
+ *
+ * @since 1.2
+ */
+public class CasToken implements RememberMeAuthenticationToken {
+    
+    private static final long serialVersionUID = 8587329689973009598L;
+    
+    // the service ticket returned by the CAS server
+    private String ticket = null;
+    
+    // the user identifier
+    private String userId = null;
+    
+    // is the user in a remember me mode ?
+    private boolean isRememberMe = false;
+    
+    public CasToken(String ticket) {
+        this.ticket = ticket;
+    }
+    
+    public Object getPrincipal() {
+        return userId;
+    }
+    
+    public Object getCredentials() {
+        return ticket;
+    }
+    
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+    
+    public boolean isRememberMe() {
+        return isRememberMe;
+    }
+    
+    public void setRememberMe(boolean isRememberMe) {
+        this.isRememberMe = isRememberMe;
+    }
+}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
new file mode 100644
index 0000000..da3d09c
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+import org.apache.shiro.authc.AuthenticationInfo
+import org.apache.shiro.authz.AuthorizationInfo
+
+/**
+ * Unit tests for the {@link CasRealm} implementation.
+ *
+ * @since 1.2
+ */
+class CasRealmTest extends GroovyTestCase {
+
+    /**
+     * Creates a CAS realm with a ticket validator mock.
+     *
+     * @return CasRealm The CAS realm for testing.
+     */
+    private CasRealm createCasRealm() {
+        new CasRealm(ticketValidator: new MockServiceTicketValidator());
+    }
+
+    void testNoAttribute() {
+        CasRealm casRealm = createCasRealm();
+        CasToken casToken = new CasToken('$=defaultId');
+        AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authenticationInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.asList()[1] //returns a map
+        assertEquals 0, attributes.size()
+        AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertNull authorizationInfo.stringPermissions
+        assertNull authorizationInfo.roles
+    }
+
+    void testNoAttributeDefaultRoleAndPermission() {
+        CasRealm casRealm = createCasRealm();
+        casRealm.defaultRoles = "defaultRole"
+        casRealm.defaultPermissions = "defaultPermission"
+        CasToken casToken = new CasToken('$=defaultId');
+        AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authenticationInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals 0, attributes.size()
+        AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertTrue authorizationInfo.roles.contains("defaultRole")
+        assertTrue authorizationInfo.stringPermissions.contains("defaultPermission")
+    }
+
+    void testNoAttributeDefaultRolesAndPermissions() {
+        CasRealm casRealm = createCasRealm();
+        casRealm.defaultRoles = "defaultRole1, defaultRole2"
+        casRealm.defaultPermissions = "defaultPermission1,defaultPermission2"
+        CasToken casToken = new CasToken('$=defaultId');
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals 0, attributes.size()
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals)
+        assertEquals 2, authzInfo.roles.size()
+        assertTrue authzInfo.roles.contains("defaultRole1")
+        assertTrue authzInfo.roles.contains("defaultRole2")
+        assertEquals 2, authzInfo.stringPermissions.size()
+        assertTrue authzInfo.stringPermissions.contains("defaultPermission1")
+        assertTrue authzInfo.stringPermissions.contains("defaultPermission2")
+    }
+
+    void testRoleAndPermission() {
+        CasRealm casRealm = createCasRealm();
+        casRealm.roleAttributeNames = "role"
+        casRealm.permissionAttributeNames = "permission"
+        CasToken casToken = new CasToken('$=defaultId|role=aRole|permission=aPermission');
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals 2, attributes.size()
+        assertEquals "aRole", attributes['role']
+        assertEquals "aPermission", attributes['permission']
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertTrue authzInfo.roles.contains("aRole")
+        assertTrue authzInfo.stringPermissions.contains("aPermission")
+    }
+
+    void testRolesAndPermissions() {
+        CasRealm casRealm = createCasRealm();
+        casRealm.setRoleAttributeNames("role1 , role2");
+        casRealm.setPermissionAttributeNames("permission1,permission2");
+        CasToken casToken = new CasToken(
+                '$=defaultId|role1=role11 , role12|role2=role21,role22|permission1=permission11, permission12|permission2=permission21 ,permission22');
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals "role11 , role12", attributes['role1']
+        assertEquals "role21,role22", attributes['role2']
+        assertEquals "permission11, permission12", attributes['permission1']
+        assertEquals "permission21 ,permission22", attributes['permission2']
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertEquals 4, authzInfo.roles.size()
+        assertTrue authzInfo.roles.contains("role11")
+        assertTrue authzInfo.roles.contains("role12")
+        assertTrue authzInfo.roles.contains("role21")
+        assertTrue authzInfo.roles.contains("role22")
+        assertTrue authzInfo.stringPermissions.contains("permission11")
+        assertTrue authzInfo.stringPermissions.contains("permission12")
+        assertTrue authzInfo.stringPermissions.contains("permission21")
+        assertTrue authzInfo.stringPermissions.contains("permission22")
+    }
+
+    void testNotRememberMe() {
+        CasRealm casRealm = createCasRealm();
+        CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=false");
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals "false", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
+        assertFalse casToken.rememberMe
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertNull authzInfo.stringPermissions
+        assertNull authzInfo.roles
+    }
+
+    void testRememberMe() {
+        CasRealm casRealm = createCasRealm();
+        CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=true");
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals "true", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
+        assertTrue casToken.rememberMe
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertNull authzInfo.stringPermissions
+        assertNull authzInfo.roles
+    }
+
+    void testRememberMeNewAttributeName() {
+        CasRealm casRealm = createCasRealm();
+        casRealm.rememberMeAttributeName = "rme"
+        CasToken casToken = new CasToken('$=defaultId|rme=true');
+        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+        def principals = authcInfo.principals
+        assertEquals "defaultId", principals.primaryPrincipal
+        def attributes = principals.oneByType(Map)
+        assertEquals "true", attributes[casRealm.rememberMeAttributeName]
+        assertTrue casToken.rememberMe
+        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+        assertNull authzInfo.stringPermissions
+        assertNull authzInfo.roles
+    }
+
+}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
new file mode 100644
index 0000000..ad3526f
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+/**
+ * Unit tests for the {@link CasToken} implementation.
+ *
+ * @since 1.2
+ */
+class CasTokenTest extends GroovyTestCase {
+
+    void testPrincipal() {
+        CasToken casToken = new CasToken("fakeTicket")
+        assertNull casToken.principal
+        casToken.userId = "myUserId"
+        assertEquals "myUserId", casToken.principal
+    }
+
+    void testCredentials() {
+        CasToken casToken = new CasToken("fakeTicket")
+        assertEquals "fakeTicket", casToken.credentials
+    }
+
+    void testRememberMe() {
+        CasToken casToken = new CasToken("fakeTicket")
+        assertFalse casToken.rememberMe
+        casToken.rememberMe = true
+        assertTrue casToken.rememberMe
+    }
+}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
new file mode 100644
index 0000000..643b93b
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+import org.apache.shiro.util.StringUtils
+import org.jasig.cas.client.authentication.AttributePrincipalImpl
+import org.jasig.cas.client.validation.Assertion
+import org.jasig.cas.client.validation.AssertionImpl
+import org.jasig.cas.client.validation.TicketValidationException
+import org.jasig.cas.client.validation.TicketValidator
+
+/**
+ * @since 1.2
+ */
+class MockServiceTicketValidator implements TicketValidator {
+
+    /**
+     * Returns different assertions according to the ticket input. The format of the mock ticket must be :
+     * key1=value1,key2=value2,...,keyN=valueN. If keyX is $, valueX is considered to be the name of the principal, otherwise (keyX, valueX)
+     * is considered to be an attribute of the principal.
+     */
+    public Assertion validate(String ticket, String service) throws TicketValidationException {
+        String name = null;
+        def attributes = [:]
+        String[] elements = StringUtils.split(ticket, '|' as char);
+        int length = elements.length;
+        for (int i = 0; i < length; i++) {
+            String[] pair = StringUtils.split(elements[i], '=' as char);
+            String key = pair[0].trim();
+            String value = pair[1].trim();
+            if ('$'.equals(key)) {
+                name = value;
+            } else {
+                attributes.put(key, value);
+            }
+        }
+        AttributePrincipalImpl attributePrincipalImpl = new AttributePrincipalImpl(name, attributes);
+        return new AssertionImpl(attributePrincipalImpl, [:]);
+
+    }
+}
diff --git a/support/ehcache/pom.xml b/support/ehcache/pom.xml
new file mode 100644
index 0000000..186386c
--- /dev/null
+++ b/support/ehcache/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-ehcache</artifactId>
+    <name>Apache Shiro :: Support :: EHCache</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache-core</artifactId>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.ehcache</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.cache.ehcache*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            net.sf.ehcache*;version="[2.3, 3)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCache.java b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCache.java
new file mode 100644
index 0000000..9393e74
--- /dev/null
+++ b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCache.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache.ehcache;
+
+import net.sf.ehcache.Element;
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.util.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * Shiro {@link org.apache.shiro.cache.Cache} implementation that wraps an {@link net.sf.ehcache.Ehcache} instance.
+ *
+ * @since 0.2
+ */
+public class EhCache<K, V> implements Cache<K, V> {
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(EhCache.class);
+
+    /**
+     * The wrapped Ehcache instance.
+     */
+    private net.sf.ehcache.Ehcache cache;
+
+    /**
+     * Constructs a new EhCache instance with the given cache.
+     *
+     * @param cache - delegate EhCache instance this Shiro cache instance will wrap.
+     */
+    public EhCache(net.sf.ehcache.Ehcache cache) {
+        if (cache == null) {
+            throw new IllegalArgumentException("Cache argument cannot be null.");
+        }
+        this.cache = cache;
+    }
+
+    /**
+     * Gets a value of an element which matches the given key.
+     *
+     * @param key the key of the element to return.
+     * @return The value placed into the cache with an earlier put, or null if not found or expired
+     */
+    public V get(K key) throws CacheException {
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("Getting object from cache [" + cache.getName() + "] for key [" + key + "]");
+            }
+            if (key == null) {
+                return null;
+            } else {
+                Element element = cache.get(key);
+                if (element == null) {
+                    if (log.isTraceEnabled()) {
+                        log.trace("Element for [" + key + "] is null.");
+                    }
+                    return null;
+                } else {
+                    //noinspection unchecked
+                    return (V) element.getObjectValue();
+                }
+            }
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Puts an object into the cache.
+     *
+     * @param key   the key.
+     * @param value the value.
+     */
+    public V put(K key, V value) throws CacheException {
+        if (log.isTraceEnabled()) {
+            log.trace("Putting object in cache [" + cache.getName() + "] for key [" + key + "]");
+        }
+        try {
+            V previous = get(key);
+            Element element = new Element(key, value);
+            cache.put(element);
+            return previous;
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Removes the element which matches the key.
+     *
+     * <p>If no element matches, nothing is removed and no Exception is thrown.</p>
+     *
+     * @param key the key of the element to remove
+     */
+    public V remove(K key) throws CacheException {
+        if (log.isTraceEnabled()) {
+            log.trace("Removing object from cache [" + cache.getName() + "] for key [" + key + "]");
+        }
+        try {
+            V previous = get(key);
+            cache.remove(key);
+            return previous;
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Removes all elements in the cache, but leaves the cache in a useable state.
+     */
+    public void clear() throws CacheException {
+        if (log.isTraceEnabled()) {
+            log.trace("Clearing all objects from cache [" + cache.getName() + "]");
+        }
+        try {
+            cache.removeAll();
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    public int size() {
+        try {
+            return cache.getSize();
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    public Set<K> keys() {
+        try {
+            @SuppressWarnings({"unchecked"})
+            List<K> keys = cache.getKeys();
+            if (!CollectionUtils.isEmpty(keys)) {
+                return Collections.unmodifiableSet(new LinkedHashSet<K>(keys));
+            } else {
+                return Collections.emptySet();
+            }
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    public Collection<V> values() {
+        try {
+            @SuppressWarnings({"unchecked"})
+            List<K> keys = cache.getKeys();
+            if (!CollectionUtils.isEmpty(keys)) {
+                List<V> values = new ArrayList<V>(keys.size());
+                for (K key : keys) {
+                    V value = get(key);
+                    if (value != null) {
+                        values.add(value);
+                    }
+                }
+                return Collections.unmodifiableList(values);
+            } else {
+                return Collections.emptyList();
+            }
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Returns the size (in bytes) that this EhCache is using in memory (RAM), or <code>-1</code> if that
+     * number is unknown or cannot be calculated.
+     *
+     * @return the size (in bytes) that this EhCache is using in memory (RAM), or <code>-1</code> if that
+     *         number is unknown or cannot be calculated.
+     */
+    public long getMemoryUsage() {
+        try {
+            return cache.calculateInMemorySize();
+        }
+        catch (Throwable t) {
+            return -1;
+        }
+    }
+
+    /**
+     * Returns the size (in bytes) that this EhCache's memory store is using (RAM), or <code>-1</code> if
+     * that number is unknown or cannot be calculated.
+     *
+     * @return the size (in bytes) that this EhCache's memory store is using (RAM), or <code>-1</code> if
+     *         that number is unknown or cannot be calculated.
+     */
+    public long getMemoryStoreSize() {
+        try {
+            return cache.getMemoryStoreSize();
+        }
+        catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Returns the size (in bytes) that this EhCache's disk store is consuming or <code>-1</code> if
+     * that number is unknown or cannot be calculated.
+     *
+     * @return the size (in bytes) that this EhCache's disk store is consuming or <code>-1</code> if
+     *         that number is unknown or cannot be calculated.
+     */
+    public long getDiskStoreSize() {
+        try {
+            return cache.getDiskStoreSize();
+        } catch (Throwable t) {
+            throw new CacheException(t);
+        }
+    }
+
+    /**
+     * Returns "EhCache [" + cache.getName() + "]"
+     *
+     * @return "EhCache [" + cache.getName() + "]"
+     */
+    public String toString() {
+        return "EhCache [" + cache.getName() + "]";
+    }
+}
diff --git a/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCacheManager.java b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCacheManager.java
new file mode 100644
index 0000000..3a802f2
--- /dev/null
+++ b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/EhCacheManager.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache.ehcache;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.cache.CacheManager;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Shiro {@code CacheManager} implementation utilizing the Ehcache framework for all cache functionality.
+ * <p/>
+ * This class can {@link #setCacheManager(net.sf.ehcache.CacheManager) accept} a manually configured
+ * {@link net.sf.ehcache.CacheManager net.sf.ehcache.CacheManager} instance,
+ * or an {@code ehcache.xml} path location can be specified instead and one will be constructed. If neither are
+ * specified, Shiro's failsafe <code><a href="./ehcache.xml">ehcache.xml</a>} file will be used by default.
+ * <p/>
+ * This implementation requires EhCache 1.2 and above. Make sure EhCache 1.1 or earlier
+ * is not in the classpath or it will not work.
+ * <p/>
+ * Please see the <a href="http://ehcache.sf.net" target="_top">Ehcache website</a> for their documentation.
+ *
+ * @see <a href="http://ehcache.sf.net" target="_top">The Ehcache website</a>
+ * @since 0.2
+ */
+public class EhCacheManager implements CacheManager, Initializable, Destroyable {
+
+    /**
+     * This class's private log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class);
+
+    /**
+     * The EhCache cache manager used by this implementation to create caches.
+     */
+    protected net.sf.ehcache.CacheManager manager;
+
+    /**
+     * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that
+     * it should be automatically cleaned up as well on shutdown.
+     */
+    private boolean cacheManagerImplicitlyCreated = false;
+
+    /**
+     * Classpath file location of the ehcache CacheManager config file.
+     */
+    private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml";
+
+    /**
+     * Default no argument constructor
+     */
+    public EhCacheManager() {
+    }
+
+    /**
+     * Returns the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
+     *
+     * @return the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
+     */
+    public net.sf.ehcache.CacheManager getCacheManager() {
+        return manager;
+    }
+
+    /**
+     * Sets the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
+     *
+     * @param manager the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
+     */
+    public void setCacheManager(net.sf.ehcache.CacheManager manager) {
+        this.manager = manager;
+    }
+
+    /**
+     * Returns the resource location of the config file used to initialize a new
+     * EhCache CacheManager instance.  The string can be any resource path supported by the
+     * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call.
+     * <p/>
+     * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to
+     * lazily create a CacheManager if one is not already provided.
+     *
+     * @return the resource location of the config file used to initialize the wrapped
+     *         EhCache CacheManager instance.
+     */
+    public String getCacheManagerConfigFile() {
+        return this.cacheManagerConfigFile;
+    }
+
+    /**
+     * Sets the resource location of the config file used to initialize the wrapped
+     * EhCache CacheManager instance.  The string can be any resource path supported by the
+     * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call.
+     * <p/>
+     * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to
+     * lazily create a CacheManager if one is not already provided.
+     *
+     * @param classpathLocation resource location of the config file used to create the wrapped
+     *                          EhCache CacheManager instance.
+     */
+    public void setCacheManagerConfigFile(String classpathLocation) {
+        this.cacheManagerConfigFile = classpathLocation;
+    }
+
+    /**
+     * Acquires the InputStream for the ehcache configuration file using
+     * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} with the
+     * path returned from {@link #getCacheManagerConfigFile() getCacheManagerConfigFile()}.
+     *
+     * @return the InputStream for the ehcache configuration file.
+     */
+    protected InputStream getCacheManagerConfigFileInputStream() {
+        String configFile = getCacheManagerConfigFile();
+        try {
+            return ResourceUtils.getInputStreamForPath(configFile);
+        } catch (IOException e) {
+            throw new ConfigurationException("Unable to obtain input stream for cacheManagerConfigFile [" +
+                    configFile + "]", e);
+        }
+    }
+
+    /**
+     * Loads an existing EhCache from the cache manager, or starts a new cache if one is not found.
+     *
+     * @param name the name of the cache to load/create.
+     */
+    public final <K, V> Cache<K, V> getCache(String name) throws CacheException {
+
+        if (log.isTraceEnabled()) {
+            log.trace("Acquiring EhCache instance named [" + name + "]");
+        }
+
+        try {
+            net.sf.ehcache.Ehcache cache = ensureCacheManager().getEhcache(name);
+            if (cache == null) {
+                if (log.isInfoEnabled()) {
+                    log.info("Cache with name '{}' does not yet exist.  Creating now.", name);
+                }
+                this.manager.addCache(name);
+
+                cache = manager.getCache(name);
+
+                if (log.isInfoEnabled()) {
+                    log.info("Added EhCache named [" + name + "]");
+                }
+            } else {
+                if (log.isInfoEnabled()) {
+                    log.info("Using existing EHCache named [" + cache.getName() + "]");
+                }
+            }
+            return new EhCache<K, V>(cache);
+        } catch (net.sf.ehcache.CacheException e) {
+            throw new CacheException(e);
+        }
+    }
+
+    /**
+     * Initializes this instance.
+     * <p/>
+     * If a {@link #setCacheManager CacheManager} has been
+     * explicitly set (e.g. via Dependency Injection or programatically) prior to calling this
+     * method, this method does nothing.
+     * <p/>
+     * However, if no {@code CacheManager} has been set, the default Ehcache singleton will be initialized, where
+     * Ehcache will look for an {@code ehcache.xml} file at the root of the classpath.  If one is not found,
+     * Ehcache will use its own failsafe configuration file.
+     * <p/>
+     * Because Shiro cannot use the failsafe defaults (fail-safe expunges cached objects after 2 minutes,
+     * something not desirable for Shiro sessions), this class manages an internal default configuration for
+     * this case.
+     *
+     * @throws org.apache.shiro.cache.CacheException
+     *          if there are any CacheExceptions thrown by EhCache.
+     * @see net.sf.ehcache.CacheManager#create
+     */
+    public final void init() throws CacheException {
+        ensureCacheManager();
+    }
+
+    private net.sf.ehcache.CacheManager ensureCacheManager() {
+        try {
+            if (this.manager == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("cacheManager property not set.  Constructing CacheManager instance... ");
+                }
+                //using the CacheManager constructor, the resulting instance is _not_ a VM singleton
+                //(as would be the case by calling CacheManager.getInstance().  We do not use the getInstance here
+                //because we need to know if we need to destroy the CacheManager instance - using the static call,
+                //we don't know which component is responsible for shutting it down.  By using a single EhCacheManager,
+                //it will always know to shut down the instance if it was responsible for creating it.
+                this.manager = new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream());
+                if (log.isTraceEnabled()) {
+                    log.trace("instantiated Ehcache CacheManager instance.");
+                }
+                cacheManagerImplicitlyCreated = true;
+                if (log.isDebugEnabled()) {
+                    log.debug("implicit cacheManager created successfully.");
+                }
+            }
+            return this.manager;
+        } catch (Exception e) {
+            throw new CacheException(e);
+        }
+    }
+
+    /**
+     * Shuts-down the wrapped Ehcache CacheManager <b>only if implicitly created</b>.
+     * <p/>
+     * If another component injected
+     * a non-null CacheManager into this instace before calling {@link #init() init}, this instance expects that same
+     * component to also destroy the CacheManager instance, and it will not attempt to do so.
+     */
+    public void destroy() {
+        if (cacheManagerImplicitlyCreated) {
+            try {
+                net.sf.ehcache.CacheManager cacheMgr = getCacheManager();
+                cacheMgr.shutdown();
+            } catch (Exception e) {
+                if (log.isWarnEnabled()) {
+                    log.warn("Unable to cleanly shutdown implicitly created CacheManager instance.  " +
+                            "Ignoring (shutting down)...");
+                }
+            }
+            cacheManagerImplicitlyCreated = false;
+        }
+    }
+}
diff --git a/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/package-info.java b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/package-info.java
new file mode 100644
index 0000000..72faf54
--- /dev/null
+++ b/support/ehcache/src/main/java/org/apache/shiro/cache/ehcache/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * <a href="http://ehcache.sourceforge.net" target="_top">Ehcache</a>-based implementations of Shiro's
+ * cache interfaces.
+ */
+package org.apache.shiro.cache.ehcache;
diff --git a/support/ehcache/src/main/resources/org/apache/shiro/cache/ehcache/ehcache.xml b/support/ehcache/src/main/resources/org/apache/shiro/cache/ehcache/ehcache.xml
new file mode 100644
index 0000000..fad756a
--- /dev/null
+++ b/support/ehcache/src/main/resources/org/apache/shiro/cache/ehcache/ehcache.xml
@@ -0,0 +1,99 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<ehcache>
+
+    <!-- Sets the path to the directory where cache .data files are created.
+
+         If the path is a Java System Property it is replaced by
+         its value in the running VM. The following properties are translated:
+
+            user.home - User's home directory
+            user.dir - User's current working directory
+            java.io.tmpdir - Default temp file path
+    -->
+    <diskStore path="java.io.tmpdir/shiro-ehcache"/>
+
+
+    <!--Default Cache configuration. These will applied to caches programmatically created through
+    the CacheManager.
+
+    The following attributes are required:
+
+    maxElementsInMemory            - Sets the maximum number of objects that will be created in memory
+    eternal                        - Sets whether elements are eternal. If eternal,  timeouts are ignored and the
+                                     element is never expired.
+    overflowToDisk                 - Sets whether elements can overflow to disk when the in-memory cache
+                                     has reached the maxInMemory limit.
+
+    The following attributes are optional:
+    timeToIdleSeconds              - Sets the time to idle for an element before it expires.
+                                     i.e. The maximum amount of time between accesses before an element expires
+                                     Is only used if the element is not eternal.
+                                     Optional attribute. A value of 0 means that an Element can idle for infinity.
+                                     The default value is 0.
+    timeToLiveSeconds              - Sets the time to live for an element before it expires.
+                                     i.e. The maximum time between creation time and when an element expires.
+                                     Is only used if the element is not eternal.
+                                     Optional attribute. A value of 0 means that and Element can live for infinity.
+                                     The default value is 0.
+    diskPersistent                 - Whether the disk store persists between restarts of the Virtual Machine.
+                                     The default value is false.
+    diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+                                     is 120 seconds.
+    memoryStoreEvictionPolicy      - Policy would be enforced upon reaching the maxElementsInMemory limit. Default
+                                     policy is Least Recently Used (specified as LRU). Other policies available -
+                                     First In First Out (specified as FIFO) and Less Frequently Used
+                                     (specified as LFU)
+    -->
+
+    <defaultCache
+            maxElementsInMemory="10000"
+            eternal="false"
+            timeToIdleSeconds="120"
+            timeToLiveSeconds="120"
+            overflowToDisk="false"
+            diskPersistent="false"
+            diskExpiryThreadIntervalSeconds="120"
+            />
+
+    <!-- We want eternal="true" and no timeToIdle or timeToLive settings because Shiro manages session
+         expirations explicitly.  If we set it to false and then set corresponding timeToIdle and timeToLive properties,
+         ehcache would evict sessions without Shiro's knowledge, which would cause many problems
+        (e.g. "My Shiro session timeout is 30 minutes - why isn't a session available after 2 minutes?"
+               Answer - ehcache expired it due to the timeToIdle property set to 120 seconds.)
+
+        diskPersistent=true since we want an enterprise session management feature - ability to use sessions after
+        even after a JVM restart.  -->
+    <cache name="shiro-activeSessionCache"
+           maxElementsInMemory="10000"
+           overflowToDisk="true"
+           eternal="true"
+           timeToLiveSeconds="0"
+           timeToIdleSeconds="0"
+           diskPersistent="true"
+           diskExpiryThreadIntervalSeconds="600"/>
+
+    <cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts"
+           maxElementsInMemory="1000"
+           eternal="true"
+           overflowToDisk="true"/>
+
+</ehcache>
+
+
diff --git a/support/ehcache/src/test/java/org/apache/shiro/cache/ehcache/EhCacheManagerTest.java b/support/ehcache/src/test/java/org/apache/shiro/cache/ehcache/EhCacheManagerTest.java
new file mode 100644
index 0000000..c15ffa8
--- /dev/null
+++ b/support/ehcache/src/test/java/org/apache/shiro/cache/ehcache/EhCacheManagerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cache.ehcache;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.util.LifecycleUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @since May 11, 2010 12:41:38 PM
+ */
+public class EhCacheManagerTest {
+
+    private EhCacheManager cacheManager;
+
+    @Before
+    public void setUp() {
+        cacheManager = new EhCacheManager();
+    }
+
+    @After
+    public void tearDown() {
+        LifecycleUtils.destroy(cacheManager);
+    }
+
+    @Test
+    public void testCacheManagerCreationDuringInit() {
+        net.sf.ehcache.CacheManager ehCacheManager = cacheManager.getCacheManager();
+        assertNull(ehCacheManager);
+        cacheManager.init();
+        //now assert that an internal CacheManager has been created:
+        ehCacheManager = cacheManager.getCacheManager();
+        assertNotNull(ehCacheManager);
+    }
+
+    @Test
+    public void testLazyCacheManagerCreationWithoutCallingInit() {
+        net.sf.ehcache.CacheManager ehCacheManager = cacheManager.getCacheManager();
+        assertNull(ehCacheManager);
+
+        //don't call init here - the ehcache CacheManager should be lazily created
+        //because of the default Shiro ehcache.xml file in the classpath.  Just acquire a cache:
+        Cache<String, String> cache = cacheManager.getCache("test");
+
+        //now assert that an internal CacheManager has been created:
+        ehCacheManager = cacheManager.getCacheManager();
+        assertNotNull(ehCacheManager);
+
+        assertNotNull(cache);
+        cache.put("hello", "world");
+        String value = cache.get("hello");
+        assertNotNull(value);
+        assertEquals(value, "world");
+    }
+
+}
diff --git a/support/ehcache/src/test/resources/log4j.properties b/support/ehcache/src/test/resources/log4j.properties
new file mode 100644
index 0000000..0d51520
--- /dev/null
+++ b/support/ehcache/src/test/resources/log4j.properties
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+log4j.rootLogger=TRACE, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# Pattern to output: date priority [category] - message
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
+log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
+
+# Spring logging level is WARN
+log4j.logger.net.sf.ehcache=INFO
+
+# General Apache libraries is WARN
+log4j.logger.org.apache=WARN
+
+log4j.logger.org.apache.shiro=TRACE
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
\ No newline at end of file
diff --git a/support/features/pom.xml b/support/features/pom.xml
new file mode 100644
index 0000000..9cd8c3b
--- /dev/null
+++ b/support/features/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-features</artifactId>
+    <name>Apache Shiro :: Support :: Karaf Features</name>
+    <packaging>pom</packaging>
+
+    <properties>
+        <aspectj.bundle.version>1.6.16_1</aspectj.bundle.version>
+        <ehcache.bundle.version>2.5.0_1</ehcache.bundle.version>
+        <quartz.bundle.version>1.6.1_1</quartz.bundle.version>
+        <!-- Not a Shiro dependency - used for quartz bundle resolution only (see features.xml): -->
+        <commons.collections.version>3.2.1</commons.collections.version>
+    </properties>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>2.4.3</version>
+                <configuration>
+                    <useDefaultDelimiters>false</useDefaultDelimiters>
+                    <delimiters>
+                        <delimiter>${*}</delimiter>
+                    </delimiters>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>filter</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>resources</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-artifacts</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attach-artifact</goal>
+                        </goals>
+                        <configuration>
+                            <artifacts>
+                                <artifact>
+                                    <file>target/classes/features.xml</file>
+                                    <type>xml</type>
+                                    <classifier>features</classifier>
+                                </artifact>
+                            </artifacts>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/features/src/main/resources/features.xml b/support/features/src/main/resources/features.xml
new file mode 100644
index 0000000..72451a2
--- /dev/null
+++ b/support/features/src/main/resources/features.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<features name="shiro-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.0.0">
+
+    <!-- Shiro core features: -->
+
+    <feature name="shiro-core" version="${project.version}" resolver="(obr)">
+        <bundle>mvn:org.apache.shiro/shiro-core/${project.version}</bundle>
+    </feature>
+
+    <feature name="shiro-web" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <feature version="[2,4)">war</feature>
+        <bundle>mvn:org.apache.shiro/shiro-web/${project.version}</bundle>
+    </feature>
+
+    <!-- 3rd party support (alphabetized please): -->
+
+    <feature name="shiro-aspectj" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aspectj/${aspectj.bundle.version}</bundle>
+        <bundle>mvn:org.apache.shiro/shiro-aspectj/${project.version}</bundle>
+    </feature>
+
+    <feature name="shiro-cas" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <!-- Is there a CAS client osgi bundle somewhere?
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aspectj/${casclient.bundle.version}</bundle>
+        -->
+        <bundle>mvn:org.apache.shiro/shiro-cas/${project.version}</bundle>
+    </feature>
+
+    <feature name="shiro-ehcache" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.ehcache/${ehcache.bundle.version}</bundle>
+        <bundle>mvn:org.apache.shiro/shiro-ehcache/${project.version}</bundle>
+    </feature>
+
+    <feature name="shiro-quartz" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <bundle dependency='true'>mvn:commons-collections/commons-collections/${commons.collections.version}</bundle>
+        <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.quartz/${quartz.bundle.version}</bundle>
+        <bundle>mvn:org.apache.shiro/shiro-quartz/${project.version}</bundle>
+    </feature>
+
+    <feature name="shiro-spring" version="${project.version}" resolver="(obr)">
+        <feature version="${project.version}">shiro-core</feature>
+        <feature version="[3,4)">spring</feature>
+        <bundle>mvn:org.apache.shiro/shiro-spring/${project.version}</bundle>
+    </feature>
+
+</features>
diff --git a/support/guice/pom.xml b/support/guice/pom.xml
new file mode 100644
index 0000000..c3874f6
--- /dev/null
+++ b/support/guice/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-guice</artifactId>
+    <name>Apache Shiro :: Support :: Guice</name>
+    <packaging>bundle</packaging>
+
+    <properties>
+        <guice.version>3.0</guice.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-servlet</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.guice</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.guice*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.aopalliance*;version="[1.0.0, 2.0.0)",
+                            com.google.inject*;version="[3.0.0, 4.0.0)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/BeanTypeListener.java b/support/guice/src/main/java/org/apache/shiro/guice/BeanTypeListener.java
new file mode 100644
index 0000000..6570f38
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/BeanTypeListener.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.common.primitives.Primitives;
+import com.google.inject.*;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.multibindings.MapBinder;
+import com.google.inject.name.Names;
+import com.google.inject.spi.TypeEncounter;
+import com.google.inject.spi.TypeListener;
+import com.google.inject.util.Types;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.shiro.SecurityUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TypeListener that injects setter methods on Shiro objects.
+ */
+class BeanTypeListener implements TypeListener {
+    public static final Package SHIRO_GUICE_PACKAGE = ShiroModule.class.getPackage();
+    public static final Package SHIRO_PACKAGE = SecurityUtils.class.getPackage();
+
+    private static Matcher<Class> shiroMatcher = Matchers.inSubpackage(SHIRO_PACKAGE.getName());
+    private static Matcher<Class> shiroGuiceMatcher = Matchers.inSubpackage(SHIRO_GUICE_PACKAGE.getName());
+
+    private static Matcher<Class> classMatcher = ShiroMatchers.ANY_PACKAGE.and(shiroMatcher.and(Matchers.not(shiroGuiceMatcher)));
+
+    public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(classMatcher);
+
+    private static final String BEAN_TYPE_MAP_NAME = "__SHIRO_BEAN_TYPES__";
+    static final Key<?> MAP_KEY = Key.get(Types.mapOf(TypeLiteral.class, BeanTypeKey.class), Names.named(BEAN_TYPE_MAP_NAME));
+
+    public <I> void hear(TypeLiteral<I> type, final TypeEncounter<I> encounter) {
+        PropertyDescriptor propertyDescriptors[] = PropertyUtils.getPropertyDescriptors(type.getRawType());
+        final Map<PropertyDescriptor, Key<?>> propertyDependencies = new HashMap<PropertyDescriptor, Key<?>>(propertyDescriptors.length);
+        final Provider<Injector> injectorProvider = encounter.getProvider(Injector.class);
+        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
+            if (propertyDescriptor.getWriteMethod() != null && Modifier.isPublic(propertyDescriptor.getWriteMethod().getModifiers())) {
+                Type propertyType = propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0];
+                propertyDependencies.put(propertyDescriptor, createDependencyKey(propertyDescriptor, propertyType));
+            }
+        }
+        encounter.register(new MembersInjector<I>() {
+            public void injectMembers(I instance) {
+                for (Map.Entry<PropertyDescriptor, Key<?>> dependency : propertyDependencies.entrySet()) {
+                    try {
+                        final Injector injector = injectorProvider.get();
+
+                        Object value = injector.getInstance(getMappedKey(injector, dependency.getValue()));
+                        dependency.getKey().getWriteMethod().invoke(instance, value);
+
+                    } catch (ConfigurationException e) {
+                        // This is ok, it simply means that we can't fulfill this dependency.
+                        // Is there a better way to do this?
+                    } catch (InvocationTargetException e) {
+                        throw new RuntimeException("Couldn't set property " + dependency.getKey().getDisplayName(), e);
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException("We shouldn't have ever reached this point, we don't try to inject to non-accessible methods.", e);
+                    }
+                }
+
+            }
+        });
+    }
+
+    private static Key<?> getMappedKey(Injector injector, Key<?> key) {
+        Map<TypeLiteral, BeanTypeKey> beanTypeMap = getBeanTypeMap(injector);
+        if(key.getAnnotation() == null && beanTypeMap.containsKey(key.getTypeLiteral())) {
+            return beanTypeMap.get(key.getTypeLiteral()).key;
+        } else {
+            return key;
+        }
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private static Map<TypeLiteral, BeanTypeKey> getBeanTypeMap(Injector injector) {
+        return (Map<TypeLiteral, BeanTypeKey>) injector.getInstance(MAP_KEY);
+    }
+
+    private static Key<?> createDependencyKey(PropertyDescriptor propertyDescriptor, Type propertyType) {
+        if(requiresName(propertyType)) {
+            return Key.get(propertyType, Names.named("shiro." + propertyDescriptor.getName()));
+        } else {
+            return Key.get(propertyType);
+        }
+    }
+
+    private static boolean requiresName(Type propertyType) {
+        if (propertyType instanceof Class) {
+            Class<?> aClass = (Class<?>) propertyType;
+            return aClass.isPrimitive() || aClass.isEnum() || Primitives.isWrapperType(aClass) || CharSequence.class.isAssignableFrom(aClass);
+        } else {
+            return false;
+        }
+    }
+
+    static void ensureBeanTypeMapExists(Binder binder) {
+        beanTypeMapBinding(binder).addBinding(TypeLiteral.get(BeanTypeKey.class)).toInstance(new BeanTypeKey(null));
+    }
+
+    static <T> void bindBeanType(Binder binder, TypeLiteral<T> typeLiteral, Key<? extends T> key) {
+        beanTypeMapBinding(binder).addBinding(typeLiteral).toInstance(new BeanTypeKey(key));
+    }
+
+    private static MapBinder<TypeLiteral, BeanTypeKey> beanTypeMapBinding(Binder binder) {
+        return MapBinder.newMapBinder(binder, TypeLiteral.class, BeanTypeKey.class, Names.named(BEAN_TYPE_MAP_NAME));
+    }
+
+    private static class BeanTypeKey {
+        Key<?> key;
+
+        private BeanTypeKey(Key<?> key) {
+            this.key = key;
+        }
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/DestroyableInjectionListener.java b/support/guice/src/main/java/org/apache/shiro/guice/DestroyableInjectionListener.java
new file mode 100644
index 0000000..cd866b3
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/DestroyableInjectionListener.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.spi.InjectionListener;
+import org.apache.shiro.util.Destroyable;
+
+/**
+ * Injection listener that assists with honoring the {@link org.apache.shiro.util.Destroyable} interface.
+ *
+ * @param <I>
+ */
+class DestroyableInjectionListener<I extends Destroyable> implements InjectionListener<I> {
+    public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(Matchers.subclassesOf(Destroyable.class));
+
+    private DestroyableRegistry registry;
+
+    public DestroyableInjectionListener(DestroyableRegistry registry) {
+        this.registry = registry;
+    }
+
+    public void afterInjection(Destroyable injectee) {
+        registry.add(injectee);
+    }
+
+    public static interface DestroyableRegistry {
+        void add(Destroyable destroyable);
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/GuiceEnvironment.java b/support/guice/src/main/java/org/apache/shiro/guice/GuiceEnvironment.java
new file mode 100644
index 0000000..5538369
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/GuiceEnvironment.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.Inject;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+
+class GuiceEnvironment implements Environment {
+    private SecurityManager securityManager;
+
+    @Inject
+    public GuiceEnvironment(SecurityManager securityManager) {
+        this.securityManager = securityManager;
+    }
+
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/InitializableInjectionListener.java b/support/guice/src/main/java/org/apache/shiro/guice/InitializableInjectionListener.java
new file mode 100644
index 0000000..0afed34
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/InitializableInjectionListener.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.spi.InjectionListener;
+import org.apache.shiro.util.Initializable;
+
+/**
+ * Injection listener that honors the {@link org.apache.shiro.util.Initializable} interface.
+ *
+ * @param <I>
+ */
+class InitializableInjectionListener<I extends Initializable> implements InjectionListener<I> {
+    public static final Matcher<TypeLiteral> MATCHER = ShiroMatchers.typeLiteral(Matchers.subclassesOf(Initializable.class));
+
+    public void afterInjection(Initializable injectee) {
+        injectee.init();
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/LifecycleTypeListener.java b/support/guice/src/main/java/org/apache/shiro/guice/LifecycleTypeListener.java
new file mode 100644
index 0000000..62f04c9
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/LifecycleTypeListener.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.InjectionListener;
+import com.google.inject.spi.TypeEncounter;
+import com.google.inject.spi.TypeListener;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+
+
+class LifecycleTypeListener implements TypeListener {
+    public static final Matcher<TypeLiteral> MATCHER = InitializableInjectionListener.MATCHER.or(DestroyableInjectionListener.MATCHER);
+    private DestroyableInjectionListener.DestroyableRegistry registry;
+
+    public LifecycleTypeListener(DestroyableInjectionListener.DestroyableRegistry registry) {
+        this.registry = registry;
+    }
+
+    public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
+        if (InitializableInjectionListener.MATCHER.matches(type)) {
+            encounter.register(this.<I>castListener(new InitializableInjectionListener<Initializable>()));
+        }
+        if (DestroyableInjectionListener.MATCHER.matches(type)) {
+            encounter.register(this.<I>castListener(new DestroyableInjectionListener<Destroyable>(registry)));
+        }
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private <I> InjectionListener<? super I> castListener(InjectionListener<?> listener) {
+        return (InjectionListener<? super I>) listener;
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/ShiroMatchers.java b/support/guice/src/main/java/org/apache/shiro/guice/ShiroMatchers.java
new file mode 100644
index 0000000..6bf0db3
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroMatchers.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.AbstractMatcher;
+import com.google.inject.matcher.Matcher;
+
+class ShiroMatchers {
+    public static Matcher<Class> ANY_PACKAGE = new AbstractMatcher<Class>() {
+
+        public boolean matches(Class aClass) {
+            return aClass.getPackage() != null;
+        }
+    };
+
+    public static Matcher<TypeLiteral> typeLiteral(final Matcher<Class> classMatcher) {
+        return new AbstractMatcher<TypeLiteral>() {
+
+            public boolean matches(TypeLiteral typeLiteral) {
+                return classMatcher.matches(typeLiteral.getRawType());
+            }
+        };
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/ShiroModule.java b/support/guice/src/main/java/org/apache/shiro/guice/ShiroModule.java
new file mode 100644
index 0000000..c71f486
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroModule.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.common.collect.Sets;
+import com.google.inject.Key;
+import com.google.inject.PrivateModule;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Types;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.util.Destroyable;
+
+import javax.annotation.PreDestroy;
+import java.util.Collection;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+
+/**
+ * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
+ * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.  At least one realm must be added by using
+ * {@link #bindRealm() bindRealm}.
+ */
+public abstract class ShiroModule extends PrivateModule implements Destroyable {
+
+    private Set<Destroyable> destroyables = Sets.newSetFromMap(new WeakHashMap<Destroyable, Boolean>());
+
+    public void configure() {
+        // setup security manager
+        bindSecurityManager(bind(SecurityManager.class));
+        bindSessionManager(bind(SessionManager.class));
+        bindEnvironment(bind(Environment.class));
+        bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
+        final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
+            public void add(Destroyable destroyable) {
+                ShiroModule.this.add(destroyable);
+            }
+
+            @PreDestroy
+            public void destroy() throws Exception {
+                ShiroModule.this.destroy();
+            }
+        };
+        bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
+
+        expose(SecurityManager.class);
+
+        configureShiro();
+        bind(realmCollectionKey())
+                .to(realmSetKey());
+
+        bind(DestroyableInjectionListener.DestroyableRegistry.class).toInstance(registry);
+        BeanTypeListener.ensureBeanTypeMapExists(binder());
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Key<Set<Realm>> realmSetKey() {
+        return (Key<Set<Realm>>) Key.get(TypeLiteral.get(Types.setOf(Realm.class)));
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Key<Collection<Realm>> realmCollectionKey() {
+        return (Key<Collection<Realm>>) Key.get(Types.newParameterizedType(Collection.class, Realm.class));
+    }
+
+    /**
+     * Implement this method in order to configure your realms and any other Shiro customization you may need.
+     */
+    protected abstract void configureShiro();
+
+    /**
+     * This is the preferred manner to bind a realm.  The {@link org.apache.shiro.mgt.SecurityManager} will be injected with any Realm bound
+     * with this method.
+     *
+     * @return a binding builder for a realm
+     */
+    protected final LinkedBindingBuilder<Realm> bindRealm() {
+        Multibinder<Realm> multibinder = Multibinder.newSetBinder(binder(), Realm.class);
+        return multibinder.addBinding();
+    }
+
+    /**
+     * Binds the security manager.  Override this method in order to provide your own security manager binding.
+     * <p/>
+     * By default, a {@link org.apache.shiro.mgt.DefaultSecurityManager} is bound as an eager singleton.
+     *
+     * @param bind
+     */
+    protected void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
+        try {
+            bind.toConstructor(DefaultSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
+        } catch (NoSuchMethodException e) {
+            throw new ConfigurationException("This really shouldn't happen.  Either something has changed in Shiro, or there's a bug in " + ShiroModule.class.getSimpleName(), e);
+        }
+    }
+
+    /**
+     * Binds the session manager.  Override this method in order to provide your own session manager binding.
+     * <p/>
+     * By default, a {@link org.apache.shiro.session.mgt.DefaultSessionManager} is bound as an eager singleton.
+     *
+     * @param bind
+     */
+    protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
+        bind.to(DefaultSessionManager.class).asEagerSingleton();
+    }
+
+    /**
+     * Binds the environment.  Override this method in order to provide your own environment binding.
+     * <p/>
+     * By default, a {@link GuiceEnvironment} is bound as an eager singleton.
+     *
+     * @param bind
+     */
+    protected void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
+        bind.to(GuiceEnvironment.class).asEagerSingleton();
+    }
+
+    /**
+     * Binds a key to use for injecting setters in shiro classes.
+     * @param typeLiteral the bean property type
+     * @param key the key to use to satisfy the bean property dependency
+     * @param <T>
+     */
+    protected final <T> void bindBeanType(TypeLiteral<T> typeLiteral, Key<? extends T> key) {
+        BeanTypeListener.bindBeanType(binder(), typeLiteral, key);
+    }
+
+    /**
+     * Destroys all beans created within this module that implement {@link org.apache.shiro.util.Destroyable}.  Should be called when this
+     * module will no longer be used.
+     *
+     * @throws Exception
+     */
+    public final void destroy() throws Exception {
+        for (Destroyable destroyable : destroyables) {
+            destroyable.destroy();
+        }
+    }
+
+    public void add(Destroyable destroyable) {
+        this.destroyables.add(destroyable);
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
new file mode 100644
index 0000000..d318eea
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+
+/**
+ * Guice scope for Shiro sessions.  Not bound by default.
+ */
+public class ShiroSessionScope implements Scope {
+    public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
+        return new Provider<T>() {
+            public T get() {
+                Subject subject = ThreadContext.getSubject();
+                if (subject == null) {
+                    throw new OutOfScopeException("There is no Shiro Session currently in scope.");
+                }
+                Session session = subject.getSession();
+                T scoped = castSessionAttribute(session);
+                if (scoped == null) {
+                    scoped = unscoped.get();
+                }
+                return scoped;
+            }
+
+            @SuppressWarnings({"unchecked"})
+            private T castSessionAttribute(Session session) {
+                return (T) session.getAttribute(key);
+            }
+
+            @Override
+            public String toString() {
+                return unscoped.toString();
+            }
+        };
+    }
+
+    @Override
+    public String toString() {
+        return "ShiroSessionScope";
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapter.java b/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapter.java
new file mode 100644
index 0000000..21c80e7
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * Adapts a Shiro {@link org.apache.shiro.aop.MethodInterceptor MethodInterceptor} to an AOPAlliance
+ * {@link org.aopalliance.intercept.MethodInterceptor}.
+ */
+class AopAllianceMethodInterceptorAdapter implements MethodInterceptor {
+    org.apache.shiro.aop.MethodInterceptor shiroInterceptor;
+
+    public AopAllianceMethodInterceptorAdapter(org.apache.shiro.aop.MethodInterceptor shiroInterceptor) {
+        this.shiroInterceptor = shiroInterceptor;
+    }
+
+    public Object invoke(MethodInvocation invocation) throws Throwable {
+        return shiroInterceptor.invoke(new AopAllianceMethodInvocationAdapter(invocation));
+    }
+
+    @Override
+    public String toString() {
+        return "AopAlliance Adapter for " + shiroInterceptor.toString();
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapter.java b/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapter.java
new file mode 100644
index 0000000..026ae2e
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapter.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+
+/**
+ * Adapts a Shiro {@link org.apache.shiro.aop.MethodInvocation MethodInvocation} to an AOPAlliance
+ * {@link org.aopalliance.intercept.MethodInvocation}.
+ */
+class AopAllianceMethodInvocationAdapter implements org.apache.shiro.aop.MethodInvocation {
+    private final MethodInvocation mi;
+
+    public AopAllianceMethodInvocationAdapter(MethodInvocation mi) {
+        this.mi = mi;
+    }
+
+    public Method getMethod() {
+        return mi.getMethod();
+    }
+
+    public Object[] getArguments() {
+        return mi.getArguments();
+    }
+
+    public String toString() {
+        return "Method invocation [" + mi.getMethod() + "]";
+    }
+
+    public Object proceed() throws Throwable {
+        return mi.proceed();
+    }
+
+    public Object getThis() {
+        return mi.getThis();
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
new file mode 100644
index 0000000..be8158e
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.matcher.AbstractMatcher;
+import com.google.inject.matcher.Matchers;
+import org.apache.shiro.aop.AnnotationMethodInterceptor;
+import org.apache.shiro.aop.AnnotationResolver;
+import org.apache.shiro.aop.DefaultAnnotationResolver;
+import org.apache.shiro.authz.aop.*;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * Install this module to enable Shiro AOP functionality in Guice.  You may extend it to add your own Shiro
+ * interceptors, override the default ones, or provide a specific {@link org.apache.shiro.aop.AnnotationResolver}.
+ */
+public class ShiroAopModule extends AbstractModule {
+    @Override
+    protected final void configure() {
+        AnnotationResolver resolver = createAnnotationResolver();
+        configureDefaultInterceptors(resolver);
+        configureInterceptors(resolver);
+    }
+
+    protected final void bindShiroInterceptor(final AnnotationMethodInterceptor methodInterceptor) {
+        bindInterceptor(Matchers.any(), new AbstractMatcher<Method>() {
+            public boolean matches(Method method) {
+                Class<? extends Annotation> annotation = methodInterceptor.getHandler().getAnnotationClass();
+                return method.getAnnotation(annotation) != null
+                        || method.getDeclaringClass().getAnnotation(annotation) != null;
+            }
+        }, new AopAllianceMethodInterceptorAdapter(methodInterceptor));
+    }
+
+    protected AnnotationResolver createAnnotationResolver() {
+        return new DefaultAnnotationResolver();
+    }
+
+    protected void configureDefaultInterceptors(AnnotationResolver resolver) {
+        bindShiroInterceptor(new RoleAnnotationMethodInterceptor(resolver));
+        bindShiroInterceptor(new PermissionAnnotationMethodInterceptor(resolver));
+        bindShiroInterceptor(new AuthenticatedAnnotationMethodInterceptor(resolver));
+        bindShiroInterceptor(new UserAnnotationMethodInterceptor(resolver));
+        bindShiroInterceptor(new GuestAnnotationMethodInterceptor(resolver));
+    }
+
+    protected void configureInterceptors(AnnotationResolver resolver) {
+
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/AbstractInjectionProvider.java b/support/guice/src/main/java/org/apache/shiro/guice/web/AbstractInjectionProvider.java
new file mode 100644
index 0000000..8c8e564
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/AbstractInjectionProvider.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.ProvisionException;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ProviderWithDependencies;
+
+import java.lang.reflect.Constructor;
+import java.util.Set;
+
+class AbstractInjectionProvider<T> implements ProviderWithDependencies<T> {
+    private Key<T> key;
+
+    @Inject
+    Injector injector;
+
+    private InjectionPoint constructorInjectionPoint;
+    private Set<Dependency<?>> dependencies;
+
+    public AbstractInjectionProvider(Key<T> key) {
+        this.key = key;
+        constructorInjectionPoint = InjectionPoint.forConstructorOf(key.getTypeLiteral());
+
+        ImmutableSet.Builder<Dependency<?>> dependencyBuilder = ImmutableSet.builder();
+        dependencyBuilder.addAll(constructorInjectionPoint.getDependencies());
+        for (InjectionPoint injectionPoint : InjectionPoint.forInstanceMethodsAndFields(key.getTypeLiteral())) {
+            dependencyBuilder.addAll(injectionPoint.getDependencies());
+        }
+        this.dependencies = dependencyBuilder.build();
+    }
+
+    public T get() {
+        Constructor<T> constructor = getConstructor();
+        Object[] params = new Object[constructor.getParameterTypes().length];
+        for (Dependency<?> dependency : constructorInjectionPoint.getDependencies()) {
+            params[dependency.getParameterIndex()] = injector.getInstance(dependency.getKey());
+        }
+        T t;
+        try {
+            t = constructor.newInstance(params);
+        } catch (Exception e) {
+            throw new ProvisionException("Could not instantiate " + key + "", e);
+        }
+        injector.injectMembers(t);
+        return postProcess(t);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Constructor<T> getConstructor() {
+        return (Constructor<T>) constructorInjectionPoint.getMember();
+    }
+
+    protected T postProcess(T t) {
+        // do nothing by default
+        return t;
+    }
+
+    public Set<Dependency<?>> getDependencies() {
+        return dependencies;
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/FilterChainResolverProvider.java b/support/guice/src/main/java/org/apache/shiro/guice/web/FilterChainResolverProvider.java
new file mode 100644
index 0000000..474c263
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/FilterChainResolverProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Singleton;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.ProviderWithDependencies;
+import org.apache.shiro.util.AntPathMatcher;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+
+import javax.servlet.Filter;
+import java.util.Map;
+import java.util.Set;
+
+ at Singleton
+class FilterChainResolverProvider implements ProviderWithDependencies<FilterChainResolver> {
+    @Inject
+    Injector injector;
+
+    private final Map<String, Key<? extends Filter>[]> chains;
+
+    private final Set<Dependency<?>> dependencies;
+
+    private PatternMatcher patternMatcher = new AntPathMatcher();
+
+    public FilterChainResolverProvider(Map<String, Key<? extends Filter>[]> chains) {
+        this.chains = chains;
+        ImmutableSet.Builder<Dependency<?>> dependenciesBuilder = ImmutableSet.builder();
+        for (String chain : chains.keySet()) {
+            for (Key<? extends Filter> filterKey : chains.get(chain)) {
+                dependenciesBuilder.add(Dependency.get(filterKey));
+            }
+        }
+        this.dependencies = dependenciesBuilder.build();
+    }
+
+    @Inject(optional = true)
+    public void setPatternMatcher(PatternMatcher patternMatcher) {
+        this.patternMatcher = patternMatcher;
+    }
+
+    public Set<Dependency<?>> getDependencies() {
+        return dependencies;
+    }
+
+    public FilterChainResolver get() {
+        return new SimpleFilterChainResolver(chains, injector, patternMatcher);
+    }
+
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java b/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
new file mode 100644
index 0000000..e58449f
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.servlet.AbstractShiroFilter;
+
+import javax.inject.Inject;
+
+/**
+ * Shiro filter that is managed by and receives its filter chain configurations from Guice.  The convenience method to
+ * map this filter to your application is
+ * {@link ShiroWebModule#bindGuiceFilter(com.google.inject.Binder) bindGuiceFilter}.
+ */
+public class GuiceShiroFilter extends AbstractShiroFilter {
+    @Inject
+    GuiceShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver filterChainResolver) {
+        this.setSecurityManager(webSecurityManager);
+        this.setFilterChainResolver(filterChainResolver);
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/PathMatchingFilterProvider.java b/support/guice/src/main/java/org/apache/shiro/guice/web/PathMatchingFilterProvider.java
new file mode 100644
index 0000000..a76922c
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/PathMatchingFilterProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Key;
+import org.apache.shiro.web.filter.PathMatchingFilter;
+
+import java.util.Map;
+
+class PathMatchingFilterProvider<T extends PathMatchingFilter> extends AbstractInjectionProvider<T> {
+    private Map<String, String> pathConfigs;
+
+    public PathMatchingFilterProvider(Key<T> key, Map<String, String> pathConfigs) {
+        super(key);
+        this.pathConfigs = pathConfigs;
+    }
+
+    @Override
+    protected T postProcess(T filter) {
+        for (Map.Entry<String, String> pathConfig : this.pathConfigs.entrySet()) {
+            filter.processPathConfig(pathConfig.getKey(), pathConfig.getValue());
+        }
+        return filter;
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
new file mode 100644
index 0000000..1334320
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.name.Names;
+import com.google.inject.servlet.ServletModule;
+import org.apache.shiro.guice.ShiroModule;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.PathMatchingFilter;
+import org.apache.shiro.web.filter.authc.*;
+import org.apache.shiro.web.filter.authz.*;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletContext;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
+ * {@link org.apache.shiro.web.mgt.WebSecurityManager}, {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.  At least one realm must be added by
+ * using {@link #bindRealm() bindRealm}.
+ * <p/>
+ * Also provides for the configuring of filter chains and binds a {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information.
+ */
+public abstract class ShiroWebModule extends ShiroModule {
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<PortFilter> PORT = Key.get(PortFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static final Key<UserFilter> USER = Key.get(UserFilter.class);
+
+
+    static final String NAME = "SHIRO";
+
+    /**
+     * We use a LinkedHashMap here to ensure that iterator order is the same as add order.  This is important, as the
+     * FilterChainResolver uses iterator order when searching for a matching chain.
+     */
+    private final Map<String, Key<? extends Filter>[]> filterChains = new LinkedHashMap<String, Key<? extends Filter>[]>();
+    private final ServletContext servletContext;
+
+    public ShiroWebModule(ServletContext servletContext) {
+        this.servletContext = servletContext;
+    }
+
+    public static void bindGuiceFilter(Binder binder) {
+        binder.install(guiceFilterModule());
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public static void bindGuiceFilter(final String pattern, Binder binder) {
+        binder.install(guiceFilterModule(pattern));
+    }
+
+    public static ServletModule guiceFilterModule() {
+        return guiceFilterModule("/*");
+    }
+
+    public static ServletModule guiceFilterModule(final String pattern) {
+        return new ServletModule() {
+            @Override
+            protected void configureServlets() {
+                filter(pattern).through(GuiceShiroFilter.class);
+            }
+        };
+    }
+
+    @Override
+    protected final void configureShiro() {
+        bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
+        bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext);
+        bindWebSecurityManager(bind(WebSecurityManager.class));
+        bindWebEnvironment(bind(WebEnvironment.class));
+        bind(GuiceShiroFilter.class).asEagerSingleton();
+        expose(GuiceShiroFilter.class);
+
+        this.configureShiroWeb();
+
+        setupFilterChainConfigs();
+
+        bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(filterChains));
+    }
+
+    private void setupFilterChainConfigs() {
+        Table<Key<? extends PathMatchingFilter>, String, String> configs = HashBasedTable.create();
+
+        for (Map.Entry<String, Key<? extends Filter>[]> filterChain : filterChains.entrySet()) {
+            for (int i = 0; i < filterChain.getValue().length; i++) {
+                Key<? extends Filter> key = filterChain.getValue()[i];
+                if (key instanceof FilterConfigKey) {
+                    FilterConfigKey<? extends PathMatchingFilter> configKey = (FilterConfigKey<? extends PathMatchingFilter>) key;
+                    key = configKey.getKey();
+                    filterChain.getValue()[i] = key;
+                    if (!PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+                        throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
+                    }
+                    configs.put(castToPathMatching(key), filterChain.getKey(), configKey.getConfigValue());
+                } else if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+                    configs.put(castToPathMatching(key), filterChain.getKey(), "");
+                }
+            }
+        }
+        for (Key<? extends PathMatchingFilter> filterKey : configs.rowKeySet()) {
+            bindPathMatchingFilter(filterKey, configs.row(filterKey));
+        }
+    }
+
+    private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
+        bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) {
+        return (Key<? extends PathMatchingFilter>) key;
+    }
+
+    protected abstract void configureShiroWeb();
+
+    @SuppressWarnings({"unchecked"})
+    @Override
+    protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
+        bindWebSecurityManager(bind);
+    }
+
+    /**
+     * Binds the security manager.  Override this method in order to provide your own security manager binding.
+     * <p/>
+     * By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton.
+     *
+     * @param bind
+     */
+    protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
+        try {
+            bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
+        } catch (NoSuchMethodException e) {
+            throw new ConfigurationException("This really shouldn't happen.  Either something has changed in Shiro, or there's a bug in ShiroModule.", e);
+        }
+    }
+
+    /**
+     * Binds the session manager.  Override this method in order to provide your own session manager binding.
+     * <p/>
+     * By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton.
+     *
+     * @param bind
+     */
+    @Override
+    protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
+        bind.to(ServletContainerSessionManager.class).asEagerSingleton();
+    }
+
+    @Override
+    protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
+        bindWebEnvironment(bind);
+    }
+
+    protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
+        bind.to(WebGuiceEnvironment.class).asEagerSingleton();
+    }
+
+    /**
+     * Adds a filter chain to the shiro configuration.
+     * <p/>
+     * NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter}, it will be registered with a proper
+     * provider.
+     *
+     * @param pattern
+     * @param keys
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
+        filterChains.put(pattern, keys);
+    }
+
+    protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
+        return new FilterConfigKey<T>(baseKey, configValue);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
+        return config(Key.get(typeLiteral), configValue);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
+        return config(Key.get(type), configValue);
+    }
+
+    private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
+        private Key<T> key;
+        private String configValue;
+
+        private FilterConfigKey(Key<T> key, String configValue) {
+            super();
+            this.key = key;
+            this.configValue = configValue;
+        }
+
+        public Key<T> getKey() {
+            return key;
+        }
+
+        public String getConfigValue() {
+            return configValue;
+        }
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChain.java b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChain.java
new file mode 100644
index 0000000..4ff8b56
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChain.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.util.Iterator;
+
+class SimpleFilterChain implements FilterChain {
+
+
+    private final FilterChain originalChain;
+    private final Iterator<? extends Filter> chain;
+
+    private boolean originalCalled = false;
+
+    public SimpleFilterChain(FilterChain originalChain, Iterator<? extends Filter> chain) {
+        this.originalChain = originalChain;
+        this.chain = chain;
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+        if (chain.hasNext()) {
+            Filter filter = chain.next();
+            filter.doFilter(request, response, this);
+        } else if (!originalCalled) {
+            originalCalled = true;
+            originalChain.doFilter(request, response);
+        }
+    }
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
new file mode 100644
index 0000000..526012c
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterators;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.util.Map;
+
+class SimpleFilterChainResolver implements FilterChainResolver {
+    private final Map<String, Key<? extends Filter>[]> chains;
+    private final Injector injector;
+    private final PatternMatcher patternMatcher;
+
+    SimpleFilterChainResolver(Map<String, Key<? extends Filter>[]> chains, Injector injector, PatternMatcher patternMatcher) {
+        this.chains = chains;
+        this.injector = injector;
+        this.patternMatcher = patternMatcher;
+    }
+
+    public FilterChain getChain(ServletRequest request, ServletResponse response, final FilterChain originalChain) {
+        String path = WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
+        for (final String pathPattern : chains.keySet()) {
+            if (patternMatcher.matches(pathPattern, path)) {
+                return new SimpleFilterChain(originalChain, Iterators.transform(Iterators.forArray(chains.get(pathPattern)),
+                        new Function<Key<? extends Filter>, Filter>() {
+                            public Filter apply(Key<? extends Filter> input) {
+                                return injector.getInstance(input);
+                            }
+                        }));
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java b/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java
new file mode 100644
index 0000000..591d1c6
--- /dev/null
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.env.EnvironmentLoaderListener;
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.inject.Named;
+import javax.servlet.ServletContext;
+
+ at Singleton
+class WebGuiceEnvironment implements WebEnvironment {
+    private FilterChainResolver filterChainResolver;
+    private ServletContext servletContext;
+    private WebSecurityManager securityManager;
+
+    @Inject
+    WebGuiceEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager) {
+        this.filterChainResolver = filterChainResolver;
+        this.servletContext = servletContext;
+        this.securityManager = securityManager;
+
+        servletContext.setAttribute(EnvironmentLoaderListener.ENVIRONMENT_ATTRIBUTE_KEY, this);
+    }
+
+    public FilterChainResolver getFilterChainResolver() {
+        return filterChainResolver;
+    }
+
+    public ServletContext getServletContext() {
+        return servletContext;
+    }
+
+    public WebSecurityManager getWebSecurityManager() {
+        return securityManager;
+    }
+
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/BeanTypeListenerTest.java b/support/guice/src/test/java/org/apache/shiro/guice/BeanTypeListenerTest.java
new file mode 100644
index 0000000..c73ef71
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/BeanTypeListenerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.*;
+import com.google.inject.name.Names;
+import com.google.inject.spi.Message;
+import com.google.inject.spi.TypeEncounter;
+import org.apache.shiro.guice.aop.ShiroAopModule;
+import org.apache.shiro.guice.web.ShiroWebModule;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.aop.DefaultAnnotationResolver;
+import org.apache.shiro.crypto.BlowfishCipherService;
+import org.easymock.Capture;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Test Cases::
+ * Test package matching
+ * injects on classes in shiro package and sub packages
+ * excludes classes in shiro-guice package and sub packages
+ * Test that properties are set properly
+ * ensure optional
+ * ensure property names are correct
+ * ensure "named" properties require a name, and unnamed do not
+ */
+public class BeanTypeListenerTest {
+    @Test
+    public void testUnmatchedPackage() throws Exception {
+        assertFalse(BeanTypeListener.MATCHER.matches(TypeLiteral.get(GuiceEnvironment.class)));
+        assertFalse(BeanTypeListener.MATCHER.matches(TypeLiteral.get(ShiroWebModule.class)));
+        assertFalse(BeanTypeListener.MATCHER.matches(TypeLiteral.get(ShiroAopModule.class)));
+    }
+
+    @Test
+    public void testMatchedPackage() throws Exception {
+        assertTrue(BeanTypeListener.MATCHER.matches(TypeLiteral.get(SecurityUtils.class)));
+        assertTrue(BeanTypeListener.MATCHER.matches(TypeLiteral.get(DefaultAnnotationResolver.class)));
+        assertTrue(BeanTypeListener.MATCHER.matches(TypeLiteral.get(BlowfishCipherService.class)));
+    }
+
+    @Test
+    public void testPropertySetting() throws Exception {
+        IMocksControl control = createControl();
+        TypeEncounter<SomeInjectableBean> encounter = control.createMock(TypeEncounter.class);
+
+        Provider<Injector> injectorProvider = control.createMock(Provider.class);
+        Injector injector = control.createMock(Injector.class);
+
+        expect(encounter.getProvider(Injector.class)).andReturn(injectorProvider);
+
+        expect(injectorProvider.get()).andReturn(injector).anyTimes();
+
+        Capture<MembersInjector<SomeInjectableBean>> capture = new Capture<MembersInjector<SomeInjectableBean>>();
+        encounter.register(and(anyObject(MembersInjector.class), capture(capture)));
+
+        SecurityManager securityManager = control.createMock(SecurityManager.class);
+        String property = "myPropertyValue";
+
+        expect(injector.getInstance(Key.get(SecurityManager.class))).andReturn(securityManager);
+        expect(injector.getInstance(Key.get(String.class, Names.named("shiro.myProperty")))).andReturn(property);
+        expect(injector.getInstance(Key.get(String.class, Names.named("shiro.unavailableProperty"))))
+                .andThrow(new ConfigurationException(Collections.singleton(new Message("Not Available!"))));
+        expect(injector.getInstance(BeanTypeListener.MAP_KEY)).andReturn(Collections.EMPTY_MAP).anyTimes();
+
+        control.replay();
+
+        BeanTypeListener underTest = new BeanTypeListener();
+
+        underTest.hear(TypeLiteral.get(SomeInjectableBean.class), encounter);
+
+        SomeInjectableBean bean = new SomeInjectableBean();
+
+        capture.getValue().injectMembers(bean);
+
+        assertSame(securityManager, bean.securityManager);
+        assertSame(property, bean.myProperty);
+        assertNull(bean.unavailableProperty);
+
+        control.verify();
+    }
+
+    public static class SomeInjectableBean {
+        private SecurityManager securityManager;
+        private String myProperty;
+        private String unavailableProperty;
+
+        public void setSecurityManager(SecurityManager securityManager) {
+
+            this.securityManager = securityManager;
+        }
+
+        public void setMyProperty(String myProperty) {
+
+            this.myProperty = myProperty;
+        }
+
+        public void setUnavailableProperty(String unavailableProperty) {
+
+            this.unavailableProperty = unavailableProperty;
+        }
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/DestroyableInjectionListenerTest.java b/support/guice/src/test/java/org/apache/shiro/guice/DestroyableInjectionListenerTest.java
new file mode 100644
index 0000000..efd652a
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/DestroyableInjectionListenerTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import org.apache.shiro.util.Destroyable;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+public class DestroyableInjectionListenerTest {
+    @Test
+    public void testAfterInjection() throws Exception {
+        DestroyableInjectionListener.DestroyableRegistry registry = createMock(DestroyableInjectionListener.DestroyableRegistry.class);
+        Destroyable destroyable = createMock(Destroyable.class);
+
+        registry.add(destroyable);
+
+        replay(registry, destroyable);
+
+        DestroyableInjectionListener underTest = new DestroyableInjectionListener(registry);
+        underTest.afterInjection(destroyable);
+
+        verify(registry, destroyable);
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/GuiceEnvironmentTest.java b/support/guice/src/test/java/org/apache/shiro/guice/GuiceEnvironmentTest.java
new file mode 100644
index 0000000..c91fd18
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/GuiceEnvironmentTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.spi.InjectionPoint;
+import org.apache.shiro.mgt.SecurityManager;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+public class GuiceEnvironmentTest {
+    @Test
+    public void testGetSecurityManager() throws Exception {
+        SecurityManager securityManager = createMock(SecurityManager.class);
+
+        GuiceEnvironment underTest = new GuiceEnvironment(securityManager);
+        assertSame(securityManager, underTest.getSecurityManager());
+    }
+
+    @Test
+    public void ensureInjectable() {
+        try {
+            InjectionPoint ip = InjectionPoint.forConstructorOf(GuiceEnvironment.class);
+        } catch (Exception e) {
+            fail("Could not create constructor injection point.");
+        }
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/InitializableInjectionListenerTest.java b/support/guice/src/test/java/org/apache/shiro/guice/InitializableInjectionListenerTest.java
new file mode 100644
index 0000000..bbecfaa
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/InitializableInjectionListenerTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import org.apache.shiro.util.Initializable;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+public class InitializableInjectionListenerTest {
+    @Test
+    public void testAfterInjection() throws Exception {
+        Initializable initializable = createMock(Initializable.class);
+
+        initializable.init();
+
+        replay(initializable);
+
+        InitializableInjectionListener underTest = new InitializableInjectionListener();
+        underTest.afterInjection(initializable);
+
+        verify(initializable);
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/LifecycleTypeListenerTest.java b/support/guice/src/test/java/org/apache/shiro/guice/LifecycleTypeListenerTest.java
new file mode 100644
index 0000000..aff8580
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/LifecycleTypeListenerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.TypeEncounter;
+import org.apache.shiro.ShiroException;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+public class LifecycleTypeListenerTest {
+    @Test
+    public void testHearInitializable() throws Exception {
+        TypeEncounter encounter = createMock(TypeEncounter.class);
+
+        encounter.register(anyObject(InitializableInjectionListener.class));
+
+        replay(encounter);
+
+        LifecycleTypeListener underTest = new LifecycleTypeListener(null);
+
+        underTest.hear(TypeLiteral.get(MyInitializable.class), encounter);
+
+        verify(encounter);
+    }
+
+    @Test
+    public void testHearDestroyable() throws Exception {
+        TypeEncounter encounter = createMock(TypeEncounter.class);
+
+        encounter.register(anyObject(DestroyableInjectionListener.class));
+
+        replay(encounter);
+
+        LifecycleTypeListener underTest = new LifecycleTypeListener(null);
+
+        underTest.hear(TypeLiteral.get(MyDestroyable.class), encounter);
+
+        verify(encounter);
+    }
+
+    static class MyInitializable implements Initializable {
+
+        public void init() throws ShiroException {
+            // do nothing
+        }
+    }
+
+    static class MyDestroyable implements Destroyable {
+        public void destroy() throws Exception {
+            // do nothing
+        }
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/ShiroMatchersTest.java b/support/guice/src/test/java/org/apache/shiro/guice/ShiroMatchersTest.java
new file mode 100644
index 0000000..19b028c
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/ShiroMatchersTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matcher;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ShiroMatchersTest {
+    @Test
+    public void testTypeLiteral() throws Exception {
+        Matcher<Class> classMatcher = createMock(Matcher.class);
+        expect(classMatcher.matches(MatchingClass.class)).andReturn(true);
+        expect(classMatcher.matches(NotMatchingClass.class)).andReturn(false);
+
+        replay(classMatcher);
+
+        Matcher<TypeLiteral> underTest = ShiroMatchers.typeLiteral(classMatcher);
+
+        assertTrue(underTest.matches(TypeLiteral.get(MatchingClass.class)));
+        assertFalse(underTest.matches(TypeLiteral.get(NotMatchingClass.class)));
+
+        verify(classMatcher);
+    }
+
+
+    static class MatchingClass {
+    }
+
+    static class NotMatchingClass {
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/ShiroModuleTest.java b/support/guice/src/test/java/org/apache/shiro/guice/ShiroModuleTest.java
new file mode 100644
index 0000000..4507076
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/ShiroModuleTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.Destroyable;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ShiroModuleTest {
+
+    @Test
+    public void basicInstantiation() {
+
+        final MockRealm mockRealm = createMock(MockRealm.class);
+
+        Injector injector = Guice.createInjector(new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+        });
+        SecurityManager securityManager = injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+    }
+
+    @Test
+    public void testConfigure() {
+        final MockRealm mockRealm = createMock(MockRealm.class);
+        AuthenticationToken authToken = createMock(AuthenticationToken.class);
+        AuthenticationInfo info = new SimpleAuthenticationInfo("mockUser", "password", "mockRealm");
+
+        expect(mockRealm.supports(authToken)).andReturn(true);
+        expect(mockRealm.getAuthenticationInfo(authToken)).andReturn(info);
+
+        replay(mockRealm);
+
+        Injector injector = Guice.createInjector(new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+        });
+        SecurityManager securityManager = injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+        SecurityUtils.setSecurityManager(securityManager);
+
+        final Subject subject = new Subject.Builder(securityManager).buildSubject();
+        securityManager.login(subject, authToken);
+
+        verify(mockRealm);
+    }
+
+    @Test
+    public void testBindSecurityManager() {
+        final MockRealm mockRealm = createMock(MockRealm.class);
+
+        Injector injector = Guice.createInjector(new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+
+            @Override
+            protected void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
+                bind.to(MyDefaultSecurityManager.class);
+            }
+        });
+        SecurityManager securityManager = injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+        assertTrue(securityManager instanceof MyDefaultSecurityManager);
+    }
+
+    @Test
+    public void testBindSessionManager() {
+        final MockRealm mockRealm = createMock(MockRealm.class);
+
+        Injector injector = Guice.createInjector(new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+
+            @Override
+            protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
+                bind.to(MyDefaultSessionManager.class);
+            }
+        });
+        DefaultSecurityManager securityManager = (DefaultSecurityManager) injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+        assertNotNull(securityManager.getSessionManager());
+        assertTrue(securityManager.getSessionManager() instanceof MyDefaultSessionManager);
+    }
+
+    @Test
+    public void testBindEnvironment() {
+        final MockRealm mockRealm = createMock(MockRealm.class);
+
+        Injector injector = Guice.createInjector(new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+                expose(Environment.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+
+            @Override
+            protected void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
+                bind.to(MyEnvironment.class);
+            }
+        });
+        Environment environment = injector.getInstance(Environment.class);
+        assertNotNull(environment);
+        assertTrue(environment instanceof MyEnvironment);
+    }
+
+    @Test
+    public void testDestroy() throws Exception {
+        final MockRealm mockRealm = createMock(MockRealm.class);
+        final MyDestroyable myDestroyable = createMock(MyDestroyable.class);
+
+        myDestroyable.destroy();
+
+        replay(myDestroyable);
+
+        final ShiroModule shiroModule = new ShiroModule() {
+            @Override
+            protected void configureShiro() {
+                bindRealm().to(MockRealm.class);
+                bind(MyDestroyable.class).toInstance(myDestroyable);
+                expose(MyDestroyable.class);
+            }
+
+            @Provides
+            public MockRealm createRealm() {
+                return mockRealm;
+            }
+
+        };
+        Injector injector = Guice.createInjector(shiroModule);
+        injector.getInstance(MyDestroyable.class);
+        shiroModule.destroy();
+
+        verify(myDestroyable);
+    }
+
+    public static interface MockRealm extends Realm {
+
+    }
+
+    public static class MyDefaultSecurityManager extends DefaultSecurityManager {
+        @Inject
+        public MyDefaultSecurityManager(Collection<Realm> realms) {
+            super(realms);
+        }
+    }
+
+    public static class MyDefaultSessionManager extends DefaultSessionManager {
+    }
+
+    public static class MyEnvironment extends GuiceEnvironment {
+        @Inject
+        public MyEnvironment(SecurityManager securityManager) {
+            super(securityManager);
+        }
+    }
+
+    public static interface MyDestroyable extends Destroyable {
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/ShiroSessionScopeTest.java b/support/guice/src/test/java/org/apache/shiro/guice/ShiroSessionScopeTest.java
new file mode 100644
index 0000000..9b8c901
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/ShiroSessionScopeTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice;
+
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertSame;
+
+public class ShiroSessionScopeTest {
+    @Test
+    public void testScope() throws Exception {
+        Subject subject = createMock(Subject.class);
+        try {
+            ThreadContext.bind(subject);
+
+            final Key<SomeClass> key = Key.get(SomeClass.class);
+            Provider<SomeClass> mockProvider = createMock(Provider.class);
+            Session session = createMock(Session.class);
+
+            SomeClass retuned = new SomeClass();
+
+            expect(subject.getSession()).andReturn(session);
+            expect(session.getAttribute(key)).andReturn(null);
+            expect(mockProvider.get()).andReturn(retuned);
+
+            expect(subject.getSession()).andReturn(session);
+            expect(session.getAttribute(key)).andReturn(retuned);
+
+
+            replay(subject, mockProvider, session);
+
+            ShiroSessionScope underTest = new ShiroSessionScope();
+
+            // first time the session doesn't contain it, we expect the provider to be invoked
+            assertSame(retuned, underTest.scope(key, mockProvider).get());
+            // second time the session does contain it, we expect the provider to not be invoked
+            assertSame(retuned, underTest.scope(key, mockProvider).get());
+
+            verify(subject, mockProvider, session);
+        } finally {
+            ThreadContext.unbindSubject();
+        }
+
+    }
+
+    @Test(expected = OutOfScopeException.class)
+    public void testOutOfScope() throws Exception {
+        ShiroSessionScope underTest = new ShiroSessionScope();
+
+        Provider<SomeClass> mockProvider = createMock(Provider.class);
+
+        replay(mockProvider);
+
+        underTest.scope(Key.get(SomeClass.class), mockProvider).get();
+    }
+
+
+    static class SomeClass {
+
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapterTest.java b/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapterTest.java
new file mode 100644
index 0000000..ce4b65d
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInterceptorAdapterTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.shiro.aop.MethodInterceptor;
+import org.easymock.IAnswer;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertSame;
+
+
+public class AopAllianceMethodInterceptorAdapterTest {
+    @Test
+    public void testInvoke() throws Throwable {
+        MethodInvocation allianceInvocation = createMock(MethodInvocation.class);
+        MethodInterceptor mockShiroInterceptor = createMock(MethodInterceptor.class);
+        expect(mockShiroInterceptor.invoke(anyObject(AopAllianceMethodInvocationAdapter.class))).andAnswer(new IAnswer<Object>() {
+            public Object answer() throws Throwable {
+                return getCurrentArguments()[0];
+            }
+        });
+        final Object expectedValue = new Object();
+        expect(allianceInvocation.proceed()).andReturn(expectedValue);
+
+        replay(mockShiroInterceptor, allianceInvocation);
+
+        AopAllianceMethodInterceptorAdapter underTest = new AopAllianceMethodInterceptorAdapter(mockShiroInterceptor);
+        Object invocation = underTest.invoke(allianceInvocation);
+        Object value = ((AopAllianceMethodInvocationAdapter) invocation).proceed();
+
+        assertSame("Adapter invocation returned a different value.", expectedValue, value);
+
+        verify(mockShiroInterceptor, allianceInvocation);
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapterTest.java b/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapterTest.java
new file mode 100644
index 0000000..54be927
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/aop/AopAllianceMethodInvocationAdapterTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: jbunting
+ * Date: 6/18/11
+ * Time: 5:02 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class AopAllianceMethodInvocationAdapterTest {
+    @Test
+    public void testGetMethod() throws Exception {
+        MethodInvocation mock = createMock(MethodInvocation.class);
+        Method method = AopAllianceMethodInvocationAdapterTest.class.getMethod("testGetMethod");
+        expect(mock.getMethod()).andReturn(method);
+        AopAllianceMethodInvocationAdapter underTest = new AopAllianceMethodInvocationAdapter(mock);
+
+        replay(mock);
+
+        assertSame(method, underTest.getMethod());
+
+        verify(mock);
+    }
+
+    @Test
+    public void testGetArguments() throws Exception {
+        MethodInvocation mock = createMock(MethodInvocation.class);
+        Object[] args = new Object[0];
+        expect(mock.getArguments()).andReturn(args);
+        AopAllianceMethodInvocationAdapter underTest = new AopAllianceMethodInvocationAdapter(mock);
+
+        replay(mock);
+
+        assertSame(args, underTest.getArguments());
+
+        verify(mock);
+    }
+
+    @Test
+    public void testProceed() throws Throwable {
+        MethodInvocation mock = createMock(MethodInvocation.class);
+        Object value = new Object();
+        expect(mock.proceed()).andReturn(value);
+        AopAllianceMethodInvocationAdapter underTest = new AopAllianceMethodInvocationAdapter(mock);
+
+        replay(mock);
+
+        assertSame(value, underTest.proceed());
+
+        verify(mock);
+    }
+
+    @Test
+    public void testGetThis() throws Exception {
+        MethodInvocation mock = createMock(MethodInvocation.class);
+        Object value = new Object();
+        expect(mock.getThis()).andReturn(value);
+        AopAllianceMethodInvocationAdapter underTest = new AopAllianceMethodInvocationAdapter(mock);
+
+        replay(mock);
+
+        assertSame(value, underTest.getThis());
+
+        verify(mock);
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/aop/ShiroAopModuleTest.java b/support/guice/src/test/java/org/apache/shiro/guice/aop/ShiroAopModuleTest.java
new file mode 100644
index 0000000..d474824
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/aop/ShiroAopModuleTest.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.aop;
+
+import com.google.inject.Binding;
+import com.google.inject.Key;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.InterceptorBinding;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.apache.shiro.aop.*;
+import org.apache.shiro.authz.annotation.*;
+import org.apache.shiro.authz.aop.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class ShiroAopModuleTest {
+    @Test
+    public void testGetAnnotationResolver() {
+
+        final AnnotationResolver annotationResolver = new DefaultAnnotationResolver();
+
+        ShiroAopModule underTest = new ShiroAopModule() {
+
+            @Override
+            protected AnnotationResolver createAnnotationResolver() {
+                return annotationResolver;
+            }
+
+            @Override
+            protected void configureDefaultInterceptors(AnnotationResolver resolver) {
+                assertSame(annotationResolver, resolver);
+                bind(Object.class).annotatedWith(Names.named("configureDefaultInterceptors"));
+            }
+
+            @Override
+            protected void configureInterceptors(AnnotationResolver resolver) {
+                assertSame(annotationResolver, resolver);
+                bind(Object.class).annotatedWith(Names.named("configureInterceptors"));
+            }
+        };
+
+        boolean calledDefault = false;
+        boolean calledCustom = false;
+
+        for (Element e : Elements.getElements(underTest)) {
+            if (e instanceof Binding) {
+                Key key = ((Binding) e).getKey();
+                if (Named.class.isAssignableFrom(key.getAnnotation().annotationType())
+                        && "configureInterceptors".equals(((Named) key.getAnnotation()).value())
+                        && key.getTypeLiteral().getRawType().equals(Object.class)) {
+                    calledCustom = true;
+                }
+                if (Named.class.isAssignableFrom(key.getAnnotation().annotationType())
+                        && "configureDefaultInterceptors".equals(((Named) key.getAnnotation()).value())
+                        && key.getTypeLiteral().getRawType().equals(Object.class)) {
+                    calledDefault = true;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testBindShiroInterceptor() {
+
+
+        ShiroAopModule underTest = new ShiroAopModule() {
+            @Override
+            protected void configureInterceptors(AnnotationResolver resolver) {
+                bindShiroInterceptor(new MyAnnotationMethodInterceptor());
+            }
+        };
+
+        List<Element> elements = Elements.getElements(underTest);
+
+        for (Element element : elements) {
+            if (element instanceof InterceptorBinding) {
+                InterceptorBinding binding = (InterceptorBinding) element;
+                assertTrue(binding.getClassMatcher().matches(getClass()));
+                Method method = null;
+                Class<? extends Annotation> theAnnotation = null;
+
+                for (Class<? extends Annotation> annotation : protectedMethods.keySet()) {
+                    if (binding.getMethodMatcher().matches(protectedMethods.get(annotation))) {
+                        method = protectedMethods.get(annotation);
+                        theAnnotation = annotation;
+                        protectedMethods.remove(annotation);
+                        break;
+                    }
+                }
+
+                if (method == null) {
+                    fail("Did not expect interceptor binding " + binding.getInterceptors());
+                }
+
+                List<MethodInterceptor> interceptors = binding.getInterceptors();
+                assertEquals(1, interceptors.size());
+                assertTrue(interceptors.get(0) instanceof AopAllianceMethodInterceptorAdapter);
+                assertTrue(interceptorTypes.get(theAnnotation).isInstance(((AopAllianceMethodInterceptorAdapter) interceptors.get(0)).shiroInterceptor));
+
+            }
+        }
+
+        assertTrue("Not all interceptors were bound.", protectedMethods.isEmpty());
+    }
+
+    @Target({ElementType.TYPE, ElementType.METHOD})
+    @Retention(RetentionPolicy.RUNTIME)
+    private static @interface MyTestAnnotation {
+    }
+
+    private static class MyAnnotationHandler extends AnnotationHandler {
+
+        /**
+         * Constructs an <code>AnnotationHandler</code> who processes annotations of the
+         * specified type.  Immediately calls {@link #setAnnotationClass(Class)}.
+         *
+         * @param annotationClass the type of annotation this handler will process.
+         */
+        public MyAnnotationHandler(Class<? extends Annotation> annotationClass) {
+            super(annotationClass);
+        }
+    }
+
+    private static class MyAnnotationMethodInterceptor extends AnnotationMethodInterceptor {
+        public MyAnnotationMethodInterceptor() {
+            super(new MyAnnotationHandler(MyTestAnnotation.class));
+        }
+
+        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+            return null;
+        }
+    }
+
+
+    @RequiresRoles("role")
+    public void roleProtected() {
+
+    }
+
+    @RequiresPermissions("permission")
+    public void permissionProtected() {
+
+    }
+
+    @RequiresAuthentication
+    public void authProtected() {
+
+    }
+
+    @RequiresUser
+    public void userProtected() {
+
+    }
+
+    @RequiresGuest
+    public void guestProtected() {
+
+    }
+
+    @ShiroAopModuleTest.MyTestAnnotation
+    public void myTestProtected() {
+
+    }
+
+    private Map<Class<? extends Annotation>, Method> protectedMethods;
+    private Map<Class<? extends Annotation>, Class<? extends AnnotationMethodInterceptor>> interceptorTypes;
+
+    @Before
+    public void setup() throws NoSuchMethodException {
+        protectedMethods = new HashMap<Class<? extends Annotation>, Method>();
+        protectedMethods.put(RequiresRoles.class, getClass().getMethod("roleProtected"));
+        protectedMethods.put(RequiresPermissions.class, getClass().getMethod("permissionProtected"));
+        protectedMethods.put(RequiresAuthentication.class, getClass().getMethod("authProtected"));
+        protectedMethods.put(RequiresUser.class, getClass().getMethod("userProtected"));
+        protectedMethods.put(RequiresGuest.class, getClass().getMethod("guestProtected"));
+        protectedMethods.put(MyTestAnnotation.class, getClass().getMethod("myTestProtected"));
+
+        interceptorTypes = new HashMap<Class<? extends Annotation>, Class<? extends AnnotationMethodInterceptor>>();
+        interceptorTypes.put(RequiresRoles.class, RoleAnnotationMethodInterceptor.class);
+        interceptorTypes.put(RequiresPermissions.class, PermissionAnnotationMethodInterceptor.class);
+        interceptorTypes.put(RequiresAuthentication.class, AuthenticatedAnnotationMethodInterceptor.class);
+        interceptorTypes.put(RequiresUser.class, UserAnnotationMethodInterceptor.class);
+        interceptorTypes.put(RequiresGuest.class, GuestAnnotationMethodInterceptor.class);
+        interceptorTypes.put(MyTestAnnotation.class, MyAnnotationMethodInterceptor.class);
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/AbstractInjectionProviderTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/AbstractInjectionProviderTest.java
new file mode 100644
index 0000000..af7f4a4
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/AbstractInjectionProviderTest.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import com.google.inject.spi.Dependency;
+import org.junit.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class AbstractInjectionProviderTest {
+
+    @Test
+    public void testGet() throws Exception {
+        Injector mockInjector = createMock(Injector.class);
+
+        Object c1 = new Object();
+        Object c2 = new Object();
+        final AtomicBoolean postProcessCalled = new AtomicBoolean(false);
+
+        expect(mockInjector.getInstance(keyC1)).andReturn(c1);
+        expect(mockInjector.getInstance(keyC2)).andReturn(c2);
+        mockInjector.injectMembers(anyObject(SomeInjectedClass.class));
+
+        replay(mockInjector);
+
+        AbstractInjectionProvider<SomeInjectedClass> underTest =
+                new AbstractInjectionProvider<SomeInjectedClass>(Key.get(SomeInjectedClass.class)) {
+                    @Override
+                    protected SomeInjectedClass postProcess(SomeInjectedClass someInjectedClass) {
+                        postProcessCalled.set(true);
+                        return super.postProcess(someInjectedClass);
+                    }
+                };
+
+        underTest.injector = mockInjector;
+
+        SomeInjectedClass got = underTest.get();
+
+        assertEquals("Wrong parameter passed to constructor (index 0).", c1, got.c1);
+        assertEquals("Wrong parameter passed to constructor (index 1).", c2, got.c2);
+
+        assertTrue("postProcess method was not called.", postProcessCalled.get());
+
+        verify(mockInjector);
+    }
+
+    @Test
+    public void testGetDependencies() throws Exception {
+        AbstractInjectionProvider<SomeInjectedClass> underTest =
+                new AbstractInjectionProvider<SomeInjectedClass>(Key.get(SomeInjectedClass.class));
+
+        boolean foundC1 = false;
+        boolean foundC2 = false;
+        boolean foundV1 = false;
+        boolean foundV2 = false;
+        boolean foundF1 = false;
+
+        for (Dependency<?> dependency : underTest.getDependencies()) {
+            if (dependency.getInjectionPoint().getMember() instanceof Constructor) {
+                if (dependency.getParameterIndex() == 0 && dependency.getKey().equals(keyC1)) {
+                    foundC1 = true;
+                } else if (dependency.getParameterIndex() == 1 && dependency.getKey().equals(keyC2)) {
+                    foundC2 = true;
+                } else {
+                    fail("Did not expect constructor dependency with key " + dependency.getKey() + " at parameter index " + dependency.getParameterIndex());
+                }
+            } else if (dependency.getInjectionPoint().getMember() instanceof Method) {
+                if (dependency.getKey().equals(keyV1)) {
+                    foundV1 = true;
+                } else if (dependency.getKey().equals(keyV2)) {
+                    foundV2 = true;
+                } else {
+                    fail("Did not expect method dependency with key " + dependency.getKey());
+                }
+            } else if (dependency.getInjectionPoint().getMember() instanceof Field) {
+                if (dependency.getKey().equals(keyF1)) {
+                    foundF1 = true;
+                } else {
+                    fail("Did not expect field dependency with key " + dependency.getKey());
+                }
+            } else {
+                fail("Did not expect dependency with key " + dependency.getKey());
+            }
+        }
+
+        assertTrue("Did not find dependency C1", foundC1);
+        assertTrue("Did not find dependency C2", foundC2);
+        assertTrue("Did not find dependency V1", foundV1);
+        assertTrue("Did not find dependency V2", foundV2);
+        assertTrue("Did not find dependency F1", foundF1);
+    }
+
+    static Key keyC1 = Key.get(Object.class, Names.named("constructor1"));
+    static Key keyC2 = Key.get(Object.class, Names.named("constructor2"));
+    static Key keyV1 = Key.get(Object.class, Names.named("val1"));
+    static Key keyV2 = Key.get(Object.class, Names.named("val2"));
+    static Key keyF1 = Key.get(Object.class, Names.named("field1"));
+
+
+    static class SomeInjectedClass {
+
+        @Inject
+        @Named("field1")
+        private Object field;
+        private Object c1;
+        private Object c2;
+
+        @Inject
+        public SomeInjectedClass(@Named("constructor1") Object c1, @Named("constructor2") Object c2) {
+
+            this.c1 = c1;
+            this.c2 = c2;
+        }
+
+        @Inject
+        public void setVal1(@Named("val1") Object v1) {
+
+        }
+
+        @Inject
+        public void setVal2(@Named("val2") Object v2) {
+
+        }
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/DefaultFiltersTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/DefaultFiltersTest.java
new file mode 100644
index 0000000..948bbd3
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/DefaultFiltersTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Key;
+import org.apache.shiro.web.filter.mgt.DefaultFilter;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.EnumSet;
+
+import static org.junit.Assert.fail;
+
+public class DefaultFiltersTest {
+    @Test
+    public void checkDefaultFilters() throws Exception {
+        EnumSet<DefaultFilter> defaultFilters = EnumSet.allOf(DefaultFilter.class);
+        for(Field field: ShiroWebModule.class.getFields()) {
+            if(Modifier.isStatic(field.getModifiers()) && Key.class.isAssignableFrom(field.getType())) {
+                Class<? extends Filter> filterType = ((Key)field.get(null)).getTypeLiteral().getRawType();
+                boolean found = false;
+                for(DefaultFilter filter: defaultFilters) {
+                    if(filterType.equals(filter.getFilterClass())) {
+                        found = true;
+                        defaultFilters.remove(filter);
+                        break;
+                    }
+                }
+                if(!found) {
+                    fail("Guice ShiroWebModule containts a default filter that Shiro proper does not. (" + filterType.getName() + ")");
+                }
+            }
+        }
+        if(!defaultFilters.isEmpty()) {
+            fail("Guice ShiroWebModule is missing one or more filters. " + defaultFilters);
+        }
+    }
+
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/FilterChainResolverProviderTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterChainResolverProviderTest.java
new file mode 100644
index 0000000..03b132a
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterChainResolverProviderTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import com.google.inject.spi.Dependency;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.*;
+
+/**
+ * This test relies on the internal structure of FilterChainResolver in order to check that it got created correctly.
+ */
+public class FilterChainResolverProviderTest {
+
+    private Map<String, Key<? extends Filter>[]> chains;
+    private Key<? extends Filter> key1a;
+    private Key<? extends Filter> key1b;
+    private Key<? extends Filter> key1c;
+    private Key<? extends Filter> key2a;
+    private FilterChainResolverProvider underTest;
+
+    @Before
+    public void setup() {
+        chains = new LinkedHashMap<String, Key<? extends Filter>[]>();
+
+        key1a = Key.get(Filter.class, Names.named("key1a"));
+        key1b = Key.get(Filter.class, Names.named("key1b"));
+        key1c = Key.get(Filter.class, Names.named("key1c"));
+        key2a = Key.get(Filter.class, Names.named("key2a"));
+
+        chains.put("one", new Key[]{key1a, key1b, key1c});
+        chains.put("two", new Key[]{key2a});
+
+        underTest = new FilterChainResolverProvider(chains);
+    }
+
+    @Test
+    public void testGetDependencies() throws Exception {
+
+        Set<Dependency<?>> dependencySet = underTest.getDependencies();
+        assertEquals(4, dependencySet.size());
+
+        assertTrue("Dependency set doesn't contain key1a.", dependencySet.contains(Dependency.get(key1a)));
+        assertTrue("Dependency set doesn't contain key1b.", dependencySet.contains(Dependency.get(key1b)));
+        assertTrue("Dependency set doesn't contain key1c.", dependencySet.contains(Dependency.get(key1c)));
+        assertTrue("Dependency set doesn't contain key2a.", dependencySet.contains(Dependency.get(key2a)));
+    }
+
+
+    @Test
+    public void testGet() throws Exception {
+
+        Injector injector = createMock(Injector.class);
+        PatternMatcher patternMatcher = createMock(PatternMatcher.class);
+
+        underTest.injector = injector;
+        underTest.setPatternMatcher(patternMatcher);
+
+        FilterChainResolver resolver = underTest.get();
+
+        Field chainsField = SimpleFilterChainResolver.class.getDeclaredField("chains");
+        chainsField.setAccessible(true);
+        Field injectorField = SimpleFilterChainResolver.class.getDeclaredField("injector");
+        injectorField.setAccessible(true);
+        Field patternMatcherField = SimpleFilterChainResolver.class.getDeclaredField("patternMatcher");
+        patternMatcherField.setAccessible(true);
+
+        assertSame(chains, chainsField.get(resolver));
+        assertSame(injector, injectorField.get(resolver));
+        assertSame(patternMatcher, patternMatcherField.get(resolver));
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
new file mode 100644
index 0000000..98add89
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import org.apache.shiro.guice.ShiroModuleTest;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.util.WebUtils;
+import org.junit.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertNotNull;
+
+public class FilterConfigTest {
+    private FilterChainResolver setupResolver() {
+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
+        ServletContext servletContext = createMock(ServletContext.class);
+
+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
+            @Override
+            protected void configureShiroWeb() {
+                bindRealm().to(ShiroModuleTest.MockRealm.class);
+
+                addFilterChain("/index.html", AUTHC_BASIC);
+                addFilterChain("/index2.html", config(PERMS, "permission"));
+            }
+
+            @Provides
+            public ShiroModuleTest.MockRealm createRealm() {
+                return mockRealm;
+            }
+        });
+        GuiceShiroFilter filter = injector.getInstance(GuiceShiroFilter.class);
+        return filter.getFilterChainResolver();
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        FilterChainResolver resolver = setupResolver();
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+        HttpServletRequest request = createMockRequest("/index.html");
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNotNull(resolved);
+        verify(request);
+    }
+
+    @Test
+    public void testWithConfig() throws Exception {
+        FilterChainResolver resolver = setupResolver();
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+        HttpServletRequest request = createMockRequest("/index2.html");
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNotNull(resolved);
+        verify(request);
+    }
+
+    private HttpServletRequest createMockRequest(String path) {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+        expect(request.getContextPath()).andReturn("");
+        expect(request.getRequestURI()).andReturn(path);
+        replay(request);
+        return request;
+    }
+
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java
new file mode 100644
index 0000000..dbc1017
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.spi.InjectionPoint;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+public class GuiceShiroFilterTest {
+
+    @Test
+    public void ensureInjectable() {
+        try {
+            InjectionPoint ip = InjectionPoint.forConstructorOf(GuiceShiroFilter.class);
+        } catch (Exception e) {
+            fail("Could not create constructor injection point.");
+        }
+    }
+
+    @Test
+    public void testConstructor() {
+        WebSecurityManager securityManager = createMock(WebSecurityManager.class);
+        FilterChainResolver filterChainResolver = createMock(FilterChainResolver.class);
+
+        GuiceShiroFilter underTest = new GuiceShiroFilter(securityManager, filterChainResolver);
+
+        assertSame(securityManager, underTest.getSecurityManager());
+        assertSame(filterChainResolver, underTest.getFilterChainResolver());
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatchingFilterProviderTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatchingFilterProviderTest.java
new file mode 100644
index 0000000..93e1522
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatchingFilterProviderTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Key;
+import org.apache.shiro.web.filter.PathMatchingFilter;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.*;
+
+public class PathMatchingFilterProviderTest {
+    @Test
+    public void testPostProcess() {
+        PathMatchingFilter filter = createMock(PathMatchingFilter.class);
+
+        expect(filter.processPathConfig("/1", "first")).andReturn(filter);
+        expect(filter.processPathConfig("/2", "second")).andReturn(filter);
+
+        replay(filter);
+
+        Map<String, String> pathConfigMap = new HashMap<String, String>();
+        pathConfigMap.put("/1", "first");
+        pathConfigMap.put("/2", "second");
+
+        PathMatchingFilterProvider underTest = new PathMatchingFilterProvider(Key.get(PathMatchingFilter.class), pathConfigMap);
+
+        underTest.postProcess(filter);
+
+        verify(filter);
+    }
+
+
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
new file mode 100644
index 0000000..908f322
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import org.apache.shiro.guice.ShiroModuleTest;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
+import org.junit.Test;
+
+import javax.inject.Named;
+import javax.servlet.ServletContext;
+import java.util.Collection;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ShiroWebModuleTest {
+
+
+    @Test
+    public void basicInstantiation() {
+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
+        ServletContext servletContext = createMock(ServletContext.class);
+
+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
+            @Override
+            protected void configureShiroWeb() {
+                bindRealm().to(ShiroModuleTest.MockRealm.class);
+                expose(SessionManager.class);
+            }
+
+            @Provides
+            public ShiroModuleTest.MockRealm createRealm() {
+                return mockRealm;
+            }
+
+        });
+        // we're not getting a WebSecurityManager here b/c it's not exposed.  There didn't seem to be a good reason to
+        // expose it outside of the Shiro module.
+        SecurityManager securityManager = injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+        assertTrue(securityManager instanceof WebSecurityManager);
+        SessionManager sessionManager = injector.getInstance(SessionManager.class);
+        assertNotNull(sessionManager);
+        assertTrue(sessionManager instanceof ServletContainerSessionManager);
+        assertTrue(((DefaultWebSecurityManager)securityManager).getSessionManager() instanceof ServletContainerSessionManager);
+    }
+
+    @Test
+    public void testBindGuiceFilter() throws Exception {
+
+    }
+
+    @Test
+    public void testBindWebSecurityManager() throws Exception {
+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
+        ServletContext servletContext = createMock(ServletContext.class);
+
+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
+            @Override
+            protected void configureShiroWeb() {
+                bindRealm().to(ShiroModuleTest.MockRealm.class);
+                expose(WebSecurityManager.class);
+            }
+
+            @Provides
+            public ShiroModuleTest.MockRealm createRealm() {
+                return mockRealm;
+            }
+
+            @Override
+            protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
+                bind.to(MyDefaultWebSecurityManager.class);
+            }
+        });
+        SecurityManager securityManager = injector.getInstance(SecurityManager.class);
+        assertNotNull(securityManager);
+        assertTrue(securityManager instanceof MyDefaultWebSecurityManager);
+        WebSecurityManager webSecurityManager = injector.getInstance(WebSecurityManager.class);
+        assertNotNull(webSecurityManager);
+        assertTrue(webSecurityManager instanceof MyDefaultWebSecurityManager);
+
+    }
+
+    @Test
+    public void testBindWebEnvironment() throws Exception {
+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
+        ServletContext servletContext = createMock(ServletContext.class);
+
+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
+            @Override
+            protected void configureShiroWeb() {
+                bindRealm().to(ShiroModuleTest.MockRealm.class);
+                expose(WebEnvironment.class);
+                expose(Environment.class);
+            }
+
+            @Provides
+            public ShiroModuleTest.MockRealm createRealm() {
+                return mockRealm;
+            }
+
+            @Override
+            protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
+                bind.to(MyWebEnvironment.class);
+            }
+        });
+        Environment environment = injector.getInstance(Environment.class);
+        assertNotNull(environment);
+        assertTrue(environment instanceof MyWebEnvironment);
+        WebEnvironment webEnvironment = injector.getInstance(WebEnvironment.class);
+        assertNotNull(webEnvironment);
+        assertTrue(webEnvironment instanceof MyWebEnvironment);
+    }
+
+    public static class MyDefaultWebSecurityManager extends DefaultWebSecurityManager {
+        @Inject
+        public MyDefaultWebSecurityManager(Collection<Realm> realms) {
+            super(realms);
+        }
+    }
+
+    public static class MyDefaultWebSessionManager extends DefaultWebSessionManager {
+    }
+
+    public static class MyWebEnvironment extends WebGuiceEnvironment {
+        @Inject
+        MyWebEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager) {
+            super(filterChainResolver, servletContext, securityManager);
+        }
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
new file mode 100644
index 0000000..8d70ebf
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.web.util.WebUtils;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertNull;
+
+
+/**
+ * Note that this test is highly dependent on the implementation of SimpleFilterChain.  There's really no way around that
+ * that I can see.  We determine that the resolver has created it correctly by observing it's behavior.
+ */
+public class SimpleFilterChainResolverTest {
+    @Test
+    public void testGetChain() throws Exception {
+        // test that it uses the pattern matcher - check
+        // test that the FIRST chain found is the one that gets returned - check
+        // test that the chain returned actually contains the filters returned by the injector - check
+        // test that the keys specified for the chain are requested from the injector - check
+        // test that filters are looked up lazily - check
+
+        IMocksControl ctrl = createStrictControl();
+
+        Injector injector = ctrl.createMock(Injector.class);
+        Map<String, Key<? extends Filter>[]> chainMap = new LinkedHashMap<String, Key<? extends Filter>[]>();
+
+        final String chainOne = "one";
+        final String chainTwo = "two";
+        final String chainThree = "three";
+
+        final Key<? extends Filter> key1a = Key.get(Filter.class, Names.named("key1a"));
+        final Key<? extends Filter> key1b = Key.get(Filter.class, Names.named("key1b"));
+        final Key<? extends Filter> key2a = Key.get(Filter.class, Names.named("key2a"));
+        final Key<? extends Filter> key2b = Key.get(Filter.class, Names.named("key2b"));
+        final Key<? extends Filter> key3a = Key.get(Filter.class, Names.named("key3a"));
+        final Key<? extends Filter> key3b = Key.get(Filter.class, Names.named("key3b"));
+
+        chainMap.put(chainOne, new Key[]{key1a, key1b});
+        chainMap.put(chainTwo, new Key[]{key2a, key2b});
+        chainMap.put(chainThree, new Key[]{key3a, key3b});
+
+        PatternMatcher patternMatcher = ctrl.createMock(PatternMatcher.class);
+        ServletRequest request = ctrl.createMock(HttpServletRequest.class);
+        ServletResponse response = ctrl.createMock(HttpServletResponse.class);
+        FilterChain originalChain = ctrl.createMock(FilterChain.class);
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn("/context");
+        expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn("/mychain");
+
+        expect(request.getCharacterEncoding()).andStubReturn(null);
+
+        expect(patternMatcher.matches(chainOne, "/mychain")).andReturn(false);
+        expect(patternMatcher.matches(chainTwo, "/mychain")).andReturn(true);
+
+        Filter filter2a = ctrl.createMock(Filter.class);
+        Filter filter2b = ctrl.createMock(Filter.class);
+
+        expect(injector.getInstance(key2a)).andReturn(filter2a);
+        filter2a.doFilter(same(request), same(response), anyObject(FilterChain.class));
+        expect(injector.getInstance(key2b)).andReturn(filter2b);
+        filter2b.doFilter(same(request), same(response), anyObject(FilterChain.class));
+        originalChain.doFilter(request, response);
+
+        ctrl.replay();
+
+        SimpleFilterChainResolver underTest = new SimpleFilterChainResolver(chainMap, injector, patternMatcher);
+
+        FilterChain got = underTest.getChain(request, response, originalChain);
+
+        got.doFilter(request, response);
+        got.doFilter(request, response);
+        got.doFilter(request, response);
+
+        ctrl.verify();
+
+        ctrl.reset();
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn("/context");
+        expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn("/nochain");
+
+        expect(request.getCharacterEncoding()).andStubReturn(null);
+
+        expect(patternMatcher.matches(chainOne, "/nochain")).andReturn(false);
+        expect(patternMatcher.matches(chainTwo, "/nochain")).andReturn(false);
+        expect(patternMatcher.matches(chainThree, "/nochain")).andReturn(false);
+
+        ctrl.replay();
+
+        assertNull("Expected no chain to match, did not get a null value in return.", underTest.getChain(request, response, originalChain));
+
+        ctrl.verify();
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainTest.java
new file mode 100644
index 0000000..5938550
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.collect.Iterators;
+import org.easymock.Capture;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import static org.easymock.EasyMock.*;
+
+public class SimpleFilterChainTest {
+    @Test
+    public void testDoFilter() throws Exception {
+        IMocksControl ctrl = createStrictControl();
+
+        FilterChain originalChain = ctrl.createMock(FilterChain.class);
+        Filter filter1 = ctrl.createMock("filter1", Filter.class);
+        Filter filter2 = ctrl.createMock("filter2", Filter.class);
+
+        ServletRequest request = ctrl.createMock(ServletRequest.class);
+        ServletResponse response = ctrl.createMock(ServletResponse.class);
+
+        Capture<FilterChain> fc1 = new Capture<FilterChain>();
+        Capture<FilterChain> fc2 = new Capture<FilterChain>();
+        filter1.doFilter(same(request), same(response), and(anyObject(FilterChain.class), capture(fc1)));
+        filter2.doFilter(same(request), same(response), and(anyObject(FilterChain.class), capture(fc2)));
+        originalChain.doFilter(request, response);
+
+        ctrl.replay();
+
+        SimpleFilterChain underTest = new SimpleFilterChain(originalChain, Iterators.forArray(filter1, filter2));
+
+        // all we actually care about is that, if we keep calling the filter chain, everything is called in the right
+        // order - we don't care what fc actually contains
+        underTest.doFilter(request, response);
+        fc1.getValue().doFilter(request, response);
+        fc2.getValue().doFilter(request, response);
+
+        ctrl.verify();
+    }
+}
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java
new file mode 100644
index 0000000..22913ad
--- /dev/null
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.spi.InjectionPoint;
+import org.apache.shiro.web.env.EnvironmentLoaderListener;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.easymock.Capture;
+import org.junit.Test;
+
+import javax.servlet.ServletContext;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+public class WebGuiceEnvironmentTest {
+
+    @Test
+    public void ensureInjectable() {
+        try {
+            InjectionPoint ip = InjectionPoint.forConstructorOf(WebGuiceEnvironment.class);
+        } catch (Exception e) {
+            fail("Could not create constructor injection point.");
+        }
+    }
+
+    @Test
+    public void testConstructor() {
+        WebSecurityManager securityManager = createMock(WebSecurityManager.class);
+        FilterChainResolver filterChainResolver = createMock(FilterChainResolver.class);
+        ServletContext servletContext = createMock(ServletContext.class);
+
+        Capture<WebGuiceEnvironment> capture = new Capture<WebGuiceEnvironment>();
+        servletContext.setAttribute(eq(EnvironmentLoaderListener.ENVIRONMENT_ATTRIBUTE_KEY), and(anyObject(WebGuiceEnvironment.class), capture(capture)));
+
+        replay(servletContext, securityManager, filterChainResolver);
+
+        WebGuiceEnvironment underTest = new WebGuiceEnvironment(filterChainResolver, servletContext, securityManager);
+
+        assertSame(securityManager, underTest.getSecurityManager());
+        assertSame(filterChainResolver, underTest.getFilterChainResolver());
+        assertSame(securityManager, underTest.getWebSecurityManager());
+        assertSame(servletContext, underTest.getServletContext());
+
+        assertSame(underTest, capture.getValue());
+
+        verify(servletContext);
+    }
+}
diff --git a/support/pom.xml b/support/pom.xml
new file mode 100644
index 0000000..e5a441d
--- /dev/null
+++ b/support/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-support</artifactId>
+    <name>Apache Shiro :: Support</name>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>aspectj</module>
+        <module>ehcache</module>
+        <module>quartz</module>
+        <module>spring</module>
+        <module>guice</module>
+        <module>features</module>
+        <module>cas</module>
+    </modules>
+
+</project>
+
diff --git a/support/quartz/pom.xml b/support/quartz/pom.xml
new file mode 100644
index 0000000..0dd0cbd
--- /dev/null
+++ b/support/quartz/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-quartz</artifactId>
+    <name>Apache Shiro :: Support :: Quartz</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opensymphony.quartz</groupId>
+            <artifactId>quartz</artifactId>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.quartz</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.quartz*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.quartz*;version="[1.6, 2)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationJob.java b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationJob.java
new file mode 100644
index 0000000..9f482ad
--- /dev/null
+++ b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationJob.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.quartz;
+
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.session.mgt.ValidatingSessionManager;
+
+/**
+ * A quartz job that basically just calls the {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions()}
+ * method on a configured session manager.  The session manager will automatically be injected by the
+ * superclass if it is in the job data map or the scheduler map.
+ *
+ * @since 0.1
+ */
+public class QuartzSessionValidationJob implements Job {
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * Key used to store the session manager in the job data map for this job.
+     */
+    static final String SESSION_MANAGER_KEY = "sessionManager";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(QuartzSessionValidationJob.class);
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Called when the job is executed by quartz.  This method delegates to the
+     * <tt>validateSessions()</tt> method on the associated session manager.
+     *
+     * @param context the Quartz job execution context for this execution.
+     */
+    public void execute(JobExecutionContext context) throws JobExecutionException {
+
+        JobDataMap jobDataMap = context.getMergedJobDataMap();
+        ValidatingSessionManager sessionManager = (ValidatingSessionManager) jobDataMap.get(SESSION_MANAGER_KEY);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Executing session validation Quartz job...");
+        }
+
+        sessionManager.validateSessions();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Session validation Quartz job complete.");
+        }
+    }
+}
diff --git a/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationScheduler.java b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationScheduler.java
new file mode 100644
index 0000000..4ef3102
--- /dev/null
+++ b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/QuartzSessionValidationScheduler.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.session.mgt.quartz;
+
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SimpleTrigger;
+import org.quartz.impl.StdSchedulerFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.session.mgt.SessionValidationScheduler;
+import org.apache.shiro.session.mgt.ValidatingSessionManager;
+
+
+/**
+ * An implementation of the {@link org.apache.shiro.session.mgt.SessionValidationScheduler SessionValidationScheduler} that uses Quartz to schedule a
+ * job to call {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions()} on
+ * a regular basis.
+ *
+ * @since 0.1
+ */
+public class QuartzSessionValidationScheduler implements SessionValidationScheduler {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * The default interval at which sessions will be validated (1 hour);
+     * This can be overridden by calling {@link #setSessionValidationInterval(long)}
+     */
+    public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
+
+    /**
+     * The name assigned to the quartz job.
+     */
+    private static final String JOB_NAME = "SessionValidationJob";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(QuartzSessionValidationScheduler.class);
+
+    /**
+     * The configured Quartz scheduler to use to schedule the Quartz job.  If no scheduler is
+     * configured, the schedular will be retrieved by calling {@link StdSchedulerFactory#getDefaultScheduler()}
+     */
+    private Scheduler scheduler;
+
+    private boolean schedulerImplicitlyCreated = false;
+
+    private boolean enabled = false;
+
+    /**
+     * The session manager used to validate sessions.
+     */
+    private ValidatingSessionManager sessionManager;
+
+    /**
+     * The session validation interval in milliseconds.
+     */
+    private long sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /**
+     * Default constructor.
+     */
+    public QuartzSessionValidationScheduler() {
+    }
+
+    /**
+     * Constructor that specifies the session manager that should be used for validating sessions.
+     *
+     * @param sessionManager the <tt>SessionManager</tt> that should be used to validate sessions.
+     */
+    public QuartzSessionValidationScheduler(ValidatingSessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    protected Scheduler getScheduler() throws SchedulerException {
+        if (scheduler == null) {
+            scheduler = StdSchedulerFactory.getDefaultScheduler();
+            schedulerImplicitlyCreated = true;
+        }
+        return scheduler;
+    }
+
+    public void setScheduler(Scheduler scheduler) {
+        this.scheduler = scheduler;
+    }
+
+    public void setSessionManager(ValidatingSessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+    }
+
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    /**
+     * Specifies how frequently (in milliseconds) this Scheduler will call the
+     * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions() ValidatingSessionManager#validateSessions()} method.
+     *
+     * <p>Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
+     *
+     * @param sessionValidationInterval
+     */
+    public void setSessionValidationInterval(long sessionValidationInterval) {
+        this.sessionValidationInterval = sessionValidationInterval;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    /**
+     * Starts session validation by creating a Quartz simple trigger, linking it to
+     * the {@link QuartzSessionValidationJob}, and scheduling it with the Quartz scheduler.
+     */
+    public void enableSessionValidation() {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Scheduling session validation job using Quartz with " +
+                    "session validation interval of [" + sessionValidationInterval + "]ms...");
+        }
+
+        try {
+            SimpleTrigger trigger = new SimpleTrigger(getClass().getName(),
+                    Scheduler.DEFAULT_GROUP,
+                    SimpleTrigger.REPEAT_INDEFINITELY,
+                    sessionValidationInterval);
+
+            JobDetail detail = new JobDetail(JOB_NAME, Scheduler.DEFAULT_GROUP, QuartzSessionValidationJob.class);
+            detail.getJobDataMap().put(QuartzSessionValidationJob.SESSION_MANAGER_KEY, sessionManager);
+
+            Scheduler scheduler = getScheduler();
+
+            scheduler.scheduleJob(detail, trigger);
+            if (schedulerImplicitlyCreated) {
+                scheduler.start();
+                if (log.isDebugEnabled()) {
+                    log.debug("Successfully started implicitly created Quartz Scheduler instance.");
+                }
+            }
+            this.enabled = true;
+
+            if (log.isDebugEnabled()) {
+                log.debug("Session validation job successfully scheduled with Quartz.");
+            }
+
+        } catch (SchedulerException e) {
+            if (log.isErrorEnabled()) {
+                log.error("Error starting the Quartz session validation job.  Session validation may not occur.", e);
+            }
+        }
+    }
+
+    public void disableSessionValidation() {
+        if (log.isDebugEnabled()) {
+            log.debug("Stopping Quartz session validation job...");
+        }
+
+        Scheduler scheduler;
+        try {
+            scheduler = getScheduler();
+            if (scheduler == null) {
+                if (log.isWarnEnabled()) {
+                    log.warn("getScheduler() method returned a null Quartz scheduler, which is unexpected.  Please " +
+                            "check your configuration and/or implementation.  Returning quietly since there is no " +
+                            "validation job to remove (scheduler does not exist).");
+                }
+                return;
+            }
+        } catch (SchedulerException e) {
+            if (log.isWarnEnabled()) {
+                log.warn("Unable to acquire Quartz Scheduler.  Ignoring and returning (already stopped?)", e);
+            }
+            return;
+        }
+
+        try {
+            scheduler.unscheduleJob(JOB_NAME, Scheduler.DEFAULT_GROUP);
+            if (log.isDebugEnabled()) {
+                log.debug("Quartz session validation job stopped successfully.");
+            }
+        } catch (SchedulerException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Could not cleanly remove SessionValidationJob from Quartz scheduler.  " +
+                        "Ignoring and stopping.", e);
+            }
+        }
+
+        this.enabled = false;
+
+        if (schedulerImplicitlyCreated) {
+            try {
+                scheduler.shutdown();
+            } catch (SchedulerException e) {
+                if (log.isWarnEnabled()) {
+                    log.warn("Unable to cleanly shutdown implicitly created Quartz Scheduler instance.", e);
+                }
+            } finally {
+                setScheduler(null);
+                schedulerImplicitlyCreated = false;
+            }
+        }
+
+
+    }
+}
\ No newline at end of file
diff --git a/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/package-info.java b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/package-info.java
new file mode 100644
index 0000000..435ff42
--- /dev/null
+++ b/support/quartz/src/main/java/org/apache/shiro/session/mgt/quartz/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * <a href="http://www.opensymphony.com/quartz/" target="_top">Quartz</a>-based implementations of
+ * components that help <tt>SessionManager</tt> implementations maintain sessions (timed expiration, orphan cleanup,
+ * etc).
+ */
+package org.apache.shiro.session.mgt.quartz;
diff --git a/support/spring/pom.xml b/support/spring/pom.xml
new file mode 100644
index 0000000..7b253c8
--- /dev/null
+++ b/support/spring/pom.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-spring</artifactId>
+    <name>Apache Shiro :: Support :: Spring</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-aspectj</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.spring</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.spring*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            org.aopalliance*;version="[1.0.0, 2.0.0)",
+                            org.springframework*;version="[2.5.0, 4.0.0)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java b/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java
new file mode 100644
index 0000000..a526769
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
+import org.springframework.core.PriorityOrdered;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+
+
+/**
+ * <p>Bean post processor for Spring that automatically calls the <tt>init()</tt> and/or
+ * <tt>destroy()</tt> methods on Shiro objects that implement the {@link org.apache.shiro.util.Initializable}
+ * or {@link org.apache.shiro.util.Destroyable} interfaces, respectfully.  This post processor makes it easier
+ * to configure Shiro beans in Spring, since the user never has to worry about whether or not if they
+ * have to specify init-method and destroy-method bean attributes.</p>
+ *
+ * <p><b>Warning: This post processor has no way to determine if <tt>init()</tt> or <tt>destroy()</tt> have
+ * already been called, so if you define this post processor in your applicationContext, do not also call these
+ * methods manually or via Spring's <tt>init-method</tt> or <tt>destroy-method</tt> bean attributes.</b></p>
+ *
+ * @since 0.2
+ */
+public class LifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor, PriorityOrdered {
+
+    /**
+     * Private internal class log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(LifecycleBeanPostProcessor.class);
+
+    /**
+     * Order value of this BeanPostProcessor.
+     */
+    private int order;
+
+    /**
+     * Default Constructor.
+     */
+    public LifecycleBeanPostProcessor() {
+        this(LOWEST_PRECEDENCE);
+    }
+
+    /**
+     * Constructor with definable {@link #getOrder() order value}.
+     *
+     * @param order order value of this BeanPostProcessor.
+     */
+    public LifecycleBeanPostProcessor(int order) {
+        this.order = order;
+    }
+
+    /**
+     * Calls the <tt>init()</tt> methods on the bean if it implements {@link org.apache.shiro.util.Initializable}
+     *
+     * @param object the object being initialized.
+     * @param name   the name of the bean being initialized.
+     * @return the initialized bean.
+     * @throws BeansException if any exception is thrown during initialization.
+     */
+    public Object postProcessBeforeInitialization(Object object, String name) throws BeansException {
+        if (object instanceof Initializable) {
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Initializing bean [" + name + "]...");
+                }
+
+                ((Initializable) object).init();
+            } catch (Exception e) {
+                throw new FatalBeanException("Error initializing bean [" + name + "]", e);
+            }
+        }
+        return object;
+    }
+
+
+    /**
+     * Does nothing - merely returns the object argument immediately.
+     */
+    public Object postProcessAfterInitialization(Object object, String name) throws BeansException {
+        // Does nothing after initialization
+        return object;
+    }
+
+
+    /**
+     * Calls the <tt>destroy()</tt> methods on the bean if it implements {@link org.apache.shiro.util.Destroyable}
+     *
+     * @param object the object being initialized.
+     * @param name   the name of the bean being initialized.
+     * @throws BeansException if any exception is thrown during initialization.
+     */
+    public void postProcessBeforeDestruction(Object object, String name) throws BeansException {
+        if (object instanceof Destroyable) {
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Destroying bean [" + name + "]...");
+                }
+
+                ((Destroyable) object).destroy();
+            } catch (Exception e) {
+                throw new FatalBeanException("Error destroying bean [" + name + "]", e);
+            }
+        }
+    }
+
+    /**
+     * Order value of this BeanPostProcessor.
+     *
+     * @return order value.
+     */
+    public int getOrder() {
+        // LifecycleBeanPostProcessor needs Order. See https://issues.apache.org/jira/browse/SHIRO-222
+        return order;
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/aop/SpringAnnotationResolver.java b/support/spring/src/main/java/org/apache/shiro/spring/aop/SpringAnnotationResolver.java
new file mode 100644
index 0000000..c148c46
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/aop/SpringAnnotationResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.aop;
+
+import org.apache.shiro.aop.AnnotationResolver;
+import org.apache.shiro.aop.MethodInvocation;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ClassUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * {@code AnnotationResolver} implementation that uses Spring's more robust
+ * {@link AnnotationUtils AnnotationUtils} to find method annotations instead of the JDKs simpler
+ * (and rather lacking) {@link Method}.{@link Method#getAnnotation(Class) getAnnotation(class)}
+ * implementation.
+ *
+ * @since 1.1
+ */
+public class SpringAnnotationResolver implements AnnotationResolver {
+
+    public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
+        Method m = mi.getMethod();
+
+        Annotation a = AnnotationUtils.findAnnotation(m, clazz);
+        if (a != null) return a;
+
+        //The MethodInvocation's method object could be a method defined in an interface.
+        //However, if the annotation existed in the interface's implementation (and not
+        //the interface itself), it won't be on the above method object.  Instead, we need to
+        //acquire the method representation from the targetClass and check directly on the
+        //implementation itself:
+        Class<?> targetClass = mi.getThis().getClass();
+        m = ClassUtils.getMostSpecificMethod(m, targetClass);
+        a = AnnotationUtils.findAnnotation(m, clazz);
+        if (a != null) return a;
+        // See if the class has the same annotation
+        return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/package-info.java b/support/spring/src/main/java/org/apache/shiro/spring/package-info.java
new file mode 100644
index 0000000..03b1968
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * <a href="http://www.springframework.org" target="_top">Spring Application Framework</a> support for enabling
+ * Shiro in spring applications.
+ */
+package org.apache.shiro.spring;
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java b/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
new file mode 100644
index 0000000..92d2321
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationExecutor.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.remoting;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.ExecutionException;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
+import org.springframework.remoting.support.RemoteInvocation;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.Callable;
+
+
+/**
+ * An implementation of the Spring {@link org.springframework.remoting.support.RemoteInvocationExecutor}
+ * that binds a {@code sessionId} to the incoming thread to make it available to the {@code SecurityManager}
+ * implementation during the thread execution.  The {@code SecurityManager} implementation can use this sessionId
+ * to reconstitute the {@code Subject} instance based on persistent state in the corresponding {@code Session}.
+ *
+ * @since 0.1
+ */
+public class SecureRemoteInvocationExecutor extends DefaultRemoteInvocationExecutor {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(SecureRemoteInvocationExecutor.class);
+
+    /**
+     * The SecurityManager used to retrieve realms that should be associated with the
+     * created <tt>Subject</tt>s upon remote invocation.
+     */
+    private SecurityManager securityManager;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
+        this.securityManager = securityManager;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+    @SuppressWarnings({"unchecked"})
+    public Object invoke(final RemoteInvocation invocation, final Object targetObject)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+
+        try {
+            SecurityManager securityManager =
+                    this.securityManager != null ? this.securityManager : SecurityUtils.getSecurityManager();
+
+            Subject.Builder builder = new Subject.Builder(securityManager);
+
+            String host = (String) invocation.getAttribute(SecureRemoteInvocationFactory.HOST_KEY);
+            if (host != null) {
+                builder.host(host);
+            }
+
+            Serializable sessionId = invocation.getAttribute(SecureRemoteInvocationFactory.SESSION_ID_KEY);
+            if (sessionId != null) {
+                builder.sessionId(sessionId);
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace("RemoteInvocation did not contain a Shiro Session id attribute under " +
+                            "key [" + SecureRemoteInvocationFactory.SESSION_ID_KEY + "].  A Subject based " +
+                            "on an existing Session will not be available during the method invocatin.");
+                }
+            }
+
+            Subject subject = builder.buildSubject();
+            return subject.execute(new Callable() {
+                public Object call() throws Exception {
+                    return SecureRemoteInvocationExecutor.super.invoke(invocation, targetObject);
+                }
+            });
+        } catch (ExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof NoSuchMethodException) {
+                throw (NoSuchMethodException) cause;
+            } else if (cause instanceof IllegalAccessException) {
+                throw (IllegalAccessException) cause;
+            } else if (cause instanceof InvocationTargetException) {
+                throw (InvocationTargetException) cause;
+            } else {
+                throw new InvocationTargetException(cause);
+            }
+        } catch (Throwable t) {
+            throw new InvocationTargetException(t);
+        }
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java b/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java
new file mode 100644
index 0000000..22c1cbf
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactory.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.remoting;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.NativeSessionManager;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
+import org.springframework.remoting.support.RemoteInvocation;
+import org.springframework.remoting.support.RemoteInvocationFactory;
+
+import java.io.Serializable;
+
+/**
+ * A {@link RemoteInvocationFactory} that passes the session ID to the server via a
+ * {@link RemoteInvocation} {@link RemoteInvocation#getAttribute(String) attribute}.
+ * This factory is the client-side part of
+ * the Shiro Spring remoting invocation.  A {@link SecureRemoteInvocationExecutor} should
+ * be used to export the server-side remote services to ensure that the appropriate
+ * Subject and Session are bound to the remote thread during execution.
+ *
+ * @since 0.1
+ */
+public class SecureRemoteInvocationFactory extends DefaultRemoteInvocationFactory {
+
+    private static final Logger log = LoggerFactory.getLogger(SecureRemoteInvocationFactory.class);
+
+    public static final String SESSION_ID_KEY = SecureRemoteInvocationFactory.class.getName() + ".SESSION_ID_KEY";
+    public static final String HOST_KEY = SecureRemoteInvocationFactory.class.getName() + ".HOST_KEY";
+
+    private static final String SESSION_ID_SYSTEM_PROPERTY_NAME = "shiro.session.id";
+
+    private String sessionId;
+
+    public SecureRemoteInvocationFactory() {
+    }
+
+    public SecureRemoteInvocationFactory(String sessionId) {
+        this();
+        this.sessionId = sessionId;
+    }
+
+    /**
+     * Creates a {@link RemoteInvocation} with the current session ID as an
+     * {@link RemoteInvocation#getAttribute(String) attribute}.
+     *
+     * @param mi the method invocation that the remote invocation should be based on.
+     * @return a remote invocation object containing the current session ID as an attribute.
+     */
+    public RemoteInvocation createRemoteInvocation(MethodInvocation mi) {
+
+        Serializable sessionId = null;
+        String host = null;
+        boolean sessionManagerMethodInvocation = false;
+
+        //If the calling MI is for a remoting SessionManager delegate, we need to acquire the session ID from the method
+        //argument and NOT interact with SecurityUtils/subject.getSession to avoid a stack overflow
+        Class miDeclaringClass = mi.getMethod().getDeclaringClass();
+        if (SessionManager.class.equals(miDeclaringClass) || NativeSessionManager.class.equals(miDeclaringClass)) {
+            sessionManagerMethodInvocation = true;
+            //for SessionManager calls, all method calls except the 'start' methods require a SessionKey
+            // as the first argument, so just get it from there:
+            if (!mi.getMethod().getName().equals("start")) {
+                SessionKey key = (SessionKey) mi.getArguments()[0];
+                sessionId = key.getSessionId();
+            }
+        }
+
+        //tried the delegate. Use the injected session id if given
+        if (sessionId == null) sessionId = this.sessionId;
+
+        // If sessionId is null, only then try the Subject:
+        if (sessionId == null) {
+            try {
+                // HACK Check if can get the securityManager - this'll cause an exception if it's not set 
+                SecurityUtils.getSecurityManager();
+                if (!sessionManagerMethodInvocation) {
+                    Subject subject = SecurityUtils.getSubject();
+                    Session session = subject.getSession(false);
+                    if (session != null) {
+                        sessionId = session.getId();
+                        host = session.getHost();
+                    }
+                }
+            }
+            catch (Exception e) {
+                log.trace("No security manager set. Trying next to get session id from system property");
+            }
+        }
+        //No call to the sessionManager, and the Subject doesn't have a session.  Try a system property
+        //as a last result:
+        if (sessionId == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("No Session found for the currently executing subject via subject.getSession(false).  " +
+                        "Attempting to revert back to the 'shiro.session.id' system property...");
+            }
+            sessionId = System.getProperty(SESSION_ID_SYSTEM_PROPERTY_NAME);
+            if (sessionId == null && log.isTraceEnabled()) {
+                log.trace("No 'shiro.session.id' system property found.  Heuristics have been exhausted; " +
+                        "RemoteInvocation will not contain a sessionId.");
+            }
+        }
+
+        RemoteInvocation ri = new RemoteInvocation(mi);
+        if (sessionId != null) {
+            ri.addAttribute(SESSION_ID_KEY, sessionId);
+        }
+        if (host != null) {
+            ri.addAttribute(HOST_KEY, host);
+        }
+
+        return ri;
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/remoting/package-info.java b/support/spring/src/main/java/org/apache/shiro/spring/remoting/package-info.java
new file mode 100644
index 0000000..c6762a2
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/remoting/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Support to enable Spring-based remote method invocations to carry a Shiro session ID as part of the
+ * invocation payload, allowing remote clients to perform security operations.
+ */
+package org.apache.shiro.spring.remoting;
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AopAllianceAnnotationsAuthorizingMethodInterceptor.java b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AopAllianceAnnotationsAuthorizingMethodInterceptor.java
new file mode 100644
index 0000000..bdfca87
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AopAllianceAnnotationsAuthorizingMethodInterceptor.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.shiro.aop.AnnotationResolver;
+import org.apache.shiro.authz.aop.*;
+import org.apache.shiro.spring.aop.SpringAnnotationResolver;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows Shiro Annotations to work in any <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a>
+ * specific implementation environment (for example, Spring).
+ *
+ * @since 0.2
+ */
+public class AopAllianceAnnotationsAuthorizingMethodInterceptor
+        extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {
+
+    public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
+        List<AuthorizingAnnotationMethodInterceptor> interceptors =
+                new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
+
+        //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the
+        //raw JDK resolution process.
+        AnnotationResolver resolver = new SpringAnnotationResolver();
+        //we can re-use the same resolver instance - it does not retain state:
+        interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
+        interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
+        interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
+        interceptors.add(new UserAnnotationMethodInterceptor(resolver));
+        interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
+
+        setMethodInterceptors(interceptors);
+    }
+    /**
+     * Creates a {@link MethodInvocation MethodInvocation} that wraps an
+     * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance,
+     * enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments
+     * (Spring, etc).
+     *
+     * @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation}
+     * @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance.
+     */
+    protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
+        final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;
+
+        return new org.apache.shiro.aop.MethodInvocation() {
+            public Method getMethod() {
+                return mi.getMethod();
+            }
+
+            public Object[] getArguments() {
+                return mi.getArguments();
+            }
+
+            public String toString() {
+                return "Method invocation [" + mi.getMethod() + "]";
+            }
+
+            public Object proceed() throws Throwable {
+                return mi.proceed();
+            }
+
+            public Object getThis() {
+                return mi.getThis();
+            }
+        };
+    }
+
+    /**
+     * Simply casts the method argument to an
+     * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then
+     * calls <code>methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}()</code>
+     *
+     * @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation}
+     * @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result.
+     * @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>.
+     */
+    protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable {
+        MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
+        return mi.proceed();
+    }
+
+    /**
+     * Creates a Shiro {@link MethodInvocation MethodInvocation} instance and then immediately calls
+     * {@link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke}.
+     *
+     * @param methodInvocation the AOP Alliance-specific <code>methodInvocation</code> instance.
+     * @return the return value from invoking the method invocation.
+     * @throws Throwable if the underlying AOP Alliance method invocation throws a <code>Throwable</code>.
+     */
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+        org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
+        return super.invoke(mi);
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AuthorizationAttributeSourceAdvisor.java b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AuthorizationAttributeSourceAdvisor.java
new file mode 100644
index 0000000..b9deb2b
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/AuthorizationAttributeSourceAdvisor.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.apache.shiro.authz.annotation.*;
+import org.apache.shiro.mgt.SecurityManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+
+/**
+ * TODO - complete JavaDoc
+ *
+ * @since 0.1
+ */
+ at SuppressWarnings({"unchecked"})
+public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
+
+    private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
+
+    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
+            new Class[] {
+                    RequiresPermissions.class, RequiresRoles.class,
+                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
+            };
+
+    protected SecurityManager securityManager = null;
+
+    /**
+     * Create a new AuthorizationAttributeSourceAdvisor.
+     */
+    public AuthorizationAttributeSourceAdvisor() {
+        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
+    }
+
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+
+    public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
+        this.securityManager = securityManager;
+    }
+
+    /**
+     * Returns <tt>true</tt> if the method has any Shiro annotations, false otherwise.
+     * The annotations inspected are:
+     * <ul>
+     * <li>{@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}</li>
+     * <li>{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}</li>
+     * <li>{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}</li>
+     * <li>{@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}</li>
+     * <li>{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}</li>
+     * </ul>
+     *
+     * @param method      the method to check for a Shiro annotation
+     * @param targetClass the class potentially declaring Shiro annotations
+     * @return <tt>true</tt> if the method has a Shiro annotation, false otherwise.
+     * @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, Class)
+     */
+    public boolean matches(Method method, Class targetClass) {
+        Method m = method;
+
+        if ( isAuthzAnnotationPresent(m) ) {
+            return true;
+        }
+
+        //The 'method' parameter could be from an interface that doesn't have the annotation.
+        //Check to see if the implementation has it.
+        if ( targetClass != null) {
+            try {
+                m = targetClass.getMethod(m.getName(), m.getParameterTypes());
+                if ( isAuthzAnnotationPresent(m) ) {
+                    return true;
+                }
+            } catch (NoSuchMethodException ignored) {
+                //default return value is false.  If we can't find the method, then obviously
+                //there is no annotation, so just use the default return value.
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isAuthzAnnotationPresent(Method method) {
+        for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
+            Annotation a = AnnotationUtils.findAnnotation(method, annClass);
+            if ( a != null ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/package-info.java b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/package-info.java
new file mode 100644
index 0000000..1925608
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/security/interceptor/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Spring AOP support for enabling Shiro annotations in Spring-configured applications.
+ */
+package org.apache.shiro.spring.security.interceptor;
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
new file mode 100644
index 0000000..5f006a1
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
@@ -0,0 +1,541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.web;
+
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Nameable;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import org.apache.shiro.web.filter.authc.AuthenticationFilter;
+import org.apache.shiro.web.filter.authz.AuthorizationFilter;
+import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
+import org.apache.shiro.web.filter.mgt.FilterChainManager;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.servlet.AbstractShiroFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+
+import javax.servlet.Filter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * {@link org.springframework.beans.factory.FactoryBean FactoryBean} to be used in Spring-based web applications for
+ * defining the master Shiro Filter.
+ * <h4>Usage</h4>
+ * Declare a DelegatingFilterProxy in {@code web.xml}, matching the filter name to the bean id:
+ * <pre>
+ * <filter>
+ *   <filter-name><b>shiroFilter</b></filter-name>
+ *   <filter-class>org.springframework.web.filter.DelegatingFilterProxy<filter-class>
+ *   <init-param>
+ *    <param-name>targetFilterLifecycle</param-name>
+ *     <param-value>true</param-value>
+ *   </init-param>
+ * </filter>
+ * </pre>
+ * Then, in your spring XML file that defines your web ApplicationContext:
+ * <pre>
+ * <bean id="<b>shiroFilter</b>" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
+ *    <property name="securityManager" ref="securityManager"/>
+ *    <!-- other properties as necessary ... -->
+ * </bean>
+ * </pre>
+ * <h4>Filter Auto-Discovery</h4>
+ * While there is a {@link #setFilters(java.util.Map) filters} property that allows you to assign a filter beans
+ * to the 'pool' of filters available when defining {@link #setFilterChainDefinitions(String) filter chains}, it is
+ * optional.
+ * <p/>
+ * This implementation is also a {@link BeanPostProcessor} and will acquire
+ * any {@link javax.servlet.Filter Filter} beans defined independently in your Spring application context.  Upon
+ * discovery, they will be automatically added to the {@link #setFilters(java.util.Map) map} keyed by the bean ID.
+ * That ID can then be used in the filter chain definitions, for example:
+ *
+ * <pre>
+ * <bean id="<b>myCustomFilter</b>" class="com.class.that.implements.javax.servlet.Filter"/>
+ * ...
+ * <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
+ *    ...
+ *    <property name="filterChainDefinitions">
+ *        <value>
+ *            /some/path/** = authc, <b>myCustomFilter</b>
+ *        </value>
+ *    </property>
+ * </bean>
+ * </pre>
+ * <h4>Global Property Values</h4>
+ * Most Shiro servlet Filter implementations exist for defining custom Filter
+ * {@link #setFilterChainDefinitions(String) chain definitions}.  Most implementations subclass one of the
+ * {@link AccessControlFilter}, {@link AuthenticationFilter}, {@link AuthorizationFilter} classes to simplify things,
+ * and each of these 3 classes has configurable properties that are application-specific.
+ * <p/>
+ * A dilemma arises where, if you want to for example set the application's 'loginUrl' for any Filter, you don't want
+ * to have to manually specify that value for <em>each</em> filter instance definied.
+ * <p/>
+ * To prevent configuration duplication, this implementation provides the following properties to allow you
+ * to set relevant values in only one place:
+ * <ul>
+ * <li>{@link #setLoginUrl(String)}</li>
+ * <li>{@link #setSuccessUrl(String)}</li>
+ * <li>{@link #setUnauthorizedUrl(String)}</li>
+ * </ul>
+ *
+ * Then at startup, any values specified via these 3 properties will be applied to all configured
+ * Filter instances so you don't have to specify them individually on each filter instance.  To ensure your own custom
+ * filters benefit from this convenience, your filter implementation should subclass one of the 3 mentioned
+ * earlier.
+ *
+ * @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
+ * @since 1.0
+ */
+public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
+
+    private static transient final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class);
+
+    private SecurityManager securityManager;
+
+    private Map<String, Filter> filters;
+
+    private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition
+
+    private String loginUrl;
+    private String successUrl;
+    private String unauthorizedUrl;
+
+    private AbstractShiroFilter instance;
+
+    public ShiroFilterFactoryBean() {
+        this.filters = new LinkedHashMap<String, Filter>();
+        this.filterChainDefinitionMap = new LinkedHashMap<String, String>(); //order matters!
+    }
+
+    /**
+     * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
+     * required property - failure to set it will throw an initialization exception.
+     *
+     * @return the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
+     */
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+
+    /**
+     * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
+     * required property - failure to set it will throw an initialization exception.
+     *
+     * @param securityManager the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
+     */
+    public void setSecurityManager(SecurityManager securityManager) {
+        this.securityManager = securityManager;
+    }
+
+    /**
+     * Returns the application's login URL to be assigned to all acquired Filters that subclass
+     * {@link AccessControlFilter} or {@code null} if no value should be assigned globally. The default value
+     * is {@code null}.
+     *
+     * @return the application's login URL to be assigned to all acquired Filters that subclass
+     *         {@link AccessControlFilter} or {@code null} if no value should be assigned globally.
+     * @see #setLoginUrl
+     */
+    public String getLoginUrl() {
+        return loginUrl;
+    }
+
+    /**
+     * Sets the application's login URL to be assigned to all acquired Filters that subclass
+     * {@link AccessControlFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
+     * as well for any default ones ({@code authc}, {@code user}, etc), this value will be passed on to each Filter
+     * via the {@link AccessControlFilter#setLoginUrl(String)} method<b>*</b>.  This eliminates the need to
+     * configure the 'loginUrl' property manually on each filter instance, and instead that can be configured once
+     * via this attribute.
+     * <p/>
+     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
+     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
+     *
+     * @param loginUrl the application's login URL to apply to as a convenience to all discovered
+     *                 {@link AccessControlFilter} instances.
+     * @see AccessControlFilter#setLoginUrl(String)
+     */
+    public void setLoginUrl(String loginUrl) {
+        this.loginUrl = loginUrl;
+    }
+
+    /**
+     * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
+     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
+     * is {@code null}.
+     *
+     * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
+     *         {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
+     * @see #setSuccessUrl
+     */
+    public String getSuccessUrl() {
+        return successUrl;
+    }
+
+    /**
+     * Sets the application's after-login success URL to be assigned to all acquired Filters that subclass
+     * {@link AuthenticationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
+     * as well for any default ones ({@code authc}, {@code user}, etc), this value will be passed on to each Filter
+     * via the {@link AuthenticationFilter#setSuccessUrl(String)} method<b>*</b>.  This eliminates the need to
+     * configure the 'successUrl' property manually on each filter instance, and instead that can be configured once
+     * via this attribute.
+     * <p/>
+     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
+     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
+     *
+     * @param successUrl the application's after-login success URL to apply to as a convenience to all discovered
+     *                   {@link AccessControlFilter} instances.
+     * @see AuthenticationFilter#setSuccessUrl(String)
+     */
+    public void setSuccessUrl(String successUrl) {
+        this.successUrl = successUrl;
+    }
+
+    /**
+     * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
+     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
+     * is {@code null}.
+     *
+     * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
+     *         {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
+     * @see #setSuccessUrl
+     */
+    public String getUnauthorizedUrl() {
+        return unauthorizedUrl;
+    }
+
+    /**
+     * Sets the application's 'unauthorized' URL to be assigned to all acquired Filters that subclass
+     * {@link AuthorizationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
+     * as well for any default ones ({@code roles}, {@code perms}, etc), this value will be passed on to each Filter
+     * via the {@link AuthorizationFilter#setUnauthorizedUrl(String)} method<b>*</b>.  This eliminates the need to
+     * configure the 'unauthorizedUrl' property manually on each filter instance, and instead that can be configured once
+     * via this attribute.
+     * <p/>
+     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
+     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
+     *
+     * @param unauthorizedUrl the application's 'unauthorized' URL to apply to as a convenience to all discovered
+     *                        {@link AuthorizationFilter} instances.
+     * @see AuthorizationFilter#setUnauthorizedUrl(String)
+     */
+    public void setUnauthorizedUrl(String unauthorizedUrl) {
+        this.unauthorizedUrl = unauthorizedUrl;
+    }
+
+    /**
+     * Returns the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
+     * All filter chain definitions will reference filters by the names in this map (i.e. the keys).
+     *
+     * @return the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
+     */
+    public Map<String, Filter> getFilters() {
+        return filters;
+    }
+
+    /**
+     * Sets the filterName-to-Filter map of filters available for reference when creating
+     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}.
+     * <p/>
+     * <b>Note:</b> This property is optional:  this {@code FactoryBean} implementation will discover all beans in the
+     * web application context that implement the {@link Filter} interface and automatically add them to this filter
+     * map under their bean name.
+     * <p/>
+     * For example, just defining this bean in a web Spring XML application context:
+     * <pre>
+     * <bean id="myFilter" class="com.class.that.implements.javax.servlet.Filter">
+     * ...
+     * </bean></pre>
+     * Will automatically place that bean into this Filters map under the key '<b>myFilter</b>'.
+     *
+     * @param filters the optional filterName-to-Filter map of filters available for reference when creating
+     *                {@link #setFilterChainDefinitionMap (java.util.Map) filter chain definitions}.
+     */
+    public void setFilters(Map<String, Filter> filters) {
+        this.filters = filters;
+    }
+
+    /**
+     * Returns the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
+     * by the Shiro Filter.  Each map entry should conform to the format defined by the
+     * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
+     * path expression) and the map value is the comma-delimited string chain definition.
+     *
+     * @return he chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
+     *         by the Shiro Filter.
+     */
+    public Map<String, String> getFilterChainDefinitionMap() {
+        return filterChainDefinitionMap;
+    }
+
+    /**
+     * Sets the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
+     * by the Shiro Filter.  Each map entry should conform to the format defined by the
+     * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
+     * path expression) and the map value is the comma-delimited string chain definition.
+     *
+     * @param filterChainDefinitionMap the chainName-to-chainDefinition map of chain definitions to use for creating
+     *                                 filter chains intercepted by the Shiro Filter.
+     */
+    public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
+        this.filterChainDefinitionMap = filterChainDefinitionMap;
+    }
+
+    /**
+     * A convenience method that sets the {@link #setFilterChainDefinitionMap(java.util.Map) filterChainDefinitionMap}
+     * property by accepting a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs).
+     * Each key/value pair must conform to the format defined by the
+     * {@link FilterChainManager#createChain(String,String)} JavaDoc - each property key is an ant URL
+     * path expression and the value is the comma-delimited chain definition.
+     *
+     * @param definitions a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs)
+     *                    where each key/value pair represents a single urlPathExpression-commaDelimitedChainDefinition.
+     */
+    public void setFilterChainDefinitions(String definitions) {
+        Ini ini = new Ini();
+        ini.load(definitions);
+        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
+        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
+        if (CollectionUtils.isEmpty(section)) {
+            //no urls section.  Since this _is_ a urls chain definition property, just assume the
+            //default section contains only the definitions:
+            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
+        }
+        setFilterChainDefinitionMap(section);
+    }
+
+    /**
+     * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
+     * {@link #createInstance} method.
+     *
+     * @return the application's Shiro Filter instance used to filter incoming web requests.
+     * @throws Exception if there is a problem creating the {@code Filter} instance.
+     */
+    public Object getObject() throws Exception {
+        if (instance == null) {
+            instance = createInstance();
+        }
+        return instance;
+    }
+
+    /**
+     * Returns <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
+     *
+     * @return <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
+     */
+    public Class getObjectType() {
+        return SpringShiroFilter.class;
+    }
+
+    /**
+     * Returns {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
+     *
+     * @return {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
+     */
+    public boolean isSingleton() {
+        return true;
+    }
+
+    protected FilterChainManager createFilterChainManager() {
+
+        DefaultFilterChainManager manager = new DefaultFilterChainManager();
+        Map<String, Filter> defaultFilters = manager.getFilters();
+        //apply global settings if necessary:
+        for (Filter filter : defaultFilters.values()) {
+            applyGlobalPropertiesIfNecessary(filter);
+        }
+
+        //Apply the acquired and/or configured filters:
+        Map<String, Filter> filters = getFilters();
+        if (!CollectionUtils.isEmpty(filters)) {
+            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
+                String name = entry.getKey();
+                Filter filter = entry.getValue();
+                applyGlobalPropertiesIfNecessary(filter);
+                if (filter instanceof Nameable) {
+                    ((Nameable) filter).setName(name);
+                }
+                //'init' argument is false, since Spring-configured filters should be initialized
+                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
+                manager.addFilter(name, filter, false);
+            }
+        }
+
+        //build up the chains:
+        Map<String, String> chains = getFilterChainDefinitionMap();
+        if (!CollectionUtils.isEmpty(chains)) {
+            for (Map.Entry<String, String> entry : chains.entrySet()) {
+                String url = entry.getKey();
+                String chainDefinition = entry.getValue();
+                manager.createChain(url, chainDefinition);
+            }
+        }
+
+        return manager;
+    }
+
+    /**
+     * This implementation:
+     * <ol>
+     * <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
+     * property has been set</li>
+     * <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
+     * configured {@link #setFilters(java.util.Map) filters} and
+     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
+     * <li>Wraps the FilterChainManager with a suitable
+     * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
+     * implementations do not know of {@code FilterChainManager}s</li>
+     * <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
+     * instance and returns that filter instance.</li>
+     * </ol>
+     *
+     * @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
+     * @throws Exception if there is a problem creating the AbstractShiroFilter instance.
+     */
+    protected AbstractShiroFilter createInstance() throws Exception {
+
+        log.debug("Creating Shiro Filter instance.");
+
+        SecurityManager securityManager = getSecurityManager();
+        if (securityManager == null) {
+            String msg = "SecurityManager property must be set.";
+            throw new BeanInitializationException(msg);
+        }
+
+        if (!(securityManager instanceof WebSecurityManager)) {
+            String msg = "The security manager does not implement the WebSecurityManager interface.";
+            throw new BeanInitializationException(msg);
+        }
+
+        FilterChainManager manager = createFilterChainManager();
+
+        //Expose the constructed FilterChainManager by first wrapping it in a
+        // FilterChainResolver implementation. The AbstractShiroFilter implementations
+        // do not know about FilterChainManagers - only resolvers:
+        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
+        chainResolver.setFilterChainManager(manager);
+
+        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
+        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
+        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
+        //injection of the SecurityManager and FilterChainResolver:
+        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
+    }
+
+    private void applyLoginUrlIfNecessary(Filter filter) {
+        String loginUrl = getLoginUrl();
+        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
+            AccessControlFilter acFilter = (AccessControlFilter) filter;
+            //only apply the login url if they haven't explicitly configured one already:
+            String existingLoginUrl = acFilter.getLoginUrl();
+            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
+                acFilter.setLoginUrl(loginUrl);
+            }
+        }
+    }
+
+    private void applySuccessUrlIfNecessary(Filter filter) {
+        String successUrl = getSuccessUrl();
+        if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
+            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
+            //only apply the successUrl if they haven't explicitly configured one already:
+            String existingSuccessUrl = authcFilter.getSuccessUrl();
+            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
+                authcFilter.setSuccessUrl(successUrl);
+            }
+        }
+    }
+
+    private void applyUnauthorizedUrlIfNecessary(Filter filter) {
+        String unauthorizedUrl = getUnauthorizedUrl();
+        if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
+            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
+            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
+            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
+            if (existingUnauthorizedUrl == null) {
+                authzFilter.setUnauthorizedUrl(unauthorizedUrl);
+            }
+        }
+    }
+
+    private void applyGlobalPropertiesIfNecessary(Filter filter) {
+        applyLoginUrlIfNecessary(filter);
+        applySuccessUrlIfNecessary(filter);
+        applyUnauthorizedUrlIfNecessary(filter);
+    }
+
+    /**
+     * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
+     * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
+     * later during filter chain construction.
+     */
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        if (bean instanceof Filter) {
+            log.debug("Found filter chain candidate filter '{}'", beanName);
+            Filter filter = (Filter) bean;
+            applyGlobalPropertiesIfNecessary(filter);
+            getFilters().put(beanName, filter);
+        } else {
+            log.trace("Ignoring non-Filter bean '{}'", beanName);
+        }
+        return bean;
+    }
+
+    /**
+     * Does nothing - only exists to satisfy the BeanPostProcessor interface and immediately returns the
+     * {@code bean} argument.
+     */
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        return bean;
+    }
+
+    /**
+     * Ordinarily the {@code AbstractShiroFilter} must be subclassed to additionally perform configuration
+     * and initialization behavior.  Because this {@code FactoryBean} implementation manually builds the
+     * {@link AbstractShiroFilter}'s
+     * {@link AbstractShiroFilter#setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager) securityManager} and
+     * {@link AbstractShiroFilter#setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver) filterChainResolver}
+     * properties, the only thing left to do is set those properties explicitly.  We do that in a simple
+     * concrete subclass in the constructor.
+     */
+    private static final class SpringShiroFilter extends AbstractShiroFilter {
+
+        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
+            super();
+            if (webSecurityManager == null) {
+                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
+            }
+            setSecurityManager(webSecurityManager);
+            if (resolver != null) {
+                setFilterChainResolver(resolver);
+            }
+        }
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/package-info.java b/support/spring/src/main/java/org/apache/shiro/spring/web/package-info.java
new file mode 100644
index 0000000..98b9554
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Spring-specific components for use in Spring-based web applications.
+ *
+ * @see ShiroFilterFactoryBean ShiroFilterFactoryBean
+ */
+package org.apache.shiro.spring.web;
\ No newline at end of file
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactoryTest.java b/support/spring/src/test/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactoryTest.java
new file mode 100644
index 0000000..9212112
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/remoting/SecureRemoteInvocationFactoryTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.remoting;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.shiro.session.mgt.DefaultSessionKey;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.remoting.support.RemoteInvocation;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * //TODO - Class JavaDoc!
+ *
+ */
+public class SecureRemoteInvocationFactoryTest {
+
+    @Before
+    public void setup() {
+        ThreadContext.remove();
+    }
+
+    @After
+    public void tearDown() {
+        ThreadContext.remove();
+    }
+
+    protected Method getMethod(String name, Class clazz) {
+        Method[] methods = clazz.getMethods();
+        for (Method method : methods) {
+            if (method.getName().equals(name)) {
+                return method;
+            }
+        }
+        throw new IllegalStateException("'" + name + "' method should exist.");
+    }
+
+    @Test
+    public void testSessionManagerProxyStartRemoteInvocation() throws Exception {
+
+        SecureRemoteInvocationFactory factory = new SecureRemoteInvocationFactory();
+
+        MethodInvocation mi = createMock(MethodInvocation.class);
+        Method startMethod = getMethod("start", SessionManager.class);
+        expect(mi.getMethod()).andReturn(startMethod).anyTimes();
+
+        Object[] args = {"localhost"};
+        expect(mi.getArguments()).andReturn(args).anyTimes();
+
+        replay(mi);
+
+        RemoteInvocation ri = factory.createRemoteInvocation(mi);
+
+        verify(mi);
+
+        assertNull(ri.getAttribute(SecureRemoteInvocationFactory.SESSION_ID_KEY));
+    }
+
+    @Test
+    public void testSessionManagerProxyNonStartRemoteInvocation() throws Exception {
+
+        SecureRemoteInvocationFactory factory = new SecureRemoteInvocationFactory();
+
+        MethodInvocation mi = createMock(MethodInvocation.class);
+        Method method = getMethod("getSession", SessionManager.class);
+        expect(mi.getMethod()).andReturn(method).anyTimes();
+
+        String dummySessionId = UUID.randomUUID().toString();
+        SessionKey sessionKey = new DefaultSessionKey(dummySessionId);
+        Object[] args = {sessionKey};
+        expect(mi.getArguments()).andReturn(args).anyTimes();
+
+        replay(mi);
+
+        RemoteInvocation ri = factory.createRemoteInvocation(mi);
+
+        verify(mi);
+
+        assertEquals(dummySessionId, ri.getAttribute(SecureRemoteInvocationFactory.SESSION_ID_KEY));
+    }
+
+    /*@Test
+    public void testNonSessionManagerCall() throws Exception {
+
+        SecureRemoteInvocationFactory factory = new SecureRemoteInvocationFactory();
+
+        MethodInvocation mi = createMock(MethodInvocation.class);
+        Method method = getMethod("login", SecurityManager.class);
+        expect(mi.getMethod()).andReturn(method).anyTimes();
+    }*/
+
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
new file mode 100644
index 0000000..5da0493
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.ThreadState;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * Common method tests across implementations.  In actuality, the methods don't change across
+ * subclasses - only the mechanism that enables AOP pointcuts and applies advice.  Those differences
+ * are in spring configuration only.
+ *
+ * @since 1.1
+ */
+ at RunWith(SpringJUnit4ClassRunner.class)
+ at ContextConfiguration
+public abstract class AbstractAuthorizationAnnotationTest {
+
+    @Autowired
+    protected TestService testService;
+    @Autowired
+    private org.apache.shiro.mgt.SecurityManager securityManager;
+    @Autowired
+    private Realm realm;
+
+    private ThreadState threadState;
+
+    protected void bind(Subject subject) {
+        clearSubject();
+        this.threadState = new SubjectThreadState(subject);
+        this.threadState.bind();
+    }
+
+    @After
+    public void clearSubject() {
+        if (threadState != null) {
+            threadState.clear();
+        }
+    }
+
+    protected void bindGuest() {
+        bind(new Subject.Builder(securityManager).buildSubject());
+    }
+
+    protected void bindUser() {
+        PrincipalCollection principals = new SimplePrincipalCollection("test", realm.getName());
+        bind(new Subject.Builder(securityManager).principals(principals).buildSubject());
+    }
+
+    protected void bindAuthenticatedUser() {
+        PrincipalCollection principals = new SimplePrincipalCollection("test", realm.getName());
+        bind(new Subject.Builder(securityManager).
+                principals(principals).authenticated(true).buildSubject());
+    }
+
+    // GUEST OPERATIONS:
+
+    @Test
+    public void testGuestImplementation() {
+        bindGuest();
+        testService.guestImplementation();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestImplementationFailure() {
+        bindUser();
+        testService.guestImplementation();
+    }
+
+    @Test
+    public void testGuestInterface() {
+        bindGuest();
+        testService.guestInterface();
+    }
+    //testGuestInterfaceFailure() cannot be in this class - the SchemaAuthorizationAnnotationTest
+    //subclass does not support annotations on interfaces (Spring AspectJ pointcut expressions
+    //do not support annotations on interface methods).  It is instead in the
+    //DapcAuthorizationAnnotationTest subclass
+
+
+    // USER OPERATIONS
+
+    @Test
+    public void testUserImplementation() {
+        bindUser();
+        testService.userImplementation();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testUserImplementationFailure() {
+        bindGuest();
+        testService.userImplementation();
+    }
+
+    @Test
+    public void testUserInterface() {
+        bindUser();
+        testService.userInterface();
+    }
+    //testUserInterfaceFailure() cannot be in this class - the SchemaAuthorizationAnnotationTest
+    //subclass does not support annotations on interfaces (Spring AspectJ pointcut expressions
+    //do not support annotations on interface methods).  It is instead in the
+    //DapcAuthorizationAnnotationTest subclass
+
+
+    // AUTHENTICATED USER OPERATIONS
+
+    @Test
+    public void testAuthenticatedImplementation() {
+        bindAuthenticatedUser();
+        testService.authenticatedImplementation();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testAuthenticatedImplementationFailure() {
+        bindUser();
+        testService.authenticatedImplementation();
+    }
+
+    @Test
+    public void testAuthenticatedInterface() {
+        bindAuthenticatedUser();
+        testService.authenticatedInterface();
+    }
+    //testAuthenticatedInterfaceFailure() cannot be in this class - the SchemaAuthorizationAnnotationTest
+    //subclass does not support annotations on interfaces (Spring AspectJ pointcut expressions
+    //do not support annotations on interface methods).  It is instead in the
+    //DapcAuthorizationAnnotationTest subclass
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest.java b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest.java
new file mode 100644
index 0000000..669067d
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * All the tests in the parent class are run.  This class only exists to ensure that a
+ * DefaultAutoProxyCreator Spring AOP environment exists and enables annotations correctly as
+ * documented in the Spring reference manual: 
+ * <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-autoproxy">
+ * Using the "autoproxy" facility</a>.
+ *
+ * @since 1.1
+ */
+ at RunWith(SpringJUnit4ClassRunner.class)
+ at ContextConfiguration
+public class DapcAuthorizationAnnotationTest extends AbstractAuthorizationAnnotationTest {
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestInterfaceFailure() {
+        bindUser();
+        testService.guestInterface();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testUserInterfaceFailure() {
+        bindGuest();
+        testService.userInterface();
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testAuthenticatedInterfaceFailure() {
+        bindGuest();
+        testService.authenticatedInterface();
+    }
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DefaultTestService.java b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DefaultTestService.java
new file mode 100644
index 0000000..69f79a3
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/DefaultTestService.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.apache.shiro.authz.annotation.*;
+
+/**
+ * @since 1.1
+ */
+public class DefaultTestService implements TestService {
+
+    @RequiresGuest
+    public void guestImplementation() {
+    }
+    public void guestInterface() {
+    }
+
+    @RequiresUser
+    public void userImplementation() {
+    }
+    public void userInterface() {
+    }
+
+    @RequiresAuthentication
+    public void authenticatedImplementation() {
+    }
+    public void authenticatedInterface() {
+    }
+
+    @RequiresRoles("test")
+    public void roleImplementation() {
+    }
+    public void roleInterface() {
+    }
+
+    @RequiresPermissions("test:execute")
+    public void permissionImplementation() {
+    }
+    public void permissionInterface() {
+    }
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest.java b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest.java
new file mode 100644
index 0000000..27de306
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * All the tests in the parent class are run.  This class exists to ensure that Shiro
+ * annotations function correctly in Spring applications configured via Spring's AOP namespace
+ * <aop:config> style of configuration, as defined in the Spring reference manual:
+ * <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema">
+ * Schema-based AOP support</a>.
+ *
+ * @since 1.1
+ */
+ at RunWith(SpringJUnit4ClassRunner.class)
+ at ContextConfiguration
+public class SchemaAuthorizationAnnotationTest extends AbstractAuthorizationAnnotationTest {
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/TestService.java b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/TestService.java
new file mode 100644
index 0000000..ea467b8
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/security/interceptor/TestService.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.security.interceptor;
+
+import org.apache.shiro.authz.annotation.*;
+
+/**
+ * @since 1.1
+ */
+public interface TestService {
+
+    // Guest operations
+    void guestImplementation();
+    @RequiresGuest
+    void guestInterface();
+
+    // User operations
+    void userImplementation();
+    @RequiresUser
+    void userInterface();
+
+    // Authenticated User operations
+    void authenticatedImplementation();
+    @RequiresAuthentication
+    void authenticatedInterface();
+
+    // Role operations
+    void roleImplementation();
+    @RequiresRoles("test")
+    void roleInterface();
+
+    // Permission operations
+    void permissionImplementation();
+    @RequiresPermissions("test:execute")
+    void permissionInterface();
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/web/DummyFilter.java b/support/spring/src/test/java/org/apache/shiro/spring/web/DummyFilter.java
new file mode 100644
index 0000000..44b1b07
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/web/DummyFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.web;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: Jan 19, 2010
+ * Time: 4:44:21 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class DummyFilter implements Filter {
+
+    public DummyFilter(){}
+
+    public void init(FilterConfig filterConfig) throws ServletException {
+        //no-op
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        chain.doFilter(request, response);
+    }
+
+    public void destroy() {
+        //no-op
+    }
+}
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java b/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
new file mode 100644
index 0000000..b2ba586
--- /dev/null
+++ b/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.spring.web;
+
+import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
+import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
+import org.apache.shiro.web.filter.mgt.NamedFilterList;
+import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
+import org.apache.shiro.web.servlet.AbstractShiroFilter;
+import org.junit.Test;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for the {@link ShiroFilterFactoryBean} implementation.
+ *
+ * @since 1.0
+ */
+//@RunWith(SpringJUnit4ClassRunner.class)
+//@ContextConfiguration(locations = {"/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml"})
+public class ShiroFilterFactoryBeanTest {
+
+    @Test
+    public void testFilterDefinition() {
+
+        ClassPathXmlApplicationContext context =
+                new ClassPathXmlApplicationContext("org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml");
+
+        AbstractShiroFilter shiroFilter = (AbstractShiroFilter) context.getBean("shiroFilter");
+
+        PathMatchingFilterChainResolver resolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
+        DefaultFilterChainManager fcManager = (DefaultFilterChainManager) resolver.getFilterChainManager();
+        NamedFilterList chain = fcManager.getChain("/test");
+        assertNotNull(chain);
+        assertEquals(chain.size(), 2);
+        Filter[] filters = new Filter[chain.size()];
+        filters = chain.toArray(filters);
+        assertTrue(filters[0] instanceof DummyFilter);
+        assertTrue(filters[1] instanceof FormAuthenticationFilter);
+    }
+
+    /**
+     * Verifies fix for <a href="https://issues.apache.org/jira/browse/SHIRO-167">SHIRO-167</a>
+     *
+     * @throws Exception if there is any unexpected error
+     */
+    @Test
+    public void testFilterDefinitionWithInit() throws Exception {
+
+        ClassPathXmlApplicationContext context =
+                new ClassPathXmlApplicationContext("org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml");
+
+        AbstractShiroFilter shiroFilter = (AbstractShiroFilter) context.getBean("shiroFilter");
+
+        FilterConfig mockFilterConfig = createNiceMock(FilterConfig.class);
+        ServletContext mockServletContext = createNiceMock(ServletContext.class);
+        expect(mockFilterConfig.getServletContext()).andReturn(mockServletContext).anyTimes();
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        expect(mockRequest.getContextPath()).andReturn("/").anyTimes();
+        expect(mockRequest.getRequestURI()).andReturn("/").anyTimes();
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        replay(mockFilterConfig);
+        replay(mockServletContext);
+        shiroFilter.init(mockFilterConfig);
+        verify(mockServletContext);
+        verify(mockFilterConfig);
+
+        FilterChain filterChain = new FilterChain() {
+            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException {
+                HttpServletRequest request = (HttpServletRequest) servletRequest;
+                assertNotNull(request.getSession());
+                //this line asserts the fix for the user-reported issue:
+                assertNotNull(request.getSession().getServletContext());
+            }
+        };
+
+        replay(mockRequest);
+        replay(mockResponse);
+
+        shiroFilter.doFilter(mockRequest, mockResponse, filterChain);
+
+        verify(mockResponse);
+        verify(mockRequest);
+    }
+}
diff --git a/support/spring/src/test/resources/log4j.properties b/support/spring/src/test/resources/log4j.properties
new file mode 100644
index 0000000..b2b1a57
--- /dev/null
+++ b/support/spring/src/test/resources/log4j.properties
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=TRACE, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# Pattern to output: date priority [category] - message
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
+log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
+
+# Spring logging level is WARN
+log4j.logger.org.springframework=WARN
+
+# General Apache libraries is WARN
+log4j.logger.org.apache=WARN
+
+log4j.logger.net.sf.ehcache=WARN
+
+log4j.logger.org.apache.shiro=INFO
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
\ No newline at end of file
diff --git a/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest-context.xml b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest-context.xml
new file mode 100644
index 0000000..2dc1d51
--- /dev/null
+++ b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/AbstractAuthorizationAnnotationTest-context.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans
+       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+    <!-- This file defines the common/shared beans used across the concrete
+       AuthorizationAnnotationTest implementations.  Each Test implementation
+       will provide another Spring file in addition to this one that will
+       turn on a different way of enabling Spring AOP.  (i.e. one will use
+       a DefaultAutoProxyCreator, another will use the <aop:config/> mechanism,
+       etc). -->
+
+    <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
+        <property name="userDefinitions">
+            <value>
+                test = test, test
+            </value>
+        </property>
+    </bean>
+
+    <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
+        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
+        <property name="realm" ref="iniRealm"/>
+    </bean>
+
+    <bean id="testService" class="org.apache.shiro.spring.security.interceptor.DefaultTestService"/>
+
+</beans>
diff --git a/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest-context.xml b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest-context.xml
new file mode 100644
index 0000000..92c98e4
--- /dev/null
+++ b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/DapcAuthorizationAnnotationTest-context.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans
+       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+    <!-- This file exists to test enabling Shiro annotations in Spring using
+         the DefaultAutoProxyCreator AOP mechanism.  It shares common beans
+         defined in AbstractAuthorizationAnnotationTest-context.xml -->
+
+    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
+    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
+        <property name="securityManager" ref="securityManager"/>
+    </bean>
+
+</beans>
diff --git a/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest-context.xml b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest-context.xml
new file mode 100644
index 0000000..98445e3
--- /dev/null
+++ b/support/spring/src/test/resources/org/apache/shiro/spring/security/interceptor/SchemaAuthorizationAnnotationTest-context.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:aop="http://www.springframework.org/schema/aop"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans
+       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+       http://www.springframework.org/schema/aop
+       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
+
+    <!-- This file exists to test enabling Shiro annotations in Spring using
+         the <aop:config/> schema-based AOP configuration.  It shares common beans
+         defined in AbstractAuthorizationAnnotationTest-context.xml -->
+
+    <!-- NOTE:  When using AspectJ-style pointcut definitions, you CAN NOT put the annotations
+                on an interface method.  They MUST be on implementation methods directly.  Spring's
+                AspectJ-style support does not support annotations on interface methods.  If you
+                want annotations on interface methods, you must use either Spring's bean
+                ProxyCreator mechanisms or AspectJ compile-time or load-time weaving. -->
+    <aop:config>
+        <aop:pointcut id="shiroAnnotatedMethod" expression="
+                      execution(@org.apache.shiro.authz.annotation.RequiresAuthentication * *(..)) ||
+                      execution(@org.apache.shiro.authz.annotation.RequiresGuest * *(..)) ||
+                      execution(@org.apache.shiro.authz.annotation.RequiresPermissions * *(..)) ||
+                      execution(@org.apache.shiro.authz.annotation.RequiresRoles * *(..)) ||
+                      execution(@org.apache.shiro.authz.annotation.RequiresUser * *(..))"/>
+        <aop:advisor pointcut-ref="shiroAnnotatedMethod"
+                     advice-ref="shiroAuthorizationAnnotationsAdvice"/>
+    </aop:config>
+    <bean id="shiroAuthorizationAnnotationsAdvice"
+          class="org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor"/>
+
+</beans>
\ No newline at end of file
diff --git a/support/spring/src/test/resources/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml b/support/spring/src/test/resources/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml
new file mode 100644
index 0000000..6403782
--- /dev/null
+++ b/support/spring/src/test/resources/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
+
+    <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
+        <property name="userDefinitions">
+            <value>
+                test = test
+            </value>
+        </property>
+    </bean>
+
+    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
+        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
+        <property name="realm" ref="iniRealm"/>
+        <property name="sessionMode" value="native"/>
+    </bean>
+
+    <!-- Just defining this will make it available to the 'filterChainDefinitions' property
+         in the shiroFilter below by the bean id: -->
+    <bean id="testFilter" class="org.apache.shiro.spring.web.DummyFilter"/>
+
+    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
+        <property name="securityManager" ref="securityManager"/>
+        <property name="filterChainDefinitions">
+            <value>
+                /test = testFilter, authc
+            </value>
+        </property>
+
+    </bean>
+
+</beans>
diff --git a/tools/hasher/pom.xml b/tools/hasher/pom.xml
new file mode 100644
index 0000000..376bf69
--- /dev/null
+++ b/tools/hasher/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro.tools</groupId>
+        <artifactId>shiro-tools</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-tools-hasher</artifactId>
+    <name>Apache Shiro :: Tools :: Hasher</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+            <exclusions>
+                <!-- BeanUtils is for ini config - this simple command line program
+                     does not init a shiro environment, so we don't need it: -->
+                <exclusion>
+                    <groupId>commons-beanutils</groupId>
+                    <artifactId>commons-beanutils</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-cli</groupId>
+            <artifactId>commons-cli</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.2.1</version>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/cli.assembly.xml</descriptor>
+                    </descriptors>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.shiro.tools.hasher.Hasher</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.tools.hasher</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.tools.hasher*;version=${project.version}</Export-Package>
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/tools/hasher/src/main/assembly/cli.assembly.xml b/tools/hasher/src/main/assembly/cli.assembly.xml
new file mode 100644
index 0000000..9926c41
--- /dev/null
+++ b/tools/hasher/src/main/assembly/cli.assembly.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+  <id>cli</id>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <useProjectArtifact>true</useProjectArtifact>
+      <unpack>true</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/tools/hasher/src/main/java/org/apache/shiro/tools/hasher/Hasher.java b/tools/hasher/src/main/java/org/apache/shiro/tools/hasher/Hasher.java
new file mode 100644
index 0000000..25523ef
--- /dev/null
+++ b/tools/hasher/src/main/java/org/apache/shiro/tools/hasher/Hasher.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.tools.hasher;
+
+import org.apache.commons.cli.*;
+import org.apache.shiro.authc.credential.DefaultPasswordService;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.UnknownAlgorithmException;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.crypto.hash.format.*;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.JavaEnvironment;
+import org.apache.shiro.util.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Commandline line utility to hash data such as strings, passwords, resources (files, urls, etc).
+ * <p/>
+ * Usage:
+ * <pre>
+ * java -jar shiro-tools-hasher<em>-version</em>-cli.jar
+ * </pre>
+ * This will print out all supported options with documentation.
+ *
+ * @since 1.2
+ */
+public final class Hasher {
+
+    private static final String HEX_PREFIX = "0x";
+    private static final String DEFAULT_ALGORITHM_NAME = "MD5";
+    private static final String DEFAULT_PASSWORD_ALGORITHM_NAME = DefaultPasswordService.DEFAULT_HASH_ALGORITHM;
+    private static final int DEFAULT_GENERATED_SALT_SIZE = 128;
+    private static final int DEFAULT_NUM_ITERATIONS = 1;
+    private static final int DEFAULT_PASSWORD_NUM_ITERATIONS = DefaultPasswordService.DEFAULT_HASH_ITERATIONS;
+
+    private static final Option ALGORITHM = new Option("a", "algorithm", true, "hash algorithm name.  Defaults to SHA-256 when password hashing, MD5 otherwise.");
+    private static final Option DEBUG = new Option("d", "debug", false, "show additional error (stack trace) information.");
+    private static final Option FORMAT = new Option("f", "format", true, "hash output format.  Defaults to 'shiro1' when password hashing, 'hex' otherwise.  See below for more information.");
+    private static final Option HELP = new Option("help", "help", false, "show this help message.");
+    private static final Option ITERATIONS = new Option("i", "iterations", true, "number of hash iterations.  Defaults to " + DEFAULT_PASSWORD_NUM_ITERATIONS + " when password hashing, 1 otherwise.");
+    private static final Option PASSWORD = new Option("p", "password", false, "hash a password (disable typing echo)");
+    private static final Option PASSWORD_NC = new Option("pnc", "pnoconfirm", false, "hash a password (disable typing echo) but disable password confirmation prompt.");
+    private static final Option RESOURCE = new Option("r", "resource", false, "read and hash the resource located at <value>.  See below for more information.");
+    private static final Option SALT = new Option("s", "salt", true, "use the specified salt.  <arg> is plaintext.");
+    private static final Option SALT_BYTES = new Option("sb", "saltbytes", true, "use the specified salt bytes.  <arg> is hex or base64 encoded text.");
+    private static final Option SALT_GEN = new Option("gs", "gensalt", false, "generate and use a random salt. Defaults to true when password hashing, false otherwise.");
+    private static final Option NO_SALT_GEN = new Option("ngs", "nogensalt", false, "do NOT generate and use a random salt (valid during password hashing).");
+    private static final Option SALT_GEN_SIZE = new Option("gss", "gensaltsize", true, "the number of salt bits (not bytes!) to generate.  Defaults to 128.");
+
+    private static final String SALT_MUTEX_MSG = createMutexMessage(SALT, SALT_BYTES);
+
+    private static final HashFormatFactory HASH_FORMAT_FACTORY = new DefaultHashFormatFactory();
+
+    static {
+        ALGORITHM.setArgName("name");
+        SALT_GEN_SIZE.setArgName("numBits");
+        ITERATIONS.setArgName("num");
+        SALT.setArgName("sval");
+        SALT_BYTES.setArgName("encTxt");
+    }
+
+    public static void main(String[] args) {
+
+        CommandLineParser parser = new PosixParser();
+
+        Options options = new Options();
+        options.addOption(HELP).addOption(DEBUG).addOption(ALGORITHM).addOption(ITERATIONS);
+        options.addOption(RESOURCE).addOption(PASSWORD).addOption(PASSWORD_NC);
+        options.addOption(SALT).addOption(SALT_BYTES).addOption(SALT_GEN).addOption(SALT_GEN_SIZE).addOption(NO_SALT_GEN);
+        options.addOption(FORMAT);
+
+        boolean debug = false;
+        String algorithm = null; //user unspecified
+        int iterations = 0; //0 means unspecified by the end-user
+        boolean resource = false;
+        boolean password = false;
+        boolean passwordConfirm = true;
+        String saltString = null;
+        String saltBytesString = null;
+        boolean generateSalt = false;
+        int generatedSaltSize = DEFAULT_GENERATED_SALT_SIZE;
+
+        String formatString = null;
+
+        char[] passwordChars = null;
+
+        try {
+            CommandLine line = parser.parse(options, args);
+
+            if (line.hasOption(HELP.getOpt())) {
+                printHelpAndExit(options, null, debug, 0);
+            }
+            if (line.hasOption(DEBUG.getOpt())) {
+                debug = true;
+            }
+            if (line.hasOption(ALGORITHM.getOpt())) {
+                algorithm = line.getOptionValue(ALGORITHM.getOpt());
+            }
+            if (line.hasOption(ITERATIONS.getOpt())) {
+                iterations = getRequiredPositiveInt(line, ITERATIONS);
+            }
+            if (line.hasOption(PASSWORD.getOpt())) {
+                password = true;
+                generateSalt = true;
+            }
+            if (line.hasOption(RESOURCE.getOpt())) {
+                resource = true;
+            }
+            if (line.hasOption(PASSWORD_NC.getOpt())) {
+                password = true;
+                generateSalt = true;
+                passwordConfirm = false;
+            }
+            if (line.hasOption(SALT.getOpt())) {
+                saltString = line.getOptionValue(SALT.getOpt());
+            }
+            if (line.hasOption(SALT_BYTES.getOpt())) {
+                saltBytesString = line.getOptionValue(SALT_BYTES.getOpt());
+            }
+            if (line.hasOption(NO_SALT_GEN.getOpt())) {
+                generateSalt = false;
+            }
+            if (line.hasOption(SALT_GEN.getOpt())) {
+                generateSalt = true;
+            }
+            if (line.hasOption(SALT_GEN_SIZE.getOpt())) {
+                generateSalt = true;
+                generatedSaltSize = getRequiredPositiveInt(line, SALT_GEN_SIZE);
+                if (generatedSaltSize % 8 != 0) {
+                    throw new IllegalArgumentException("Generated salt size must be a multiple of 8 (e.g. 128, 192, 256, 512, etc).");
+                }
+            }
+            if (line.hasOption(FORMAT.getOpt())) {
+                formatString = line.getOptionValue(FORMAT.getOpt());
+            }
+
+            String sourceValue;
+
+            Object source;
+
+            if (password) {
+                passwordChars = readPassword(passwordConfirm);
+                source = passwordChars;
+            } else {
+                String[] remainingArgs = line.getArgs();
+                if (remainingArgs == null || remainingArgs.length != 1) {
+                    printHelpAndExit(options, null, debug, -1);
+                }
+
+                assert remainingArgs != null;
+                sourceValue = toString(remainingArgs);
+
+                if (resource) {
+                    if (!ResourceUtils.hasResourcePrefix(sourceValue)) {
+                        source = toFile(sourceValue);
+                    } else {
+                        source = ResourceUtils.getInputStreamForPath(sourceValue);
+                    }
+                } else {
+                    source = sourceValue;
+                }
+            }
+
+            if (algorithm == null) {
+                if (password) {
+                    algorithm = DEFAULT_PASSWORD_ALGORITHM_NAME;
+                } else {
+                    algorithm = DEFAULT_ALGORITHM_NAME;
+                }
+            }
+
+            if (iterations < DEFAULT_NUM_ITERATIONS) {
+                //Iterations were not specified.  Default to 350,000 when password hashing, and 1 for everything else:
+                if (password) {
+                    iterations = DEFAULT_PASSWORD_NUM_ITERATIONS;
+                } else {
+                    iterations = DEFAULT_NUM_ITERATIONS;
+                }
+            }
+
+            ByteSource salt = getSalt(saltString, saltBytesString, generateSalt, generatedSaltSize);
+
+            SimpleHash hash = new SimpleHash(algorithm, source, salt, iterations);
+
+            if (formatString == null) {
+                //Output format was not specified.  Default to 'shiro1' when password hashing, and 'hex' for
+                //everything else:
+                if (password) {
+                    formatString = Shiro1CryptFormat.class.getName();
+                } else {
+                    formatString = HexFormat.class.getName();
+                }
+            }
+
+            HashFormat format = HASH_FORMAT_FACTORY.getInstance(formatString);
+
+            if (format == null) {
+                throw new IllegalArgumentException("Unrecognized hash format '" + formatString + "'.");
+            }
+
+            String output = format.format(hash);
+
+            System.out.println(output);
+
+        } catch (IllegalArgumentException iae) {
+            exit(iae, debug);
+        } catch (UnknownAlgorithmException uae) {
+            exit(uae, debug);
+        } catch (IOException ioe) {
+            exit(ioe, debug);
+        } catch (Exception e) {
+            printHelpAndExit(options, e, debug, -1);
+        } finally {
+            if (passwordChars != null && passwordChars.length > 0) {
+                for (int i = 0; i < passwordChars.length; i++) {
+                    passwordChars[i] = ' ';
+                }
+            }
+        }
+    }
+
+    private static String createMutexMessage(Option... options) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("The ");
+
+        for (int i = 0; i < options.length; i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            Option o = options[0];
+            sb.append("-").append(o.getOpt()).append("/--").append(o.getLongOpt());
+        }
+        sb.append(" and generated salt options are mutually exclusive.  Only one of them may be used at a time");
+        return sb.toString();
+    }
+
+    private static void exit(Exception e, boolean debug) {
+        printException(e, debug);
+        System.exit(-1);
+    }
+
+    private static int getRequiredPositiveInt(CommandLine line, Option option) {
+        String iterVal = line.getOptionValue(option.getOpt());
+        try {
+            return Integer.parseInt(iterVal);
+        } catch (NumberFormatException e) {
+            String msg = "'" + option.getLongOpt() + "' value must be a positive integer.";
+            throw new IllegalArgumentException(msg, e);
+        }
+    }
+
+    private static ByteSource getSalt(String saltString, String saltBytesString, boolean generateSalt, int generatedSaltSize) {
+
+        if (saltString != null) {
+            if (generateSalt || (saltBytesString != null)) {
+                throw new IllegalArgumentException(SALT_MUTEX_MSG);
+            }
+            return ByteSource.Util.bytes(saltString);
+        }
+
+        if (saltBytesString != null) {
+            if (generateSalt) {
+                throw new IllegalArgumentException(SALT_MUTEX_MSG);
+            }
+
+            String value = saltBytesString;
+            boolean base64 = true;
+            if (saltBytesString.startsWith(HEX_PREFIX)) {
+                //hex:
+                base64 = false;
+                value = value.substring(HEX_PREFIX.length());
+            }
+            byte[] bytes;
+            if (base64) {
+                bytes = Base64.decode(value);
+            } else {
+                bytes = Hex.decode(value);
+            }
+            return ByteSource.Util.bytes(bytes);
+        }
+
+        if (generateSalt) {
+            SecureRandomNumberGenerator generator = new SecureRandomNumberGenerator();
+            int byteSize = generatedSaltSize / 8; //generatedSaltSize is in *bits* - convert to byte size:
+            return generator.nextBytes(byteSize);
+        }
+
+        //no salt used:
+        return null;
+    }
+
+    private static void printException(Exception e, boolean debug) {
+        if (e != null) {
+            System.out.println();
+            if (debug) {
+                System.out.println("Error: ");
+                e.printStackTrace(System.out);
+                System.out.println(e.getMessage());
+
+            } else {
+                System.out.println("Error: " + e.getMessage());
+                System.out.println();
+                System.out.println("Specify -d or --debug for more information.");
+            }
+        }
+    }
+
+    private static void printHelp(Options options, Exception e, boolean debug) {
+        HelpFormatter help = new HelpFormatter();
+        String command = "java -jar shiro-tools-hasher-<version>.jar [options] [<value>]";
+        String header = "\nPrint a cryptographic hash (aka message digest) of the specified <value>.\n--\nOptions:";
+        String footer = "\n" +
+                "<value> is optional only when hashing passwords (see below).  It is\n" +
+                "required all other times." +
+                "\n\n" +
+                "Password Hashing:\n" +
+                "---------------------------------\n" +
+                "Specify the -p/--password option and DO NOT enter a <value>.  You will\n" +
+                "be prompted for a password and characters will not echo as you type." +
+                "\n\n" +
+                "Salting:\n" +
+                "---------------------------------\n" +
+                "Specifying a salt:" +
+                "\n\n" +
+                "You may specify a salt using the -s/--salt option followed by the salt\n" +
+                "value.  If the salt value is a base64 or hex string representing a\n" +
+                "byte array, you must specify the -sb/--saltbytes option to indicate this,\n" +
+                "otherwise the text value bytes will be used directly." +
+                "\n\n" +
+                "When using -sb/--saltbytes, the -s/--salt value is expected to be a\n" +
+                "base64-encoded string by default.  If the value is a hex-encoded string,\n" +
+                "you must prefix the string with 0x (zero x) to indicate a hex value." +
+                "\n\n" +
+                "Generating a salt:" +
+                "\n\n" +
+                "Use the -sg/--saltgenerated option if you don't want to specify a salt,\n" +
+                "but want a strong random salt to be generated and used during hashing.\n" +
+                "The generated salt size defaults to 128 bits.  You may specify\n" +
+                "a different size by using the -sgs/--saltgeneratedsize option followed by\n" +
+                "a positive integer (size is in bits, not bytes)." +
+                "\n\n" +
+                "Because a salt must be specified if computing the\n" +
+                "hash later, generated salts will be printed, defaulting to base64\n" +
+                "encoding.  If you prefer to use hex encoding, additionally use the\n" +
+                "-sgh/--saltgeneratedhex option." +
+                "\n\n" +
+                "Files, URLs and classpath resources:\n" +
+                "---------------------------------\n" +
+                "If using the -r/--resource option, the <value> represents a resource path.\n" +
+                "By default this is expected to be a file path, but you may specify\n" +
+                "classpath or URL resources by using the classpath: or url: prefix\n" +
+                "respectively." +
+                "\n\n" +
+                "Some examples:" +
+                "\n\n" +
+                "<command> -r fileInCurrentDirectory.txt\n" +
+                "<command> -r ../../relativePathFile.xml\n" +
+                "<command> -r ~/documents/myfile.pdf\n" +
+                "<command> -r /usr/local/logs/absolutePathFile.log\n" +
+                "<command> -r url:http://foo.com/page.html\n" +
+                "<command> -r classpath:/WEB-INF/lib/something.jar" +
+                "\n\n" +
+                "Output Format:\n" +
+                "---------------------------------\n" +
+                "Specify the -f/--format option followed by either 1) the format ID (as defined\n" +
+                "by the " + DefaultHashFormatFactory.class.getName() + "\n" +
+                "JavaDoc) or 2) the fully qualified " + HashFormat.class.getName() + "\n" +
+                "implementation class name to instantiate and use for formatting.\n\n" +
+                "The default output format is 'shiro1' which is a Modular Crypt Format (MCF)\n" +
+                "that shows all relevant information as a dollar-sign ($) delimited string.\n" +
+                "This format is ideal for use in Shiro's text-based user configuration (e.g.\n" +
+                "shiro.ini or a properties file).";
+
+        printException(e, debug);
+
+        System.out.println();
+        help.printHelp(command, header, options, null);
+        System.out.println(footer);
+    }
+
+    private static void printHelpAndExit(Options options, Exception e, boolean debug, int exitCode) {
+        printHelp(options, e, debug);
+        System.exit(exitCode);
+    }
+
+    private static char[] readPassword(boolean confirm) {
+        if (!JavaEnvironment.isAtLeastVersion16()) {
+            String msg = "Password hashing (prompt without echo) uses the java.io.Console to read passwords " +
+                    "safely.  This is only available on Java 1.6 platforms and later.";
+            throw new IllegalArgumentException(msg);
+        }
+        java.io.Console console = System.console();
+        if (console == null) {
+            throw new IllegalStateException("java.io.Console is not available on the current JVM.  Cannot read passwords.");
+        }
+        char[] first = console.readPassword("%s", "Password to hash: ");
+        if (first == null || first.length == 0) {
+            throw new IllegalArgumentException("No password specified.");
+        }
+        if (confirm) {
+            char[] second = console.readPassword("%s", "Password to hash (confirm): ");
+            if (!Arrays.equals(first, second)) {
+                String msg = "Password entries do not match.";
+                throw new IllegalArgumentException(msg);
+            }
+        }
+        return first;
+    }
+
+    private static File toFile(String path) {
+        String resolved = path;
+        if (path.startsWith("~/") || path.startsWith(("~\\"))) {
+            resolved = path.replaceFirst("\\~", System.getProperty("user.home"));
+        }
+        return new File(resolved);
+    }
+
+    private static String toString(String[] strings) {
+        int len = strings != null ? strings.length : 0;
+        if (len == 0) {
+            return null;
+        }
+        return StringUtils.toDelimitedString(strings, " ");
+    }
+}
diff --git a/tools/pom.xml b/tools/pom.xml
new file mode 100644
index 0000000..717abf0
--- /dev/null
+++ b/tools/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.shiro.tools</groupId>
+    <artifactId>shiro-tools</artifactId>
+    <name>Apache Shiro :: Tools</name>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>hasher</module>
+    </modules>
+
+</project>
+
diff --git a/web/pom.xml b/web/pom.xml
new file mode 100644
index 0000000..831c660
--- /dev/null
+++ b/web/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.2.2</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-web</artifactId>
+    <name>Apache Shiro :: Web</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>jsp-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>jstl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <!-- Test dependencies - scope set appropriately already in the parent pom-->
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.shiro.web</Bundle-SymbolicName>
+                        <Export-Package>org.apache.shiro.web*;version=${project.version}</Export-Package>
+                        <!-- javax.servlet.jsp* marked as optional per SHIRO-390: -->
+                        <Import-Package>
+                            org.apache.shiro*;version="${shiro.osgi.importRange}",
+                            javax.servlet.jsp*;resolution:=optional,
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java b/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
new file mode 100644
index 0000000..ad7e7f0
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.config;
+
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniFactorySupport;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.config.ReflectionBuilder;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Factory;
+import org.apache.shiro.web.filter.mgt.FilterChainManager;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A {@link Factory} that creates {@link FilterChainResolver} instances based on {@link Ini} configuration.
+ *
+ * @since 1.0
+ */
+public class IniFilterChainResolverFactory extends IniFactorySupport<FilterChainResolver> {
+
+    public static final String FILTERS = "filters";
+    public static final String URLS = "urls";
+
+    private static transient final Logger log = LoggerFactory.getLogger(IniFilterChainResolverFactory.class);
+
+    private FilterConfig filterConfig;
+
+    private Map<String, ?> defaultBeans;
+
+    public IniFilterChainResolverFactory() {
+        super();
+    }
+
+    public IniFilterChainResolverFactory(Ini ini) {
+        super(ini);
+    }
+
+    public IniFilterChainResolverFactory(Ini ini, Map<String, ?> defaultBeans) {
+        this(ini);
+        this.defaultBeans = defaultBeans;
+    }
+
+    public FilterConfig getFilterConfig() {
+        return filterConfig;
+    }
+
+    public void setFilterConfig(FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+    }
+
+    protected FilterChainResolver createInstance(Ini ini) {
+        FilterChainResolver filterChainResolver = createDefaultInstance();
+        if (filterChainResolver instanceof PathMatchingFilterChainResolver) {
+            PathMatchingFilterChainResolver resolver = (PathMatchingFilterChainResolver) filterChainResolver;
+            FilterChainManager manager = resolver.getFilterChainManager();
+            buildChains(manager, ini);
+        }
+        return filterChainResolver;
+    }
+
+    protected FilterChainResolver createDefaultInstance() {
+        FilterConfig filterConfig = getFilterConfig();
+        if (filterConfig != null) {
+            return new PathMatchingFilterChainResolver(filterConfig);
+        } else {
+            return new PathMatchingFilterChainResolver();
+        }
+    }
+
+    protected void buildChains(FilterChainManager manager, Ini ini) {
+        //filters section:
+        Ini.Section section = ini.getSection(FILTERS);
+
+        if (!CollectionUtils.isEmpty(section)) {
+            String msg = "The [{}] section has been deprecated and will be removed in a future release!  Please " +
+                    "move all object configuration (filters and all other objects) to the [{}] section.";
+            log.warn(msg, FILTERS, IniSecurityManagerFactory.MAIN_SECTION_NAME);
+        }
+
+        Map<String, Object> defaults = new LinkedHashMap<String, Object>();
+
+        Map<String, Filter> defaultFilters = manager.getFilters();
+
+        //now let's see if there are any object defaults in addition to the filters
+        //these can be used to configure the filters:
+        //create a Map of objects to use as the defaults:
+        if (!CollectionUtils.isEmpty(defaultFilters)) {
+            defaults.putAll(defaultFilters);
+        }
+        //User-provided objects must come _after_ the default filters - to allow the user-provided
+        //ones to override the default filters if necessary.
+        if (!CollectionUtils.isEmpty(this.defaultBeans)) {
+            defaults.putAll(this.defaultBeans);
+        }
+
+        Map<String, Filter> filters = getFilters(section, defaults);
+
+        //add the filters to the manager:
+        registerFilters(filters, manager);
+
+        //urls section:
+        section = ini.getSection(URLS);
+        createChains(section, manager);
+    }
+
+    protected void registerFilters(Map<String, Filter> filters, FilterChainManager manager) {
+        if (!CollectionUtils.isEmpty(filters)) {
+            boolean init = getFilterConfig() != null; //only call filter.init if there is a FilterConfig available
+            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
+                String name = entry.getKey();
+                Filter filter = entry.getValue();
+                manager.addFilter(name, filter, init);
+            }
+        }
+    }
+
+    protected Map<String, Filter> getFilters(Map<String, String> section, Map<String, ?> defaults) {
+
+        Map<String, Filter> filters = extractFilters(defaults);
+
+        if (!CollectionUtils.isEmpty(section)) {
+            ReflectionBuilder builder = new ReflectionBuilder(defaults);
+            Map<String, ?> built = builder.buildObjects(section);
+            Map<String,Filter> sectionFilters = extractFilters(built);
+
+            if (CollectionUtils.isEmpty(filters)) {
+                filters = sectionFilters;
+            } else {
+                if (!CollectionUtils.isEmpty(sectionFilters)) {
+                    filters.putAll(sectionFilters);
+                }
+            }
+        }
+
+        return filters;
+    }
+
+    private Map<String, Filter> extractFilters(Map<String, ?> objects) {
+        if (CollectionUtils.isEmpty(objects)) {
+            return null;
+        }
+        Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
+        for (Map.Entry<String, ?> entry : objects.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof Filter) {
+                filterMap.put(key, (Filter) value);
+            }
+        }
+        return filterMap;
+    }
+
+    protected void createChains(Map<String, String> urls, FilterChainManager manager) {
+        if (CollectionUtils.isEmpty(urls)) {
+            if (log.isDebugEnabled()) {
+                log.debug("No urls to process.");
+            }
+            return;
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Before url processing.");
+        }
+
+        for (Map.Entry<String, String> entry : urls.entrySet()) {
+            String path = entry.getKey();
+            String value = entry.getValue();
+            manager.createChain(path, value);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/config/WebIniSecurityManagerFactory.java b/web/src/main/java/org/apache/shiro/web/config/WebIniSecurityManagerFactory.java
new file mode 100644
index 0000000..e9f453c
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/config/WebIniSecurityManagerFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.config;
+
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.filter.mgt.DefaultFilter;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+
+import javax.servlet.Filter;
+import java.util.Map;
+
+/**
+ * Differs from the parent class only in the {@link #createDefaultInstance()} method, to
+ * ensure a web-capable {@code SecurityManager} instance is created by default.
+ *
+ * @since 1.0
+ */
+public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory {
+
+    /**
+     * Creates a new {@code WebIniSecurityManagerFactory} instance which will construct web-capable
+     * {@code SecurityManager} instances.
+     */
+    public WebIniSecurityManagerFactory() {
+        super();
+    }
+
+    /**
+     * Creates a new {@code WebIniSecurityManagerFactory} instance which will construct web-capable
+     * {@code SecurityManager} instances.  Uses the given {@link Ini} instance to construct the instance.
+     *
+     * @param config the Ini configuration that will be used to construct new web-capable {@code SecurityManager}
+     *               instances.
+     */
+    public WebIniSecurityManagerFactory(Ini config) {
+        super(config);
+    }
+
+    /**
+     * Simply returns <code>new {@link DefaultWebSecurityManager}();</code> to ensure a web-capable
+     * {@code SecurityManager} is available by default.
+     *
+     * @return a new web-capable {@code SecurityManager} instance.
+     */
+    @Override
+    protected SecurityManager createDefaultInstance() {
+        return new DefaultWebSecurityManager();
+    }
+
+    @SuppressWarnings({"unchecked"})
+    @Override
+    protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
+        Map defaults = super.createDefaults(ini, mainSection);
+        //add the default filters:
+        Map<String, Filter> defaultFilters = DefaultFilter.createInstanceMap(null);
+        defaults.putAll(defaultFilters);
+        return defaults;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/config/package-info.java b/web/src/main/java/org/apache/shiro/web/config/package-info.java
new file mode 100644
index 0000000..2da4a03
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/config/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Web-specific implementation extensions to the <code>org.apache.shiro.config</code> components.
+ */
+package org.apache.shiro.web.config;
diff --git a/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
new file mode 100644
index 0000000..d5658ab
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.env.DefaultEnvironment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+import java.util.Map;
+
+/**
+ * Default {@link WebEnvironment} implementation based on a backing {@link Map} instance.
+ *
+ * @since 1.2
+ */
+public class DefaultWebEnvironment extends DefaultEnvironment implements MutableWebEnvironment {
+
+    private static final String DEFAULT_FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";
+
+    private ServletContext servletContext;
+
+    public DefaultWebEnvironment() {
+        super();
+    }
+
+    public FilterChainResolver getFilterChainResolver() {
+        return getObject(DEFAULT_FILTER_CHAIN_RESOLVER_NAME, FilterChainResolver.class);
+    }
+
+    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
+        setObject(DEFAULT_FILTER_CHAIN_RESOLVER_NAME, filterChainResolver);
+    }
+
+    @Override
+    public SecurityManager getSecurityManager() throws IllegalStateException {
+        return getWebSecurityManager();
+    }
+
+    @Override
+    public void setSecurityManager(SecurityManager securityManager) {
+        assertWebSecurityManager(securityManager);
+        super.setSecurityManager(securityManager);
+    }
+
+    public WebSecurityManager getWebSecurityManager() {
+        SecurityManager sm = super.getSecurityManager();
+        assertWebSecurityManager(sm);
+        return (WebSecurityManager)sm;
+    }
+
+    public void setWebSecurityManager(WebSecurityManager wsm) {
+        super.setSecurityManager(wsm);
+    }
+
+    private void assertWebSecurityManager(SecurityManager sm) {
+        if (!(sm instanceof WebSecurityManager)) {
+            String msg = "SecurityManager instance must be a " + WebSecurityManager.class.getName() + " instance.";
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    public void setServletContext(ServletContext servletContext) {
+        this.servletContext = servletContext;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java b/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java
new file mode 100644
index 0000000..4b6698d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoader.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.ResourceConfigurable;
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.util.UnknownClassException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+
+/**
+ * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment}
+ * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the
+ * {@code ServletContext} at application startup.
+ * <p/>
+ * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and
+ * any additional objects (security filters, etc).  However, any component not filtered by the Shiro Filter (such
+ * as other context listeners) was not able to easily acquire the these objects to perform security operations.
+ * <p/>
+ * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the
+ * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize
+ * a Shiro environment.  The Shiro Filter, while still required for request filtering, will not perform this
+ * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first.
+ * <h2>Usage</h2>
+ * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}:
+ * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance
+ * will be initialized.
+ * <h3>shiroEnvironmentClass</h3>
+ * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the
+ * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate.  For example:
+ * <pre>
+ * <context-param>
+ *     <param-name>shiroEnvironmentClass</param-name>
+ *     <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value>
+ * </context-param>
+ * </pre>
+ * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default
+ * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a>
+ * <h3>shiroConfigLocations</h3>
+ * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s)
+ * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}.  For example:
+ * <pre>
+ * <context-param>
+ *     <param-name>shiroConfigLocations</param-name>
+ *     <param-value>/WEB-INF/someLocation/shiro.ini</param-value>
+ * </context-param>
+ * </pre>
+ * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to
+ * acquire the {@code shiroConfigLocations} value.
+ * <p/>
+ * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource
+ * lookup behavior.  For example, the {@link IniWebEnvironment} will check the following two locations for INI config
+ * by default (in order):
+ * <ol>
+ * <li>/WEB-INF/shiro.ini</li>
+ * <li>classpath:shiro.ini</li>
+ * </ol>
+ * <h2>Web Security Enforcement</h2>
+ * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or
+ * perform web-specific security operations.  To do this, you must ensure that you have also configured the
+ * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}.
+ * <p/>
+ * Finally, it should be noted that this implementation was based on ideas in Spring 3's
+ * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common
+ * behavior.
+ *
+ * @see EnvironmentLoaderListener
+ * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter
+ * @since 1.2
+ */
+public class EnvironmentLoader {
+
+    /**
+     * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use:
+     * {@code shiroEnvironmentClass}
+     */
+    public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
+
+    /**
+     * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance:
+     * {@code shiroConfigLocations}
+     */
+    public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";
+
+    public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";
+
+    private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class);
+
+    /**
+     * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the
+     * {@link #CONFIG_LOCATIONS_PARAM} value.
+     *
+     * @param servletContext current servlet context
+     * @return the new Shiro {@code WebEnvironment} instance.
+     * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with
+     *                               the specified {@code ServletContext}.
+     */
+    public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
+
+        if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) {
+            String msg = "There is already a Shiro environment associated with the current ServletContext.  " +
+                    "Check if you have multiple EnvironmentLoader* definitions in your web.xml!";
+            throw new IllegalStateException(msg);
+        }
+
+        servletContext.log("Initializing Shiro environment");
+        log.info("Starting Shiro environment initialization.");
+
+        long startTime = System.currentTimeMillis();
+
+        try {
+            WebEnvironment environment = createEnvironment(servletContext);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment);
+
+            log.debug("Published WebEnvironment as ServletContext attribute with name [{}]",
+                    ENVIRONMENT_ATTRIBUTE_KEY);
+
+            if (log.isInfoEnabled()) {
+                long elapsed = System.currentTimeMillis() - startTime;
+                log.info("Shiro environment initialized in {} ms.", elapsed);
+            }
+
+            return environment;
+        } catch (RuntimeException ex) {
+            log.error("Shiro environment initialization failed", ex);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
+            throw ex;
+        } catch (Error err) {
+            log.error("Shiro environment initialization failed", err);
+            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
+            throw err;
+        }
+    }
+
+    /**
+     * Return the WebEnvironment implementation class to use, either the default
+     * {@link IniWebEnvironment} or a custom class if specified.
+     *
+     * @param servletContext current servlet context
+     * @return the WebEnvironment implementation class to use
+     * @see #ENVIRONMENT_CLASS_PARAM
+     * @see IniWebEnvironment
+     */
+    protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
+        String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
+        if (className != null) {
+            try {
+                return ClassUtils.forName(className);
+            } catch (UnknownClassException ex) {
+                throw new ConfigurationException(
+                        "Failed to load custom WebEnvironment class [" + className + "]", ex);
+            }
+        } else {
+            return IniWebEnvironment.class;
+        }
+    }
+
+    /**
+     * Instantiates a {@link WebEnvironment} based on the specified ServletContext.
+     * <p/>
+     * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a
+     * {@link WebEnvironment} implementation class to use.  That class is instantiated, configured, and returned.
+     * <p/>
+     * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if
+     * desired.  If not specified, the default {@link IniWebEnvironment} implementation will be used.
+     *
+     * @param sc current servlet context
+     * @return the constructed Shiro WebEnvironment instance
+     * @see MutableWebEnvironment
+     * @see ResourceConfigurable
+     */
+    protected WebEnvironment createEnvironment(ServletContext sc) {
+
+        Class<?> clazz = determineWebEnvironmentClass(sc);
+        if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
+            throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() +
+                    "] is not of required type [" + WebEnvironment.class.getName() + "]");
+        }
+
+        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
+        boolean configSpecified = StringUtils.hasText(configLocations);
+
+        if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
+            String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " +
+                    ResourceConfigurable.class.getName() + "interface.  This is required to accept any " +
+                    "configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
+            throw new ConfigurationException(msg);
+        }
+
+        MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
+
+        environment.setServletContext(sc);
+
+        if (configSpecified && (environment instanceof ResourceConfigurable)) {
+            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
+        }
+
+        customizeEnvironment(environment);
+
+        LifecycleUtils.init(environment);
+
+        return environment;
+    }
+
+    protected void customizeEnvironment(WebEnvironment environment) {
+    }
+
+    /**
+     * Destroys the {@link WebEnvironment} for the given servlet context.
+     *
+     * @param servletContext the ServletContext attributed to the WebSecurityManager
+     */
+    public void destroyEnvironment(ServletContext servletContext) {
+        servletContext.log("Cleaning up Shiro Environment");
+        try {
+            Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
+            LifecycleUtils.destroy(environment);
+        } finally {
+            servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java b/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java
new file mode 100644
index 0000000..028d4dd
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/EnvironmentLoaderListener.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * Bootstrap listener to startup and shutdown the web application's Shiro
+ * {@link WebEnvironment} at ServletContext startup and shutdown respectively.  This class exists only to
+ * implement the {@link ServletContextListener} interface. All 'real' logic is done in the parent
+ * {@link EnvironmentLoader} class.
+ * <h2>Usage</h2>
+ * Define the following in {@code web.xml}:
+ * <pre>
+ * <listener>
+ *     <listener-class><code>org.apache.shiro.web.env.EnvironmentLoaderListener</code></listener-class>
+ * </listener>
+ * </pre>
+ * Configuration options, such as the {@code WebEnvironment} class to instantiate as well as Shiro configuration
+ * resource locations are specified as {@code ServletContext} {@code context-param}s and are documented in the
+ * {@link EnvironmentLoader} JavaDoc.
+ * <h2>Shiro Filter</h2>
+ * This listener is almost always defined in conjunction with the
+ * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} to ensure security operations for web requests.  Please
+ * see the {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} JavaDoc for more.
+ *
+ *
+ * @see EnvironmentLoader
+ * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter
+ * @since 1.2
+ */
+public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {
+
+    /**
+     * Initializes the Shiro {@code WebEnvironment} and binds it to the {@code ServletContext} at application
+     * startup for future reference.
+     *
+     * @param sce the ServletContextEvent triggered upon application startup
+     */
+    public void contextInitialized(ServletContextEvent sce) {
+        initEnvironment(sce.getServletContext());
+    }
+
+    /**
+     * Destroys any previously created/bound {@code WebEnvironment} instance created by
+     * the {@link #contextInitialized(javax.servlet.ServletContextEvent)} method.
+     *
+     * @param sce the ServletContextEvent triggered upon application shutdown
+     */
+    public void contextDestroyed(ServletContextEvent sce) {
+        destroyEnvironment(sce.getServletContext());
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
new file mode 100644
index 0000000..afb15ba
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniFactorySupport;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Destroyable;
+import org.apache.shiro.util.Initializable;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * {@link WebEnvironment} implementation configured by an {@link Ini} instance or {@code Ini} resource locations.
+ *
+ * @since 1.2
+ */
+public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
+
+    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
+
+    private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
+
+    /**
+     * The Ini that configures this WebEnvironment instance.
+     */
+    private Ini ini;
+
+    /**
+     * Initializes this instance by resolving any potential (explicit or resource-configured) {@link Ini}
+     * configuration and calling {@link #configure() configure} for actual instance configuration.
+     */
+    public void init() {
+        Ini ini = getIni();
+
+        String[] configLocations = getConfigLocations();
+
+        if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) &&
+                configLocations != null && configLocations.length > 0) {
+            log.warn("Explicit INI instance has been provided, but configuration locations have also been " +
+                    "specified.  The {} implementation does not currently support multiple Ini config, but this may " +
+                    "be supported in the future. Only the INI instance will be used for configuration.",
+                    IniWebEnvironment.class.getName());
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("Checking any specified config locations.");
+            ini = getSpecifiedIni(configLocations);
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.debug("No INI instance or config locations specified.  Trying default config locations.");
+            ini = getDefaultIni();
+        }
+
+        if (CollectionUtils.isEmpty(ini)) {
+            String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
+            throw new ConfigurationException(msg);
+        }
+
+        setIni(ini);
+
+        configure();
+    }
+
+    protected void configure() {
+
+        this.objects.clear();
+
+        WebSecurityManager securityManager = createWebSecurityManager();
+        setWebSecurityManager(securityManager);
+
+        FilterChainResolver resolver = createFilterChainResolver();
+        if (resolver != null) {
+            setFilterChainResolver(resolver);
+        }
+    }
+
+    protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException {
+
+        Ini ini = null;
+
+        if (configLocations != null && configLocations.length > 0) {
+
+            if (configLocations.length > 1) {
+                log.warn("More than one Shiro .ini config location has been specified.  Only the first will be " +
+                        "used for configuration as the {} implementation does not currently support multiple " +
+                        "files.  This may be supported in the future however.", IniWebEnvironment.class.getName());
+            }
+
+            //required, as it is user specified:
+            ini = createIni(configLocations[0], true);
+        }
+
+        return ini;
+    }
+
+    protected Ini getDefaultIni() {
+
+        Ini ini = null;
+
+        String[] configLocations = getDefaultConfigLocations();
+        if (configLocations != null) {
+            for (String location : configLocations) {
+                ini = createIni(location, false);
+                if (!CollectionUtils.isEmpty(ini)) {
+                    log.debug("Discovered non-empty INI configuration at location '{}'.  Using for configuration.",
+                            location);
+                    break;
+                }
+            }
+        }
+
+        return ini;
+    }
+
+    /**
+     * Creates an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
+     * is not required.
+     * <p/>
+     * If the path is required and does not exist or is empty, a {@link ConfigurationException} will be thrown.
+     *
+     * @param configLocation the resource path to load into an {@code Ini} instance.
+     * @param required       if the path must exist and be converted to a non-empty {@link Ini} instance.
+     * @return an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
+     *         is not required.
+     * @throws ConfigurationException if the path is required but results in a null or empty Ini instance.
+     */
+    protected Ini createIni(String configLocation, boolean required) throws ConfigurationException {
+
+        Ini ini = null;
+
+        if (configLocation != null) {
+            ini = convertPathToIni(configLocation, required);
+        }
+        if (required && CollectionUtils.isEmpty(ini)) {
+            String msg = "Required configuration location '" + configLocation + "' does not exist or did not " +
+                    "contain any INI configuration.";
+            throw new ConfigurationException(msg);
+        }
+
+        return ini;
+    }
+
+    protected FilterChainResolver createFilterChainResolver() {
+
+        FilterChainResolver resolver = null;
+
+        Ini ini = getIni();
+
+        if (!CollectionUtils.isEmpty(ini)) {
+            //only create a resolver if the 'filters' or 'urls' sections are defined:
+            Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
+            Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
+            if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
+                //either the urls section or the filters section was defined.  Go ahead and create the resolver:
+                IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
+                resolver = factory.getInstance();
+            }
+        }
+
+        return resolver;
+    }
+
+    protected WebSecurityManager createWebSecurityManager() {
+        WebIniSecurityManagerFactory factory;
+        Ini ini = getIni();
+        if (CollectionUtils.isEmpty(ini)) {
+            factory = new WebIniSecurityManagerFactory();
+        } else {
+            factory = new WebIniSecurityManagerFactory(ini);
+        }
+
+        WebSecurityManager wsm = (WebSecurityManager)factory.getInstance();
+
+        //SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call,
+        //which always returned null.
+        Map<String, ?> beans = factory.getBeans();
+        if (!CollectionUtils.isEmpty(beans)) {
+            this.objects.putAll(beans);
+        }
+
+        return wsm;
+    }
+
+    /**
+     * Returns an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
+     *
+     * @return an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
+     */
+    protected String[] getDefaultConfigLocations() {
+        return new String[]{
+                DEFAULT_WEB_INI_RESOURCE_PATH,
+                IniFactorySupport.DEFAULT_INI_RESOURCE_PATH
+        };
+    }
+
+    /**
+     * Converts the specified file path to an {@link Ini} instance.
+     * <p/>
+     * If the path does not have a resource prefix as defined by {@link org.apache.shiro.io.ResourceUtils#hasResourcePrefix(String)}, the
+     * path is expected to be resolvable by the {@code ServletContext} via
+     * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
+     *
+     * @param path     the path of the INI resource to load into an INI instance.
+     * @param required if the specified path must exist
+     * @return an INI instance populated based on the given INI resource path.
+     */
+    private Ini convertPathToIni(String path, boolean required) {
+
+        //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
+
+        Ini ini = null;
+
+        if (StringUtils.hasText(path)) {
+            InputStream is = null;
+
+            //SHIRO-178: Check for servlet context resource and not only resource paths:
+            if (!ResourceUtils.hasResourcePrefix(path)) {
+                is = getServletContextResourceStream(path);
+            } else {
+                try {
+                    is = ResourceUtils.getInputStreamForPath(path);
+                } catch (IOException e) {
+                    if (required) {
+                        throw new ConfigurationException(e);
+                    } else {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unable to load optional path '" + path + "'.", e);
+                        }
+                    }
+                }
+            }
+            if (is != null) {
+                ini = new Ini();
+                ini.load(is);
+            } else {
+                if (required) {
+                    throw new ConfigurationException("Unable to load resource path '" + path + "'");
+                }
+            }
+        }
+
+        return ini;
+    }
+
+    //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
+    private InputStream getServletContextResourceStream(String path) {
+        InputStream is = null;
+
+        path = WebUtils.normalize(path);
+        ServletContext sc = getServletContext();
+        if (sc != null) {
+            is = sc.getResourceAsStream(path);
+        }
+
+        return is;
+    }
+
+    /**
+     * Returns the {@code Ini} instance reflecting this WebEnvironment's configuration.
+     *
+     * @return the {@code Ini} instance reflecting this WebEnvironment's configuration.
+     */
+    public Ini getIni() {
+        return this.ini;
+    }
+
+    /**
+     * Allows for configuration via a direct {@link Ini} instance instead of via
+     * {@link #getConfigLocations() config locations}.
+     * <p/>
+     * If the specified instance is null or empty, the fallback/default resource-based configuration will be used.
+     *
+     * @param ini the ini instance to use for creation.
+     */
+    public void setIni(Ini ini) {
+        this.ini = ini;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
new file mode 100644
index 0000000..e071e31
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+
+/**
+ * A {@code WebEnvironment} that supports 'write' operations operations.  This mainly exists to shield
+ * {@code WebEnvironment} API consumers from modification operations, which are mostly only used during Shiro
+ * environment initialization.
+ *
+ * @since 1.2
+ */
+public interface MutableWebEnvironment extends WebEnvironment {
+
+    /**
+     * Sets the {@code WebEnvironment}'s {@link FilterChainResolver}.
+     *
+     * @param filterChainResolver the {@code WebEnvironment}'s {@link FilterChainResolver}.
+     */
+    void setFilterChainResolver(FilterChainResolver filterChainResolver);
+
+    /**
+     * Sets the {@link WebEnvironment}'s associated {@code ServletContext} instance.  Invoking this method merely
+     * makes the {@code ServletContext} available to the underlying instance - it does not trigger initialization
+     * behavior.
+     *
+     * @param servletContext the {@link WebEnvironment}'s associated {@code ServletContext} instance.
+     */
+    void setServletContext(ServletContext servletContext);
+
+    /**
+     * Sets the {@code WebEnvironment}'s {@link WebSecurityManager}.
+     *
+     * @param webSecurityManager the {@code WebEnvironment}'s {@link WebSecurityManager}.
+     */
+    void setWebSecurityManager(WebSecurityManager webSecurityManager);
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java
new file mode 100644
index 0000000..e0d826a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/ResourceBasedWebEnvironment.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.config.ResourceConfigurable;
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * Abstract implementation for {@code WebEnvironment}s that can be initialized via resource paths (config files).
+ *
+ * @since 1.2
+ */
+public abstract class ResourceBasedWebEnvironment extends DefaultWebEnvironment implements ResourceConfigurable {
+
+    private String[] configLocations;
+
+    public String[] getConfigLocations() {
+        return configLocations;
+    }
+
+    public void setConfigLocations(String locations) {
+        if (!StringUtils.hasText(locations)) {
+            throw new IllegalArgumentException("Null/empty locations argument not allowed.");
+        }
+        String[] arr = StringUtils.split(locations);
+        setConfigLocations(arr);
+    }
+
+    public void setConfigLocations(String[] configLocations) {
+        this.configLocations = configLocations;
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
new file mode 100644
index 0000000..ea7693d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env;
+
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+
+import javax.servlet.ServletContext;
+
+/**
+ * A web-specific {@link Environment} instance, used in web applications.
+ *
+ * @since 1.2
+ */
+public interface WebEnvironment extends Environment {
+
+    /**
+     * Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
+     * is not available.
+     *
+     * @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
+     *         is not available.
+     */
+    FilterChainResolver getFilterChainResolver();
+
+    /**
+     * Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance.  A web application
+     * typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}.
+     *
+     * @return the {@code ServletContext} associated with this {@code WebEnvironment} instance.
+     */
+    ServletContext getServletContext();
+
+    /**
+     * Returns the web application's security manager instance.
+     *
+     * @return the web application's security manager instance.
+     */
+    WebSecurityManager getWebSecurityManager();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/package-info.java b/web/src/main/java/org/apache/shiro/web/env/package-info.java
new file mode 100644
index 0000000..6a2c9c3
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/env/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Web-specific {@link org.apache.shiro.env.Environment Environment} implementation and support.  The most important
+ * components are the {@link EnvironmentLoader} and {@link EnvironmentLoaderListener}, which are used in conjunction
+ * with the {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} to enable Shiro in a web application.
+ *
+ * @see EnvironmentLoaderListener
+ * @see EnvironmentLoader
+ */
+package org.apache.shiro.web.env;
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/filter/AccessControlFilter.java b/web/src/main/java/org/apache/shiro/web/filter/AccessControlFilter.java
new file mode 100644
index 0000000..91c401a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/AccessControlFilter.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * Superclass for any filter that controls access to a resource and may redirect the user to the login page
+ * if they are not authenticated.  This superclass provides the method
+ * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+ * which is used by many subclasses as the behavior when a user is unauthenticated.
+ *
+ * @since 0.9
+ */
+public abstract class AccessControlFilter extends PathMatchingFilter {
+
+    /**
+     * Simple default login URL equal to <code>/login.jsp</code>, which can be overridden by calling the
+     * {@link #setLoginUrl(String) setLoginUrl} method.
+     */
+    public static final String DEFAULT_LOGIN_URL = "/login.jsp";
+
+    /**
+     * Constant representing the HTTP 'GET' request method, equal to <code>GET</code>.
+     */
+    public static final String GET_METHOD = "GET";
+
+    /**
+     * Constant representing the HTTP 'POST' request method, equal to <code>POST</code>.
+     */
+    public static final String POST_METHOD = "POST";
+
+    /**
+     * The login url to used to authenticate a user, used when redirecting users if authentication is required.
+     */
+    private String loginUrl = DEFAULT_LOGIN_URL;
+
+    /**
+     * Returns the login URL used to authenticate a user.
+     * <p/>
+     * Most Shiro filters use this url
+     * as the location to redirect a user when the filter requires authentication.  Unless overridden, the
+     * {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed, which can be overridden via
+     * {@link #setLoginUrl(String) setLoginUrl}.
+     *
+     * @return the login URL used to authenticate a user, used when redirecting users if authentication is required.
+     */
+    public String getLoginUrl() {
+        return loginUrl;
+    }
+
+    /**
+     * Sets the login URL used to authenticate a user.
+     * <p/>
+     * Most Shiro filters use this url as the location to redirect a user when the filter requires
+     * authentication.  Unless overridden, the {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed.
+     *
+     * @param loginUrl the login URL used to authenticate a user, used when redirecting users if authentication is required.
+     */
+    public void setLoginUrl(String loginUrl) {
+        this.loginUrl = loginUrl;
+    }
+
+    /**
+     * Convenience method that acquires the Subject associated with the request.
+     * <p/>
+     * The default implementation simply returns
+     * {@link org.apache.shiro.SecurityUtils#getSubject() SecurityUtils.getSubject()}.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @return the Subject associated with the request.
+     */
+    protected Subject getSubject(ServletRequest request, ServletResponse response) {
+        return SecurityUtils.getSubject();
+    }
+
+    /**
+     * Returns <code>true</code> if the request is allowed to proceed through the filter normally, or <code>false</code>
+     * if the request should be handled by the
+     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(request,response,mappedValue)}
+     * method instead.
+     *
+     * @param request     the incoming <code>ServletRequest</code>
+     * @param response    the outgoing <code>ServletResponse</code>
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
+     * @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
+     *         request should be processed by this filter's
+     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
+     * @throws Exception if an error occurs during processing.
+     */
+    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
+
+    /**
+     * Processes requests where the subject was denied access as determined by the
+     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
+     * method, retaining the {@code mappedValue} that was used during configuration.
+     * <p/>
+     * This method immediately delegates to {@link #onAccessDenied(ServletRequest,ServletResponse)} as a
+     * convenience in that most post-denial behavior does not need the mapped config again.
+     *
+     * @param request     the incoming <code>ServletRequest</code>
+     * @param response    the outgoing <code>ServletResponse</code>
+     * @param mappedValue the config specified for the filter in the matching request's filter chain.
+     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
+     *         handle/render the response directly.
+     * @throws Exception if there is an error processing the request.
+     * @since 1.0
+     */
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        return onAccessDenied(request, response);
+    }
+
+    /**
+     * Processes requests where the subject was denied access as determined by the
+     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
+     * method.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
+     *         handle/render the response directly.
+     * @throws Exception if there is an error processing the request.
+     */
+    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
+
+    /**
+     * Returns <code>true</code> if
+     * {@link #isAccessAllowed(ServletRequest,ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
+     * otherwise returns the result of
+     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
+     *
+     * @return <code>true</code> if
+     *         {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
+     *         otherwise returns the result of
+     *         {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
+     * @throws Exception if an error occurs.
+     */
+    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
+    }
+
+    /**
+     * Returns <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
+     * <p/>
+     * The default implementation merely returns <code>true</code> if the incoming request matches the configured
+     * {@link #getLoginUrl() loginUrl} by calling
+     * <code>{@link #pathsMatch(String, String) pathsMatch(loginUrl, request)}</code>.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @return <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
+     */
+    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
+        return pathsMatch(getLoginUrl(), request);
+    }
+
+    /**
+     * Convenience method for subclasses to use when a login redirect is required.
+     * <p/>
+     * This implementation simply calls {@link #saveRequest(javax.servlet.ServletRequest) saveRequest(request)}
+     * and then {@link #redirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) redirectToLogin(request,response)}.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @throws IOException if an error occurs.
+     */
+    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
+        saveRequest(request);
+        redirectToLogin(request, response);
+    }
+
+    /**
+     * Convenience method merely delegates to
+     * {@link WebUtils#saveRequest(javax.servlet.ServletRequest) WebUtils.saveRequest(request)} to save the request
+     * state for reuse later.  This is mostly used to retain user request state when a redirect is issued to
+     * return the user to their originally requested url/resource.
+     * <p/>
+     * If you need to save and then immediately redirect the user to login, consider using
+     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     * saveRequestAndRedirectToLogin(request,response)} directly.
+     *
+     * @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
+     */
+    protected void saveRequest(ServletRequest request) {
+        WebUtils.saveRequest(request);
+    }
+
+    /**
+     * Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
+     * the request to that url.
+     * <p/>
+     * <b>N.B.</b>  If you want to issue a redirect with the intention of allowing the user to then return to their
+     * originally requested URL, don't use this method directly.  Instead you should call
+     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     * saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
+     * be reconstructed and re-used after a successful login.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @throws IOException if an error occurs.
+     */
+    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
+        String loginUrl = getLoginUrl();
+        WebUtils.issueRedirect(request, response, loginUrl);
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/PathConfigProcessor.java b/web/src/main/java/org/apache/shiro/web/filter/PathConfigProcessor.java
new file mode 100644
index 0000000..aa5be81
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/PathConfigProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter;
+
+import javax.servlet.Filter;
+
+/**
+ * A PathConfigProcessor processes configuration entries on a per path (url) basis.
+ *
+ * @since 0.9
+ */
+public interface PathConfigProcessor {
+
+    /**
+     * Processes the specified {@code config}, unique to the given {@code path}, and returns the Filter that should
+     * execute for that path/config combination.
+     *
+     * @param path   the path for which the {@code config} should be applied
+     * @param config the configuration for the {@code Filter} specific to the given {@code path}
+     * @return the {@code Filter} that should execute for the given path/config combination.
+     */
+    Filter processPathConfig(String path, String config);
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/PathMatchingFilter.java b/web/src/main/java/org/apache/shiro/web/filter/PathMatchingFilter.java
new file mode 100644
index 0000000..aa3b679
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/PathMatchingFilter.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter;
+
+import org.apache.shiro.util.AntPathMatcher;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.servlet.AdviceFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.apache.shiro.util.StringUtils.split;
+
+/**
+ * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>
+ *
+ * @since 0.9
+ */
+public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
+
+    /**
+     * Log available to this class only
+     */
+    private static final Logger log = LoggerFactory.getLogger(PathMatchingFilter.class);
+
+    /**
+     * PatternMatcher used in determining which paths to react to for a given request.
+     */
+    protected PatternMatcher pathMatcher = new AntPathMatcher();
+
+    /**
+     * A collection of path-to-config entries where the key is a path which this filter should process and
+     * the value is the (possibly null) configuration element specific to this Filter for that specific path.
+     * <p/>
+     * <p>To put it another way, the keys are the paths (urls) that this Filter will process.
+     * <p>The values are filter-specific data that this Filter should use when processing the corresponding
+     * key (path).  The values can be null if no Filter-specific config was specified for that url.
+     */
+    protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
+
+    /**
+     * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
+     * <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
+     * <p/>
+     * That is:
+     * <pre><code>
+     * String[] values = null;
+     * if (config != null) {
+     *     values = split(config);
+     * }
+     * <p/>
+     * this.{@link #appliedPaths appliedPaths}.put(path, values);
+     * </code></pre>
+     *
+     * @param path   the application context path to match for executing this filter.
+     * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
+     * @return this configured filter.
+     */
+    public Filter processPathConfig(String path, String config) {
+        String[] values = null;
+        if (config != null) {
+            values = split(config);
+        }
+
+        this.appliedPaths.put(path, values);
+        return this;
+    }
+
+    /**
+     * Returns the context path within the application based on the specified <code>request</code>.
+     * <p/>
+     * This implementation merely delegates to
+     * {@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) WebUtils.getPathWithinApplication(request)},
+     * but can be overridden by subclasses for custom logic.
+     *
+     * @param request the incoming <code>ServletRequest</code>
+     * @return the context path within the application.
+     */
+    protected String getPathWithinApplication(ServletRequest request) {
+        return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
+    }
+
+    /**
+     * Returns <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
+     * <code>false</code> otherwise.
+     * <p/>
+     * The default implementation acquires the <code>request</code>'s path within the application and determines
+     * if that matches:
+     * <p/>
+     * <code>String requestURI = {@link #getPathWithinApplication(javax.servlet.ServletRequest) getPathWithinApplication(request)};<br/>
+     * return {@link #pathsMatch(String, String) pathsMatch(path,requestURI)}</code>
+     *
+     * @param path    the configured url pattern to check the incoming request against.
+     * @param request the incoming ServletRequest
+     * @return <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
+     *         <code>false</code> otherwise.
+     */
+    protected boolean pathsMatch(String path, ServletRequest request) {
+        String requestURI = getPathWithinApplication(request);
+        log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI);
+        return pathsMatch(path, requestURI);
+    }
+
+    /**
+     * Returns <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
+     * <code>false</code> otherwise.
+     * <p/>
+     * Simply delegates to
+     * <b><code>this.pathMatcher.{@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>,
+     * but can be overridden by subclasses for custom matching behavior.
+     *
+     * @param pattern the pattern to match against
+     * @param path    the value to match with the specified <code>pattern</code>
+     * @return <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
+     *         <code>false</code> otherwise.
+     */
+    protected boolean pathsMatch(String pattern, String path) {
+        return pathMatcher.matches(pattern, path);
+    }
+
+    /**
+     * Implementation that handles path-matching behavior before a request is evaluated.  If the path matches and
+     * the filter
+     * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) isEnabled} for
+     * that path/config, the request will be allowed through via the result from
+     * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}.  If the
+     * path does not match or the filter is not enabled for that path, this filter will allow passthrough immediately
+     * to allow the {@code FilterChain} to continue executing.
+     * <p/>
+     * In order to retain path-matching functionality, subclasses should not override this method if at all
+     * possible, and instead override
+     * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @return {@code true} if the filter chain is allowed to continue to execute, {@code false} if a subclass has
+     *         handled the request explicitly.
+     * @throws Exception if an error occurs
+     */
+    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+
+        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
+            if (log.isTraceEnabled()) {
+                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
+            }
+            return true;
+        }
+
+        for (String path : this.appliedPaths.keySet()) {
+            // If the path does match, then pass on to the subclass implementation for specific checks
+            //(first match 'wins'):
+            if (pathsMatch(path, request)) {
+                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
+                Object config = this.appliedPaths.get(path);
+                return isFilterChainContinued(request, response, path, config);
+            }
+        }
+
+        //no path matched, allow the request to go through:
+        return true;
+    }
+
+    /**
+     * Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly.
+     *
+     * @since 1.2
+     */
+    @SuppressWarnings({"JavaDoc"})
+    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
+                                           String path, Object pathConfig) throws Exception {
+
+        if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
+            if (log.isTraceEnabled()) {
+                log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}].  " +
+                        "Delegating to subclass implementation for 'onPreHandle' check.",
+                        new Object[]{getName(), path, pathConfig});
+            }
+            //The filter is enabled for this specific request, so delegate to subclass implementations
+            //so they can decide if the request should continue through the chain or not:
+            return onPreHandle(request, response, pathConfig);
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}].  " +
+                    "The next element in the FilterChain will be called immediately.",
+                    new Object[]{getName(), path, pathConfig});
+        }
+        //This filter is disabled for this specific request,
+        //return 'true' immediately to indicate that the filter will not process the request
+        //and let the request/response to continue through the filter chain:
+        return true;
+    }
+
+    /**
+     * This default implementation always returns {@code true} and should be overridden by subclasses for custom
+     * logic if necessary.
+     *
+     * @param request     the incoming ServletRequest
+     * @param response    the outgoing ServletResponse
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
+     * @return {@code true} if the request should be able to continue, {@code false} if the filter will
+     *         handle the response directly.
+     * @throws Exception if an error occurs
+     * @see #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object)
+     */
+    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        return true;
+    }
+
+    /**
+     * Path-matching version of the parent class's
+     * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method, but additionally allows
+     * for inspection of any path-specific configuration values corresponding to the specified request.  Subclasses
+     * may wish to inspect this additional mapped configuration to determine if the filter is enabled or not.
+     * <p/>
+     * This method's default implementation ignores the {@code path} and {@code mappedValue} arguments and merely
+     * returns the value from a call to {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
+     * It is expected that subclasses override this method if they need to perform enable/disable logic for a specific
+     * request based on any path-specific config for the filter instance.
+     *
+     * @param request     the incoming servlet request
+     * @param response    the outbound servlet response
+     * @param path        the path matched for the incoming servlet request that has been configured with the given {@code mappedValue}.
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings for the given {@code path}.
+     * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the
+     *         request/response pass through immediately to the next element in the {@code FilterChain}.
+     * @throws Exception in the case of any error
+     * @since 1.2
+     */
+    @SuppressWarnings({"UnusedParameters"})
+    protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue)
+            throws Exception {
+        return isEnabled(request, response);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/AnonymousFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/AnonymousFilter.java
new file mode 100644
index 0000000..18e6b41
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/AnonymousFilter.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.web.filter.PathMatchingFilter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Filter that allows access to a path immeidately without performing security checks of any kind.
+ * <p/>
+ * This filter is useful primarily in exclusionary policies, where you have defined a url pattern
+ * to require a certain security level, but maybe only subset of urls in that pattern should allow any access.
+ * <p/>
+ * For example, if you had a user-only section of a website, you might want to require that access to
+ * any url in that section must be from an authenticated user.
+ * <p/>
+ * Here is how that would look in the IniShiroFilter configuration:
+ * <p/>
+ * <code>[urls]<br/>
+ * /user/** = authc</code>
+ * <p/>
+ * But if you wanted <code>/user/signup/**</code> to be available to anyone, you have to exclude that path since
+ * it is a subset of the first.  This is where the AnonymousFilter ('anon') is useful:
+ * <p/>
+ * <code>[urls]<br/>
+ * /user/signup/** = anon<br/>
+ * /user/** = authc</code>>
+ * <p/>
+ * Since the url pattern definitions follow a 'first match wins' paradigm, the <code>anon</code> filter will
+ * match the <code>/user/signup/**</code> paths and the <code>/user/**</code> path chain will not be evaluated.
+ *
+ * @since 0.9
+ */
+public class AnonymousFilter extends PathMatchingFilter {
+
+    /**
+     * Always returns <code>true</code> allowing unchecked access to the underlying path or resource.
+     *
+     * @return <code>true</code> always, allowing unchecked access to the underlying path or resource.
+     */
+    @Override
+    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
+        // Always return true since we allow access to anyone
+        return true;
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticatingFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticatingFilter.java
new file mode 100644
index 0000000..2d58931
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticatingFilter.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * An <code>AuthenticationFilter</code> that is capable of automatically performing an authentication attempt
+ * based on the incoming request.
+ *
+ * @since 0.9
+ */
+public abstract class AuthenticatingFilter extends AuthenticationFilter {
+    public static final String PERMISSIVE = "permissive";
+
+    //TODO - complete JavaDoc
+
+    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
+        AuthenticationToken token = createToken(request, response);
+        if (token == null) {
+            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
+                    "must be created in order to execute a login attempt.";
+            throw new IllegalStateException(msg);
+        }
+        try {
+            Subject subject = getSubject(request, response);
+            subject.login(token);
+            return onLoginSuccess(token, subject, request, response);
+        } catch (AuthenticationException e) {
+            return onLoginFailure(token, e, request, response);
+        }
+    }
+
+    protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;
+
+    protected AuthenticationToken createToken(String username, String password,
+                                              ServletRequest request, ServletResponse response) {
+        boolean rememberMe = isRememberMe(request);
+        String host = getHost(request);
+        return createToken(username, password, rememberMe, host);
+    }
+
+    protected AuthenticationToken createToken(String username, String password,
+                                              boolean rememberMe, String host) {
+        return new UsernamePasswordToken(username, password, rememberMe, host);
+    }
+
+    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
+                                     ServletRequest request, ServletResponse response) throws Exception {
+        return true;
+    }
+
+    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
+                                     ServletRequest request, ServletResponse response) {
+        return false;
+    }
+
+    /**
+     * Returns the host name or IP associated with the current subject.  This method is primarily provided for use
+     * during construction of an <code>AuthenticationToken</code>.
+     * <p/>
+     * The default implementation merely returns {@link ServletRequest#getRemoteHost()}.
+     *
+     * @param request the incoming ServletRequest
+     * @return the <code>InetAddress</code> to associate with the login attempt.
+     */
+    protected String getHost(ServletRequest request) {
+        return request.getRemoteHost();
+    }
+
+    /**
+     * Returns <code>true</code> if "rememberMe" should be enabled for the login attempt associated with the
+     * current <code>request</code>, <code>false</code> otherwise.
+     * <p/>
+     * This implementation always returns <code>false</code> and is provided as a template hook to subclasses that
+     * support <code>rememberMe</code> logins and wish to determine <code>rememberMe</code> in a custom mannner
+     * based on the current <code>request</code>.
+     *
+     * @param request the incoming ServletRequest
+     * @return <code>true</code> if "rememberMe" should be enabled for the login attempt associated with the
+     *         current <code>request</code>, <code>false</code> otherwise.
+     */
+    protected boolean isRememberMe(ServletRequest request) {
+        return false;
+    }
+
+    /**
+     * Determines whether the current subject should be allowed to make the current request.
+     * <p/>
+     * The default implementation returns <code>true</code> if the user is authenticated.  Will also return
+     * <code>true</code> if the {@link #isLoginRequest} returns false and the "permissive" flag is set.
+     *
+     * @return <code>true</code> if request should be allowed access
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        return super.isAccessAllowed(request, response, mappedValue) ||
+                (!isLoginRequest(request, response) && isPermissive(mappedValue));
+    }
+
+    /**
+     * Returns <code>true</code> if the mappedValue contains the {@link #PERMISSIVE} qualifier.
+     *
+     * @return <code>true</code> if this filter should be permissive
+     */
+    protected boolean isPermissive(Object mappedValue) {
+        if(mappedValue != null) {
+            String[] values = (String[]) mappedValue;
+            return Arrays.binarySearch(values, PERMISSIVE) >= 0;
+        }
+        return false;
+    }
+
+    /**
+     * Overrides the default behavior to call {@link #onAccessDenied} and swallow the exception if the exception is
+     * {@link UnauthenticatedException}.
+     */
+    @Override
+    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
+        if (existing instanceof UnauthenticatedException || (existing instanceof ServletException && existing.getCause() instanceof UnauthenticatedException))
+        {
+            try {
+                onAccessDenied(request, response);
+                existing = null;
+            } catch (Exception e) {
+                existing = e;
+            }
+        }
+        super.cleanup(request, response, existing);
+
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticationFilter.java
new file mode 100644
index 0000000..8f0abfa
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/AuthenticationFilter.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Base class for all Filters that require the current user to be authenticated. This class encapsulates the
+ * logic of checking whether a user is already authenticated in the system while subclasses are required to perform
+ * specific logic for unauthenticated requests.
+ *
+ * @since 0.9
+ */
+public abstract class AuthenticationFilter extends AccessControlFilter {
+
+    //TODO - complete JavaDoc
+
+    public static final String DEFAULT_SUCCESS_URL = "/";
+
+    private String successUrl = DEFAULT_SUCCESS_URL;
+
+    /**
+     * Returns the success url to use as the default location a user is sent after logging in.  Typically a redirect
+     * after login will redirect to the originally request URL; this property is provided mainly as a fallback in case
+     * the original request URL is not available or not specified.
+     * <p/>
+     * The default value is {@link #DEFAULT_SUCCESS_URL}.
+     *
+     * @return the success url to use as the default location a user is sent after logging in.
+     */
+    public String getSuccessUrl() {
+        return successUrl;
+    }
+
+    /**
+     * Sets the default/fallback success url to use as the default location a user is sent after logging in.  Typically
+     * a redirect after login will redirect to the originally request URL; this property is provided mainly as a
+     * fallback in case the original request URL is not available or not specified.
+     * <p/>
+     * The default value is {@link #DEFAULT_SUCCESS_URL}.
+     *
+     * @param successUrl the success URL to redirect the user to after a successful login.
+     */
+    public void setSuccessUrl(String successUrl) {
+        this.successUrl = successUrl;
+    }
+
+
+    /**
+     * Determines whether the current subject is authenticated.
+     * <p/>
+     * The default implementation {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) acquires}
+     * the currently executing Subject and then returns
+     * {@link org.apache.shiro.subject.Subject#isAuthenticated() subject.isAuthenticated()};
+     *
+     * @return true if the subject is authenticated; false if the subject is unauthenticated
+     */
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        Subject subject = getSubject(request, response);
+        return subject.isAuthenticated();
+    }
+
+    /**
+     * Redirects to user to the previously attempted URL after a successful login.  This implementation simply calls
+     * <code>{@link org.apache.shiro.web.util.WebUtils WebUtils}.{@link WebUtils#redirectToSavedRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) redirectToSavedRequest}</code>
+     * using the {@link #getSuccessUrl() successUrl} as the {@code fallbackUrl} argument to that call.
+     *
+     * @param request  the incoming request
+     * @param response the outgoing response
+     * @throws Exception if there is a problem redirecting.
+     */
+    protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
+        WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/BasicHttpAuthenticationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/BasicHttpAuthenticationFilter.java
new file mode 100644
index 0000000..75f15c5
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/BasicHttpAuthenticationFilter.java
@@ -0,0 +1,369 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Locale;
+
+
+/**
+ * Requires the requesting user to be {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated} for the
+ * request to continue, and if they're not, requires the user to login via the HTTP Basic protocol-specific challenge.
+ * Upon successful login, they're allowed to continue on to the requested resource/url.
+ * <p/>
+ * This implementation is a 'clean room' Java implementation of Basic HTTP Authentication specification per
+ * <a href="ftp://ftp.isi.edu/in-notes/rfc2617.txt">RFC 2617</a>.
+ * <p/>
+ * Basic authentication functions as follows:
+ * <ol>
+ * <li>A request comes in for a resource that requires authentication.</li>
+ * <li>The server replies with a 401 response status, sets the <code>WWW-Authenticate</code> header, and the contents of a
+ * page informing the user that the incoming resource requires authentication.</li>
+ * <li>Upon receiving this <code>WWW-Authenticate</code> challenge from the server, the client then takes a
+ * username and a password and puts them in the following format:
+ * <p><code>username:password</code></p></li>
+ * <li>This token is then base 64 encoded.</li>
+ * <li>The client then sends another request for the same resource with the following header:<br/>
+ * <p><code>Authorization: Basic <em>Base64_encoded_username_and_password</em></code></p></li>
+ * </ol>
+ * The {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method will
+ * only be called if the subject making the request is not
+ * {@link org.apache.shiro.subject.Subject#isAuthenticated() authenticated}
+ *
+ * @see <a href="ftp://ftp.isi.edu/in-notes/rfc2617.txt">RFC 2617</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Basic_access_authentication">Basic Access Authentication</a>
+ * @since 0.9
+ */
+public class BasicHttpAuthenticationFilter extends AuthenticatingFilter {
+
+    /**
+     * This class's private logger.
+     */
+    private static final Logger log = LoggerFactory.getLogger(BasicHttpAuthenticationFilter.class);
+
+    /**
+     * HTTP Authorization header, equal to <code>Authorization</code>
+     */
+    protected static final String AUTHORIZATION_HEADER = "Authorization";
+
+    /**
+     * HTTP Authentication header, equal to <code>WWW-Authenticate</code>
+     */
+    protected static final String AUTHENTICATE_HEADER = "WWW-Authenticate";
+
+    /**
+     * The name that is displayed during the challenge process of authentication, defauls to <code>application</code>
+     * and can be overridden by the {@link #setApplicationName(String) setApplicationName} method.
+     */
+    private String applicationName = "application";
+
+    /**
+     * The authcScheme to look for in the <code>Authorization</code> header, defaults to <code>BASIC</code>
+     */
+    private String authcScheme = HttpServletRequest.BASIC_AUTH;
+
+    /**
+     * The authzScheme value to look for in the <code>Authorization</code> header, defaults to <code>BASIC</code>
+     */
+    private String authzScheme = HttpServletRequest.BASIC_AUTH;
+
+    /**
+     * Returns the name to use in the ServletResponse's <b><code>WWW-Authenticate</code></b> header.
+     * <p/>
+     * Per RFC 2617, this name name is displayed to the end user when they are asked to authenticate.  Unless overridden
+     * by the {@link #setApplicationName(String) setApplicationName(String)} method, the default value is 'application'.
+     * <p/>
+     * Please see {@link #setApplicationName(String) setApplicationName(String)} for an example of how this functions.
+     *
+     * @return the name to use in the ServletResponse's 'WWW-Authenticate' header.
+     */
+    public String getApplicationName() {
+        return applicationName;
+    }
+
+    /**
+     * Sets the name to use in the ServletResponse's <b><code>WWW-Authenticate</code></b> header.
+     * <p/>
+     * Per RFC 2617, this name name is displayed to the end user when they are asked to authenticate.  Unless overridden
+     * by this method, the default value is "application"
+     * <p/>
+     * For example, setting this property to the value <b><code>Awesome Webapp</code></b> will result in the
+     * following header:
+     * <p/>
+     * <code>WWW-Authenticate: Basic realm="<b>Awesome Webapp</b>"</code>
+     * <p/>
+     * Side note: As you can see from the header text, the HTTP Basic specification calls
+     * this the authentication 'realm', but we call this the 'applicationName' instead to avoid confusion with
+     * Shiro's Realm constructs.
+     *
+     * @param applicationName the name to use in the ServletResponse's 'WWW-Authenticate' header.
+     */
+    public void setApplicationName(String applicationName) {
+        this.applicationName = applicationName;
+    }
+
+    /**
+     * Returns the HTTP <b><code>Authorization</code></b> header value that this filter will respond to as indicating
+     * a login request.
+     * <p/>
+     * Unless overridden by the {@link #setAuthzScheme(String) setAuthzScheme(String)} method, the
+     * default value is <code>BASIC</code>.
+     *
+     * @return the Http 'Authorization' header value that this filter will respond to as indicating a login request
+     */
+    public String getAuthzScheme() {
+        return authzScheme;
+    }
+
+    /**
+     * Sets the HTTP <b><code>Authorization</code></b> header value that this filter will respond to as indicating a
+     * login request.
+     * <p/>
+     * Unless overridden by this method, the default value is <code>BASIC</code>
+     *
+     * @param authzScheme the HTTP <code>Authorization</code> header value that this filter will respond to as
+     *                    indicating a login request.
+     */
+    public void setAuthzScheme(String authzScheme) {
+        this.authzScheme = authzScheme;
+    }
+
+    /**
+     * Returns the HTTP <b><code>WWW-Authenticate</code></b> header scheme that this filter will use when sending
+     * the HTTP Basic challenge response.  The default value is <code>BASIC</code>.
+     *
+     * @return the HTTP <code>WWW-Authenticate</code> header scheme that this filter will use when sending the HTTP
+     *         Basic challenge response.
+     * @see #sendChallenge
+     */
+    public String getAuthcScheme() {
+        return authcScheme;
+    }
+
+    /**
+     * Sets the HTTP <b><code>WWW-Authenticate</code></b> header scheme that this filter will use when sending the
+     * HTTP Basic challenge response.  The default value is <code>BASIC</code>.
+     *
+     * @param authcScheme the HTTP <code>WWW-Authenticate</code> header scheme that this filter will use when
+     *                    sending the Http Basic challenge response.
+     * @see #sendChallenge
+     */
+    public void setAuthcScheme(String authcScheme) {
+        this.authcScheme = authcScheme;
+    }
+
+    /**
+     * Processes unauthenticated requests. It handles the two-stage request/challenge authentication protocol.
+     *
+     * @param request  incoming ServletRequest
+     * @param response outgoing ServletResponse
+     * @return true if the request should be processed; false if the request should not continue to be processed
+     */
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        boolean loggedIn = false; //false by default or we wouldn't be in this method
+        if (isLoginAttempt(request, response)) {
+            loggedIn = executeLogin(request, response);
+        }
+        if (!loggedIn) {
+            sendChallenge(request, response);
+        }
+        return loggedIn;
+    }
+
+    /**
+     * Determines whether the incoming request is an attempt to log in.
+     * <p/>
+     * The default implementation obtains the value of the request's
+     * {@link #AUTHORIZATION_HEADER AUTHORIZATION_HEADER}, and if it is not <code>null</code>, delegates
+     * to {@link #isLoginAttempt(String) isLoginAttempt(authzHeaderValue)}. If the header is <code>null</code>,
+     * <code>false</code> is returned.
+     *
+     * @param request  incoming ServletRequest
+     * @param response outgoing ServletResponse
+     * @return true if the incoming request is an attempt to log in based, false otherwise
+     */
+    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
+        String authzHeader = getAuthzHeader(request);
+        return authzHeader != null && isLoginAttempt(authzHeader);
+    }
+
+    /**
+     * Delegates to {@link #isLoginAttempt(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isLoginAttempt}.
+     */
+    @Override
+    protected final boolean isLoginRequest(ServletRequest request, ServletResponse response) {
+        return this.isLoginAttempt(request, response);
+    }
+
+    /**
+     * Returns the {@link #AUTHORIZATION_HEADER AUTHORIZATION_HEADER} from the specified ServletRequest.
+     * <p/>
+     * This implementation merely casts the request to an <code>HttpServletRequest</code> and returns the header:
+     * <p/>
+     * <code>HttpServletRequest httpRequest = {@link WebUtils#toHttp(javax.servlet.ServletRequest) toHttp(reaquest)};<br/>
+     * return httpRequest.getHeader({@link #AUTHORIZATION_HEADER AUTHORIZATION_HEADER});</code>
+     *
+     * @param request the incoming <code>ServletRequest</code>
+     * @return the <code>Authorization</code> header's value.
+     */
+    protected String getAuthzHeader(ServletRequest request) {
+        HttpServletRequest httpRequest = WebUtils.toHttp(request);
+        return httpRequest.getHeader(AUTHORIZATION_HEADER);
+    }
+
+    /**
+     * Default implementation that returns <code>true</code> if the specified <code>authzHeader</code>
+     * starts with the same (case-insensitive) characters specified by the
+     * {@link #getAuthzScheme() authzScheme}, <code>false</code> otherwise.
+     * <p/>
+     * That is:
+     * <p/>
+     * <code>String authzScheme = getAuthzScheme().toLowerCase();<br/>
+     * return authzHeader.toLowerCase().startsWith(authzScheme);</code>
+     *
+     * @param authzHeader the 'Authorization' header value (guaranteed to be non-null if the
+     *                    {@link #isLoginAttempt(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method is not overriden).
+     * @return <code>true</code> if the authzHeader value matches that configured as defined by
+     *         the {@link #getAuthzScheme() authzScheme}.
+     */
+    protected boolean isLoginAttempt(String authzHeader) {
+        //SHIRO-415: use English Locale:
+        String authzScheme = getAuthzScheme().toLowerCase(Locale.ENGLISH);
+        return authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme);
+    }
+
+    /**
+     * Builds the challenge for authorization by setting a HTTP <code>401</code> (Unauthorized) status as well as the
+     * response's {@link #AUTHENTICATE_HEADER AUTHENTICATE_HEADER}.
+     * <p/>
+     * The header value constructed is equal to:
+     * <p/>
+     * <code>{@link #getAuthcScheme() getAuthcScheme()} + " realm=\"" + {@link #getApplicationName() getApplicationName()} + "\"";</code>
+     *
+     * @param request  incoming ServletRequest, ignored by this implementation
+     * @param response outgoing ServletResponse
+     * @return false - this sends the challenge to be sent back
+     */
+    protected boolean sendChallenge(ServletRequest request, ServletResponse response) {
+        if (log.isDebugEnabled()) {
+            log.debug("Authentication required: sending 401 Authentication challenge response.");
+        }
+        HttpServletResponse httpResponse = WebUtils.toHttp(response);
+        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        String authcHeader = getAuthcScheme() + " realm=\"" + getApplicationName() + "\"";
+        httpResponse.setHeader(AUTHENTICATE_HEADER, authcHeader);
+        return false;
+    }
+
+    /**
+     * Creates an AuthenticationToken for use during login attempt with the provided credentials in the http header.
+     * <p/>
+     * This implementation:
+     * <ol><li>acquires the username and password based on the request's
+     * {@link #getAuthzHeader(javax.servlet.ServletRequest) authorization header} via the
+     * {@link #getPrincipalsAndCredentials(String, javax.servlet.ServletRequest) getPrincipalsAndCredentials} method</li>
+     * <li>The return value of that method is converted to an <code>AuthenticationToken</code> via the
+     * {@link #createToken(String, String, javax.servlet.ServletRequest, javax.servlet.ServletResponse) createToken} method</li>
+     * <li>The created <code>AuthenticationToken</code> is returned.</li>
+     * </ol>
+     *
+     * @param request  incoming ServletRequest
+     * @param response outgoing ServletResponse
+     * @return the AuthenticationToken used to execute the login attempt
+     */
+    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
+        String authorizationHeader = getAuthzHeader(request);
+        if (authorizationHeader == null || authorizationHeader.length() == 0) {
+            // Create an empty authentication token since there is no
+            // Authorization header.
+            return createToken("", "", request, response);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Attempting to execute login with headers [" + authorizationHeader + "]");
+        }
+
+        String[] prinCred = getPrincipalsAndCredentials(authorizationHeader, request);
+        if (prinCred == null || prinCred.length < 2) {
+            // Create an authentication token with an empty password,
+            // since one hasn't been provided in the request.
+            String username = prinCred == null || prinCred.length == 0 ? "" : prinCred[0];
+            return createToken(username, "", request, response);
+        }
+
+        String username = prinCred[0];
+        String password = prinCred[1];
+
+        return createToken(username, password, request, response);
+    }
+
+    /**
+     * Returns the username obtained from the
+     * {@link #getAuthzHeader(javax.servlet.ServletRequest) authorizationHeader}.
+     * <p/>
+     * Once the {@code authzHeader} is split per the RFC (based on the space character ' '), the resulting split tokens
+     * are translated into the username/password pair by the
+     * {@link #getPrincipalsAndCredentials(String, String) getPrincipalsAndCredentials(scheme,encoded)} method.
+     *
+     * @param authorizationHeader the authorization header obtained from the request.
+     * @param request             the incoming ServletRequest
+     * @return the username (index 0)/password pair (index 1) submitted by the user for the given header value and request.
+     * @see #getAuthzHeader(javax.servlet.ServletRequest)
+     */
+    protected String[] getPrincipalsAndCredentials(String authorizationHeader, ServletRequest request) {
+        if (authorizationHeader == null) {
+            return null;
+        }
+        String[] authTokens = authorizationHeader.split(" ");
+        if (authTokens == null || authTokens.length < 2) {
+            return null;
+        }
+        return getPrincipalsAndCredentials(authTokens[0], authTokens[1]);
+    }
+
+    /**
+     * Returns the username and password pair based on the specified <code>encoded</code> String obtained from
+     * the request's authorization header.
+     * <p/>
+     * Per RFC 2617, the default implementation first Base64 decodes the string and then splits the resulting decoded
+     * string into two based on the ":" character.  That is:
+     * <p/>
+     * <code>String decoded = Base64.decodeToString(encoded);<br/>
+     * return decoded.split(":");</code>
+     *
+     * @param scheme  the {@link #getAuthcScheme() authcScheme} found in the request
+     *                {@link #getAuthzHeader(javax.servlet.ServletRequest) authzHeader}.  It is ignored by this implementation,
+     *                but available to overriding implementations should they find it useful.
+     * @param encoded the Base64-encoded username:password value found after the scheme in the header
+     * @return the username (index 0)/password (index 1) pair obtained from the encoded header data.
+     */
+    protected String[] getPrincipalsAndCredentials(String scheme, String encoded) {
+        String decoded = Base64.decodeToString(encoded);
+        return decoded.split(":", 2);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/FormAuthenticationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/FormAuthenticationFilter.java
new file mode 100644
index 0000000..340842d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/FormAuthenticationFilter.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user
+ * to login via by redirecting them to the {@link #setLoginUrl(String) loginUrl} you configure.
+ * <p/>
+ * <p>This filter constructs a {@link UsernamePasswordToken UsernamePasswordToken} with the values found in
+ * {@link #setUsernameParam(String) username}, {@link #setPasswordParam(String) password},
+ * and {@link #setRememberMeParam(String) rememberMe} request parameters.  It then calls
+ * {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) Subject.login(usernamePasswordToken)},
+ * effectively automatically performing a login attempt.  Note that the login attempt will only occur when the
+ * {@link #isLoginSubmission(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isLoginSubmission(request,response)}
+ * is <code>true</code>, which by default occurs when the request is for the {@link #setLoginUrl(String) loginUrl} and
+ * is a POST request.
+ * <p/>
+ * <p>If the login attempt fails, the resulting <code>AuthenticationException</code> fully qualified class name will
+ * be set as a request attribute under the {@link #setFailureKeyAttribute(String) failureKeyAttribute} key.  This
+ * FQCN can be used as an i18n key or lookup mechanism to explain to the user why their login attempt failed
+ * (e.g. no account, incorrect password, etc).
+ * <p/>
+ * <p>If you would prefer to handle the authentication validation and login in your own code, consider using the
+ * {@link PassThruAuthenticationFilter} instead, which allows requests to the
+ * {@link #loginUrl} to pass through to your application's code directly.
+ *
+ * @see PassThruAuthenticationFilter
+ * @since 0.9
+ */
+public class FormAuthenticationFilter extends AuthenticatingFilter {
+
+    //TODO - complete JavaDoc
+
+    public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";
+
+    public static final String DEFAULT_USERNAME_PARAM = "username";
+    public static final String DEFAULT_PASSWORD_PARAM = "password";
+    public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
+
+    private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilter.class);
+
+    private String usernameParam = DEFAULT_USERNAME_PARAM;
+    private String passwordParam = DEFAULT_PASSWORD_PARAM;
+    private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
+
+    private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;
+
+    public FormAuthenticationFilter() {
+        setLoginUrl(DEFAULT_LOGIN_URL);
+    }
+
+    @Override
+    public void setLoginUrl(String loginUrl) {
+        String previous = getLoginUrl();
+        if (previous != null) {
+            this.appliedPaths.remove(previous);
+        }
+        super.setLoginUrl(loginUrl);
+        if (log.isTraceEnabled()) {
+            log.trace("Adding login url to applied paths.");
+        }
+        this.appliedPaths.put(getLoginUrl(), null);
+    }
+
+    public String getUsernameParam() {
+        return usernameParam;
+    }
+
+    /**
+     * Sets the request parameter name to look for when acquiring the username.  Unless overridden by calling this
+     * method, the default is <code>username</code>.
+     *
+     * @param usernameParam the name of the request param to check for acquiring the username.
+     */
+    public void setUsernameParam(String usernameParam) {
+        this.usernameParam = usernameParam;
+    }
+
+    public String getPasswordParam() {
+        return passwordParam;
+    }
+
+    /**
+     * Sets the request parameter name to look for when acquiring the password.  Unless overridden by calling this
+     * method, the default is <code>password</code>.
+     *
+     * @param passwordParam the name of the request param to check for acquiring the password.
+     */
+    public void setPasswordParam(String passwordParam) {
+        this.passwordParam = passwordParam;
+    }
+
+    public String getRememberMeParam() {
+        return rememberMeParam;
+    }
+
+    /**
+     * Sets the request parameter name to look for when acquiring the rememberMe boolean value.  Unless overridden
+     * by calling this method, the default is <code>rememberMe</code>.
+     * <p/>
+     * RememberMe will be <code>true</code> if the parameter value equals any of those supported by
+     * {@link org.apache.shiro.web.util.WebUtils#isTrue(javax.servlet.ServletRequest, String) WebUtils.isTrue(request,value)}, <code>false</code>
+     * otherwise.
+     *
+     * @param rememberMeParam the name of the request param to check for acquiring the rememberMe boolean value.
+     */
+    public void setRememberMeParam(String rememberMeParam) {
+        this.rememberMeParam = rememberMeParam;
+    }
+
+    public String getFailureKeyAttribute() {
+        return failureKeyAttribute;
+    }
+
+    public void setFailureKeyAttribute(String failureKeyAttribute) {
+        this.failureKeyAttribute = failureKeyAttribute;
+    }
+
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        if (isLoginRequest(request, response)) {
+            if (isLoginSubmission(request, response)) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Login submission detected.  Attempting to execute login.");
+                }
+                return executeLogin(request, response);
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace("Login page view.");
+                }
+                //allow them to see the login page ;)
+                return true;
+            }
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
+                        "Authentication url [" + getLoginUrl() + "]");
+            }
+
+            saveRequestAndRedirectToLogin(request, response);
+            return false;
+        }
+    }
+
+    /**
+     * This default implementation merely returns <code>true</code> if the request is an HTTP <code>POST</code>,
+     * <code>false</code> otherwise. Can be overridden by subclasses for custom login submission detection behavior.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse.
+     * @return <code>true</code> if the request is an HTTP <code>POST</code>, <code>false</code> otherwise.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
+        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
+    }
+
+    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
+        String username = getUsername(request);
+        String password = getPassword(request);
+        return createToken(username, password, request, response);
+    }
+
+    protected boolean isRememberMe(ServletRequest request) {
+        return WebUtils.isTrue(request, getRememberMeParam());
+    }
+
+    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
+                                     ServletRequest request, ServletResponse response) throws Exception {
+        issueSuccessRedirect(request, response);
+        //we handled the success redirect directly, prevent the chain from continuing:
+        return false;
+    }
+
+    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
+                                     ServletRequest request, ServletResponse response) {
+        setFailureAttribute(request, e);
+        //login failed, let request continue back to the login page:
+        return true;
+    }
+
+    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
+        String className = ae.getClass().getName();
+        request.setAttribute(getFailureKeyAttribute(), className);
+    }
+
+    protected String getUsername(ServletRequest request) {
+        return WebUtils.getCleanParam(request, getUsernameParam());
+    }
+
+    protected String getPassword(ServletRequest request) {
+        return WebUtils.getCleanParam(request, getPasswordParam());
+    }
+
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
new file mode 100644
index 0000000..c0d695b
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.servlet.AdviceFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Simple Filter that, upon receiving a request, will immediately log-out the currently executing
+ * {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
+ * and then redirect them to a configured {@link #getRedirectUrl() redirectUrl}.
+ *
+ * @since 1.2
+ */
+public class LogoutFilter extends AdviceFilter {
+    
+    private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);
+
+    /**
+     * The default redirect URL to where the user will be redirected after logout.  The value is {@code "/"}, Shiro's
+     * representation of the web application's context root.
+     */
+    public static final String DEFAULT_REDIRECT_URL = "/";
+
+    /**
+     * The URL to where the user will be redirected after logout.
+     */
+    private String redirectUrl = DEFAULT_REDIRECT_URL;
+
+    /**
+     * Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject},
+     * a potentially Subject or request-specific
+     * {@link #getRedirectUrl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, org.apache.shiro.subject.Subject) redirectUrl},
+     * and redirects the end-user to that redirect url.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @return {@code false} always as typically no further interaction should be done after user logout.
+     * @throws Exception if there is any error.
+     */
+    @Override
+    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+        Subject subject = getSubject(request, response);
+        String redirectUrl = getRedirectUrl(request, response, subject);
+        //try/catch added for SHIRO-298:
+        try {
+            subject.logout();
+        } catch (SessionException ise) {
+            log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);
+        }
+        issueRedirect(request, response, redirectUrl);
+        return false;
+    }
+
+    /**
+     * Returns the currently executing {@link Subject}.  This implementation merely defaults to calling
+     * {@code SecurityUtils.}{@link org.apache.shiro.SecurityUtils#getSubject() getSubject()}, but can be overridden
+     * by subclasses for different retrieval strategies.
+     *
+     * @param request  the incoming Servlet request
+     * @param response the outgoing Servlet response
+     * @return the currently executing {@link Subject}.
+     */
+    protected Subject getSubject(ServletRequest request, ServletResponse response) {
+        return SecurityUtils.getSubject();
+    }
+
+    /**
+     * Issues an HTTP redirect to the specified URL after subject logout.  This implementation simply calls
+     * {@code WebUtils.}{@link WebUtils#issueRedirect(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) issueRedirect(request,response,redirectUrl)}.
+     *
+     * @param request  the incoming Servlet request
+     * @param response the outgoing Servlet response
+     * @param redirectUrl the URL to where the browser will be redirected immediately after Subject logout.
+     * @throws Exception if there is any error.
+     */
+    protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl) throws Exception {
+        WebUtils.issueRedirect(request, response, redirectUrl);
+    }
+
+    /**
+     * Returns the redirect URL to send the user after logout.  This default implementation ignores the arguments and
+     * returns the static configured {@link #getRedirectUrl() redirectUrl} property, but this method may be overridden
+     * by subclasses to dynamically construct the URL based on the request or subject if necessary.
+     * <p/>
+     * Note: the Subject is <em>not</em> yet logged out at the time this method is invoked.  You may access the Subject's
+     * session if one is available and if necessary.
+     * <p/>
+     * Tip: if you need to access the Subject's session, consider using the
+     * {@code Subject.}{@link Subject#getSession(boolean) getSession(false)} method to ensure a new session isn't created unnecessarily.
+     * If a session would be created, it will be immediately stopped after logout, not providing any value and
+     * unnecessarily taxing session infrastructure/resources.
+     *
+     * @param request the incoming Servlet request
+     * @param response the outgoing ServletResponse
+     * @param subject the not-yet-logged-out currently executing Subject
+     * @return the redirect URL to send the user after logout.
+     */
+    protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) {
+        return getRedirectUrl();
+    }
+
+    /**
+     * Returns the URL to where the user will be redirected after logout.  Default is the web application's context
+     * root, i.e. {@code "/"}
+     *
+     * @return the URL to where the user will be redirected after logout.
+     */
+    public String getRedirectUrl() {
+        return redirectUrl;
+    }
+
+    /**
+     * Sets the URL to where the user will be redirected after logout.  Default is the web application's context
+     * root, i.e. {@code "/"}
+     *
+     * @param redirectUrl the url to where the user will be redirected after logout
+     */
+    public void setRedirectUrl(String redirectUrl) {
+        this.redirectUrl = redirectUrl;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/PassThruAuthenticationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/PassThruAuthenticationFilter.java
new file mode 100644
index 0000000..92699d3
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/PassThruAuthenticationFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * An authentication filter that redirects the user to the login page when they are trying to access
+ * a protected resource.  However, if the user is trying to access the login page, the filter lets
+ * the request pass through to the application code.
+ * <p/>
+ * The difference between this filter and the {@link FormAuthenticationFilter FormAuthenticationFilter} is that
+ * on a login submission (by default an HTTP POST to the login URL), the <code>FormAuthenticationFilter</code> filter
+ * attempts to automatically authenticate the user by passing the <code>username</code> and <code>password</code>
+ * request parameter values to
+ * {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) Subject.login(usernamePasswordToken)}
+ * directly.
+ * <p/>
+ * Conversely, this controller always passes all requests to the {@link #setLoginUrl loginUrl} through, both GETs and
+ * POSTs.  This is useful in cases where the developer wants to write their own login behavior, which should include a
+ * call to {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) Subject.login(AuthenticationToken)}
+ * at some point.  For example,  if the developer has their own custom MVC login controller or validator,
+ * this <code>PassThruAuthenticationFilter</code> may be appropriate.
+ *
+ * @see FormAuthenticationFilter
+ * @since 0.9
+ */
+public class PassThruAuthenticationFilter extends AuthenticationFilter {
+
+    //TODO - complete JavaDoc
+
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        if (isLoginRequest(request, response)) {
+            return true;
+        } else {
+            saveRequestAndRedirectToLogin(request, response);
+            return false;
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/UserFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/UserFilter.java
new file mode 100644
index 0000000..8816625
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/UserFilter.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.AccessControlFilter;
+
+/**
+ * Filter that allows access to resources if the accessor is a known user, which is defined as
+ * having a known principal.  This means that any user who is authenticated or remembered via a
+ * 'remember me' feature will be allowed access from this filter.
+ * <p/>
+ * If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}</p>
+ *
+ * @since 0.9
+ */
+public class UserFilter extends AccessControlFilter {
+
+    /**
+     * Returns <code>true</code> if the request is a
+     * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
+     * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
+     * is not <code>null</code>, <code>false</code> otherwise.
+     *
+     * @return <code>true</code> if the request is a
+     * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
+     * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
+     * is not <code>null</code>, <code>false</code> otherwise.
+     */
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        if (isLoginRequest(request, response)) {
+            return true;
+        } else {
+            Subject subject = getSubject(request, response);
+            // If principal is not null, then the user is known and should be allowed access.
+            return subject.getPrincipal() != null;
+        }
+    }
+
+    /**
+     * This default implementation simply calls
+     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) saveRequestAndRedirectToLogin}
+     * and then immediately returns <code>false</code>, thereby preventing the chain from continuing so the redirect may
+     * execute.
+     */
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        saveRequestAndRedirectToLogin(request, response);
+        return false;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/package-info.java b/web/src/main/java/org/apache/shiro/web/filter/authc/package-info.java
new file mode 100644
index 0000000..f94395d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Servlet {@link javax.servlet.Filter Filter} implementations specific to controlling access based on a
+ * subject's authentication status, or those that can execute authentications (log-ins) directly.
+ */
+package org.apache.shiro.web.filter.authc;
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/AuthorizationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/AuthorizationFilter.java
new file mode 100644
index 0000000..8500cbe
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/AuthorizationFilter.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Superclass for authorization-related filters.  If an request is unauthorized, response handling is delegated to the
+ * {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied} method, which
+ * provides reasonable handling for most applications.
+ *
+ * @see #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ * @since 0.9
+ */
+public abstract class AuthorizationFilter extends AccessControlFilter {
+
+    /**
+     * The URL to which users should be redirected if they are denied access to an underlying path or resource,
+     * {@code null} by default which will issue a raw {@link HttpServletResponse#SC_UNAUTHORIZED} response
+     * (401 Unauthorized).
+     */
+    private String unauthorizedUrl;
+
+    /**
+     * Returns the URL to which users should be redirected if they are denied access to an underlying path or resource,
+     * or {@code null} if a raw {@link HttpServletResponse#SC_UNAUTHORIZED} response should be issued (401 Unauthorized).
+     * <p/>
+     * The default is {@code null}, ensuring default web server behavior.  Override this default by calling the
+     * {@link #setUnauthorizedUrl(String) setUnauthorizedUrl} method with a meaningful path within your application
+     * if you would like to show the user a 'nice' page in the event of unauthorized access.
+     *
+     * @return the URL to which users should be redirected if they are denied access to an underlying path or resource,
+     *         or {@code null} if a raw {@link HttpServletResponse#SC_UNAUTHORIZED} response should be issued (401 Unauthorized).
+     */
+    public String getUnauthorizedUrl() {
+        return unauthorizedUrl;
+    }
+
+    /**
+     * Sets the URL to which users should be redirected if they are denied access to an underlying path or resource.
+     * <p/>
+     * If the value is {@code null} a raw {@link HttpServletResponse#SC_UNAUTHORIZED} response will
+     * be issued (401 Unauthorized), retaining default web server behavior.
+     * <p/>
+     * Unless overridden by calling this method, the default value is {@code null}.  If desired, you can specify a
+     * meaningful path within your application if you would like to show the user a 'nice' page in the event of
+     * unauthorized access.
+     *
+     * @param unauthorizedUrl the URL to which users should be redirected if they are denied access to an underlying
+     *                        path or resource, or {@code null} to a ensure raw {@link HttpServletResponse#SC_UNAUTHORIZED} response is
+     *                        issued (401 Unauthorized).
+     */
+    public void setUnauthorizedUrl(String unauthorizedUrl) {
+        this.unauthorizedUrl = unauthorizedUrl;
+    }
+
+    /**
+     * Handles the response when access has been denied.  It behaves as follows:
+     * <ul>
+     * <li>If the {@code Subject} is unknown<sup><a href="#known">[1]</a></sup>:
+     * <ol><li>The incoming request will be saved and they will be redirected to the login page for authentication
+     * (via the {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+     * method).</li>
+     * <li>Once successfully authenticated, they will be redirected back to the originally attempted page.</li></ol>
+     * </li>
+     * <li>If the Subject is known:</li>
+     * <ol>
+     * <li>The HTTP {@link HttpServletResponse#SC_UNAUTHORIZED} header will be set (401 Unauthorized)</li>
+     * <li>If the {@link #getUnauthorizedUrl() unauthorizedUrl} has been configured, a redirect will be issued to that
+     * URL.  Otherwise the 401 response is rendered normally</li>
+     * </ul>
+     * <code><a name="known">[1]</a></code>: A {@code Subject} is 'known' when
+     * <code>subject.{@link org.apache.shiro.subject.Subject#getPrincipal() getPrincipal()}</code> is not {@code null},
+     * which implicitly means that the subject is either currently authenticated or they have been remembered via
+     * 'remember me' services.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @return {@code false} always for this implementation.
+     * @throws IOException if there is any servlet error.
+     */
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
+
+        Subject subject = getSubject(request, response);
+        // If the subject isn't identified, redirect to login URL
+        if (subject.getPrincipal() == null) {
+            saveRequestAndRedirectToLogin(request, response);
+        } else {
+            // If subject is known but not authorized, redirect to the unauthorized URL if there is one
+            // If no unauthorized URL is specified, just return an unauthorized HTTP status code
+            String unauthorizedUrl = getUnauthorizedUrl();
+            //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:
+            if (StringUtils.hasText(unauthorizedUrl)) {
+                WebUtils.issueRedirect(request, response, unauthorizedUrl);
+            } else {
+                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/HostFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/HostFilter.java
new file mode 100644
index 0000000..c215741
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/HostFilter.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.apache.shiro.util.StringUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.util.regex.Pattern;
+import java.util.Map;
+
+/**
+ * A Filter that can allow or deny access based on the host that sent the request.
+ *
+ * <b>WARNING:</b> NOT YET FULLY IMPLEMENTED!!!  Work in progress.
+ *
+ * @since 1.0
+ */
+public class HostFilter extends AuthorizationFilter {
+
+    public static final String IPV4_QUAD_REGEX = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))";
+
+    public static final String IPV4_REGEX = "(?:" + IPV4_QUAD_REGEX + "\\.){3}" + IPV4_QUAD_REGEX + "$";
+    public static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
+
+    public static final String PRIVATE_CLASS_B_SUBSET = "(?:1[6-9]|2[0-9]|3[0-1])";
+
+    public static final String PRIVATE_CLASS_A_REGEX = "10\\.(?:" + IPV4_QUAD_REGEX + "\\.){2}" + IPV4_QUAD_REGEX + "$";
+
+    public static final String PRIVATE_CLASS_B_REGEX =
+            "172\\." + PRIVATE_CLASS_B_SUBSET + "\\." + IPV4_QUAD_REGEX + "\\." + IPV4_QUAD_REGEX + "$";
+
+    public static final String PRIVATE_CLASS_C_REGEX = "192\\.168\\." + IPV4_QUAD_REGEX + "\\." + IPV4_QUAD_REGEX + "$";
+
+    Map<String, String> authorizedIps; //user-configured IP (which can be wildcarded) to constructed regex mapping
+    Map<String, String> deniedIps;
+    Map<String, String> authorizedHostnames;
+    Map<String, String> deniedHostnames;
+
+
+    public void setAuthorizedHosts(String authorizedHosts) {
+        if (!StringUtils.hasText(authorizedHosts)) {
+            throw new IllegalArgumentException("authorizedHosts argument cannot be null or empty.");
+        }
+        String[] hosts = StringUtils.tokenizeToStringArray(authorizedHosts, ", \t");
+
+        for (String host : hosts) {
+            //replace any periods with \\. to ensure the regex works:
+            String periodsReplaced = host.replace(".", "\\.");
+            //check for IPv4:
+            String wildcardsReplaced = periodsReplaced.replace("*", IPV4_QUAD_REGEX);
+
+            if (IPV4_PATTERN.matcher(wildcardsReplaced).matches()) {
+                authorizedIps.put(host, wildcardsReplaced);
+            } else {
+
+            }
+
+
+        }
+
+    }
+
+    public void setDeniedHosts(String deniedHosts) {
+        if (!StringUtils.hasText(deniedHosts)) {
+            throw new IllegalArgumentException("deniedHosts argument cannot be null or empty.");
+        }
+    }
+
+    protected boolean isIpv4Candidate(String host) {
+        String[] quads = StringUtils.tokenizeToStringArray(host, ".");
+        if (quads == null || quads.length != 4) {
+            return false;
+        }
+        for (String quad : quads) {
+            if (!quad.equals("*")) {
+                try {
+                    Integer.parseInt(quad);
+                } catch (NumberFormatException nfe) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        throw new UnsupportedOperationException("Not yet fully implemented!!!" );
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
new file mode 100644
index 0000000..c06271e
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A filter that translates an HTTP Request's Method (eg GET, POST, etc)
+ * into an corresponding action (verb) and uses that verb to construct a permission that will be checked to determine
+ * access.
+ * <p/>
+ * This Filter is primarily provided to support REST environments where the type (Method)
+ * of request translates to an action being performed on one or more resources.  This paradigm works well with Shiro's
+ * concepts of using permissions for access control and can be leveraged to easily perform permission checks.
+ * <p/>
+ * This filter functions as follows:
+ * <ol>
+ * <li>The incoming HTTP request's Method (GET, POST, PUT, DELETE, etc) is discovered.</li>
+ * <li>The Method is translated into a more 'application friendly' verb, such as 'create', edit', 'delete', etc.</li>
+ * <li>The verb is appended to any configured permissions for the
+ * {@link org.apache.shiro.web.filter.PathMatchingFilter currently matching path}.</li>
+ * <li>If the current {@code Subject} {@link org.apache.shiro.subject.Subject#isPermitted(String) isPermitted} to
+ * perform the resolved action, the request is allowed to continue.</li>
+ * </ol>
+ * <p/>
+ * For example, if the following filter chain was defined, where 'rest' was the name given to a filter instance of
+ * this class:
+ * <pre>
+ * /user/** = rest[user]</pre>
+ * Then an HTTP {@code GET} request to {@code /user/1234} would translate to the constructed permission
+ * {@code user:read} (GET is mapped to the 'read' action) and execute the permission check
+ * <code>Subject.isPermitted("user:read")</code> in order to allow the request to continue.
+ * <p/>
+ * Similarly, an HTTP {@code POST} to {@code /user} would translate to the constructed permission
+ * {@code user:create} (POST is mapped to the 'create' action) and execute the permission check
+ * <code>Subject.isPermitted("user:create")</code> in order to allow the request to continue.
+ * <p/>
+ * <h3>Method To Verb Mapping</h3>
+ * The following table represents the default HTTP Method-to-action verb mapping:
+ * <table>
+ * <tr><th>HTTP Method</th><th>Mapped Action</th><th>Example Permission</th><th>Runtime Check</th></tr>
+ * <tr><td>head</td><td>read</td><td>perm1</td><td>perm1:read</td></tr>
+ * <tr><td>get</td><td>read</td><td>perm2</td><td>perm2:read</td></tr>
+ * <tr><td>put</td><td>update</td><td>perm3</td><td>perm3:update</td></tr>
+ * <tr><td>post</td><td>create</td><td>perm4</td><td>perm4:create</td></tr>
+ * <tr><td>mkcol</td><td>create</td><td>perm5</td><td>perm5:create</td></tr>
+ * <tr><td>options</td><td>read</td><td>perm6</td><td>perm6:read</td></tr>
+ * <tr><td>trace</td><td>read</td><td>perm7</td><td>perm7:read</td></tr>
+ * </table>
+ *
+ * @since 1.0
+ */
+public class HttpMethodPermissionFilter extends PermissionsAuthorizationFilter {
+
+    /**
+     * This class's private logger.
+     */
+    private static final Logger log = LoggerFactory.getLogger(HttpMethodPermissionFilter.class);
+
+    /**
+     * Map that contains a mapping between http methods to permission actions (verbs)
+     */
+    private final Map<String, String> httpMethodActions = new HashMap<String, String>();
+
+    //Actions representing HTTP Method values (GET -> read, POST -> create, etc)
+    private static final String CREATE_ACTION = "create";
+    private static final String READ_ACTION = "read";
+    private static final String UPDATE_ACTION = "update";
+    private static final String DELETE_ACTION = "delete";
+
+    /**
+     * Enum of constants for well-defined mapping values.  Used in the Filter's constructor to perform the map instance
+     * used at runtime.
+     */
+    private static enum HttpMethodAction {
+
+        DELETE(DELETE_ACTION),
+        GET(READ_ACTION),
+        HEAD(READ_ACTION),
+        MKCOL(CREATE_ACTION), //webdav, but useful here
+        OPTIONS(READ_ACTION),
+        POST(CREATE_ACTION),
+        PUT(UPDATE_ACTION),
+        TRACE(READ_ACTION);
+
+        private final String action;
+
+        private HttpMethodAction(String action) {
+            this.action = action;
+        }
+
+        public String getAction() {
+            return this.action;
+        }
+    }
+
+    /**
+     * Creates the filter instance with default method-to-action values in the instance's
+     * {@link #getHttpMethodActions() http method actions map}.
+     */
+    public HttpMethodPermissionFilter() {
+        for (HttpMethodAction methodAction : HttpMethodAction.values()) {
+            httpMethodActions.put(methodAction.name().toLowerCase(), methodAction.getAction());
+        }
+    }
+
+    /**
+     * Returns the HTTP Method name (key) to action verb (value) mapping used to resolve actions based on an
+     * incoming {@code HttpServletRequest}.  All keys and values are lower-case.  The
+     * default key/value pairs are defined in the top class-level JavaDoc.
+     *
+     * @return the HTTP Method lower-case name (key) to lower-case action verb (value) mapping
+     */
+    protected Map<String, String> getHttpMethodActions() {
+        return this.httpMethodActions;
+    }
+
+    /**
+     * Determines the action (verb) attempting to be performed on the filtered resource by the current request.
+     * <p/>
+     * This implementation expects the incoming request to be an {@link HttpServletRequest} and returns a mapped
+     * action based on the HTTP request {@link javax.servlet.http.HttpServletRequest#getMethod() method}.
+     *
+     * @param request to pull the method from.
+     * @return The string equivalent verb of the http method.
+     */
+    protected String getHttpMethodAction(ServletRequest request) {
+        String method = ((HttpServletRequest) request).getMethod();
+        return getHttpMethodAction(method);
+    }
+
+    /**
+     * Determines the corresponding application action that will be performed on the filtered resource based on the
+     * specified HTTP method (GET, POST, etc).
+     *
+     * @param method to be translated into the verb.
+     * @return The string equivalent verb of the method.
+     */
+    protected String getHttpMethodAction(String method) {
+        String lc = method.toLowerCase();
+        String resolved = getHttpMethodActions().get(lc);
+        return resolved != null ? resolved : method;
+    }
+
+    /**
+     * Returns a collection of String permissions with which to perform a permission check to determine if the filter
+     * will allow the request to continue.
+     * <p/>
+     * This implementation merely delegates to {@link #buildPermissions(String[], String)} and ignores the inbound
+     * HTTP servlet request, but it can be overridden by subclasses for more complex request-specific building logic
+     * if necessary.
+     *
+     * @param request         the inbound HTTP request - ignored in this implementation, but available to
+     *                        subclasses for more complex construction building logic if necessary
+     * @param configuredPerms any url-specific permissions mapped to this filter in the URL rules mappings.
+     * @param action          the application-friendly action (verb) resolved based on the HTTP Method name.
+     * @return a collection of String permissions with which to perform a permission check to determine if the filter
+     *         will allow the request to continue.
+     */
+    protected String[] buildPermissions(HttpServletRequest request, String[] configuredPerms, String action) {
+        return buildPermissions(configuredPerms, action);
+    }
+
+    /**
+     * Builds a new array of permission strings based on the original argument, appending the specified action verb
+     * to each one per {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} conventions.  The
+     * built permission strings will be the ones used at runtime during the permission check that determines if filter
+     * access should be allowed to continue or not.
+     * <p/>
+     * For example, if the {@code configuredPerms} argument contains the following 3 permission strings:
+     * <p/>
+     * <ol>
+     * <li>permission:one</li>
+     * <li>permission:two</li>
+     * <li>permission:three</li>
+     * </ol>
+     * And the action is {@code read}, then the return value will be:
+     * <ol>
+     * <li>permission:one:read</li>
+     * <li>permission:two:read</li>
+     * <li>permission:three:read</li>
+     * </ol>
+     * per {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} conventions.  Subclasses
+     * are of course free to override this method or the
+     * {@link #buildPermissions(javax.servlet.http.HttpServletRequest, String[], String) buildPermissions} request
+     * variant for custom building logic or with different permission formats.
+     *
+     * @param configuredPerms list of configuredPerms to be converted.
+     * @param action          the resolved action based on the request method to be appended to permission strings.
+     * @return an array of permission strings with each element appended with the action.
+     */
+    protected String[] buildPermissions(String[] configuredPerms, String action) {
+        if (configuredPerms == null || configuredPerms.length <= 0 || !StringUtils.hasText(action)) {
+            return configuredPerms;
+        }
+
+        String[] mappedPerms = new String[configuredPerms.length];
+
+        // loop and append :action
+        for (int i = 0; i < configuredPerms.length; i++) {
+            mappedPerms[i] = configuredPerms[i] + ":" + action;
+        }
+
+        if (log.isTraceEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < mappedPerms.length; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(mappedPerms[i]);
+            }
+            log.trace("MAPPED '{}' action to permission(s) '{}'", action, sb);
+        }
+
+        return mappedPerms;
+    }
+
+    /**
+     * Resolves an 'application friendly' action verb based on the {@code HttpServletRequest}'s method, appends that
+     * action to each configured permission (the {@code mappedValue} argument is a {@code String[]} array), and
+     * delegates the permission check for the newly constructed permission(s) to the superclass
+     * {@link PermissionsAuthorizationFilter#isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
+     * implementation to perform the actual permission check.
+     *
+     * @param request     the inbound {@code ServletRequest}
+     * @param response    the outbound {@code ServletResponse}
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
+     * @return {@code true} if the request should proceed through the filter normally, {@code false} if the
+     *         request should be processed by this filter's
+     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
+     * @throws IOException
+     */
+    @Override
+    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
+        String[] perms = (String[]) mappedValue;
+        // append the http action to the end of the permissions and then back to super
+        String action = getHttpMethodAction(request);
+        String[] resolvedPerms = buildPermissions(perms, action);
+        return super.isAccessAllowed(request, response, resolvedPerms);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/PermissionsAuthorizationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/PermissionsAuthorizationFilter.java
new file mode 100644
index 0000000..fb703fe
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/PermissionsAuthorizationFilter.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import java.io.IOException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * Filter that allows access if the current user has the permissions specified by the mapped value, or denies access
+ * if the user does not have all of the permissions specified.
+ *
+ * @since 0.9
+ */
+public class PermissionsAuthorizationFilter extends AuthorizationFilter {
+
+    //TODO - complete JavaDoc
+
+    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
+
+        Subject subject = getSubject(request, response);
+        String[] perms = (String[]) mappedValue;
+
+        boolean isPermitted = true;
+        if (perms != null && perms.length > 0) {
+            if (perms.length == 1) {
+                if (!subject.isPermitted(perms[0])) {
+                    isPermitted = false;
+                }
+            } else {
+                if (!subject.isPermittedAll(perms)) {
+                    isPermitted = false;
+                }
+            }
+        }
+
+        return isPermitted;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/PortFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/PortFilter.java
new file mode 100644
index 0000000..14dfd58
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/PortFilter.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * A Filter that requires the request to be on a specific port, and if not, redirects to the same URL on that port.
+ * <p/>
+ * Example config:
+ * <pre>
+ * [filters]
+ * port.port = 80
+ * <p/>
+ * [urls]
+ * /some/path/** = port
+ * # override for just this path:
+ * /another/path/** = port[8080]
+ * </pre>
+ *
+ * @since 1.0
+ */
+public class PortFilter extends AuthorizationFilter {
+
+    public static final int DEFAULT_HTTP_PORT = 80;
+    public static final String HTTP_SCHEME = "http";
+
+    private int port = DEFAULT_HTTP_PORT;
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    protected int toPort(Object mappedValue) {
+        String[] ports = (String[]) mappedValue;
+        if (ports == null || ports.length == 0) {
+            return getPort();
+        }
+        if (ports.length > 1) {
+            throw new ConfigurationException("PortFilter can only be configured with a single port.  You have " +
+                    "configured " + ports.length + ": " + StringUtils.toString(ports));
+        }
+        return Integer.parseInt(ports[0]);
+    }
+
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        int requiredPort = toPort(mappedValue);
+        int requestPort = request.getServerPort();
+        return requiredPort == requestPort;
+    }
+
+    protected String getScheme(String requestScheme, int port) {
+        if (port == DEFAULT_HTTP_PORT) {
+            return HTTP_SCHEME;
+        } else if (port == SslFilter.DEFAULT_HTTPS_PORT) {
+            return SslFilter.HTTPS_SCHEME;
+        } else {
+            return requestScheme;
+        }
+    }
+
+    /**
+     * Redirects the request to the same exact incoming URL, but with the port listed in the filter's configuration.
+     *
+     * @param request     the incoming <code>ServletRequest</code>
+     * @param response    the outgoing <code>ServletResponse</code>
+     * @param mappedValue the config specified for the filter in the matching request's filter chain.
+     * @return {@code false} always to force a redirect.
+     */
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
+
+        //just redirect to the specified port:
+        int port = toPort(mappedValue);
+
+        String scheme = getScheme(request.getScheme(), port);
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(scheme).append("://");
+        sb.append(request.getServerName());
+        if (port != DEFAULT_HTTP_PORT && port != SslFilter.DEFAULT_HTTPS_PORT) {
+            sb.append(":");
+            sb.append(port);
+        }
+        if (request instanceof HttpServletRequest) {
+            sb.append(WebUtils.toHttp(request).getRequestURI());
+            String query = WebUtils.toHttp(request).getQueryString();
+            if (query != null) {
+                sb.append("?").append(query);
+            }
+        }
+
+        WebUtils.issueRedirect(request, response, sb.toString());
+
+        return false;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/RolesAuthorizationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/RolesAuthorizationFilter.java
new file mode 100644
index 0000000..d0a6196
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/RolesAuthorizationFilter.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import java.io.IOException;
+import java.util.Set;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.CollectionUtils;
+
+
+/**
+ * Filter that allows access if the current user has the roles specified by the mapped value, or denies access
+ * if the user does not have all of the roles specified.
+ *
+ * @since 0.9
+ */
+public class RolesAuthorizationFilter extends AuthorizationFilter {
+
+    //TODO - complete JavaDoc
+
+    @SuppressWarnings({"unchecked"})
+    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
+
+        Subject subject = getSubject(request, response);
+        String[] rolesArray = (String[]) mappedValue;
+
+        if (rolesArray == null || rolesArray.length == 0) {
+            //no roles specified, so nothing to check - allow access.
+            return true;
+        }
+
+        Set<String> roles = CollectionUtils.asSet(rolesArray);
+        return subject.hasAllRoles(roles);
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java
new file mode 100644
index 0000000..3a6ab7a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Filter which requires a request to be over SSL.  Access is allowed if the request is received on the configured
+ * server {@link #setPort(int) port} <em>and</em> the
+ * {@code request.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}.  If either condition is {@code false},
+ * the filter chain will not continue.
+ * <p/>
+ * The {@link #getPort() port} property defaults to {@code 443} and also additionally guarantees that the
+ * request scheme is always 'https' (except for port 80, which retains the 'http' scheme).
+ * <p/>
+ * Example config:
+ * <pre>
+ * [urls]
+ * /secure/path/** = ssl
+ * </pre>
+ *
+ * @since 1.0
+ */
+public class SslFilter extends PortFilter {
+
+    public static final int DEFAULT_HTTPS_PORT = 443;
+    public static final String HTTPS_SCHEME = "https";
+
+    public SslFilter() {
+        setPort(DEFAULT_HTTPS_PORT);
+    }
+
+    @Override
+    protected String getScheme(String requestScheme, int port) {
+        if (port == DEFAULT_HTTP_PORT) {
+            return PortFilter.HTTP_SCHEME;
+        } else {
+            return HTTPS_SCHEME;
+        }
+    }
+
+    /**
+     * Retains the parent method's port-matching behavior but additionally guarantees that the
+     *{@code ServletRequest.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}.  If the port does not match or
+     * the request is not secure, access is denied.
+     *
+     * @param request     the incoming {@code ServletRequest}
+     * @param response    the outgoing {@code ServletResponse} - ignored in this implementation
+     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings - ignored by this implementation.
+     * @return {@code true} if the request is received on an expected SSL port and the
+     * {@code request.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}, {@code false} otherwise.
+     * @throws Exception if the call to {@code super.isAccessAllowed} throws an exception.
+     * @since 1.2
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        return super.isAccessAllowed(request, response, mappedValue) && request.isSecure();
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/package-info.java b/web/src/main/java/org/apache/shiro/web/filter/authz/package-info.java
new file mode 100644
index 0000000..580671a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Servlet {@link javax.servlet.Filter Filter} implementations that perform authorization (access control)
+ * checks based on the Subject's abilities (for example, role or permission checks).
+ */
+package org.apache.shiro.web.filter.authz;
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
new file mode 100644
index 0000000..ff5d194
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.web.filter.authc.*;
+import org.apache.shiro.web.filter.authz.*;
+import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Enum representing all of the default Shiro Filter instances available to web applications.  Each filter instance is
+ * typically accessible in configuration the {@link #name() name} of the enum constant.
+ *
+ * @since 1.0
+ */
+public enum DefaultFilter {
+
+    anon(AnonymousFilter.class),
+    authc(FormAuthenticationFilter.class),
+    authcBasic(BasicHttpAuthenticationFilter.class),
+    logout(LogoutFilter.class),
+    noSessionCreation(NoSessionCreationFilter.class),
+    perms(PermissionsAuthorizationFilter.class),
+    port(PortFilter.class),
+    rest(HttpMethodPermissionFilter.class),
+    roles(RolesAuthorizationFilter.class),
+    ssl(SslFilter.class),
+    user(UserFilter.class);
+
+    private final Class<? extends Filter> filterClass;
+
+    private DefaultFilter(Class<? extends Filter> filterClass) {
+        this.filterClass = filterClass;
+    }
+
+    public Filter newInstance() {
+        return (Filter) ClassUtils.newInstance(this.filterClass);
+    }
+
+    public Class<? extends Filter> getFilterClass() {
+        return this.filterClass;
+    }
+
+    public static Map<String, Filter> createInstanceMap(FilterConfig config) {
+        Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
+        for (DefaultFilter defaultFilter : values()) {
+            Filter filter = defaultFilter.newInstance();
+            if (config != null) {
+                try {
+                    filter.init(config);
+                } catch (ServletException e) {
+                    String msg = "Unable to correctly init default filter instance of type " +
+                            filter.getClass().getName();
+                    throw new IllegalStateException(msg, e);
+                }
+            }
+            filters.put(defaultFilter.name(), filter);
+        }
+        return filters;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
new file mode 100644
index 0000000..a880108
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.Nameable;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.filter.PathConfigProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Default {@link FilterChainManager} implementation maintaining a map of {@link Filter Filter} instances
+ * (key: filter name, value: Filter) as well as a map of {@link NamedFilterList NamedFilterList}s created from these
+ * {@code Filter}s (key: filter chain name, value: NamedFilterList).  The {@code NamedFilterList} is essentially a
+ * {@link FilterChain} that also has a name property by which it can be looked up.
+ *
+ * @see NamedFilterList
+ * @since 1.0
+ */
+public class DefaultFilterChainManager implements FilterChainManager {
+
+    private static transient final Logger log = LoggerFactory.getLogger(DefaultFilterChainManager.class);
+
+    private FilterConfig filterConfig;
+
+    private Map<String, Filter> filters; //pool of filters available for creating chains
+
+    private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
+
+    public DefaultFilterChainManager() {
+        this.filters = new LinkedHashMap<String, Filter>();
+        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
+        addDefaultFilters(false);
+    }
+
+    public DefaultFilterChainManager(FilterConfig filterConfig) {
+        this.filters = new LinkedHashMap<String, Filter>();
+        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
+        setFilterConfig(filterConfig);
+        addDefaultFilters(true);
+    }
+
+    /**
+     * Returns the {@code FilterConfig} provided by the Servlet container at webapp startup.
+     *
+     * @return the {@code FilterConfig} provided by the Servlet container at webapp startup.
+     */
+    public FilterConfig getFilterConfig() {
+        return filterConfig;
+    }
+
+    /**
+     * Sets the {@code FilterConfig} provided by the Servlet container at webapp startup.
+     *
+     * @param filterConfig the {@code FilterConfig} provided by the Servlet container at webapp startup.
+     */
+    public void setFilterConfig(FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+    }
+
+    public Map<String, Filter> getFilters() {
+        return filters;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setFilters(Map<String, Filter> filters) {
+        this.filters = filters;
+    }
+
+    public Map<String, NamedFilterList> getFilterChains() {
+        return filterChains;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setFilterChains(Map<String, NamedFilterList> filterChains) {
+        this.filterChains = filterChains;
+    }
+
+    public Filter getFilter(String name) {
+        return this.filters.get(name);
+    }
+
+    public void addFilter(String name, Filter filter) {
+        addFilter(name, filter, false);
+    }
+
+    public void addFilter(String name, Filter filter, boolean init) {
+        addFilter(name, filter, init, true);
+    }
+
+    public void createChain(String chainName, String chainDefinition) {
+        if (!StringUtils.hasText(chainName)) {
+            throw new NullPointerException("chainName cannot be null or empty.");
+        }
+        if (!StringUtils.hasText(chainDefinition)) {
+            throw new NullPointerException("chainDefinition cannot be null or empty.");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
+        }
+
+        //parse the value by tokenizing it to get the resulting filter-specific config entries
+        //
+        //e.g. for a value of
+        //
+        //     "authc, roles[admin,user], perms[file:edit]"
+        //
+        // the resulting token array would equal
+        //
+        //     { "authc", "roles[admin,user]", "perms[file:edit]" }
+        //
+        String[] filterTokens = splitChainDefinition(chainDefinition);
+
+        //each token is specific to each filter.
+        //strip the name and extract any filter-specific config between brackets [ ]
+        for (String token : filterTokens) {
+            String[] nameConfigPair = toNameConfigPair(token);
+
+            //now we have the filter name, path and (possibly null) path-specific config.  Let's apply them:
+            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
+        }
+    }
+
+    /**
+     * Splits the comma-delimited filter chain definition line into individual filter definition tokens.
+     * <p/>
+     * Example Input:
+     * <pre>
+     *     foo, bar[baz], blah[x, y]
+     * </pre>
+     * Resulting Output:
+     * <pre>
+     *     output[0] == foo
+     *     output[1] == bar[baz]
+     *     output[2] == blah[x, y]
+     * </pre>
+     * @param chainDefinition the comma-delimited filter chain definition.
+     * @return an array of filter definition tokens
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-205">SHIRO-205</a>
+     */
+    protected String[] splitChainDefinition(String chainDefinition) {
+        return StringUtils.split(chainDefinition, StringUtils.DEFAULT_DELIMITER_CHAR, '[', ']', true, true);
+    }
+
+    /**
+     * Based on the given filter chain definition token (e.g. 'foo' or 'foo[bar, baz]'), this will return the token
+     * as a name/value pair, removing any brackets as necessary.  Examples:
+     * <table>
+     *     <tr>
+     *         <th>Input</th>
+     *         <th>Result</th>
+     *     </tr>
+     *     <tr>
+     *         <td>{@code foo}</td>
+     *         <td>returned[0] == {@code foo}<br/>returned[1] == {@code null}</td>
+     *     </tr>
+     *     <tr>
+     *         <td>{@code foo[bar, baz]}</td>
+     *         <td>returned[0] == {@code foo}<br/>returned[1] == {@code bar, baz}</td>
+     *     </tr>
+     * </table>
+     * @param token the filter chain definition token
+     * @return A name/value pair representing the filter name and a (possibly null) config value.
+     * @throws ConfigurationException if the token cannot be parsed
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-205">SHIRO-205</a>
+     */
+    protected String[] toNameConfigPair(String token) throws ConfigurationException {
+
+        try {
+            String[] pair = token.split("\\[", 2);
+            String name = StringUtils.clean(pair[0]);
+
+            if (name == null) {
+                throw new IllegalArgumentException("Filter name not found for filter chain definition token: " + token);
+            }
+            String config = null;
+
+            if (pair.length == 2) {
+                config = StringUtils.clean(pair[1]);
+                //if there was an open bracket, it assumed there is a closing bracket, so strip it too:
+                config = config.substring(0, config.length() - 1);
+                config = StringUtils.clean(config);
+
+                //backwards compatibility prior to implementing SHIRO-205:
+                //prior to SHIRO-205 being implemented, it was common for end-users to quote the config inside brackets
+                //if that config required commas.  We need to strip those quotes to get to the interior quoted definition
+                //to ensure any existing quoted definitions still function for end users:
+                if (config != null && config.startsWith("\"") && config.endsWith("\"")) {
+                    String stripped = config.substring(1, config.length() - 1);
+                    stripped = StringUtils.clean(stripped);
+
+                    //if the stripped value does not have any internal quotes, we can assume that the entire config was
+                    //quoted and we can use the stripped value.
+                    if (stripped != null && stripped.indexOf('"') == -1) {
+                        config = stripped;
+                    }
+                    //else:
+                    //the remaining config does have internal quotes, so we need to assume that each comma delimited
+                    //pair might be quoted, in which case we need the leading and trailing quotes that we stripped
+                    //So we ignore the stripped value.
+                }
+            }
+            
+            return new String[]{name, config};
+
+        } catch (Exception e) {
+            String msg = "Unable to parse filter chain definition token: " + token;
+            throw new ConfigurationException(msg, e);
+        }
+    }
+
+    protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
+        Filter existing = getFilter(name);
+        if (existing == null || overwrite) {
+            if (filter instanceof Nameable) {
+                ((Nameable) filter).setName(name);
+            }
+            if (init) {
+                initFilter(filter);
+            }
+            this.filters.put(name, filter);
+        }
+    }
+
+    public void addToChain(String chainName, String filterName) {
+        addToChain(chainName, filterName, null);
+    }
+
+    public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
+        if (!StringUtils.hasText(chainName)) {
+            throw new IllegalArgumentException("chainName cannot be null or empty.");
+        }
+        Filter filter = getFilter(filterName);
+        if (filter == null) {
+            throw new IllegalArgumentException("There is no filter with name '" + filterName +
+                    "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " +
+                    "filter with that name/path has first been registered with the addFilter method(s).");
+        }
+
+        applyChainConfig(chainName, filter, chainSpecificFilterConfig);
+
+        NamedFilterList chain = ensureChain(chainName);
+        chain.add(filter);
+    }
+
+    protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
+        if (log.isDebugEnabled()) {
+            log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
+                    "with config [" + chainSpecificFilterConfig + "]");
+        }
+        if (filter instanceof PathConfigProcessor) {
+            ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig);
+        } else {
+            if (StringUtils.hasText(chainSpecificFilterConfig)) {
+                //they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor
+                //this is an erroneous config:
+                String msg = "chainSpecificFilterConfig was specified, but the underlying " +
+                        "Filter instance is not an 'instanceof' " +
+                        PathConfigProcessor.class.getName() + ".  This is required if the filter is to accept " +
+                        "chain-specific configuration.";
+                throw new ConfigurationException(msg);
+            }
+        }
+    }
+
+    protected NamedFilterList ensureChain(String chainName) {
+        NamedFilterList chain = getChain(chainName);
+        if (chain == null) {
+            chain = new SimpleNamedFilterList(chainName);
+            this.filterChains.put(chainName, chain);
+        }
+        return chain;
+    }
+
+    public NamedFilterList getChain(String chainName) {
+        return this.filterChains.get(chainName);
+    }
+
+    public boolean hasChains() {
+        return !CollectionUtils.isEmpty(this.filterChains);
+    }
+
+    public Set<String> getChainNames() {
+        //noinspection unchecked
+        return this.filterChains != null ? this.filterChains.keySet() : Collections.EMPTY_SET;
+    }
+
+    public FilterChain proxy(FilterChain original, String chainName) {
+        NamedFilterList configured = getChain(chainName);
+        if (configured == null) {
+            String msg = "There is no configured chain under the name/key [" + chainName + "].";
+            throw new IllegalArgumentException(msg);
+        }
+        return configured.proxy(original);
+    }
+
+    /**
+     * Initializes the filter by calling <code>filter.init( {@link #getFilterConfig() getFilterConfig()} );</code>.
+     *
+     * @param filter the filter to initialize with the {@code FilterConfig}.
+     */
+    protected void initFilter(Filter filter) {
+        FilterConfig filterConfig = getFilterConfig();
+        if (filterConfig == null) {
+            throw new IllegalStateException("FilterConfig attribute has not been set.  This must occur before filter " +
+                    "initialization can occur.");
+        }
+        try {
+            filter.init(filterConfig);
+        } catch (ServletException e) {
+            throw new ConfigurationException(e);
+        }
+    }
+
+    protected void addDefaultFilters(boolean init) {
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
new file mode 100644
index 0000000..ebbc716
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.config.ConfigurationException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@code FilterChainManager} manages the creation and modification of {@link Filter} chains from an available pool
+ * of {@link Filter} instances.
+ *
+ * @since 1.0
+ */
+public interface FilterChainManager {
+
+    /**
+     * Returns the pool of available {@code Filter}s managed by this manager, keyed by {@code name}.
+     *
+     * @return the pool of available {@code Filter}s managed by this manager, keyed by {@code name}.
+     */
+    Map<String, Filter> getFilters();
+
+    /**
+     * Returns the filter chain identified by the specified {@code chainName} or {@code null} if there is no chain with
+     * that name.
+     *
+     * @param chainName the name identifying the filter chain.
+     * @return the filter chain identified by the specified {@code chainName} or {@code null} if there is no chain with
+     *         that name.
+     */
+    NamedFilterList getChain(String chainName);
+
+    /**
+     * Returns {@code true} if one or more configured chains are available, {@code false} if none are configured.
+     *
+     * @return {@code true} if one or more configured chains are available, {@code false} if none are configured.
+     */
+    boolean hasChains();
+
+    /**
+     * Returns the names of all configured chains or an empty {@code Set} if no chains have been configured.
+     *
+     * @return the names of all configured chains or an empty {@code Set} if no chains have been configured.
+     */
+    Set<String> getChainNames();
+
+    /**
+     * Proxies the specified {@code original} FilterChain with the named chain.  The returned
+     * {@code FilterChain} instance will first execute the configured named chain and then lastly invoke the given
+     * {@code original} chain.
+     *
+     * @param original  the original FilterChain to proxy
+     * @param chainName the name of the internal configured filter chain that should 'sit in front' of the specified
+     *                  original chain.
+     * @return a {@code FilterChain} instance that will execute the named chain and then finally the
+     *         specified {@code original} FilterChain instance.
+     * @throws IllegalArgumentException if there is no configured chain with the given {@code chainName}.
+     */
+    FilterChain proxy(FilterChain original, String chainName);
+
+    /**
+     * Adds a filter to the 'pool' of available filters that can be used when
+     * {@link #addToChain(String, String, String) creating filter chains}.
+     * <p/>
+     * Calling this method is effectively the same as calling
+     * <code>{@link #addFilter(String, javax.servlet.Filter, boolean) addFilter}(name, filter, <b>false</b>);</code>
+     *
+     * @param name   the name to assign to the filter, used to reference the filter in chain definitions
+     * @param filter the filter to initialize and then add to the pool of available filters that can be used
+     */
+    void addFilter(String name, Filter filter);
+
+    /**
+     * Adds a filter to the 'pool' of available filters that can be used when
+     * {@link #addToChain(String, String, String) creating filter chains}.
+     *
+     * @param name   the name to assign to the filter, used to reference the filter in chain definitions
+     * @param filter the filter to assign to the filter pool
+     * @param init   whether or not the {@code Filter} should be
+     *               {@link Filter#init(javax.servlet.FilterConfig) initialized} first before being added to the pool.
+     */
+    void addFilter(String name, Filter filter, boolean init);
+
+    /**
+     * Creates a filter chain for the given {@code chainName} with the specified {@code chainDefinition}
+     * String.
+     * <h3>Conventional Use</h3>
+     * Because the {@code FilterChainManager} interface does not impose any restrictions on filter chain names,
+     * (it expects only Strings), a convenient convention is to make the chain name an actual URL path expression
+     * (such as an {@link org.apache.shiro.util.AntPathMatcher Ant path expression}).  For example:
+     * <p/>
+     * <code>createChain(<b><em>path_expression</em></b>, <em>path_specific_filter_chain_definition</em>);</code>
+     * This convention can be used by a {@link FilterChainResolver} to inspect request URL paths
+     * against the chain name (path) and, if a match is found, return the corresponding chain for runtime filtering.
+     * <h3>Chain Definition Format</h3>
+     * The {@code chainDefinition} method argument is expected to conform to the following format:
+     * <pre>
+     * filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]</pre>
+     * where
+     * <ol>
+     * <li>{@code filterN} is the name of a filter previously
+     * {@link #addFilter(String, javax.servlet.Filter) registered} with the manager, and</li>
+     * <li>{@code [optional_configN]} is an optional bracketed string that has meaning for that particular filter for
+     * <em>this particular chain</em></li>
+     * </ol>
+     * If the filter does not need specific config for that chain name/URL path,
+     * you may discard the brackets - that is, {@code filterN[]} just becomes {@code filterN}.
+     * <p/>
+     * And because this method does create a chain, remember that order matters!  The comma-delimited filter tokens in
+     * the {@code chainDefinition} specify the chain's execution order.
+     * <h3>Examples</h3>
+     * <pre>/account/** = authcBasic</pre>
+     * This example says "Create a filter named '{@code /account/**}' consisting of only the '{@code authcBasic}'
+     * filter".  Also because the {@code authcBasic} filter does not need any path-specific
+     * config, it doesn't have any config brackets {@code []}.
+     * <p/>
+     * <pre>/remoting/** = authcBasic, roles[b2bClient], perms["remote:invoke:wan,lan"]</pre>
+     * This example by contrast uses the 'roles' and 'perms' filters which <em>do</em> use bracket notation.  This
+     * definition says:
+     * <p/>
+     * Construct a filter chain named '{@code /remoting/**}' which
+     * <ol>
+     * <li>ensures the user is first authenticated ({@code authcBasic}) then</li>
+     * <li>ensures that user has the {@code b2bClient} role, and then finally</li>
+     * <li>ensures that they have the {@code remote:invoke:lan,wan} permission.</li>
+     * </ol>
+     * <p/>
+     * <b>Note</b>: because elements within brackets [ ] can be comma-delimited themselves, you must quote the
+     * internal bracket definition if commas are needed (the above example has 'lan,wan').  If we didn't do that, the
+     * parser would interpret the chain definition as four tokens:
+     * <ol>
+     * <li>authcBasic</li>
+     * <li>roles[b2bclient]</li>
+     * <li>perms[remote:invoke:lan</li>
+     * <li>wan]</li>
+     * </ol>
+     * which is obviously incorrect.  So remember to use quotes if your internal bracket definitions need to use commas.
+     *
+     * @param chainName       the name to associate with the chain, conventionally a URL path pattern.
+     * @param chainDefinition the string-formatted chain definition used to construct an actual
+     *                        {@link NamedFilterList} chain instance.
+     * @see FilterChainResolver
+     * @see org.apache.shiro.util.AntPathMatcher AntPathMatcher
+     */
+    void createChain(String chainName, String chainDefinition);
+
+    /**
+     * Adds (appends) a filter to the filter chain identified by the given {@code chainName}.  If there is no chain
+     * with the given name, a new one is created and the filter will be the first in the chain.
+     *
+     * @param chainName  the name of the chain where the filter will be appended.
+     * @param filterName the name of the {@link #addFilter registered} filter to add to the chain.
+     * @throws IllegalArgumentException if there is not a {@link #addFilter(String, javax.servlet.Filter) registered}
+     *                                  filter under the given {@code filterName}
+     */
+    void addToChain(String chainName, String filterName);
+
+    /**
+     * Adds (appends) a filter to the filter chain identified by the given {@code chainName}.  If there is no chain
+     * with the given name, a new one is created and the filter will be the first in the chain.
+     * <p/>
+     * Note that the final argument expects the associated filter to be an instance of
+     * a {@link org.apache.shiro.web.filter.PathConfigProcessor PathConfigProcessor} to accept per-chain configuration.
+     * If it is not, a {@link IllegalArgumentException} will be thrown.
+     *
+     * @param chainName                 the name of the chain where the filter will be appended.
+     * @param filterName                the name of the {@link #addFilter registered} filter to add to the chain.
+     * @param chainSpecificFilterConfig the filter-specific configuration that should be applied for only the specified
+     *                                  filter chain.
+     * @throws IllegalArgumentException if there is not a {@link #addFilter(String, javax.servlet.Filter) registered}
+     *                                  filter under the given {@code filterName}
+     * @throws ConfigurationException   if the filter is not capable of accepting {@code chainSpecificFilterConfig}
+     *                                  (usually such filters implement the
+     *                                  {@link org.apache.shiro.web.filter.PathConfigProcessor PathConfigProcessor}
+     *                                  interface).
+     */
+    void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) throws ConfigurationException;
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainResolver.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainResolver.java
new file mode 100644
index 0000000..fc0daab
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainResolver.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code FilterChainResolver} can resolve an appropriate {@link FilterChain} to execute during a
+ * {@code ServletRequest}.  It allows resolution of arbitrary filter chains which can be executed for any given
+ * request or URI/URL.
+ * <p/>
+ * This mechanism allows for a much more flexible FilterChain resolution than normal {@code web.xml} servlet filter
+ * definitions:  it allows arbitrary filter chains to be defined per URL in a much more concise and easy to read manner,
+ * and even allows filter chains to be dynamically resolved or constructed at runtime if the underlying implementation
+ * supports it.
+ *
+ * @since 1.0
+ */
+public interface FilterChainResolver {
+
+    /**
+     * Returns the filter chain that should be executed for the given request, or {@code null} if the
+     * original chain should be used.
+     * <p/>
+     * This method allows a implementation to define arbitrary security {@link javax.servlet.Filter Filter}
+     * chains for any given request or URL pattern.
+     *
+     * @param request       the incoming ServletRequest
+     * @param response      the outgoing ServletResponse
+     * @param originalChain the original {@code FilterChain} intercepted by the ShiroFilter implementation.
+     * @return the filter chain that should be executed for the given request, or {@code null} if the
+     *         original chain should be used.
+     */
+    FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/NamedFilterList.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/NamedFilterList.java
new file mode 100644
index 0000000..4419f2a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/NamedFilterList.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import java.util.List;
+
+/**
+ * A {@code NamedFilterList} is a {@code List} of {@code Filter} instances that is uniquely identified by a
+ * {@link #getName() name}.  It has the ability to generate new {@link FilterChain} instances reflecting this list's
+ * filter order via the {@link #proxy proxy} method.
+ *
+ * @since 1.0
+ */
+public interface NamedFilterList extends List<Filter> {
+
+    /**
+     * Returns the configuration-unique name assigned to this {@code Filter} list.
+     *
+     * @return the configuration-unique name assigned to this {@code Filter} list.
+     */
+    String getName();
+
+    /**
+     * Returns a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
+     * and end with the execution of the given {@code filterChain} instance.
+     *
+     * @param filterChain the {@code FilterChain} instance to execute after this list's {@code Filter}s have executed.
+     * @return a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
+     *         and end with the execution of the given {@code filterChain} instance.
+     */
+    FilterChain proxy(FilterChain filterChain);
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
new file mode 100644
index 0000000..bb70885
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.util.AntPathMatcher;
+import org.apache.shiro.util.PatternMatcher;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code FilterChainResolver} that resolves {@link FilterChain}s based on url path
+ * matching, as determined by a configurable {@link #setPathMatcher(org.apache.shiro.util.PatternMatcher) PathMatcher}.
+ * <p/>
+ * This implementation functions by consulting a {@link org.apache.shiro.web.filter.mgt.FilterChainManager} for all configured filter chains (keyed
+ * by configured path pattern).  If an incoming Request path matches one of the configured path patterns (via
+ * the {@code PathMatcher}, the corresponding configured {@code FilterChain} is returned.
+ *
+ * @since 1.0
+ */
+public class PathMatchingFilterChainResolver implements FilterChainResolver {
+
+    private static transient final Logger log = LoggerFactory.getLogger(PathMatchingFilterChainResolver.class);
+
+    private FilterChainManager filterChainManager;
+
+    private PatternMatcher pathMatcher;
+
+    public PathMatchingFilterChainResolver() {
+        this.pathMatcher = new AntPathMatcher();
+        this.filterChainManager = new DefaultFilterChainManager();
+    }
+
+    public PathMatchingFilterChainResolver(FilterConfig filterConfig) {
+        this.pathMatcher = new AntPathMatcher();
+        this.filterChainManager = new DefaultFilterChainManager(filterConfig);
+    }
+
+    /**
+     * Returns the {@code PatternMatcher} used when determining if an incoming request's path
+     * matches a configured filter chain.  Unless overridden, the
+     * default implementation is an {@link org.apache.shiro.util.AntPathMatcher AntPathMatcher}.
+     *
+     * @return the {@code PatternMatcher} used when determining if an incoming request's path
+     *         matches a configured filter chain.
+     */
+    public PatternMatcher getPathMatcher() {
+        return pathMatcher;
+    }
+
+    /**
+     * Sets the {@code PatternMatcher} used when determining if an incoming request's path
+     * matches a configured filter chain.  Unless overridden, the
+     * default implementation is an {@link org.apache.shiro.util.AntPathMatcher AntPathMatcher}.
+     *
+     * @param pathMatcher the {@code PatternMatcher} used when determining if an incoming request's path
+     *                    matches a configured filter chain.
+     */
+    public void setPathMatcher(PatternMatcher pathMatcher) {
+        this.pathMatcher = pathMatcher;
+    }
+
+    public FilterChainManager getFilterChainManager() {
+        return filterChainManager;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setFilterChainManager(FilterChainManager filterChainManager) {
+        this.filterChainManager = filterChainManager;
+    }
+
+    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
+        FilterChainManager filterChainManager = getFilterChainManager();
+        if (!filterChainManager.hasChains()) {
+            return null;
+        }
+
+        String requestURI = getPathWithinApplication(request);
+
+        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
+        //as the chain name for the FilterChainManager's requirements
+        for (String pathPattern : filterChainManager.getChainNames()) {
+
+            // If the path does match, then pass on to the subclass implementation for specific checks:
+            if (pathMatches(pathPattern, requestURI)) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
+                            "Utilizing corresponding filter chain...");
+                }
+                return filterChainManager.proxy(originalChain, pathPattern);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if an incoming request path (the {@code path} argument)
+     * matches a configured filter chain path (the {@code pattern} argument), {@code false} otherwise.
+     * <p/>
+     * Simply delegates to
+     * <b><code>{@link #getPathMatcher() getPathMatcher()}.{@link org.apache.shiro.util.PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>.
+     * Subclass implementors should think carefully before overriding this method, as typically a custom
+     * {@code PathMatcher} should be configured for custom path matching behavior instead.  Favor OO composition
+     * rather than inheritance to limit your exposure to Shiro implementation details which may change over time.
+     *
+     * @param pattern the pattern to match against
+     * @param path    the value to match with the specified {@code pattern}
+     * @return {@code true} if the request {@code path} matches the specified filter chain url {@code pattern},
+     *         {@code false} otherwise.
+     */
+    protected boolean pathMatches(String pattern, String path) {
+        PatternMatcher pathMatcher = getPathMatcher();
+        return pathMatcher.matches(pattern, path);
+    }
+
+    /**
+     * Merely returns
+     * <code>WebUtils.{@link org.apache.shiro.web.util.WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) getPathWithinApplication(request)}</code>
+     * and can be overridden by subclasses for custom request-to-application-path resolution behavior.
+     *
+     * @param request the incoming {@code ServletRequest}
+     * @return the request's path within the appliation.
+     */
+    protected String getPathWithinApplication(ServletRequest request) {
+        return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterList.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterList.java
new file mode 100644
index 0000000..a0bf79d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterList.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.servlet.ProxiedFilterChain;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import java.util.*;
+
+/**
+ * Simple {@code NamedFilterList} implementation that is supported by a backing {@link List} instance and a simple
+ * {@link #getName() name} property. All {@link List} method implementations are immediately delegated to the
+ * wrapped backing list.
+ *
+ * @since 1.0
+ */
+public class SimpleNamedFilterList implements NamedFilterList {
+
+    private String name;
+    private List<Filter> backingList;
+
+    /**
+     * Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name}, defaulting to a new
+     * {@link ArrayList ArrayList} instance as the backing list.
+     *
+     * @param name the name to assign to this instance.
+     * @throws IllegalArgumentException if {@code name} is null or empty.
+     */
+    public SimpleNamedFilterList(String name) {
+        this(name, new ArrayList<Filter>());
+    }
+
+    /**
+     * Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name} and {@code backingList}.
+     *
+     * @param name        the name to assign to this instance.
+     * @param backingList the list instance used to back all of this class's {@link List} method implementations.
+     * @throws IllegalArgumentException if {@code name} is null or empty.
+     * @throws NullPointerException     if the backing list is {@code null}.
+     */
+    public SimpleNamedFilterList(String name, List<Filter> backingList) {
+        if (backingList == null) {
+            throw new NullPointerException("backingList constructor argument cannot be null.");
+        }
+        this.backingList = backingList;
+        setName(name);
+    }
+
+    protected void setName(String name) {
+        if (!StringUtils.hasText(name)) {
+            throw new IllegalArgumentException("Cannot specify a null or empty name.");
+        }
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public FilterChain proxy(FilterChain orig) {
+        return new ProxiedFilterChain(orig, this);
+    }
+
+    public boolean add(Filter filter) {
+        return this.backingList.add(filter);
+    }
+
+    public void add(int index, Filter filter) {
+        this.backingList.add(index, filter);
+    }
+
+    public boolean addAll(Collection<? extends Filter> c) {
+        return this.backingList.addAll(c);
+    }
+
+    public boolean addAll(int index, Collection<? extends Filter> c) {
+        return this.backingList.addAll(index, c);
+    }
+
+    public void clear() {
+        this.backingList.clear();
+    }
+
+    public boolean contains(Object o) {
+        return this.backingList.contains(o);
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        return this.backingList.containsAll(c);
+    }
+
+    public Filter get(int index) {
+        return this.backingList.get(index);
+    }
+
+    public int indexOf(Object o) {
+        return this.backingList.indexOf(o);
+    }
+
+    public boolean isEmpty() {
+        return this.backingList.isEmpty();
+    }
+
+    public Iterator<Filter> iterator() {
+        return this.backingList.iterator();
+    }
+
+    public int lastIndexOf(Object o) {
+        return this.backingList.lastIndexOf(o);
+    }
+
+    public ListIterator<Filter> listIterator() {
+        return this.backingList.listIterator();
+    }
+
+    public ListIterator<Filter> listIterator(int index) {
+        return this.backingList.listIterator(index);
+    }
+
+    public Filter remove(int index) {
+        return this.backingList.remove(index);
+    }
+
+    public boolean remove(Object o) {
+        return this.backingList.remove(o);
+    }
+
+    public boolean removeAll(Collection<?> c) {
+        return this.backingList.removeAll(c);
+    }
+
+    public boolean retainAll(Collection<?> c) {
+        return this.backingList.retainAll(c);
+    }
+
+    public Filter set(int index, Filter filter) {
+        return this.backingList.set(index, filter);
+    }
+
+    public int size() {
+        return this.backingList.size();
+    }
+
+    public List<Filter> subList(int fromIndex, int toIndex) {
+        return this.backingList.subList(fromIndex, toIndex);
+    }
+
+    public Object[] toArray() {
+        return this.backingList.toArray();
+    }
+
+    public <T> T[] toArray(T[] a) {
+        //noinspection SuspiciousToArrayCall
+        return this.backingList.toArray(a);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/package-info.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/package-info.java
new file mode 100644
index 0000000..28b9a93
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * The filter 'mgt' (management) package contains components used in managing Filters that are available for
+ * filter chain construction, the filter chains themselves, as well as resolving filter chains based by name.
+ *
+ * @see FilterChainManager
+ * @see FilterChainResolver
+ */
+package org.apache.shiro.web.filter.mgt;
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/filter/package-info.java b/web/src/main/java/org/apache/shiro/web/filter/package-info.java
new file mode 100644
index 0000000..47e7134
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Base package supporting all Servlet {@link javax.servlet.Filter Filter} implementations used to control
+ * access to web pages and URL resources.
+ */
+package org.apache.shiro.web.filter;
diff --git a/web/src/main/java/org/apache/shiro/web/filter/session/NoSessionCreationFilter.java b/web/src/main/java/org/apache/shiro/web/filter/session/NoSessionCreationFilter.java
new file mode 100644
index 0000000..086804a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/session/NoSessionCreationFilter.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.session;
+
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.web.filter.PathMatchingFilter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code PathMatchingFilter} that will disable creating new Sessions during the request.  This is a useful
+ * filter to place in the front of any filter chains that may result in REST, SOAP or other service invocations that
+ * are not intended to participate in a session.
+ * <p/>
+ * This filter enables the following behavior:
+ * <ol>
+ * <li>If a {@code Subject} does not yet have a Session by the time this filter is called, this filter effectively
+ * disables all calls to {@code subject}.{@link org.apache.shiro.subject.Subject#getSession() getSession()} and
+ * {@code subject}.{@link org.apache.shiro.subject.Subject#getSession(boolean) getSession(true)}.  If either are called
+ * during the request, an exception will be thrown.</li>
+ * <li>
+ * However, if the {@code Subject} already has an associated session before this filter is invoked, either because it
+ * was created in another part of the application, or a filter higher in the chain created one, this filter has no
+ * effect.
+ * </li>
+ * </ol>
+ * Finally, calls to <code>subject.getSession(false)</code> (i.e. a {@code false} boolean value) will be unaffected
+ * and may be called without repercussion in all cases.
+ *
+ * @since 1.2
+ */
+public class NoSessionCreationFilter extends PathMatchingFilter {
+
+    @Override
+    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.FALSE);
+        return true;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
new file mode 100644
index 0000000..0c777ac
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.mgt.AbstractRememberMeManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.servlet.Cookie;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Remembers a Subject's identity by saving the Subject's {@link Subject#getPrincipals() principals} to a {@link Cookie}
+ * for later retrieval.
+ * <p/>
+ * Cookie attributes (path, domain, maxAge, etc) may be set on this class's default
+ * {@link #getCookie() cookie} attribute, which acts as a template to use to set all properties of outgoing cookies
+ * created by this implementation.
+ * <p/>
+ * The default cookie has the following attribute values set:
+ * <table>
+ * <tr>
+ * <th>Attribute Name</th>
+ * <th>Value</th>
+ * </tr>
+ * <tr><td>{@link Cookie#getName() name}</td>
+ * <td>{@code rememberMe}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Cookie#getPath() path}</td>
+ * <td>{@code /}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Cookie#getMaxAge() maxAge}</td>
+ * <td>{@link Cookie#ONE_YEAR Cookie.ONE_YEAR}</td>
+ * </tr>
+ * </table>
+ * <p/>
+ * Note that because this class subclasses the {@link AbstractRememberMeManager} which already provides serialization
+ * and encryption logic, this class utilizes both for added security before setting the cookie value.
+ *
+ * @since 1.0
+ */
+public class CookieRememberMeManager extends AbstractRememberMeManager {
+
+    //TODO - complete JavaDoc
+
+    private static transient final Logger log = LoggerFactory.getLogger(CookieRememberMeManager.class);
+
+    /**
+     * The default name of the underlying rememberMe cookie which is {@code rememberMe}.
+     */
+    public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = "rememberMe";
+
+    private Cookie cookie;
+
+    /**
+     * Constructs a new {@code CookieRememberMeManager} with a default {@code rememberMe} cookie template.
+     */
+    public CookieRememberMeManager() {
+        Cookie cookie = new SimpleCookie(DEFAULT_REMEMBER_ME_COOKIE_NAME);
+        cookie.setHttpOnly(true);
+        //One year should be long enough - most sites won't object to requiring a user to log in if they haven't visited
+        //in a year:
+        cookie.setMaxAge(Cookie.ONE_YEAR);
+        this.cookie = cookie;
+    }
+
+    /**
+     * Returns the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
+     * this {@code RememberMeManager}.  Outgoing cookies will match this one except for the
+     * {@link Cookie#getValue() value} attribute, which is necessarily set dynamically at runtime.
+     * <p/>
+     * Please see the class-level JavaDoc for the default cookie's attribute values.
+     *
+     * @return the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
+     *         this {@code RememberMeManager}.
+     */
+    public Cookie getCookie() {
+        return cookie;
+    }
+
+    /**
+     * Sets the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
+     * this {@code RememberMeManager}.  Outgoing cookies will match this one except for the
+     * {@link Cookie#getValue() value} attribute, which is necessarily set dynamically at runtime.
+     * <p/>
+     * Please see the class-level JavaDoc for the default cookie's attribute values.
+     *
+     * @param cookie the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created
+     *               by this {@code RememberMeManager}.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setCookie(Cookie cookie) {
+        this.cookie = cookie;
+    }
+
+    /**
+     * Base64-encodes the specified serialized byte array and sets that base64-encoded String as the cookie value.
+     * <p/>
+     * The {@code subject} instance is expected to be a {@link WebSubject} instance with an HTTP Request/Response pair
+     * so an HTTP cookie can be set on the outgoing response.  If it is not a {@code WebSubject} or that
+     * {@code WebSubject} does not have an HTTP Request/Response pair, this implementation does nothing.
+     *
+     * @param subject    the Subject for which the identity is being serialized.
+     * @param serialized the serialized bytes to be persisted.
+     */
+    protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
+
+        if (!WebUtils.isHttp(subject)) {
+            if (log.isDebugEnabled()) {
+                String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet " +
+                        "request and response in order to set the rememberMe cookie. Returning immediately and " +
+                        "ignoring rememberMe operation.";
+                log.debug(msg);
+            }
+            return;
+        }
+
+
+        HttpServletRequest request = WebUtils.getHttpRequest(subject);
+        HttpServletResponse response = WebUtils.getHttpResponse(subject);
+
+        //base 64 encode it and store as a cookie:
+        String base64 = Base64.encodeToString(serialized);
+
+        Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
+        Cookie cookie = new SimpleCookie(template);
+        cookie.setValue(base64);
+        cookie.saveTo(request, response);
+    }
+
+    private boolean isIdentityRemoved(WebSubjectContext subjectContext) {
+        ServletRequest request = subjectContext.resolveServletRequest();
+        if (request != null) {
+            Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
+            return removed != null && removed;
+        }
+        return false;
+    }
+
+
+    /**
+     * Returns a previously serialized identity byte array or {@code null} if the byte array could not be acquired.
+     * This implementation retrieves an HTTP cookie, Base64-decodes the cookie value, and returns the resulting byte
+     * array.
+     * <p/>
+     * The {@code SubjectContext} instance is expected to be a {@link WebSubjectContext} instance with an HTTP
+     * Request/Response pair so an HTTP cookie can be retrieved from the incoming request.  If it is not a
+     * {@code WebSubjectContext} or that {@code WebSubjectContext} does not have an HTTP Request/Response pair, this
+     * implementation returns {@code null}.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
+     *                       is being used to construct a {@link Subject} instance.  To be used to assist with data
+     *                       lookup.
+     * @return a previously serialized identity byte array or {@code null} if the byte array could not be acquired.
+     */
+    protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
+
+        if (!WebUtils.isHttp(subjectContext)) {
+            if (log.isDebugEnabled()) {
+                String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +
+                        "servlet request and response in order to retrieve the rememberMe cookie. Returning " +
+                        "immediately and ignoring rememberMe operation.";
+                log.debug(msg);
+            }
+            return null;
+        }
+
+        WebSubjectContext wsc = (WebSubjectContext) subjectContext;
+        if (isIdentityRemoved(wsc)) {
+            return null;
+        }
+
+        HttpServletRequest request = WebUtils.getHttpRequest(wsc);
+        HttpServletResponse response = WebUtils.getHttpResponse(wsc);
+
+        String base64 = getCookie().readValue(request, response);
+        // Browsers do not always remove cookies immediately (SHIRO-183)
+        // ignore cookies that are scheduled for removal
+        if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;
+
+        if (base64 != null) {
+            base64 = ensurePadding(base64);
+            if (log.isTraceEnabled()) {
+                log.trace("Acquired Base64 encoded identity [" + base64 + "]");
+            }
+            byte[] decoded = Base64.decode(base64);
+            if (log.isTraceEnabled()) {
+                log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
+            }
+            return decoded;
+        } else {
+            //no cookie set - new site visitor?
+            return null;
+        }
+    }
+
+    /**
+     * Sometimes a user agent will send the rememberMe cookie value without padding,
+     * most likely because {@code =} is a separator in the cookie header.
+     * <p/>
+     * Contributed by Luis Arias.  Thanks Luis!
+     *
+     * @param base64 the base64 encoded String that may need to be padded
+     * @return the base64 String padded if necessary.
+     */
+    private String ensurePadding(String base64) {
+        int length = base64.length();
+        if (length % 4 != 0) {
+            StringBuilder sb = new StringBuilder(base64);
+            for (int i = 0; i < length % 4; ++i) {
+                sb.append('=');
+            }
+            base64 = sb.toString();
+        }
+        return base64;
+    }
+
+    /**
+     * Removes the 'rememberMe' cookie from the associated {@link WebSubject}'s request/response pair.
+     * <p/>
+     * The {@code subject} instance is expected to be a {@link WebSubject} instance with an HTTP Request/Response pair.
+     * If it is not a {@code WebSubject} or that {@code WebSubject} does not have an HTTP Request/Response pair, this
+     * implementation does nothing.
+     *
+     * @param subject the subject instance for which identity data should be forgotten from the underlying persistence
+     */
+    protected void forgetIdentity(Subject subject) {
+        if (WebUtils.isHttp(subject)) {
+            HttpServletRequest request = WebUtils.getHttpRequest(subject);
+            HttpServletResponse response = WebUtils.getHttpResponse(subject);
+            forgetIdentity(request, response);
+        }
+    }
+
+    /**
+     * Removes the 'rememberMe' cookie from the associated {@link WebSubjectContext}'s request/response pair.
+     * <p/>
+     * The {@code SubjectContext} instance is expected to be a {@link WebSubjectContext} instance with an HTTP
+     * Request/Response pair.  If it is not a {@code WebSubjectContext} or that {@code WebSubjectContext} does not
+     * have an HTTP Request/Response pair, this implementation does nothing.
+     *
+     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation
+     */
+    public void forgetIdentity(SubjectContext subjectContext) {
+        if (WebUtils.isHttp(subjectContext)) {
+            HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);
+            HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);
+            forgetIdentity(request, response);
+        }
+    }
+
+    /**
+     * Removes the rememberMe cookie from the given request/response pair.
+     *
+     * @param request  the incoming HTTP servlet request
+     * @param response the outgoing HTTP servlet response
+     */
+    private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
+        getCookie().removeFrom(request, response);
+    }
+}
+
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
new file mode 100644
index 0000000..77606b5
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+import org.apache.shiro.mgt.SessionStorageEvaluator;
+import org.apache.shiro.mgt.SubjectDAO;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.session.mgt.*;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+import org.apache.shiro.web.subject.support.DefaultWebSubjectContext;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.Serializable;
+import java.util.Collection;
+
+
+/**
+ * Default {@link WebSecurityManager WebSecurityManager} implementation used in web-based applications or any
+ * application that requires HTTP connectivity (SOAP, http remoting, etc).
+ *
+ * @since 0.2
+ */
+public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultWebSecurityManager.class);
+
+    @Deprecated
+    public static final String HTTP_SESSION_MODE = "http";
+    @Deprecated
+    public static final String NATIVE_SESSION_MODE = "native";
+
+    /**
+     * @deprecated as of 1.2.  This should NOT be used for anything other than determining if the sessionMode has changed.
+     */
+    @Deprecated
+    private String sessionMode;
+
+    public DefaultWebSecurityManager() {
+        super();
+        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
+        this.sessionMode = HTTP_SESSION_MODE;
+        setSubjectFactory(new DefaultWebSubjectFactory());
+        setRememberMeManager(new CookieRememberMeManager());
+        setSessionManager(new ServletContainerSessionManager());
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public DefaultWebSecurityManager(Realm singleRealm) {
+        this();
+        setRealm(singleRealm);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public DefaultWebSecurityManager(Collection<Realm> realms) {
+        this();
+        setRealms(realms);
+    }
+
+    @Override
+    protected SubjectContext createSubjectContext() {
+        return new DefaultWebSubjectContext();
+    }
+
+    @Override
+    //since 1.2.1 for fixing SHIRO-350
+    public void setSubjectDAO(SubjectDAO subjectDAO) {
+        super.setSubjectDAO(subjectDAO);
+        applySessionManagerToSessionStorageEvaluatorIfPossible();
+    }
+
+    //since 1.2.1 for fixing SHIRO-350
+    @Override
+    protected void afterSessionManagerSet() {
+        super.afterSessionManagerSet();
+        applySessionManagerToSessionStorageEvaluatorIfPossible();
+    }
+
+    //since 1.2.1 for fixing SHIRO-350:
+    private void applySessionManagerToSessionStorageEvaluatorIfPossible() {
+        SubjectDAO subjectDAO = getSubjectDAO();
+        if (subjectDAO instanceof DefaultSubjectDAO) {
+            SessionStorageEvaluator evaluator = ((DefaultSubjectDAO)subjectDAO).getSessionStorageEvaluator();
+            if (evaluator instanceof DefaultWebSessionStorageEvaluator) {
+                ((DefaultWebSessionStorageEvaluator)evaluator).setSessionManager(getSessionManager());
+            }
+        }
+    }
+
+    @Override
+    protected SubjectContext copy(SubjectContext subjectContext) {
+        if (subjectContext instanceof WebSubjectContext) {
+            return new DefaultWebSubjectContext((WebSubjectContext) subjectContext);
+        }
+        return super.copy(subjectContext);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    @Deprecated
+    public String getSessionMode() {
+        return sessionMode;
+    }
+
+    /**
+     * @param sessionMode
+     * @deprecated since 1.2
+     */
+    @Deprecated
+    public void setSessionMode(String sessionMode) {
+        log.warn("The 'sessionMode' property has been deprecated.  Please configure an appropriate WebSessionManager " +
+                "instance instead of using this property.  This property/method will be removed in a later version.");
+        String mode = sessionMode;
+        if (mode == null) {
+            throw new IllegalArgumentException("sessionMode argument cannot be null.");
+        }
+        mode = sessionMode.toLowerCase();
+        if (!HTTP_SESSION_MODE.equals(mode) && !NATIVE_SESSION_MODE.equals(mode)) {
+            String msg = "Invalid sessionMode [" + sessionMode + "].  Allowed values are " +
+                    "public static final String constants in the " + getClass().getName() + " class: '"
+                    + HTTP_SESSION_MODE + "' or '" + NATIVE_SESSION_MODE + "', with '" +
+                    HTTP_SESSION_MODE + "' being the default.";
+            throw new IllegalArgumentException(msg);
+        }
+        boolean recreate = this.sessionMode == null || !this.sessionMode.equals(mode);
+        this.sessionMode = mode;
+        if (recreate) {
+            LifecycleUtils.destroy(getSessionManager());
+            SessionManager sessionManager = createSessionManager(mode);
+            this.setInternalSessionManager(sessionManager);
+        }
+    }
+
+    @Override
+    public void setSessionManager(SessionManager sessionManager) {
+        this.sessionMode = null;
+        if (sessionManager != null && !(sessionManager instanceof WebSessionManager)) {
+            if (log.isWarnEnabled()) {
+                String msg = "The " + getClass().getName() + " implementation expects SessionManager instances " +
+                        "that implement the " + WebSessionManager.class.getName() + " interface.  The " +
+                        "configured instance is of type [" + sessionManager.getClass().getName() + "] which does not " +
+                        "implement this interface..  This may cause unexpected behavior.";
+                log.warn(msg);
+            }
+        }
+        setInternalSessionManager(sessionManager);
+    }
+
+    /**
+     * @param sessionManager
+     * @since 1.2
+     */
+    private void setInternalSessionManager(SessionManager sessionManager) {
+        super.setSessionManager(sessionManager);
+    }
+
+    /**
+     * @since 1.0
+     */
+    public boolean isHttpSessionMode() {
+        SessionManager sessionManager = getSessionManager();
+        return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
+    }
+
+    protected SessionManager createSessionManager(String sessionMode) {
+        if (sessionMode == null || !sessionMode.equalsIgnoreCase(NATIVE_SESSION_MODE)) {
+            log.info("{} mode - enabling ServletContainerSessionManager (HTTP-only Sessions)", HTTP_SESSION_MODE);
+            return new ServletContainerSessionManager();
+        } else {
+            log.info("{} mode - enabling DefaultWebSessionManager (non-HTTP and HTTP Sessions)", NATIVE_SESSION_MODE);
+            return new DefaultWebSessionManager();
+        }
+    }
+
+    @Override
+    protected SessionContext createSessionContext(SubjectContext subjectContext) {
+        SessionContext sessionContext = super.createSessionContext(subjectContext);
+        if (subjectContext instanceof WebSubjectContext) {
+            WebSubjectContext wsc = (WebSubjectContext) subjectContext;
+            ServletRequest request = wsc.resolveServletRequest();
+            ServletResponse response = wsc.resolveServletResponse();
+            DefaultWebSessionContext webSessionContext = new DefaultWebSessionContext(sessionContext);
+            if (request != null) {
+                webSessionContext.setServletRequest(request);
+            }
+            if (response != null) {
+                webSessionContext.setServletResponse(response);
+            }
+
+            sessionContext = webSessionContext;
+        }
+        return sessionContext;
+    }
+
+    @Override
+    protected SessionKey getSessionKey(SubjectContext context) {
+        if (WebUtils.isWeb(context)) {
+            Serializable sessionId = context.getSessionId();
+            ServletRequest request = WebUtils.getRequest(context);
+            ServletResponse response = WebUtils.getResponse(context);
+            return new WebSessionKey(sessionId, request, response);
+        } else {
+            return super.getSessionKey(context);
+
+        }
+    }
+
+    @Override
+    protected void beforeLogout(Subject subject) {
+        super.beforeLogout(subject);
+        removeRequestIdentity(subject);
+    }
+
+    protected void removeRequestIdentity(Subject subject) {
+        if (subject instanceof WebSubject) {
+            WebSubject webSubject = (WebSubject) subject;
+            ServletRequest request = webSubject.getServletRequest();
+            if (request != null) {
+                request.setAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY, Boolean.TRUE);
+            }
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluator.java b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluator.java
new file mode 100644
index 0000000..33685f9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluator.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
+import org.apache.shiro.session.mgt.NativeSessionManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.util.WebUtils;
+
+/**
+ * A web-specific {@code SessionStorageEvaluator} that performs the same logic as the parent class
+ * {@link DefaultSessionStorageEvaluator} but additionally checks for a request-specific flag that may enable or
+ * disable session access.
+ * <p/>
+ * This implementation usually works in conjunction with the
+ * {@link org.apache.shiro.web.filter.session.NoSessionCreationFilter}:  If the {@code NoSessionCreationFilter}
+ * is configured in a filter chain, that filter will set a specific
+ * {@code ServletRequest} {@link javax.servlet.ServletRequest#setAttribute attribute} indicating that session creation
+ * should be disabled.
+ * <p/>
+ * This {@code DefaultWebSessionStorageEvaluator} will then inspect this attribute, and if it has been set, will return
+ * {@code false} from {@link #isSessionStorageEnabled(org.apache.shiro.subject.Subject)} method, thereby preventing
+ * Shiro from creating a session for the purpose of storing subject state.
+ * <p/>
+ * If the request attribute has
+ * not been set (i.e. the {@code NoSessionCreationFilter} is not configured or has been disabled), this class does
+ * nothing and delegates to the parent class for existing behavior.
+ *
+ * @since 1.2
+ */
+public class DefaultWebSessionStorageEvaluator extends DefaultSessionStorageEvaluator {
+
+    //since 1.2.1
+    private SessionManager sessionManager;
+
+    /**
+     * Sets the session manager to use when checking to see if session storage is possible.
+     * @param sessionManager the session manager instance for checking.
+     * @since 1.2.1
+     */
+    //package protected on purpose to maintain point-version compatibility: (1.2.3 -> 1.2.1 should work always).
+    void setSessionManager(SessionManager sessionManager) {
+        this.sessionManager = sessionManager;
+    }
+
+    /**
+     * Returns {@code true} if session storage is generally available (as determined by the super class's global
+     * configuration property {@link #isSessionStorageEnabled()} and no request-specific override has turned off
+     * session storage, {@code false} otherwise.
+     * <p/>
+     * This means session storage is disabled if the {@link #isSessionStorageEnabled()} property is {@code false} or if
+     * a request attribute is discovered that turns off session storage for the current request.
+     *
+     * @param subject the {@code Subject} for which session state persistence may be enabled
+     * @return {@code true} if session storage is generally available (as determined by the super class's global
+     *         configuration property {@link #isSessionStorageEnabled()} and no request-specific override has turned off
+     *         session storage, {@code false} otherwise.
+     */
+    @SuppressWarnings({"SimplifiableIfStatement"})
+    @Override
+    public boolean isSessionStorageEnabled(Subject subject) {
+        if (subject.getSession(false) != null) {
+            //use what already exists
+            return true;
+        }
+
+        if (!isSessionStorageEnabled()) {
+            //honor global setting:
+            return false;
+        }
+
+        //SHIRO-350: non-web subject instances can't be saved to web-only session managers:
+        //since 1.2.1:
+        if (!(subject instanceof WebSubject) && (this.sessionManager != null && !(this.sessionManager instanceof NativeSessionManager))) {
+            return false;
+        }
+
+        return WebUtils._isSessionCreationEnabled(subject);
+    }
+
+
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java
new file mode 100644
index 0000000..b3f3d11
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSubjectFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.mgt.DefaultSubjectFactory;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.subject.WebSubjectContext;
+import org.apache.shiro.web.subject.support.WebDelegatingSubject;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code SubjectFactory} implementation that creates {@link WebDelegatingSubject} instances.
+ * <p/>
+ * {@code WebDelegatingSubject} instances are required if Request/Response objects are to be maintained across
+ * threads when using the {@code Subject} {@link Subject#associateWith(java.util.concurrent.Callable) createCallable}
+ * and {@link Subject#associateWith(Runnable) createRunnable} methods.
+ *
+ * @since 1.0
+ */
+public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
+
+    public DefaultWebSubjectFactory() {
+        super();
+    }
+
+    public Subject createSubject(SubjectContext context) {
+        if (!(context instanceof WebSubjectContext)) {
+            return super.createSubject(context);
+        }
+        WebSubjectContext wsc = (WebSubjectContext) context;
+        SecurityManager securityManager = wsc.resolveSecurityManager();
+        Session session = wsc.resolveSession();
+        boolean sessionEnabled = wsc.isSessionCreationEnabled();
+        PrincipalCollection principals = wsc.resolvePrincipals();
+        boolean authenticated = wsc.resolveAuthenticated();
+        String host = wsc.resolveHost();
+        ServletRequest request = wsc.resolveServletRequest();
+        ServletResponse response = wsc.resolveServletResponse();
+
+        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
+                request, response, securityManager);
+    }
+
+    /**
+     * @deprecated since 1.2 - override {@link #createSubject(org.apache.shiro.subject.SubjectContext)} directly if you
+     *             need to instantiate a custom {@link Subject} class.
+     */
+    @Deprecated
+    protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated,
+                                         String host, Session session,
+                                         ServletRequest request, ServletResponse response,
+                                         SecurityManager securityManager) {
+        return new WebDelegatingSubject(principals, authenticated, host, session, true,
+                request, response, securityManager);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/WebSecurityManager.java b/web/src/main/java/org/apache/shiro/web/mgt/WebSecurityManager.java
new file mode 100644
index 0000000..a94b997
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/WebSecurityManager.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.mgt.SecurityManager;
+
+/**
+ * This interface represents a {@link SecurityManager} implementation that can used in web-enabled applications.
+ *
+ * @since 1.0
+ */
+public interface WebSecurityManager extends SecurityManager {
+
+    /**
+     * Security information needs to be retained from request to request, so Shiro makes use of a
+     * session for this. Typically, a security manager will use the servlet container's HTTP session
+     * but custom session implementations, for example based on EhCache, may also be used. This
+     * method indicates whether the security manager is using the HTTP session or not.
+     *
+     * @return <code>true</code> if the security manager is using the HTTP session; otherwise,
+     *         <code>false</code>.
+     */
+    boolean isHttpSessionMode();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/package-info.java b/web/src/main/java/org/apache/shiro/web/mgt/package-info.java
new file mode 100644
index 0000000..d5127e5
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/mgt/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components supporting web-specific
+ * {@link org.apache.shiro.mgt.SecurityManager SecurityManager} implementations.
+ */
+package org.apache.shiro.web.mgt;
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/package-info.java b/web/src/main/java/org/apache/shiro/web/package-info.java
new file mode 100644
index 0000000..fb50262
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Shiro's web support module to support security in any web-enabled application.
+ */
+package org.apache.shiro.web;
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java
new file mode 100644
index 0000000..9bc7189
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/AbstractFilter.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+
+/**
+ * Base abstract Filter simplifying Filter initialization and {@link #getInitParam(String) access} to init parameters.
+ * Subclass initialization logic should be performed by overriding the {@link #onFilterConfigSet()} template method.
+ * FilterChain execution logic (the
+ * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} method
+ * is left to subclasses.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractFilter extends ServletContextSupport implements Filter {
+
+    private static transient final Logger log = LoggerFactory.getLogger(AbstractFilter.class);
+
+    /**
+     * FilterConfig provided by the Servlet container at start-up.
+     */
+    protected FilterConfig filterConfig;
+
+    /**
+     * Returns the servlet container specified {@code FilterConfig} instance provided at
+     * {@link #init(javax.servlet.FilterConfig) startup}.
+     *
+     * @return the servlet container specified {@code FilterConfig} instance provided at start-up.
+     */
+    public FilterConfig getFilterConfig() {
+        return filterConfig;
+    }
+
+    /**
+     * Sets the FilterConfig <em>and</em> the {@code ServletContext} as attributes of this class for use by
+     * subclasses.  That is:
+     * <pre>
+     * this.filterConfig = filterConfig;
+     * setServletContext(filterConfig.getServletContext());</pre>
+     *
+     * @param filterConfig the FilterConfig instance provided by the Servlet container at start-up.
+     */
+    public void setFilterConfig(FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+        setServletContext(filterConfig.getServletContext());
+    }
+
+    /**
+     * Returns the value for the named {@code init-param}, or {@code null} if there was no {@code init-param}
+     * specified by that name.
+     *
+     * @param paramName the name of the {@code init-param}
+     * @return the value for the named {@code init-param}, or {@code null} if there was no {@code init-param}
+     *         specified by that name.
+     */
+    protected String getInitParam(String paramName) {
+        FilterConfig config = getFilterConfig();
+        if (config != null) {
+            return StringUtils.clean(config.getInitParameter(paramName));
+        }
+        return null;
+    }
+
+    /**
+     * Sets the filter's {@link #setFilterConfig filterConfig} and then immediately calls
+     * {@link #onFilterConfigSet() onFilterConfigSet()} to trigger any processing a subclass might wish to perform.
+     *
+     * @param filterConfig the servlet container supplied FilterConfig instance.
+     * @throws javax.servlet.ServletException if {@link #onFilterConfigSet() onFilterConfigSet()} throws an Exception.
+     */
+    public final void init(FilterConfig filterConfig) throws ServletException {
+        setFilterConfig(filterConfig);
+        try {
+            onFilterConfigSet();
+        } catch (Exception e) {
+            if (e instanceof ServletException) {
+                throw (ServletException) e;
+            } else {
+                if (log.isErrorEnabled()) {
+                    log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
+                }
+                throw new ServletException(e);
+            }
+        }
+    }
+
+    /**
+     * Template method to be overridden by subclasses to perform initialization logic at start-up.  The
+     * {@code ServletContext} and {@code FilterConfig} will be accessible
+     * (and non-{@code null}) at the time this method is invoked via the
+     * {@link #getServletContext() getServletContext()} and {@link #getFilterConfig() getFilterConfig()}
+     * methods respectively.
+     * <p/>
+     * {@code init-param} values may be conveniently obtained via the {@link #getInitParam(String)} method.
+     *
+     * @throws Exception if the subclass has an error upon initialization.
+     */
+    protected void onFilterConfigSet() throws Exception {
+    }
+
+    /**
+     * Default no-op implementation that can be overridden by subclasses for custom cleanup behavior.
+     */
+    public void destroy() {
+    }
+
+
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
new file mode 100644
index 0000000..c0010bd
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.ExecutionException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.subject.WebSubject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+/**
+ * Abstract base class that provides all standard Shiro request filtering behavior and expects
+ * subclasses to implement configuration-specific logic (INI, XML, .properties, etc).
+ * <p/>
+ * Subclasses should perform configuration and construction logic in an overridden
+ * {@link #init()} method implementation.  That implementation should make available any constructed
+ * {@code SecurityManager} and {@code FilterChainResolver} by calling
+ * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
+ * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
+ * <h3>Static SecurityManager</h3>
+ * By default the {@code SecurityManager} instance enabled by this filter <em>will not</em> be enabled in static
+ * memory via the {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}
+ * method.  Instead, it is expected that Subject instances will always be constructed on a request-processing thread
+ * via instances of this Filter class.
+ * <p/>
+ * However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might
+ * be easiest to enable the SecurityManager to be available in static memory via the
+ * {@link SecurityUtils#getSecurityManager()} method.  You can do this by additionally specifying an {@code init-param}:
+ * <pre>
+ * <filter>
+ *     ... other config here ...
+ *     <init-param>
+ *         <param-name>staticSecurityManagerEnabled</param-name>
+ *         <param-value>true</param-value>
+ *     </init-param>
+ * </filter>
+ * </pre>
+ * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to
+ * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
+ *
+ * @since 1.0
+ * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a>
+ */
+public abstract class AbstractShiroFilter extends OncePerRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);
+
+    private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
+
+    // Reference to the security manager used by this filter
+    private WebSecurityManager securityManager;
+
+    // Used to determine which chain should handle an incoming request/response
+    private FilterChainResolver filterChainResolver;
+
+    /**
+     * Whether or not to bind the constructed SecurityManager instance to static memory (via
+     * SecurityUtils.setSecurityManager).  This was added to support https://issues.apache.org/jira/browse/SHIRO-287
+     * @since 1.2
+     */
+    private boolean staticSecurityManagerEnabled;
+
+    protected AbstractShiroFilter() {
+        this.staticSecurityManagerEnabled = false;
+    }
+
+    public WebSecurityManager getSecurityManager() {
+        return securityManager;
+    }
+
+    public void setSecurityManager(WebSecurityManager sm) {
+        this.securityManager = sm;
+    }
+
+    public FilterChainResolver getFilterChainResolver() {
+        return filterChainResolver;
+    }
+
+    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
+        this.filterChainResolver = filterChainResolver;
+    }
+
+    /**
+     * Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
+     * to static memory (via
+     * {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
+     * {@code false} otherwise.
+     * <p/>
+     * The default value is {@code false}.
+     * <p/>
+     *
+     *
+     * @return {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
+     *         to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
+     *         {@code false} otherwise.
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     */
+    public boolean isStaticSecurityManagerEnabled() {
+        return staticSecurityManagerEnabled;
+    }
+
+    /**
+     * Sets if the constructed {@link #getSecurityManager() securityManager} reference should be bound
+     * to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
+     * <p/>
+     * The default value is {@code false}.
+     *
+     * @param staticSecurityManagerEnabled if the constructed {@link #getSecurityManager() securityManager} reference
+     *                                       should be bound to static memory (via
+     *                                       {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     */
+    public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
+        this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
+    }
+
+    protected final void onFilterConfigSet() throws Exception {
+        //added in 1.2 for SHIRO-287:
+        applyStaticSecurityManagerEnabledConfig();
+        init();
+        ensureSecurityManager();
+        //added in 1.2 for SHIRO-287:
+        if (isStaticSecurityManagerEnabled()) {
+            SecurityUtils.setSecurityManager(getSecurityManager());
+        }
+    }
+
+    /**
+     * Checks if the init-param that configures the filter to use static memory has been configured, and if so,
+     * sets the {@link #setStaticSecurityManagerEnabled(boolean)} attribute with the configured value.
+     *
+     * @since 1.2
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     */
+    private void applyStaticSecurityManagerEnabledConfig() {
+        String value = getInitParam(STATIC_INIT_PARAM_NAME);
+        if (value != null) {
+            Boolean b = Boolean.valueOf(value);
+            if (b != null) {
+                setStaticSecurityManagerEnabled(b);
+            }
+        }
+    }
+
+    public void init() throws Exception {
+    }
+
+    /**
+     * A fallback mechanism called in {@link #onFilterConfigSet()} to ensure that the
+     * {@link #getSecurityManager() securityManager} property has been set by configuration, and if not,
+     * creates one automatically.
+     */
+    private void ensureSecurityManager() {
+        WebSecurityManager securityManager = getSecurityManager();
+        if (securityManager == null) {
+            log.info("No SecurityManager configured.  Creating default.");
+            securityManager = createDefaultSecurityManager();
+            setSecurityManager(securityManager);
+        }
+    }
+
+    protected WebSecurityManager createDefaultSecurityManager() {
+        return new DefaultWebSecurityManager();
+    }
+
+    protected boolean isHttpSessions() {
+        return getSecurityManager().isHttpSessionMode();
+    }
+
+    /**
+     * Wraps the original HttpServletRequest in a {@link ShiroHttpServletRequest}, which is required for supporting
+     * Servlet Specification behavior backed by a {@link org.apache.shiro.subject.Subject Subject} instance.
+     *
+     * @param orig the original Servlet Container-provided incoming {@code HttpServletRequest} instance.
+     * @return {@link ShiroHttpServletRequest ShiroHttpServletRequest} instance wrapping the original.
+     * @since 1.0
+     */
+    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
+        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
+    }
+
+    /**
+     * Prepares the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request
+     * processing.
+     * <p/>
+     * If the {@code ServletRequest} is an instance of {@link HttpServletRequest}, the value returned from this method
+     * is obtained by calling {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)} to allow Shiro-specific
+     * HTTP behavior, otherwise the original {@code ServletRequest} argument is returned.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @param chain    the Servlet Container provided {@code FilterChain} that will receive the returned request.
+     * @return the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request processing.
+     * @since 1.0
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
+        ServletRequest toUse = request;
+        if (request instanceof HttpServletRequest) {
+            HttpServletRequest http = (HttpServletRequest) request;
+            toUse = wrapServletRequest(http);
+        }
+        return toUse;
+    }
+
+    /**
+     * Returns a new {@link ShiroHttpServletResponse} instance, wrapping the {@code orig} argument, in order to provide
+     * correct URL rewriting behavior required by the Servlet Specification when using Shiro-based sessions (and not
+     * Servlet Container HTTP-based sessions).
+     *
+     * @param orig    the original {@code HttpServletResponse} instance provided by the Servlet Container.
+     * @param request the {@code ShiroHttpServletRequest} instance wrapping the original request.
+     * @return the wrapped ServletResponse instance to use during {@link FilterChain} execution.
+     * @since 1.0
+     */
+    protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
+        return new ShiroHttpServletResponse(orig, getServletContext(), request);
+    }
+
+    /**
+     * Prepares the {@code ServletResponse} instance that will be passed to the {@code FilterChain} for request
+     * processing.
+     * <p/>
+     * This implementation delegates to {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)}
+     * only if Shiro-based sessions are enabled (that is, !{@link #isHttpSessions()}) and the request instance is a
+     * {@link ShiroHttpServletRequest}.  This ensures that any URL rewriting that occurs is handled correctly using the
+     * Shiro-managed Session's sessionId and not a servlet container session ID.
+     * <p/>
+     * If HTTP-based sessions are enabled (the default), then this method does nothing and just returns the
+     * {@code ServletResponse} argument as-is, relying on the default Servlet Container URL rewriting logic.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @param chain    the Servlet Container provided {@code FilterChain} that will receive the returned request.
+     * @return the {@code ServletResponse} instance that will be passed to the {@code FilterChain} during request processing.
+     * @since 1.0
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
+        ServletResponse toUse = response;
+        if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
+                (response instanceof HttpServletResponse)) {
+            //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
+            //using Shiro sessions (i.e. not simple HttpSession based sessions):
+            toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
+        }
+        return toUse;
+    }
+
+    /**
+     * Creates a {@link WebSubject} instance to associate with the incoming request/response pair which will be used
+     * throughout the request/response execution.
+     *
+     * @param request  the incoming {@code ServletRequest}
+     * @param response the outgoing {@code ServletResponse}
+     * @return the {@code WebSubject} instance to associate with the request/response execution
+     * @since 1.0
+     */
+    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
+        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
+    }
+
+    /**
+     * Updates any 'native'  Session's last access time that might exist to the timestamp when this method is called.
+     * If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
+     * session ({@code subject.getSession(false) == null}), this method does nothing.
+     * <p/>This method implementation merely calls
+     * <code>Session.{@link org.apache.shiro.session.Session#touch() touch}()</code> on the session.
+     *
+     * @param request  incoming request - ignored, but available to subclasses that might wish to override this method
+     * @param response outgoing response - ignored, but available to subclasses that might wish to override this method
+     * @since 1.0
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
+        if (!isHttpSessions()) { //'native' sessions
+            Subject subject = SecurityUtils.getSubject();
+            //Subject should never _ever_ be null, but just in case:
+            if (subject != null) {
+                Session session = subject.getSession(false);
+                if (session != null) {
+                    try {
+                        session.touch();
+                    } catch (Throwable t) {
+                        log.error("session.touch() method invocation has failed.  Unable to update" +
+                                "the corresponding session's last access time based on the incoming request.", t);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request.  It
+     * performs the following ordered operations:
+     * <ol>
+     * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
+     * the incoming {@code ServletRequest} for use during Shiro's processing</li>
+     * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
+     * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
+     * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
+     * {@link Subject} instance based on the specified request/response pair.</li>
+     * <li>Finally {@link Subject#execute(Runnable) executes} the
+     * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
+     * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
+     * methods</li>
+     * </ol>
+     * <p/>
+     * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
+     * implementation technique to guarantee proper thread binding and restoration is completed successfully.
+     *
+     * @param servletRequest  the incoming {@code ServletRequest}
+     * @param servletResponse the outgoing {@code ServletResponse}
+     * @param chain           the container-provided {@code FilterChain} to execute
+     * @throws IOException                    if an IO error occurs
+     * @throws javax.servlet.ServletException if an Throwable other than an IOException
+     */
+    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
+            throws ServletException, IOException {
+
+        Throwable t = null;
+
+        try {
+            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
+            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
+
+            final Subject subject = createSubject(request, response);
+
+            //noinspection unchecked
+            subject.execute(new Callable() {
+                public Object call() throws Exception {
+                    updateSessionLastAccessTime(request, response);
+                    executeChain(request, response, chain);
+                    return null;
+                }
+            });
+        } catch (ExecutionException ex) {
+            t = ex.getCause();
+        } catch (Throwable throwable) {
+            t = throwable;
+        }
+
+        if (t != null) {
+            if (t instanceof ServletException) {
+                throw (ServletException) t;
+            }
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            }
+            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
+            String msg = "Filtered request failed.";
+            throw new ServletException(msg, t);
+        }
+    }
+
+    /**
+     * Returns the {@code FilterChain} to execute for the given request.
+     * <p/>
+     * The {@code origChain} argument is the
+     * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
+     * more behavior by pre-pending further chains according to the Shiro configuration.
+     * <p/>
+     * This implementation returns the chain that will actually be executed by acquiring the chain from a
+     * {@link #getFilterChainResolver() filterChainResolver}.  The resolver determines exactly which chain to
+     * execute, typically based on URL configuration.  If no chain is returned from the resolver call
+     * (returns {@code null}), then the {@code origChain} will be returned by default.
+     *
+     * @param request   the incoming ServletRequest
+     * @param response  the outgoing ServletResponse
+     * @param origChain the original {@code FilterChain} provided by the Servlet Container
+     * @return the {@link FilterChain} to execute for the given request
+     * @since 1.0
+     */
+    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
+        FilterChain chain = origChain;
+
+        FilterChainResolver resolver = getFilterChainResolver();
+        if (resolver == null) {
+            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
+            return origChain;
+        }
+
+        FilterChain resolved = resolver.getChain(request, response, origChain);
+        if (resolved != null) {
+            log.trace("Resolved a configured FilterChain for the current request.");
+            chain = resolved;
+        } else {
+            log.trace("No FilterChain configured for the current request.  Using the default.");
+        }
+
+        return chain;
+    }
+
+    /**
+     * Executes a {@link FilterChain} for the given request.
+     * <p/>
+     * This implementation first delegates to
+     * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
+     * to allow the application's Shiro configuration to determine exactly how the chain should execute.  The resulting
+     * value from that call is then executed directly by calling the returned {@code FilterChain}'s
+     * {@link FilterChain#doFilter doFilter} method.  That is:
+     * <pre>
+     * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
+     * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
+     *
+     * @param request   the incoming ServletRequest
+     * @param response  the outgoing ServletResponse
+     * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
+     *                  chain of Filters.
+     * @throws IOException      if the underlying {@code chain.doFilter} call results in an IOException
+     * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
+     * @since 1.0
+     */
+    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
+            throws IOException, ServletException {
+        FilterChain chain = getExecutionChain(request, response, origChain);
+        chain.doFilter(request, response);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/AdviceFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/AdviceFilter.java
new file mode 100644
index 0000000..b2fb207
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/AdviceFilter.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * A Servlet Filter that enables AOP-style "around" advice for a ServletRequest via
+ * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) preHandle},
+ * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) postHandle},
+ * and {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion}
+ * hooks.
+ *
+ * @since 0.9
+ */
+public abstract class AdviceFilter extends OncePerRequestFilter {
+
+    /**
+     * The static logger available to this class only
+     */
+    private static final Logger log = LoggerFactory.getLogger(AdviceFilter.class);
+
+    /**
+     * Returns {@code true} if the filter chain should be allowed to continue, {@code false} otherwise.
+     * It is called before the chain is actually consulted/executed.
+     * <p/>
+     * The default implementation returns {@code true} always and exists as a template method for subclasses.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @return {@code true} if the filter chain should be allowed to continue, {@code false} otherwise.
+     * @throws Exception if there is any error.
+     */
+    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+        return true;
+    }
+
+    /**
+     * Allows 'post' advice logic to be called, but only if no exception occurs during filter chain execution.  That
+     * is, if {@link #executeChain executeChain} throws an exception, this method will never be called.  Be aware of
+     * this when implementing logic.  Most resource 'cleanup' behavior is often done in the
+     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion(request,response,exception)}
+     * implementation, which is guaranteed to be called for every request, even when the chain processing throws
+     * an Exception.
+     * <p/>
+     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @throws Exception if an error occurs.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
+    }
+
+    /**
+     * Called in all cases in a {@code finally} block even if {@link #preHandle preHandle} returns
+     * {@code false} or if an exception is thrown during filter chain processing.  Can be used for resource
+     * cleanup if so desired.
+     * <p/>
+     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
+     *
+     * @param request   the incoming ServletRequest
+     * @param response  the outgoing ServletResponse
+     * @param exception any exception thrown during {@link #preHandle preHandle}, {@link #executeChain executeChain},
+     *                  or {@link #postHandle postHandle} execution, or {@code null} if no exception was thrown
+     *                  (i.e. the chain processed successfully).
+     * @throws Exception if an error occurs.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
+    }
+
+    /**
+     * Actually executes the specified filter chain by calling <code>chain.doFilter(request,response);</code>.
+     * <p/>
+     * Can be overridden by subclasses for custom logic.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @param chain    the filter chain to execute
+     * @throws Exception if there is any error executing the chain.
+     */
+    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
+        chain.doFilter(request, response);
+    }
+
+    /**
+     * Actually implements the chain execution logic, utilizing
+     * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) pre},
+     * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) post}, and
+     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) after}
+     * advice hooks.
+     *
+     * @param request  the incoming ServletRequest
+     * @param response the outgoing ServletResponse
+     * @param chain    the filter chain to execute
+     * @throws ServletException if a servlet-related error occurs
+     * @throws IOException      if an IO error occurs
+     */
+    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+
+        Exception exception = null;
+
+        try {
+
+            boolean continueChain = preHandle(request, response);
+            if (log.isTraceEnabled()) {
+                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
+            }
+
+            if (continueChain) {
+                executeChain(request, response, chain);
+            }
+
+            postHandle(request, response);
+            if (log.isTraceEnabled()) {
+                log.trace("Successfully invoked postHandle method");
+            }
+
+        } catch (Exception e) {
+            exception = e;
+        } finally {
+            cleanup(request, response, exception);
+        }
+    }
+
+    /**
+     * Executes cleanup logic in the {@code finally} code block in the
+     * {@link #doFilterInternal(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilterInternal}
+     * implementation.
+     * <p/>
+     * This implementation specifically calls
+     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion}
+     * as well as handles any exceptions properly.
+     *
+     * @param request  the incoming {@code ServletRequest}
+     * @param response the outgoing {@code ServletResponse}
+     * @param existing any exception that might have occurred while executing the {@code FilterChain} or
+     *                 pre or post advice, or {@code null} if the pre/chain/post execution did not throw an {@code Exception}.
+     * @throws ServletException if any exception other than an {@code IOException} is thrown.
+     * @throws IOException      if the pre/chain/post execution throw an {@code IOException}
+     */
+    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
+            throws ServletException, IOException {
+        Exception exception = existing;
+        try {
+            afterCompletion(request, response, exception);
+            if (log.isTraceEnabled()) {
+                log.trace("Successfully invoked afterCompletion method.");
+            }
+        } catch (Exception e) {
+            if (exception == null) {
+                exception = e;
+            } else {
+                log.debug("afterCompletion implementation threw an exception.  This will be ignored to " +
+                        "allow the original source exception to be propagated.", e);
+            }
+        }
+        if (exception != null) {
+            if (exception instanceof ServletException) {
+                throw (ServletException) exception;
+            } else if (exception instanceof IOException) {
+                throw (IOException) exception;
+            } else {
+                if (log.isDebugEnabled()) {
+                    String msg = "Filter execution resulted in an unexpected Exception " +
+                            "(not IOException or ServletException as the Filter API recommends).  " +
+                            "Wrapping in ServletException and propagating.";
+                    log.debug(msg);
+                }
+                throw new ServletException(exception);
+            }
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java b/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java
new file mode 100644
index 0000000..065b51d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Interface representing HTTP cookie operations, supporting pojo-style getters and setters for all
+ * attributes which includes <a href="http://www.owasp.org/index.php/HttpOnly">HttpOnly</a> support.
+ * This allows Shiro to set <a href="http://www.owasp.org/index.php/HttpOnly">HttpOnly</a> cookies even on
+ * Servlet containers based on the {@code 2.4} and {@code 2.5} API (Servlet API 'native' support was only introduced in
+ * the {@code 2.6} specification).
+ *
+ * @since 1.0
+ */
+public interface Cookie {
+    /**
+     * The value of deleted cookie (with the maxAge 0).
+     */
+    public static final String DELETED_COOKIE_VALUE = "deleteMe";
+    
+
+    /**
+     * The number of seconds in one year (= 60 * 60 * 24 * 365).
+     */
+    public static final int ONE_YEAR = 60 * 60 * 24 * 365;
+
+    /**
+     * Root path to use when the path hasn't been set and request context root is empty or null.
+     */
+    public static final String ROOT_PATH = "/";
+
+    String getName();
+
+    void setName(String name);
+
+    String getValue();
+
+    void setValue(String value);
+
+    String getComment();
+
+    void setComment(String comment);
+
+    String getDomain();
+
+    void setDomain(String domain);
+
+    int getMaxAge();
+
+    void setMaxAge(int maxAge);
+
+    String getPath();
+
+    void setPath(String path);
+
+    boolean isSecure();
+
+    void setSecure(boolean secure);
+
+    int getVersion();
+
+    void setVersion(int version);
+
+    void setHttpOnly(boolean httpOnly);
+
+    boolean isHttpOnly();
+
+    void saveTo(HttpServletRequest request, HttpServletResponse response);
+
+    void removeFrom(HttpServletRequest request, HttpServletResponse response);
+
+    String readValue(HttpServletRequest request, HttpServletResponse response);
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
new file mode 100644
index 0000000..b701ad1
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
@@ -0,0 +1,356 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniFactorySupport;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * <h1>Deprecated</h1>
+ * This filter has been deprecated as of Shiro 1.2 in favor of using the {@link ShiroFilter} in {@code web.xml} instead.
+ * See the {@link ShiroFilter} JavaDoc for usage.
+ * <p/>
+ * ======================
+ * <p/>
+ * Servlet Filter that configures and enables all Shiro functions within a web application by using the
+ * <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> configuration format.
+ * <p/>
+ * The actual INI configuration contents are not covered here, but instead in Shiro's
+ * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and additional web-specific
+ * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
+ * <h2>Usage</h2>
+ * <h3>Default</h3>
+ * By default, the simplest filter declaration expects a {@code shiro.ini} resource to be located at
+ * {@code /WEB-INF/shiro.ini}, or, if not there, falls back to checking the root of the classpath
+ * (i.e. {@code classpath:shiro.ini}):
+ * <pre>
+ * <filter>
+ *     <filter-name>ShiroFilter</filter-name>
+ *     <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
+ * </filter>
+ * </pre>
+ * <h3>Custom Path</h3>
+ * If you want the INI configuration to be somewhere other than {@code /WEB-INF/shiro.ini} or
+ * {@code classpath:shiro.ini}, you may specify an alternate location via the {@code configPath init-param}:
+ * <pre>
+ * <filter>
+ *     <filter-name>ShiroFilter</filter-name>
+ *     <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
+ *     <init-param>
+ *         <param-name>configPath</param-name>
+ *         <param-value>/WEB-INF/someFile.ini</param-value>
+ *     </init-param>
+ * </filter>
+ * </pre>
+ * Unqualified (schemeless or 'non-prefixed') paths are assumed to be {@code ServletContext} resource paths, resolvable
+ * via {@link javax.servlet.ServletContext#getResourceAsStream(String) ServletContext#getResourceAsStream}.
+ * <p/>
+ * Non-ServletContext resources may be loaded from qualified locations by specifying prefixes indicating the source,
+ * e.g. {@code file:}, {@code url:}, and {@code classpath:}.  See the
+ * {@link ResourceUtils#getInputStreamForPath(String)} JavaDoc for more.
+ * <h3>Inline</h3>
+ * For relatively simple environments, you can embed the INI config directly inside the filter declaration with
+ * the {@code config init-param}:
+ * <pre>
+ * <filter>
+ *     <filter-name>ShiroFilter</filter-name>
+ *     <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
+ *     <init-param>
+ *         <param-name>config</param-name>
+ *         <param-value>
+ *             #INI config goes here...
+ *      </param-value>
+ *     </init-param>
+ * </filter>
+ * </pre>
+ * Although this is typically not recommended because any Shiro configuration changes would contribute to version control
+ * 'noise' in the web.xml file.
+ * <p/>
+ * When creating the shiro.ini configuration itself, please see Shiro's
+ * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and
+ * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
+ *
+ * @see <a href="http://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
+ * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
+ * @since 1.0
+ * @deprecated in 1.2 in favor of using the {@link ShiroFilter}
+ */
+ at Deprecated
+public class IniShiroFilter extends AbstractShiroFilter {
+
+    public static final String CONFIG_INIT_PARAM_NAME = "config";
+    public static final String CONFIG_PATH_INIT_PARAM_NAME = "configPath";
+
+    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
+
+    private static final Logger log = LoggerFactory.getLogger(IniShiroFilter.class);
+
+    private String config;
+    private String configPath;
+
+    public IniShiroFilter() {
+    }
+
+    /**
+     * Returns the actual INI configuration text to use to build the {@link SecurityManager} and
+     * {@link FilterChainResolver} used by the web application or {@code null} if the
+     * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
+     * <p/>
+     * This value is {@code null} by default, but it will be automatically set to the value of the
+     * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
+     * container at startup.
+     *
+     * @return the actual INI configuration text to use to build the {@link SecurityManager} and
+     *         {@link FilterChainResolver} used by the web application or {@code null} if the
+     *         {@link #getConfigPath() configPath} should be used to load a fallback INI source.
+     */
+    public String getConfig() {
+        return this.config;
+    }
+
+    /**
+     * Sets the actual INI configuration text to use to build the {@link SecurityManager} and
+     * {@link FilterChainResolver} used by the web application.  If this value is {@code null}, the
+     * {@link #getConfigPath() configPath} will be checked to see if a .ini file should be loaded instead.
+     * <p/>
+     * This value is {@code null} by default, but it will be automatically set to the value of the
+     * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
+     * container at startup.
+     *
+     * @param config the actual INI configuration text to use to build the {@link SecurityManager} and
+     *               {@link FilterChainResolver} used by the web application.
+     */
+    public void setConfig(String config) {
+        this.config = config;
+    }
+
+    /**
+     * Returns the config path to be used to load a .ini file for configuration if a configuration is
+     * not specified via the {@link #getConfig() config} attribute.
+     * <p/>
+     * This value is {@code null} by default, but it will be automatically set to the value of the
+     * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
+     * container at startup.
+     *
+     * @return the config path to be used to load a .ini file for configuration if a configuration is
+     *         not specified via the {@link #getConfig() config} attribute.
+     */
+    public String getConfigPath() {
+        return configPath;
+    }
+
+    /**
+     * Sets the config path to be used to load a .ini file for configuration if a configuration is
+     * not specified via the {@link #getConfig() config} attribute.
+     * <p/>
+     * This value is {@code null} by default, but it will be automatically set to the value of the
+     * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
+     * container at startup.
+     *
+     * @param configPath the config path to be used to load a .ini file for configuration if a configuration is
+     *                   not specified via the {@link #getConfig() config} attribute.
+     */
+    public void setConfigPath(String configPath) {
+        this.configPath = StringUtils.clean(configPath);
+    }
+
+    public void init() throws Exception {
+        applyInitParams();
+        configure();
+    }
+
+    protected void applyInitParams() throws Exception {
+        String config = getInitParam(CONFIG_INIT_PARAM_NAME);
+        if (config != null) {
+            setConfig(config);
+        }
+        String configPath = getInitParam(CONFIG_PATH_INIT_PARAM_NAME);
+        if (configPath != null) {
+            setConfigPath(configPath);
+        }
+    }
+
+    protected void configure() throws Exception {
+        Ini ini = loadIniFromConfig();
+
+        if (CollectionUtils.isEmpty(ini)) {
+            log.info("Null or empty configuration specified via 'config' init-param.  " +
+                    "Checking path-based configuration.");
+            ini = loadIniFromPath();
+        }
+        //added for SHIRO-178:
+        if (CollectionUtils.isEmpty(ini)) {
+            log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
+                    CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
+                    DEFAULT_WEB_INI_RESOURCE_PATH + " file.");
+            ini = getServletContextIniResource(DEFAULT_WEB_INI_RESOURCE_PATH);
+        }
+        //although the preferred default is /WEB-INF/shiro.ini per SHIRO-178, keep this
+        //for backwards compatibility:
+        if (CollectionUtils.isEmpty(ini)) {
+            log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
+                    CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
+                    IniFactorySupport.DEFAULT_INI_RESOURCE_PATH + " file.");
+            ini = IniFactorySupport.loadDefaultClassPathIni();
+        }
+
+        Map<String, ?> objects = applySecurityManager(ini);
+        applyFilterChainResolver(ini, objects);
+    }
+
+    protected Ini loadIniFromConfig() {
+        Ini ini = null;
+        String config = getConfig();
+        if (config != null) {
+            ini = convertConfigToIni(config);
+        }
+        return ini;
+    }
+
+    protected Ini loadIniFromPath() {
+        Ini ini = null;
+        String path = getConfigPath();
+        if (path != null) {
+            ini = convertPathToIni(path);
+        }
+        return ini;
+    }
+
+    protected Map<String, ?> applySecurityManager(Ini ini) {
+        WebIniSecurityManagerFactory factory;
+        if (CollectionUtils.isEmpty(ini)) {
+            factory = new WebIniSecurityManagerFactory();
+        } else {
+            factory = new WebIniSecurityManagerFactory(ini);
+        }
+
+        // Create the security manager and check that it implements WebSecurityManager.
+        // Otherwise, it can't be used with the filter.
+        SecurityManager securityManager = factory.getInstance();
+        if (!(securityManager instanceof WebSecurityManager)) {
+            String msg = "The configured security manager is not an instance of WebSecurityManager, so " +
+                    "it can not be used with the Shiro servlet filter.";
+            throw new ConfigurationException(msg);
+        }
+
+        setSecurityManager((WebSecurityManager) securityManager);
+
+        return factory.getBeans();
+    }
+
+    protected void applyFilterChainResolver(Ini ini, Map<String, ?> defaults) {
+        if (ini == null || ini.isEmpty()) {
+            //nothing to use to create the resolver, just return
+            //(the AbstractShiroFilter allows a null resolver, in which case the original FilterChain is
+            // always used).
+            return;
+        }
+
+        //only create a resolver if the 'filters' or 'urls' sections are defined:
+        Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
+        Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
+        if ((urls != null && !urls.isEmpty()) || (filters != null && !filters.isEmpty())) {
+            //either the urls section or the filters section was defined.  Go ahead and create the resolver
+            //and set it:
+            IniFilterChainResolverFactory filterChainResolverFactory = new IniFilterChainResolverFactory(ini, defaults);
+            filterChainResolverFactory.setFilterConfig(getFilterConfig());
+            FilterChainResolver resolver = filterChainResolverFactory.getInstance();
+            setFilterChainResolver(resolver);
+        }
+    }
+
+    protected Ini convertConfigToIni(String config) {
+        Ini ini = new Ini();
+        ini.load(config);
+        return ini;
+    }
+
+    /**
+     * Returns the INI instance reflecting the specified servlet context resource path or {@code null} if no
+     * resource was found.
+     *
+     * @param servletContextPath the servlet context resource path of the INI file to load
+     * @return the INI instance reflecting the specified servlet context resource path or {@code null} if no
+     *         resource was found.
+     * @since 1.2
+     */
+    protected Ini getServletContextIniResource(String servletContextPath) {
+        String path = WebUtils.normalize(servletContextPath);
+        if (getServletContext() != null) {
+            InputStream is = getServletContext().getResourceAsStream(path);
+            if (is != null) {
+                Ini ini = new Ini();
+                ini.load(is);
+                if (CollectionUtils.isEmpty(ini)) {
+                    log.warn("ServletContext INI resource '" + servletContextPath + "' exists, but it did not contain " +
+                            "any data.");
+                }
+                return ini;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Converts the specified file path to an {@link Ini} instance.
+     * <p/>
+     * If the path does not have a resource prefix as defined by {@link ResourceUtils#hasResourcePrefix(String)}, the
+     * path is expected to be resolvable by the {@code ServletContext} via
+     * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
+     *
+     * @param path the path of the INI resource to load into an INI instance.
+     * @return an INI instance populated based on the given INI resource path.
+     */
+    protected Ini convertPathToIni(String path) {
+
+        Ini ini = new Ini();
+
+        //SHIRO-178: Check for servlet context resource and not
+        //only resource paths:
+        if (!ResourceUtils.hasResourcePrefix(path)) {
+            ini = getServletContextIniResource(path);
+            if (ini == null) {
+                String msg = "There is no servlet context resource corresponding to configPath '" + path + "'  If " +
+                        "the resource is located elsewhere (not immediately resolveable in the servlet context), " +
+                        "specify an appropriate classpath:, url:, or file: resource prefix accordingly.";
+                throw new ConfigurationException(msg);
+            }
+        } else {
+            //normal resource path - load as usual:
+            ini.loadFromPath(path);
+        }
+
+        return ini;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/NameableFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/NameableFilter.java
new file mode 100644
index 0000000..de74a32
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/NameableFilter.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.util.Nameable;
+
+import javax.servlet.FilterConfig;
+
+/**
+ * Allows a filter to be named via JavaBeans-compatible
+ * {@link #getName()}/{@link #setName(String)} methods.  If no name is specified, the name of the filter will
+ * default to the name given to it in {@code web.xml} (the {@code FilterConfig}'s
+ * {@link javax.servlet.FilterConfig#getFilterName() filterName}).
+ *
+ * @since 1.0
+ */
+public abstract class NameableFilter extends AbstractFilter implements Nameable {
+
+    /**
+     * The name of this filter, unique within an application.
+     */
+    private String name;
+
+    /**
+     * Returns the filter's name.
+     * <p/>
+     * Unless overridden by calling the {@link #setName(String) setName(String)} method, this value defaults to the
+     * filter name as specified by the servlet container at start-up:
+     * <pre>
+     * this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
+     *
+     * @return the filter name, or {@code null} if none available
+     * @see javax.servlet.GenericServlet#getServletName()
+     * @see javax.servlet.FilterConfig#getFilterName()
+     */
+    protected String getName() {
+        if (this.name == null) {
+            FilterConfig config = getFilterConfig();
+            if (config != null) {
+                this.name = config.getFilterName();
+            }
+        }
+
+        return this.name;
+    }
+
+    /**
+     * Sets the filter's name.
+     * <p/>
+     * Unless overridden by calling this method, this value defaults to the filter name as specified by the
+     * servlet container at start-up:
+     * <pre>
+     * this.name = {@link #getFilterConfig() getFilterConfig()}.{@link javax.servlet.FilterConfig#getFilterName() getName()};</pre>
+     *
+     * @param name the name of the filter.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns a StringBuilder instance with the {@link #getName() name}, or if the name is {@code null}, just the
+     * {@code super.toStringBuilder()} instance.
+     *
+     * @return a StringBuilder instance to use for appending String data that will eventually be returned from a
+     *         {@code toString()} invocation.
+     */
+    protected StringBuilder toStringBuilder() {
+        String name = getName();
+        if (name == null) {
+            return super.toStringBuilder();
+        } else {
+            StringBuilder sb = new StringBuilder();
+            sb.append(name);
+            return sb;
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java
new file mode 100644
index 0000000..2d0b380
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * Filter base class that guarantees to be just executed once per request,
+ * on any servlet container. It provides a {@link #doFilterInternal}
+ * method with HttpServletRequest and HttpServletResponse arguments.
+ * <p/>
+ * The {@link #getAlreadyFilteredAttributeName} method determines how
+ * to identify that a request is already filtered. The default implementation
+ * is based on the configured name of the concrete filter instance.
+ * <h3>Controlling filter execution</h3>
+ * 1.2 introduced the {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method and
+ * {@link #isEnabled()} property to allow explicit controll over whether the filter executes (or allows passthrough)
+ * for any given request.
+ * <p/>
+ * <b>NOTE</b> This class was initially borrowed from the Spring framework but has continued modifications.
+ *
+ * @since 0.1
+ */
+public abstract class OncePerRequestFilter extends NameableFilter {
+
+    /**
+     * Private internal log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
+
+    /**
+     * Suffix that gets appended to the filter name for the "already filtered" request attribute.
+     *
+     * @see #getAlreadyFilteredAttributeName
+     */
+    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
+
+    /**
+     * Determines generally if this filter should execute or let requests fall through to the next chain element.
+     *
+     * @see #isEnabled()
+     */
+    private boolean enabled = true; //most filters wish to execute when configured, so default to true
+
+    /**
+     * Returns {@code true} if this filter should <em>generally</em><b>*</b> execute for any request,
+     * {@code false} if it should let the request/response pass through immediately to the next
+     * element in the {@link FilterChain}.  The default value is {@code true}, as most filters would inherently need
+     * to execute when configured.
+     * <p/>
+     * <b>*</b> This configuration property is for general configuration for any request that comes through
+     * the filter.  The
+     * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isEnabled(request,response)}
+     * method actually determines whether or not if the filter is enabled based on the current request.
+     *
+     * @return {@code true} if this filter should <em>generally</em> execute, {@code false} if it should let the
+     * request/response pass through immediately to the next element in the {@link FilterChain}.
+     * @since 1.2
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Sets whether or not this filter <em>generally</em> executes for any request.  See the
+     * {@link #isEnabled() isEnabled()} JavaDoc as to what <em>general</em> execution means.
+     *
+     * @param enabled whether or not this filter <em>generally</em> executes.
+     * @since 1.2
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * This {@code doFilter} implementation stores a request attribute for
+     * "already filtered", proceeding without filtering again if the
+     * attribute is already there.
+     *
+     * @see #getAlreadyFilteredAttributeName
+     * @see #shouldNotFilter
+     * @see #doFilterInternal
+     */
+    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
+            throws ServletException, IOException {
+        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
+        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
+            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
+            filterChain.doFilter(request, response);
+        } else //noinspection deprecation
+            if (/* added in 1.2: */ !isEnabled(request, response) ||
+                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
+            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
+                    getName());
+            filterChain.doFilter(request, response);
+        } else {
+            // Do invoke this filter...
+            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
+            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
+
+            try {
+                doFilterInternal(request, response, filterChain);
+            } finally {
+                // Once the request has finished, we're done and we don't
+                // need to mark as 'already filtered' any more.
+                request.removeAttribute(alreadyFilteredAttributeName);
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} if this filter should filter the specified request, {@code false} if it should let the
+     * request/response pass through immediately to the next element in the {@code FilterChain}.
+     * <p/>
+     * This default implementation merely returns the value of {@link #isEnabled() isEnabled()}, which is
+     * {@code true} by default (to ensure the filter always executes by default), but it can be overridden by
+     * subclasses for request-specific behavior if necessary.  For example, a filter could be enabled or disabled
+     * based on the request path being accessed.
+     * <p/>
+     * <b>Helpful Hint:</b> if your subclass extends {@link org.apache.shiro.web.filter.PathMatchingFilter PathMatchingFilter},
+     * you may wish to instead override the
+     * {@link org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object)
+     * PathMatchingFilter.isEnabled(request,response,path,pathSpecificConfig)}
+     * method if you want to make your enable/disable decision based on any path-specific configuration.
+     *
+     * @param request the incoming servlet request
+     * @param response the outbound servlet response
+     * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the
+     * request/response pass through immediately to the next element in the {@code FilterChain}.
+     * @throws IOException in the case of any IO error
+     * @throws ServletException in the case of any error
+     * @see org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object)
+     * @since 1.2
+     */
+    @SuppressWarnings({"UnusedParameters"})
+    protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+        return isEnabled();
+    }
+
+    /**
+     * Return name of the request attribute that identifies that a request has already been filtered.
+     * <p/>
+     * The default implementation takes the configured {@link #getName() name} and appends "{@code .FILTERED}".
+     * If the filter is not fully initialized, it falls back to the implementation's class name.
+     *
+     * @return the name of the request attribute that identifies that a request has already been filtered.
+     * @see #getName
+     * @see #ALREADY_FILTERED_SUFFIX
+     */
+    protected String getAlreadyFilteredAttributeName() {
+        String name = getName();
+        if (name == null) {
+            name = getClass().getName();
+        }
+        return name + ALREADY_FILTERED_SUFFIX;
+    }
+
+    /**
+     * Can be overridden in subclasses for custom filtering control,
+     * returning <code>true</code> to avoid filtering of the given request.
+     * <p>The default implementation always returns <code>false</code>.
+     *
+     * @param request current HTTP request
+     * @return whether the given request should <i>not</i> be filtered
+     * @throws ServletException in case of errors
+     * @deprecated in favor of overriding {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+     * for custom behavior.  This method will be removed in Shiro 2.0.
+     */
+    @Deprecated
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
+        return false;
+    }
+
+
+    /**
+     * Same contract as for
+     * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)},
+     * but guaranteed to be invoked only once per request.
+     *
+     * @param request  incoming {@code ServletRequest}
+     * @param response outgoing {@code ServletResponse}
+     * @param chain    the {@code FilterChain} to execute
+     * @throws ServletException if there is a problem processing the request
+     * @throws IOException      if there is an I/O problem processing the request
+     */
+    protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws ServletException, IOException;
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ProxiedFilterChain.java b/web/src/main/java/org/apache/shiro/web/servlet/ProxiedFilterChain.java
new file mode 100644
index 0000000..9936e11
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ProxiedFilterChain.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * A proxied filter chain is a {@link FilterChain} instance that proxies an original {@link FilterChain} as well
+ * as a {@link List List} of other {@link Filter Filter}s that might need to execute prior to the final wrapped
+ * original chain.  It allows a list of filters to execute before continuing the original (proxied)
+ * {@code FilterChain} instance.
+ *
+ * @since 0.9
+ */
+public class ProxiedFilterChain implements FilterChain {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);
+
+    private FilterChain orig;
+    private List<Filter> filters;
+    private int index = 0;
+
+    public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
+        if (orig == null) {
+            throw new NullPointerException("original FilterChain cannot be null.");
+        }
+        this.orig = orig;
+        this.filters = filters;
+        this.index = 0;
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+        if (this.filters == null || this.filters.size() == this.index) {
+            //we've reached the end of the wrapped chain, so invoke the original one:
+            if (log.isTraceEnabled()) {
+                log.trace("Invoking original filter chain.");
+            }
+            this.orig.doFilter(request, response);
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Invoking wrapped filter at index [" + this.index + "]");
+            }
+            this.filters.get(this.index++).doFilter(request, response, this);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ServletContextSupport.java b/web/src/main/java/org/apache/shiro/web/servlet/ServletContextSupport.java
new file mode 100644
index 0000000..d018edc
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ServletContextSupport.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Base implementation for any components that need to access the web application's {@link ServletContext ServletContext}.
+ *
+ * @since 0.2
+ */
+public class ServletContextSupport {
+
+    //TODO - complete JavaDoc
+    private ServletContext servletContext = null;
+
+    public ServletContext getServletContext() {
+        return servletContext;
+    }
+
+    public void setServletContext(ServletContext servletContext) {
+        this.servletContext = servletContext;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected String getContextInitParam(String paramName) {
+        return getServletContext().getInitParameter(paramName);
+    }
+
+    private ServletContext getRequiredServletContext() {
+        ServletContext servletContext = getServletContext();
+        if (servletContext == null) {
+            String msg = "ServletContext property must be set via the setServletContext method.";
+            throw new IllegalStateException(msg);
+        }
+        return servletContext;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void setContextAttribute(String key, Object value) {
+        if (value == null) {
+            removeContextAttribute(key);
+        } else {
+            getRequiredServletContext().setAttribute(key, value);
+        }
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected Object getContextAttribute(String key) {
+        return getRequiredServletContext().getAttribute(key);
+    }
+
+    protected void removeContextAttribute(String key) {
+        getRequiredServletContext().removeAttribute(key);
+    }
+
+    /**
+     * It is highly recommended not to override this method directly, and instead override the
+     * {@link #toStringBuilder() toStringBuilder()} method, a better-performing alternative.
+     *
+     * @return the String representation of this instance.
+     */
+    @Override
+    public String toString() {
+        return toStringBuilder().toString();
+    }
+
+    /**
+     * Same concept as {@link #toString() toString()}, but returns a {@link StringBuilder} instance instead.
+     *
+     * @return a StringBuilder instance to use for appending String data that will eventually be returned from a
+     *         {@code toString()} invocation.
+     */
+    protected StringBuilder toStringBuilder() {
+        return new StringBuilder(super.toString());
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
new file mode 100644
index 0000000..1c1a34f
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.util.WebUtils;
+
+/**
+ * Primary Shiro Filter for web applications configuring Shiro via Servlet <listener> in web.xml.
+ * <p/>
+ * As of Shiro 1.2, this is Shiro's preferred filter for {@code web.xml} configuration.  It expects the presence of a
+ * Shiro {@link org.apache.shiro.web.env.WebEnvironment WebEnvironment} in the {@code ServletContext}, also
+ * configured via {@code web.xml}.
+ * <h2>Usage</h2>
+ * As this Filter expects an available {@link org.apache.shiro.web.env.WebEnvironment WebEnvironment} instance to
+ * be configured, it must be defined in {@code web.xml} with the companion
+ * {@link org.apache.shiro.web.env.EnvironmentLoaderListener EnvironmentLoaderListener}, which performs the necessary
+ * environment setup.  For example:
+ * <pre>
+ * <listener>
+ *     <listener-class>{@link org.apache.shiro.web.env.EnvironmentLoaderListener}</listener-class>
+ * </listener>
+ * ...
+ * <filter>
+ *     <filter-name>ShiroFilter</filter-name>
+ *     <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
+ * </filter>
+ *
+ * <-- Filter all web requests.  This filter mapping is typically declared
+ *     before all others to ensure any other filters are secured as well: -->
+ * <filter-mapping>
+ *     <filter-name>ShiroFilter</filter-name>
+ *     <url-pattern>/*</url-pattern>
+ * </filter-mapping>
+ * </pre>
+ * Configuration options (configuration file paths, etc) are specified as part of the
+ * {@code EnvironmentLoaderListener} configuration.  See the
+ * {@link org.apache.shiro.web.env.EnvironmentLoader EnvironmentLoader} JavaDoc for configuration options.
+ *
+ * @see org.apache.shiro.web.env.EnvironmentLoader EnvironmentLoader
+ * @see org.apache.shiro.web.env.EnvironmentLoaderListener EnvironmentLoaderListener
+ * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
+ * @since 1.2
+ */
+public class ShiroFilter extends AbstractShiroFilter {
+
+    /**
+     * Configures this instance based on the existing {@link org.apache.shiro.web.env.WebEnvironment} instance
+     * available to the currently accessible {@link #getServletContext() servletContext}.
+     *
+     * @see org.apache.shiro.web.env.EnvironmentLoaderListener
+     * @since 1.2
+     */
+    @Override
+    public void init() throws Exception {
+        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
+
+        setSecurityManager(env.getWebSecurityManager());
+
+        FilterChainResolver resolver = env.getFilterChainResolver();
+        if (resolver != null) {
+            setFilterChainResolver(resolver);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletRequest.java b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletRequest.java
new file mode 100644
index 0000000..74f5da5
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletRequest.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DisabledSessionException;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+import java.security.Principal;
+
+
+/**
+ * A {@code ShiroHttpServletRequest} wraps the Servlet container's original {@code ServletRequest} instance, but ensures
+ * that all {@link HttpServletRequest} invocations that require Shiro's support ({@link #getRemoteUser getRemoteUser},
+ * {@link #getSession getSession}, etc) can be executed first by Shiro as necessary before allowing the underlying
+ * Servlet container instance's method to be invoked.
+ *
+ * @since 0.2
+ */
+public class ShiroHttpServletRequest extends HttpServletRequestWrapper {
+
+    //TODO - complete JavaDoc
+
+    //The following 7 constants support the Shiro's implementation of the Servlet Specification
+    public static final String COOKIE_SESSION_ID_SOURCE = "cookie";
+    public static final String URL_SESSION_ID_SOURCE = "url";
+    public static final String REFERENCED_SESSION_ID = ShiroHttpServletRequest.class.getName() + "_REQUESTED_SESSION_ID";
+    public static final String REFERENCED_SESSION_ID_IS_VALID = ShiroHttpServletRequest.class.getName() + "_REQUESTED_SESSION_ID_VALID";
+    public static final String REFERENCED_SESSION_IS_NEW = ShiroHttpServletRequest.class.getName() + "_REFERENCED_SESSION_IS_NEW";
+    public static final String REFERENCED_SESSION_ID_SOURCE = ShiroHttpServletRequest.class.getName() + "REFERENCED_SESSION_ID_SOURCE";
+    public static final String IDENTITY_REMOVED_KEY = ShiroHttpServletRequest.class.getName() + "_IDENTITY_REMOVED_KEY";
+
+    protected ServletContext servletContext = null;
+
+    protected HttpSession session = null;
+    protected boolean httpSessions = true;
+
+    public ShiroHttpServletRequest(HttpServletRequest wrapped, ServletContext servletContext, boolean httpSessions) {
+        super(wrapped);
+        this.servletContext = servletContext;
+        this.httpSessions = httpSessions;
+    }
+
+    public boolean isHttpSessions() {
+        return httpSessions;
+    }
+
+    public String getRemoteUser() {
+        String remoteUser;
+        Object scPrincipal = getSubjectPrincipal();
+        if (scPrincipal != null) {
+            if (scPrincipal instanceof String) {
+                return (String) scPrincipal;
+            } else if (scPrincipal instanceof Principal) {
+                remoteUser = ((Principal) scPrincipal).getName();
+            } else {
+                remoteUser = scPrincipal.toString();
+            }
+        } else {
+            remoteUser = super.getRemoteUser();
+        }
+        return remoteUser;
+    }
+
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    protected Object getSubjectPrincipal() {
+        Object userPrincipal = null;
+        Subject subject = getSubject();
+        if (subject != null) {
+            userPrincipal = subject.getPrincipal();
+        }
+        return userPrincipal;
+    }
+
+    public boolean isUserInRole(String s) {
+        Subject subject = getSubject();
+        boolean inRole = (subject != null && subject.hasRole(s));
+        if (!inRole) {
+            inRole = super.isUserInRole(s);
+        }
+        return inRole;
+    }
+
+    public Principal getUserPrincipal() {
+        Principal userPrincipal;
+        Object scPrincipal = getSubjectPrincipal();
+        if (scPrincipal != null) {
+            if (scPrincipal instanceof Principal) {
+                userPrincipal = (Principal) scPrincipal;
+            } else {
+                userPrincipal = new ObjectPrincipal(scPrincipal);
+            }
+        } else {
+            userPrincipal = super.getUserPrincipal();
+        }
+        return userPrincipal;
+    }
+
+    public String getRequestedSessionId() {
+        String requestedSessionId = null;
+        if (isHttpSessions()) {
+            requestedSessionId = super.getRequestedSessionId();
+        } else {
+            Object sessionId = getAttribute(REFERENCED_SESSION_ID);
+            if (sessionId != null) {
+                requestedSessionId = sessionId.toString();
+            }
+        }
+
+        return requestedSessionId;
+    }
+
+    public HttpSession getSession(boolean create) {
+
+        HttpSession httpSession;
+
+        if (isHttpSessions()) {
+            httpSession = super.getSession(false);
+            if (httpSession == null && create) {
+                //Shiro 1.2: assert that creation is enabled (SHIRO-266):
+                if (WebUtils._isSessionCreationEnabled(this)) {
+                    httpSession = super.getSession(create);
+                } else {
+                    throw newNoSessionCreationException();
+                }
+            }
+        } else {
+            if (this.session == null) {
+
+                boolean existing = getSubject().getSession(false) != null;
+
+                Session shiroSession = getSubject().getSession(create);
+                if (shiroSession != null) {
+                    this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
+                    if (!existing) {
+                        setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
+                    }
+                }
+            }
+            httpSession = this.session;
+        }
+
+        return httpSession;
+    }
+
+    /**
+     * Constructs and returns a {@link DisabledSessionException} with an appropriate message explaining why
+     * session creation has been disabled.
+     *
+     * @return a new DisabledSessionException with appropriate no creation message
+     * @since 1.2
+     */
+    private DisabledSessionException newNoSessionCreationException() {
+        String msg = "Session creation has been disabled for the current request.  This exception indicates " +
+                "that there is either a programming error (using a session when it should never be " +
+                "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
+                "for the current request.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
+                "for more.";
+        return new DisabledSessionException(msg);
+    }
+
+    public HttpSession getSession() {
+        return getSession(true);
+    }
+
+    public boolean isRequestedSessionIdValid() {
+        if (isHttpSessions()) {
+            return super.isRequestedSessionIdValid();
+        } else {
+            Boolean value = (Boolean) getAttribute(REFERENCED_SESSION_ID_IS_VALID);
+            return (value != null && value.equals(Boolean.TRUE));
+        }
+    }
+
+    public boolean isRequestedSessionIdFromCookie() {
+        if (isHttpSessions()) {
+            return super.isRequestedSessionIdFromCookie();
+        } else {
+            String value = (String) getAttribute(REFERENCED_SESSION_ID_SOURCE);
+            return value != null && value.equals(COOKIE_SESSION_ID_SOURCE);
+        }
+    }
+
+    public boolean isRequestedSessionIdFromURL() {
+        if (isHttpSessions()) {
+            return super.isRequestedSessionIdFromURL();
+        } else {
+            String value = (String) getAttribute(REFERENCED_SESSION_ID_SOURCE);
+            return value != null && value.equals(URL_SESSION_ID_SOURCE);
+        }
+    }
+
+    public boolean isRequestedSessionIdFromUrl() {
+        return isRequestedSessionIdFromURL();
+    }
+
+    private class ObjectPrincipal implements java.security.Principal {
+        private Object object = null;
+
+        public ObjectPrincipal(Object object) {
+            this.object = object;
+        }
+
+        public Object getObject() {
+            return object;
+        }
+
+        public String getName() {
+            return getObject().toString();
+        }
+
+        public int hashCode() {
+            return object.hashCode();
+        }
+
+        public boolean equals(Object o) {
+            if (o instanceof ObjectPrincipal) {
+                ObjectPrincipal op = (ObjectPrincipal) o;
+                return getObject().equals(op.getObject());
+            }
+            return false;
+        }
+
+        public String toString() {
+            return object.toString();
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
new file mode 100644
index 0000000..438b3e6
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+
+/**
+ * HttpServletResponse implementation to support URL Encoding of Shiro Session IDs.
+ * <p/>
+ * It is only used when using Shiro's native Session Management configuration (and not when using the Servlet
+ * Container session configuration, which is Shiro's default in a web environment).  Because the servlet container
+ * already performs url encoding of its own session ids, instances of this class are only needed when using Shiro
+ * native sessions.
+ * <p/>
+ * Note that this implementation relies in part on source code from the Tomcat 6.x distribution for
+ * encoding URLs for session ID URL Rewriting (we didn't want to re-invent the wheel).  Since Shiro is also
+ * Apache 2.0 license, all regular licenses and conditions have remained in tact.
+ *
+ * @since 0.2
+ */
+public class ShiroHttpServletResponse extends HttpServletResponseWrapper {
+
+    //TODO - complete JavaDoc
+
+    private static final String DEFAULT_SESSION_ID_PARAMETER_NAME = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
+
+    private ServletContext context = null;
+    //the associated request
+    private ShiroHttpServletRequest request = null;
+
+    public ShiroHttpServletResponse(HttpServletResponse wrapped, ServletContext context, ShiroHttpServletRequest request) {
+        super(wrapped);
+        this.context = context;
+        this.request = request;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public ServletContext getContext() {
+        return context;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setContext(ServletContext context) {
+        this.context = context;
+    }
+
+    public ShiroHttpServletRequest getRequest() {
+        return request;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setRequest(ShiroHttpServletRequest request) {
+        this.request = request;
+    }
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified redirect URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    public String encodeRedirectURL(String url) {
+        if (isEncodeable(toAbsolute(url))) {
+            return toEncoded(url, request.getSession().getId());
+        } else {
+            return url;
+        }
+    }
+
+
+    public String encodeRedirectUrl(String s) {
+        return encodeRedirectURL(s);
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    public String encodeURL(String url) {
+        String absolute = toAbsolute(url);
+        if (isEncodeable(absolute)) {
+            // W3c spec clearly said
+            if (url.equalsIgnoreCase("")) {
+                url = absolute;
+            }
+            return toEncoded(url, request.getSession().getId());
+        } else {
+            return url;
+        }
+    }
+
+    public String encodeUrl(String s) {
+        return encodeURL(s);
+    }
+
+    /**
+     * Return <code>true</code> if the specified URL should be encoded with
+     * a session identifier.  This will be true if all of the following
+     * conditions are met:
+     * <ul>
+     * <li>The request we are responding to asked for a valid session
+     * <li>The requested session ID was not received via a cookie
+     * <li>The specified URL points back to somewhere within the web
+     * application that is responding to this request
+     * </ul>
+     *
+     * @param location Absolute URL to be validated
+     * @return {@code true} if the specified URL should be encoded with a session identifier, {@code false} otherwise.
+     */
+    protected boolean isEncodeable(final String location) {
+
+        if (location == null)
+            return (false);
+
+        // Is this an intra-document reference?
+        if (location.startsWith("#"))
+            return (false);
+
+        // Are we in a valid session that is not using cookies?
+        final HttpServletRequest hreq = request;
+        final HttpSession session = hreq.getSession(false);
+        if (session == null)
+            return (false);
+        if (hreq.isRequestedSessionIdFromCookie())
+            return (false);
+
+        return doIsEncodeable(hreq, session, location);
+    }
+
+    private boolean doIsEncodeable(HttpServletRequest hreq, HttpSession session, String location) {
+        // Is this a valid absolute URL?
+        URL url;
+        try {
+            url = new URL(location);
+        } catch (MalformedURLException e) {
+            return (false);
+        }
+
+        // Does this URL match down to (and including) the context path?
+        if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
+            return (false);
+        if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
+            return (false);
+        int serverPort = hreq.getServerPort();
+        if (serverPort == -1) {
+            if ("https".equals(hreq.getScheme()))
+                serverPort = 443;
+            else
+                serverPort = 80;
+        }
+        int urlPort = url.getPort();
+        if (urlPort == -1) {
+            if ("https".equals(url.getProtocol()))
+                urlPort = 443;
+            else
+                urlPort = 80;
+        }
+        if (serverPort != urlPort)
+            return (false);
+
+        String contextPath = getRequest().getContextPath();
+        if (contextPath != null) {
+            String file = url.getFile();
+            if ((file == null) || !file.startsWith(contextPath))
+                return (false);
+            String tok = ";" + DEFAULT_SESSION_ID_PARAMETER_NAME + "=" + session.getId();
+            if (file.indexOf(tok, contextPath.length()) >= 0)
+                return (false);
+        }
+
+        // This URL belongs to our web application, so it is encodeable
+        return (true);
+
+    }
+
+
+    /**
+     * Convert (if necessary) and return the absolute URL that represents the
+     * resource referenced by this possibly relative URL.  If this URL is
+     * already absolute, return it unchanged.
+     *
+     * @param location URL to be (possibly) converted and then returned
+     * @return resource location as an absolute url
+     * @throws IllegalArgumentException if a MalformedURLException is
+     *                                  thrown when converting the relative URL to an absolute one
+     */
+    private String toAbsolute(String location) {
+
+        if (location == null)
+            return (location);
+
+        boolean leadingSlash = location.startsWith("/");
+
+        if (leadingSlash || !hasScheme(location)) {
+
+            StringBuilder buf = new StringBuilder();
+
+            String scheme = request.getScheme();
+            String name = request.getServerName();
+            int port = request.getServerPort();
+
+            try {
+                buf.append(scheme).append("://").append(name);
+                if ((scheme.equals("http") && port != 80)
+                        || (scheme.equals("https") && port != 443)) {
+                    buf.append(':').append(port);
+                }
+                if (!leadingSlash) {
+                    String relativePath = request.getRequestURI();
+                    int pos = relativePath.lastIndexOf('/');
+                    relativePath = relativePath.substring(0, pos);
+
+                    String encodedURI = URLEncoder.encode(relativePath, getCharacterEncoding());
+                    buf.append(encodedURI).append('/');
+                }
+                buf.append(location);
+            } catch (IOException e) {
+                IllegalArgumentException iae = new IllegalArgumentException(location);
+                iae.initCause(e);
+                throw iae;
+            }
+
+            return buf.toString();
+
+        } else {
+            return location;
+        }
+    }
+
+    /**
+     * Determine if the character is allowed in the scheme of a URI.
+     * See RFC 2396, Section 3.1
+     *
+     * @param c the character to check
+     * @return {@code true} if the character is allowed in a URI scheme, {@code false} otherwise.
+     */
+    public static boolean isSchemeChar(char c) {
+        return Character.isLetterOrDigit(c) ||
+                c == '+' || c == '-' || c == '.';
+    }
+
+
+    /**
+     * Returns {@code true} if the URI string has a {@code scheme} component, {@code false} otherwise.
+     *
+     * @param uri the URI string to check for a scheme component
+     * @return {@code true} if the URI string has a {@code scheme} component, {@code false} otherwise.
+     */
+    private boolean hasScheme(String uri) {
+        int len = uri.length();
+        for (int i = 0; i < len; i++) {
+            char c = uri.charAt(i);
+            if (c == ':') {
+                return i > 0;
+            } else if (!isSchemeChar(c)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return the specified URL with the specified session identifier suitably encoded.
+     *
+     * @param url       URL to be encoded with the session id
+     * @param sessionId Session id to be included in the encoded URL
+     * @return the url with the session identifer properly encoded.
+     */
+    protected String toEncoded(String url, String sessionId) {
+
+        if ((url == null) || (sessionId == null))
+            return (url);
+
+        String path = url;
+        String query = "";
+        String anchor = "";
+        int question = url.indexOf('?');
+        if (question >= 0) {
+            path = url.substring(0, question);
+            query = url.substring(question);
+        }
+        int pound = path.indexOf('#');
+        if (pound >= 0) {
+            anchor = path.substring(pound);
+            path = path.substring(0, pound);
+        }
+        StringBuilder sb = new StringBuilder(path);
+        if (sb.length() > 0) { // session id param can't be first.
+            sb.append(";");
+            sb.append(DEFAULT_SESSION_ID_PARAMETER_NAME);
+            sb.append("=");
+            sb.append(sessionId);
+        }
+        sb.append(anchor);
+        sb.append(query);
+        return (sb.toString());
+
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpSession.java b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpSession.java
new file mode 100644
index 0000000..022036a
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpSession.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.web.session.HttpServletSession;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import java.util.*;
+
+
+/**
+ * Wrapper class that uses a Shiro {@link Session Session} under the hood for all session operations instead of the
+ * Servlet Container's session mechanism.  This is required in heterogeneous client environments where the Session
+ * is used on both the business tier as well as in multiple client technologies (web, swing, flash, etc) since
+ * Servlet container sessions alone cannot support this feature.
+ *
+ * @since 0.2
+ */
+public class ShiroHttpSession implements HttpSession {
+
+    //TODO - complete JavaDoc
+
+    public static final String DEFAULT_SESSION_ID_NAME = "JSESSIONID";
+
+    private static final Enumeration EMPTY_ENUMERATION = new Enumeration() {
+        public boolean hasMoreElements() {
+            return false;
+        }
+
+        public Object nextElement() {
+            return null;
+        }
+    };
+
+    @SuppressWarnings({"deprecation"})
+    private static final javax.servlet.http.HttpSessionContext HTTP_SESSION_CONTEXT =
+            new javax.servlet.http.HttpSessionContext() {
+                public HttpSession getSession(String s) {
+                    return null;
+                }
+
+                public Enumeration getIds() {
+                    return EMPTY_ENUMERATION;
+                }
+            };
+
+    protected ServletContext servletContext = null;
+    protected HttpServletRequest currentRequest = null;
+    protected Session session = null; //'real' Shiro Session
+
+    public ShiroHttpSession(Session session, HttpServletRequest currentRequest, ServletContext servletContext) {
+        if (session instanceof HttpServletSession) {
+            String msg = "Session constructor argument cannot be an instance of HttpServletSession.  This is enforced to " +
+                    "prevent circular dependencies and infinite loops.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.session = session;
+        this.currentRequest = currentRequest;
+        this.servletContext = servletContext;
+    }
+
+    public Session getSession() {
+        return this.session;
+    }
+
+    public long getCreationTime() {
+        try {
+            return getSession().getStartTimestamp().getTime();
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public String getId() {
+        return getSession().getId().toString();
+    }
+
+    public long getLastAccessedTime() {
+        return getSession().getLastAccessTime().getTime();
+    }
+
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    public void setMaxInactiveInterval(int i) {
+        try {
+            getSession().setTimeout(i * 1000);
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public int getMaxInactiveInterval() {
+        try {
+            return (new Long(getSession().getTimeout() / 1000)).intValue();
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @SuppressWarnings({"deprecation"})
+    public javax.servlet.http.HttpSessionContext getSessionContext() {
+        return HTTP_SESSION_CONTEXT;
+    }
+
+    public Object getAttribute(String s) {
+        try {
+            return getSession().getAttribute(s);
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public Object getValue(String s) {
+        return getAttribute(s);
+    }
+
+    @SuppressWarnings({"unchecked"})
+    protected Set<String> getKeyNames() {
+        Collection<Object> keySet;
+        try {
+            keySet = getSession().getAttributeKeys();
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+        Set<String> keyNames;
+        if (keySet != null && !keySet.isEmpty()) {
+            keyNames = new HashSet<String>(keySet.size());
+            for (Object o : keySet) {
+                keyNames.add(o.toString());
+            }
+        } else {
+            keyNames = Collections.EMPTY_SET;
+        }
+        return keyNames;
+    }
+
+    public Enumeration getAttributeNames() {
+        Set<String> keyNames = getKeyNames();
+        final Iterator iterator = keyNames.iterator();
+        return new Enumeration() {
+            public boolean hasMoreElements() {
+                return iterator.hasNext();
+            }
+
+            public Object nextElement() {
+                return iterator.next();
+            }
+        };
+    }
+
+    public String[] getValueNames() {
+        Set<String> keyNames = getKeyNames();
+        String[] array = new String[keyNames.size()];
+        if (keyNames.size() > 0) {
+            array = keyNames.toArray(array);
+        }
+        return array;
+    }
+
+    protected void afterBound(String s, Object o) {
+        if (o instanceof HttpSessionBindingListener) {
+            HttpSessionBindingListener listener = (HttpSessionBindingListener) o;
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, s, o);
+            listener.valueBound(event);
+        }
+    }
+
+    protected void afterUnbound(String s, Object o) {
+        if (o instanceof HttpSessionBindingListener) {
+            HttpSessionBindingListener listener = (HttpSessionBindingListener) o;
+            HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, s, o);
+            listener.valueUnbound(event);
+        }
+    }
+
+    public void setAttribute(String s, Object o) {
+        try {
+            getSession().setAttribute(s, o);
+            afterBound(s, o);
+        } catch (InvalidSessionException e) {
+            //noinspection finally
+            try {
+                afterUnbound(s, o);
+            } finally {
+                //noinspection ThrowFromFinallyBlock
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    public void putValue(String s, Object o) {
+        setAttribute(s, o);
+    }
+
+    public void removeAttribute(String s) {
+        try {
+            Object attribute = getSession().removeAttribute(s);
+            afterUnbound(s, attribute);
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public void removeValue(String s) {
+        removeAttribute(s);
+    }
+
+    public void invalidate() {
+        try {
+            getSession().stop();
+        } catch (InvalidSessionException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public boolean isNew() {
+        Boolean value = (Boolean) currentRequest.getAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW);
+        return value != null && value.equals(Boolean.TRUE);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java b/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
new file mode 100644
index 0000000..1f27e9b
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
@@ -0,0 +1,394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Default {@link Cookie Cookie} implementation.  'HttpOnly' is supported out of the box, even on
+ * Servlet {@code 2.4} and {@code 2.5} container implementations, using raw header writing logic and not
+ * {@link javax.servlet.http.Cookie javax.servlet.http.Cookie} objects (which only has 'HttpOnly' support in Servlet
+ * {@code 2.6} specifications and above).
+ *
+ * @since 1.0
+ */
+public class SimpleCookie implements Cookie {
+
+    /**
+     * {@code -1}, indicating the cookie should expire when the browser closes.
+     */
+    public static final int DEFAULT_MAX_AGE = -1;
+
+    /**
+     * {@code -1} indicating that no version property should be set on the cookie.
+     */
+    public static final int DEFAULT_VERSION = -1;
+
+    //These constants are protected on purpose so that the test case can use them
+    protected static final String NAME_VALUE_DELIMITER = "=";
+    protected static final String ATTRIBUTE_DELIMITER = "; ";
+    protected static final long DAY_MILLIS = 86400000; //1 day = 86,400,000 milliseconds
+    protected static final String GMT_TIME_ZONE_ID = "GMT";
+    protected static final String COOKIE_DATE_FORMAT_STRING = "EEE, dd-MMM-yyyy HH:mm:ss z";
+
+    protected static final String COOKIE_HEADER_NAME = "Set-Cookie";
+    protected static final String PATH_ATTRIBUTE_NAME = "Path";
+    protected static final String EXPIRES_ATTRIBUTE_NAME = "Expires";
+    protected static final String MAXAGE_ATTRIBUTE_NAME = "Max-Age";
+    protected static final String DOMAIN_ATTRIBUTE_NAME = "Domain";
+    protected static final String VERSION_ATTRIBUTE_NAME = "Version";
+    protected static final String COMMENT_ATTRIBUTE_NAME = "Comment";
+    protected static final String SECURE_ATTRIBUTE_NAME = "Secure";
+    protected static final String HTTP_ONLY_ATTRIBUTE_NAME = "HttpOnly";
+
+    private static final transient Logger log = LoggerFactory.getLogger(SimpleCookie.class);
+
+    private String name;
+    private String value;
+    private String comment;
+    private String domain;
+    private String path;
+    private int maxAge;
+    private int version;
+    private boolean secure;
+    private boolean httpOnly;
+
+    public SimpleCookie() {
+        this.maxAge = DEFAULT_MAX_AGE;
+        this.version = DEFAULT_VERSION;
+        this.httpOnly = true; //most of the cookies ever used by Shiro should be as secure as possible.
+    }
+
+    public SimpleCookie(String name) {
+        this();
+        this.name = name;
+    }
+
+    public SimpleCookie(Cookie cookie) {
+        this.name = cookie.getName();
+        this.value = cookie.getValue();
+        this.comment = cookie.getComment();
+        this.domain = cookie.getDomain();
+        this.path = cookie.getPath();
+        this.maxAge = Math.max(DEFAULT_MAX_AGE, cookie.getMaxAge());
+        this.version = Math.max(DEFAULT_VERSION, cookie.getVersion());
+        this.secure = cookie.isSecure();
+        this.httpOnly = cookie.isHttpOnly();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (!StringUtils.hasText(name)) {
+            throw new IllegalArgumentException("Name cannot be null/empty.");
+        }
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    public void setMaxAge(int maxAge) {
+        this.maxAge = Math.max(DEFAULT_MAX_AGE, maxAge);
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int version) {
+        this.version = Math.max(DEFAULT_VERSION, version);
+    }
+
+    public boolean isSecure() {
+        return secure;
+    }
+
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+    public boolean isHttpOnly() {
+        return httpOnly;
+    }
+
+    public void setHttpOnly(boolean httpOnly) {
+        this.httpOnly = httpOnly;
+    }
+
+    /**
+     * Returns the Cookie's calculated path setting.  If the {@link javax.servlet.http.Cookie#getPath() path} is {@code null}, then the
+     * {@code request}'s {@link javax.servlet.http.HttpServletRequest#getContextPath() context path}
+     * will be returned. If getContextPath() is the empty string or null then the ROOT_PATH constant is returned.
+     *
+     * @param request the incoming HttpServletRequest
+     * @return the path to be used as the path when the cookie is created or removed
+     */
+    private String calculatePath(HttpServletRequest request) {
+        String path = StringUtils.clean(getPath());
+        if (!StringUtils.hasText(path)) {
+            path = StringUtils.clean(request.getContextPath());
+        }
+
+        //fix for http://issues.apache.org/jira/browse/SHIRO-9:
+        if (path == null) {
+            path = ROOT_PATH;
+        }
+        log.trace("calculated path: {}", path);
+        return path;
+    }
+
+    public void saveTo(HttpServletRequest request, HttpServletResponse response) {
+
+        String name = getName();
+        String value = getValue();
+        String comment = getComment();
+        String domain = getDomain();
+        String path = calculatePath(request);
+        int maxAge = getMaxAge();
+        int version = getVersion();
+        boolean secure = isSecure();
+        boolean httpOnly = isHttpOnly();
+
+        addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
+    }
+
+    private void addCookieHeader(HttpServletResponse response, String name, String value, String comment,
+                                 String domain, String path, int maxAge, int version,
+                                 boolean secure, boolean httpOnly) {
+
+        String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);
+        response.addHeader(COOKIE_HEADER_NAME, headerValue);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Added HttpServletResponse Cookie [{}]", headerValue);
+        }
+    }
+
+    /*
+     * This implementation followed the grammar defined here for convenience:
+     * <a href="http://github.com/abarth/http-state/blob/master/notes/2009-11-07-Yui-Naruse.txt">Cookie grammar</a>.
+     *
+     * @return the 'Set-Cookie' header value for this cookie instance.
+     */
+
+    protected String buildHeaderValue(String name, String value, String comment,
+                                      String domain, String path, int maxAge, int version,
+                                      boolean secure, boolean httpOnly) {
+
+        if (!StringUtils.hasText(name)) {
+            throw new IllegalStateException("Cookie name cannot be null/empty.");
+        }
+
+        StringBuilder sb = new StringBuilder(name).append(NAME_VALUE_DELIMITER);
+
+        if (StringUtils.hasText(value)) {
+            sb.append(value);
+        }
+
+        appendComment(sb, comment);
+        appendDomain(sb, domain);
+        appendPath(sb, path);
+        appendExpires(sb, maxAge);
+        appendVersion(sb, version);
+        appendSecure(sb, secure);
+        appendHttpOnly(sb, httpOnly);
+
+        return sb.toString();
+
+    }
+
+    private void appendComment(StringBuilder sb, String comment) {
+        if (StringUtils.hasText(comment)) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(COMMENT_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(comment);
+        }
+    }
+
+    private void appendDomain(StringBuilder sb, String domain) {
+        if (StringUtils.hasText(domain)) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(DOMAIN_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(domain);
+        }
+    }
+
+    private void appendPath(StringBuilder sb, String path) {
+        if (StringUtils.hasText(path)) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(PATH_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(path);
+        }
+    }
+
+    private void appendExpires(StringBuilder sb, int maxAge) {
+        // if maxAge is negative, cookie should should expire when browser closes
+	// Don't write the maxAge cookie value if it's negative - at least on Firefox it'll cause the 
+	// cookie to be deleted immediately
+        // Write the expires header used by older browsers, but may be unnecessary
+        // and it is not by the spec, see http://www.faqs.org/rfcs/rfc2965.html
+        // TODO consider completely removing the following 
+        if (maxAge >= 0) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(MAXAGE_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(maxAge);
+            sb.append(ATTRIBUTE_DELIMITER);
+            Date expires;
+            if (maxAge == 0) {
+                //delete the cookie by specifying a time in the past (1 day ago):
+                expires = new Date(System.currentTimeMillis() - DAY_MILLIS);
+            } else {
+                //Value is in seconds.  So take 'now' and add that many seconds, and that's our expiration date:
+                Calendar cal = Calendar.getInstance();
+                cal.add(Calendar.SECOND, maxAge);
+                expires = cal.getTime();
+            }
+            String formatted = toCookieDate(expires);
+            sb.append(EXPIRES_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(formatted);
+        }
+    }
+
+    private void appendVersion(StringBuilder sb, int version) {
+        if (version > DEFAULT_VERSION) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(VERSION_ATTRIBUTE_NAME).append(NAME_VALUE_DELIMITER).append(version);
+        }
+    }
+
+    private void appendSecure(StringBuilder sb, boolean secure) {
+        if (secure) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(SECURE_ATTRIBUTE_NAME); //No value for this attribute
+        }
+    }
+
+    private void appendHttpOnly(StringBuilder sb, boolean httpOnly) {
+        if (httpOnly) {
+            sb.append(ATTRIBUTE_DELIMITER);
+            sb.append(HTTP_ONLY_ATTRIBUTE_NAME); //No value for this attribute
+        }
+    }
+
+    /**
+     * Formats a date into a cookie date compatible string (Netscape's specification).
+     *
+     * @param date the date to format
+     * @return an HTTP 1.0/1.1 Cookie compatible date string (GMT-based).
+     */
+    private static String toCookieDate(Date date) {
+        TimeZone tz = TimeZone.getTimeZone(GMT_TIME_ZONE_ID);
+        DateFormat fmt = new SimpleDateFormat(COOKIE_DATE_FORMAT_STRING, Locale.US);
+        fmt.setTimeZone(tz);
+        return fmt.format(date);
+    }
+
+    public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
+        String name = getName();
+        String value = DELETED_COOKIE_VALUE;
+        String comment = null; //don't need to add extra size to the response - comments are irrelevant for deletions
+        String domain = getDomain();
+        String path = calculatePath(request);
+        int maxAge = 0; //always zero for deletion
+        int version = getVersion();
+        boolean secure = isSecure();
+        boolean httpOnly = false; //no need to add the extra text, plus the value 'deleteMe' is not sensitive at all
+
+        addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
+
+        log.trace("Removed '{}' cookie by setting maxAge=0", name);
+    }
+
+    public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
+        String name = getName();
+        String value = null;
+        javax.servlet.http.Cookie cookie = getCookie(request, name);
+        if (cookie != null) {
+            value = cookie.getValue();
+            log.debug("Found '{}' cookie value [{}]", name, value);
+        } else {
+            log.trace("No '{}' cookie value", name);
+        }
+
+        return value;
+    }
+
+    /**
+     * Returns the cookie with the given name from the request or {@code null} if no cookie
+     * with that name could be found.
+     *
+     * @param request    the current executing http request.
+     * @param cookieName the name of the cookie to find and return.
+     * @return the cookie with the given name from the request or {@code null} if no cookie
+     *         with that name could be found.
+     */
+    private static javax.servlet.http.Cookie getCookie(HttpServletRequest request, String cookieName) {
+        javax.servlet.http.Cookie cookies[] = request.getCookies();
+        if (cookies != null) {
+            for (javax.servlet.http.Cookie cookie : cookies) {
+                if (cookie.getName().equals(cookieName)) {
+                    return cookie;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/package-info.java b/web/src/main/java/org/apache/shiro/web/servlet/package-info.java
new file mode 100644
index 0000000..4482648
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/servlet/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Shiro-specific implementations of the Servlet API (Servlet Filters, et al).
+ *
+ * @see ShiroFilter
+ */
+package org.apache.shiro.web.servlet;
diff --git a/web/src/main/java/org/apache/shiro/web/session/HttpServletSession.java b/web/src/main/java/org/apache/shiro/web/session/HttpServletSession.java
new file mode 100644
index 0000000..eb6497c
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/HttpServletSession.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session;
+
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.servlet.ShiroHttpSession;
+
+import javax.servlet.http.HttpSession;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+
+/**
+ * {@link Session Session} implementation that is backed entirely by a standard servlet container
+ * {@link HttpSession HttpSession} instance.  It does not interact with any of Shiro's session-related components
+ * {@code SessionManager}, {@code SecurityManager}, etc, and instead satisfies all method implementations by interacting
+ * with a servlet container provided {@link HttpSession HttpSession} instance.
+ *
+ * @since 1.0
+ */
+public class HttpServletSession implements Session {
+
+    private static final String HOST_SESSION_KEY = HttpServletSession.class.getName() + ".HOST_SESSION_KEY";
+    private static final String TOUCH_OBJECT_SESSION_KEY = HttpServletSession.class.getName() + ".TOUCH_OBJECT_SESSION_KEY";
+
+    private HttpSession httpSession = null;
+
+    public HttpServletSession(HttpSession httpSession, String host) {
+        if (httpSession == null) {
+            String msg = "HttpSession constructor argument cannot be null.";
+            throw new IllegalArgumentException(msg);
+        }
+        if (httpSession instanceof ShiroHttpSession) {
+            String msg = "HttpSession constructor argument cannot be an instance of ShiroHttpSession.  This " +
+                    "is enforced to prevent circular dependencies and infinite loops.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.httpSession = httpSession;
+        if (StringUtils.hasText(host)) {
+            setHost(host);
+        }
+    }
+
+    public Serializable getId() {
+        return httpSession.getId();
+    }
+
+    public Date getStartTimestamp() {
+        return new Date(httpSession.getCreationTime());
+    }
+
+    public Date getLastAccessTime() {
+        return new Date(httpSession.getLastAccessedTime());
+    }
+
+    public long getTimeout() throws InvalidSessionException {
+        try {
+            return httpSession.getMaxInactiveInterval() * 1000;
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
+        try {
+            int timeout = Long.valueOf(maxIdleTimeInMillis / 1000).intValue();
+            httpSession.setMaxInactiveInterval(timeout);
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    protected void setHost(String host) {
+        setAttribute(HOST_SESSION_KEY, host);
+    }
+
+    public String getHost() {
+        return (String) getAttribute(HOST_SESSION_KEY);
+    }
+
+    public void touch() throws InvalidSessionException {
+        //just manipulate the session to update the access time:
+        try {
+            httpSession.setAttribute(TOUCH_OBJECT_SESSION_KEY, TOUCH_OBJECT_SESSION_KEY);
+            httpSession.removeAttribute(TOUCH_OBJECT_SESSION_KEY);
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    public void stop() throws InvalidSessionException {
+        try {
+            httpSession.invalidate();
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
+        try {
+            Enumeration namesEnum = httpSession.getAttributeNames();
+            Collection<Object> keys = null;
+            if (namesEnum != null) {
+                keys = new ArrayList<Object>();
+                while (namesEnum.hasMoreElements()) {
+                    keys.add(namesEnum.nextElement());
+                }
+            }
+            return keys;
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    private static String assertString(Object key) {
+        if (!(key instanceof String)) {
+            String msg = "HttpSession based implementations of the Shiro Session interface requires attribute keys " +
+                    "to be String objects.  The HttpSession class does not support anything other than String keys.";
+            throw new IllegalArgumentException(msg);
+        }
+        return (String) key;
+    }
+
+    public Object getAttribute(Object key) throws InvalidSessionException {
+        try {
+            return httpSession.getAttribute(assertString(key));
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    public void setAttribute(Object key, Object value) throws InvalidSessionException {
+        try {
+            httpSession.setAttribute(assertString(key), value);
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+
+    public Object removeAttribute(Object key) throws InvalidSessionException {
+        try {
+            String sKey = assertString(key);
+            Object removed = httpSession.getAttribute(sKey);
+            httpSession.removeAttribute(sKey);
+            return removed;
+        } catch (Exception e) {
+            throw new InvalidSessionException(e);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionContext.java b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionContext.java
new file mode 100644
index 0000000..263c60e
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionContext.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.session.mgt.DefaultSessionContext;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.util.Map;
+
+/**
+ * Default implementation of the {@link WebSessionContext} interface which provides getters and setters that
+ * wrap interaction with the underlying backing context map.
+ *
+ * @since 1.0
+ */
+public class DefaultWebSessionContext extends DefaultSessionContext implements WebSessionContext {
+
+    private static final long serialVersionUID = -3974604687792523072L;
+
+    private static final String SERVLET_REQUEST = DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST";
+    private static final String SERVLET_RESPONSE = DefaultWebSessionContext.class.getName() + ".SERVLET_RESPONSE";
+
+    public DefaultWebSessionContext() {
+        super();
+    }
+
+    public DefaultWebSessionContext(Map<String, Object> map) {
+        super(map);
+    }
+
+    public void setServletRequest(ServletRequest request) {
+        if (request != null) {
+            put(SERVLET_REQUEST, request);
+        }
+    }
+
+    public ServletRequest getServletRequest() {
+        return getTypedValue(SERVLET_REQUEST, ServletRequest.class);
+    }
+
+    public void setServletResponse(ServletResponse response) {
+        if (response != null) {
+            put(SERVLET_RESPONSE, response);
+        }
+    }
+
+    public ServletResponse getServletResponse() {
+        return getTypedValue(SERVLET_RESPONSE, ServletResponse.class);
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
new file mode 100644
index 0000000..f8baf11
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.InvalidSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.DefaultSessionManager;
+import org.apache.shiro.session.mgt.DelegatingSession;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.web.servlet.Cookie;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.servlet.ShiroHttpSession;
+import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+
+
+/**
+ * Web-application capable {@link org.apache.shiro.session.mgt.SessionManager SessionManager} implementation.
+ *
+ * @since 0.9
+ */
+public class DefaultWebSessionManager extends DefaultSessionManager implements WebSessionManager {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class);
+
+    private Cookie sessionIdCookie;
+    private boolean sessionIdCookieEnabled;
+
+    public DefaultWebSessionManager() {
+        Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        cookie.setHttpOnly(true); //more secure, protects against XSS attacks
+        this.sessionIdCookie = cookie;
+        this.sessionIdCookieEnabled = true;
+    }
+
+    public Cookie getSessionIdCookie() {
+        return sessionIdCookie;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setSessionIdCookie(Cookie sessionIdCookie) {
+        this.sessionIdCookie = sessionIdCookie;
+    }
+
+    public boolean isSessionIdCookieEnabled() {
+        return sessionIdCookieEnabled;
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) {
+        this.sessionIdCookieEnabled = sessionIdCookieEnabled;
+    }
+
+    private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
+        if (currentId == null) {
+            String msg = "sessionId cannot be null when persisting for subsequent requests.";
+            throw new IllegalArgumentException(msg);
+        }
+        Cookie template = getSessionIdCookie();
+        Cookie cookie = new SimpleCookie(template);
+        String idString = currentId.toString();
+        cookie.setValue(idString);
+        cookie.saveTo(request, response);
+        log.trace("Set session ID cookie for session with id {}", idString);
+    }
+
+    private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
+        getSessionIdCookie().removeFrom(request, response);
+    }
+
+    private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
+        if (!isSessionIdCookieEnabled()) {
+            log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
+            return null;
+        }
+        if (!(request instanceof HttpServletRequest)) {
+            log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
+            return null;
+        }
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
+    }
+
+    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
+
+        String id = getSessionIdCookieValue(request, response);
+        if (id != null) {
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
+        } else {
+            //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
+
+            //try the URI path segment parameters first:
+            id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+
+            if (id == null) {
+                //not a URI path segment parameter, try the query parameters:
+                String name = getSessionIdName();
+                id = request.getParameter(name);
+                if (id == null) {
+                    //try lowercase:
+                    id = request.getParameter(name.toLowerCase());
+                }
+            }
+            if (id != null) {
+                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                        ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
+            }
+        }
+        if (id != null) {
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+            //automatically mark it valid here.  If it is invalid, the
+            //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
+            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+        }
+        return id;
+    }
+
+    //SHIRO-351
+    //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/
+    //since 1.2.2
+    private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
+
+        if (!(servletRequest instanceof HttpServletRequest)) {
+            return null;
+        }
+        HttpServletRequest request = (HttpServletRequest)servletRequest;
+        String uri = request.getRequestURI();
+        if (uri == null) {
+            return null;
+        }
+
+        int queryStartIndex = uri.indexOf('?');
+        if (queryStartIndex >= 0) { //get rid of the query string
+            uri = uri.substring(0, queryStartIndex);
+        }
+
+        int index = uri.indexOf(';'); //now check for path segment parameters:
+        if (index < 0) {
+            //no path segment params - return:
+            return null;
+        }
+
+        //there are path segment params, let's get the last one that may exist:
+
+        final String TOKEN = paramName + "=";
+
+        uri = uri.substring(index+1); //uri now contains only the path segment params
+
+        //we only care about the last JSESSIONID param:
+        index = uri.lastIndexOf(TOKEN);
+        if (index < 0) {
+            //no segment param:
+            return null;
+        }
+
+        uri = uri.substring(index + TOKEN.length());
+
+        index = uri.indexOf(';'); //strip off any remaining segment params:
+        if(index >= 0) {
+            uri = uri.substring(0, index);
+        }
+
+        return uri; //what remains is the value
+    }
+
+    //since 1.2.1
+    private String getSessionIdName() {
+        String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null;
+        if (name == null) {
+            name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
+        }
+        return name;
+    }
+
+    protected Session createExposedSession(Session session, SessionContext context) {
+        if (!WebUtils.isWeb(context)) {
+            return super.createExposedSession(session, context);
+        }
+        ServletRequest request = WebUtils.getRequest(context);
+        ServletResponse response = WebUtils.getResponse(context);
+        SessionKey key = new WebSessionKey(session.getId(), request, response);
+        return new DelegatingSession(this, key);
+    }
+
+    protected Session createExposedSession(Session session, SessionKey key) {
+        if (!WebUtils.isWeb(key)) {
+            return super.createExposedSession(session, key);
+        }
+
+        ServletRequest request = WebUtils.getRequest(key);
+        ServletResponse response = WebUtils.getResponse(key);
+        SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
+        return new DelegatingSession(this, sessionKey);
+    }
+
+    /**
+     * Stores the Session's ID, usually as a Cookie, to associate with future requests.
+     *
+     * @param session the session that was just {@link #createSession created}.
+     */
+    @Override
+    protected void onStart(Session session, SessionContext context) {
+        super.onStart(session, context);
+
+        if (!WebUtils.isHttp(context)) {
+            log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
+                    "pair. No session ID cookie will be set.");
+            return;
+
+        }
+        HttpServletRequest request = WebUtils.getHttpRequest(context);
+        HttpServletResponse response = WebUtils.getHttpResponse(context);
+
+        if (isSessionIdCookieEnabled()) {
+            Serializable sessionId = session.getId();
+            storeSessionId(sessionId, request, response);
+        } else {
+            log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
+        }
+
+        request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
+    }
+
+    @Override
+    public Serializable getSessionId(SessionKey key) {
+        Serializable id = super.getSessionId(key);
+        if (id == null && WebUtils.isWeb(key)) {
+            ServletRequest request = WebUtils.getRequest(key);
+            ServletResponse response = WebUtils.getResponse(key);
+            id = getSessionId(request, response);
+        }
+        return id;
+    }
+
+    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
+        return getReferencedSessionId(request, response);
+    }
+
+    @Override
+    protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
+        super.onExpiration(s, ese, key);
+        onInvalidation(key);
+    }
+
+    @Override
+    protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
+        super.onInvalidation(session, ise, key);
+        onInvalidation(key);
+    }
+
+    private void onInvalidation(SessionKey key) {
+        ServletRequest request = WebUtils.getRequest(key);
+        if (request != null) {
+            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
+        }
+        if (WebUtils.isHttp(key)) {
+            log.debug("Referenced session was invalid.  Removing session ID cookie.");
+            removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
+        } else {
+            log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
+                    "pair. Session ID cookie will not be removed due to invalidated session.");
+        }
+    }
+
+    @Override
+    protected void onStop(Session session, SessionKey key) {
+        super.onStop(session, key);
+        if (WebUtils.isHttp(key)) {
+            HttpServletRequest request = WebUtils.getHttpRequest(key);
+            HttpServletResponse response = WebUtils.getHttpResponse(key);
+            log.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
+            removeSessionIdCookie(request, response);
+        } else {
+            log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
+                    "pair. Session ID cookie will not be removed due to stopped session.");
+        }
+    }
+
+    /**
+     * This is a native session manager implementation, so this method returns {@code false} always.
+     *
+     * @return {@code false} always
+     * @since 1.2
+     */
+    public boolean isServletContainerSessions() {
+        return false;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/ServletContainerSessionManager.java b/web/src/main/java/org/apache/shiro/web/session/mgt/ServletContainerSessionManager.java
new file mode 100644
index 0000000..937fc53
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/ServletContainerSessionManager.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.web.session.HttpServletSession;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * SessionManager implementation providing {@link Session} implementations that are merely wrappers for the
+ * Servlet container's {@link HttpSession}.
+ * <p/>
+ * Despite its name, this implementation <em>does not</em> itself manage Sessions since the Servlet container
+ * provides the actual management support.  This class mainly exists to 'impersonate' a regular Shiro
+ * {@code SessionManager} so it can be pluggable into a normal Shiro configuration in a pure web application.
+ * <p/>
+ * Note that because this implementation relies on the {@link HttpSession HttpSession}, it is only functional in a
+ * servlet container - it is not capable of supporting Sessions for any clients other than those using the HTTP
+ * protocol.
+ * <p/>
+ * Therefore, if you need {@code Session} support for heterogeneous clients (e.g. web browsers,
+ * RMI clients, etc), use the {@link DefaultWebSessionManager DefaultWebSessionManager}
+ * instead.  The {@code DefaultWebSessionManager} supports both traditional web-based access as well as non web-based
+ * clients.
+ *
+ * @since 0.9
+ * @see DefaultWebSessionManager
+ */
+public class ServletContainerSessionManager implements WebSessionManager {
+
+    //TODO - complete JavaDoc
+
+    //TODO - read session timeout value from web.xml
+
+    public ServletContainerSessionManager() {
+    }
+
+    public Session start(SessionContext context) throws AuthorizationException {
+        return createSession(context);
+    }
+
+    public Session getSession(SessionKey key) throws SessionException {
+        if (!WebUtils.isHttp(key)) {
+            String msg = "SessionKey must be an HTTP compatible implementation.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        HttpServletRequest request = WebUtils.getHttpRequest(key);
+
+        Session session = null;
+
+        HttpSession httpSession = request.getSession(false);
+        if (httpSession != null) {
+            session = createSession(httpSession, request.getRemoteHost());
+        }
+
+        return session;
+    }
+
+    private String getHost(SessionContext context) {
+        String host = context.getHost();
+        if (host == null) {
+            ServletRequest request = WebUtils.getRequest(context);
+            if (request != null) {
+                host = request.getRemoteHost();
+            }
+        }
+        return host;
+
+    }
+
+    /**
+     * @since 1.0
+     */
+    protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
+        if (!WebUtils.isHttp(sessionContext)) {
+            String msg = "SessionContext must be an HTTP compatible implementation.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);
+
+        HttpSession httpSession = request.getSession();
+
+        //SHIRO-240: DO NOT use the 'globalSessionTimeout' value here on the acquired session.
+        //see: https://issues.apache.org/jira/browse/SHIRO-240
+
+        String host = getHost(sessionContext);
+
+        return createSession(httpSession, host);
+    }
+
+    protected Session createSession(HttpSession httpSession, String host) {
+        return new HttpServletSession(httpSession, host);
+    }
+
+    /**
+     * This implementation always delegates to the servlet container for sessions, so this method returns
+     * {@code true} always.
+     *
+     * @return {@code true} always
+     * @since 1.2
+     */
+	public boolean isServletContainerSessions() {
+		return true;
+	}
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionContext.java b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionContext.java
new file mode 100644
index 0000000..172f7f4
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionContext.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.web.util.RequestPairSource;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code WebSubjectContext} is a {@link SessionContext} that additionally provides for type-safe
+ * methods to set and retrieve a {@link ServletRequest} and {@link ServletResponse}, as the request/response pair will
+ * often need to be referenced during construction of web-initiated {@code Session} instances.
+ *
+ * @since 1.0
+ */
+public interface WebSessionContext extends SessionContext, RequestPairSource {
+
+    /**
+     * Returns the {@code ServletRequest} received by the servlet container triggering the creation of the
+     * {@code Session} instance.
+     *
+     * @return the {@code ServletRequest} received by the servlet container triggering the creation of the
+     *         {@code Session} instance.
+     */
+    ServletRequest getServletRequest();
+
+    /**
+     * Sets the {@code ServletRequest} received by the servlet container triggering the creation of the
+     * {@code Session} instance.
+     *
+     * @param request the {@code ServletRequest} received by the servlet container triggering the creation of the
+     *                {@code Session} instance.
+     */
+    void setServletRequest(ServletRequest request);
+
+    /**
+     * The paired {@code ServletResponse} corresponding to the associated {@link #getServletRequest servletRequest}.
+     *
+     * @return the paired {@code ServletResponse} corresponding to the associated
+     *         {@link #getServletRequest servletRequest}.
+     */
+    ServletResponse getServletResponse();
+
+    /**
+     * Sets the paired {@code ServletResponse} corresponding to the associated {@link #getServletRequest servletRequest}.
+     *
+     * @param response The paired {@code ServletResponse} corresponding to the associated
+     *                 {@link #getServletRequest servletRequest}.
+     */
+    void setServletResponse(ServletResponse response);
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionKey.java b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionKey.java
new file mode 100644
index 0000000..436cd14
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionKey.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.session.mgt.DefaultSessionKey;
+import org.apache.shiro.web.util.RequestPairSource;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.Serializable;
+
+/**
+ * A {@link org.apache.shiro.session.mgt.SessionKey SessionKey} implementation that also retains the
+ * {@code ServletRequest} and {@code ServletResponse} associated with the web request that is performing the
+ * session lookup.
+ *
+ * @since 1.0
+ */
+public class WebSessionKey extends DefaultSessionKey implements RequestPairSource {
+
+    private final ServletRequest servletRequest;
+    private final ServletResponse servletResponse;
+
+    public WebSessionKey(ServletRequest request, ServletResponse response) {
+        if (request == null) {
+            throw new NullPointerException("request argument cannot be null.");
+        }
+        if (response == null) {
+            throw new NullPointerException("response argument cannot be null.");
+        }
+        this.servletRequest = request;
+        this.servletResponse = response;
+    }
+
+    public WebSessionKey(Serializable sessionId, ServletRequest request, ServletResponse response) {
+        this(request, response);
+        setSessionId(sessionId);
+    }
+
+    public ServletRequest getServletRequest() {
+        return servletRequest;
+    }
+
+    public ServletResponse getServletResponse() {
+        return servletResponse;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionManager.java b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionManager.java
new file mode 100644
index 0000000..bca2858
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/WebSessionManager.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt;
+
+import org.apache.shiro.session.mgt.SessionManager;
+
+/**
+ * {@link SessionManager} specific to web-enabled applications.
+ *
+ * @since 1.2
+ * @see ServletContainerSessionManager
+ * @see DefaultWebSessionManager
+ */
+public interface WebSessionManager extends SessionManager {
+
+    /**
+     * Returns {@code true} if session management and storage is managed by the underlying Servlet container or
+     * {@code false} if managed by Shiro directly (called 'native' sessions).
+     * <p/>
+     * If sessions are enabled, Shiro can make use of Sessions to retain security information from
+     * request to request.  This method indicates whether Shiro would use the Servlet container sessions to fulfill its
+     * needs, or if it would use its own native session management instead (which can support enterprise features
+     * - like distributed caching - in a container-independent manner).
+     *
+     * @return {@code true} if session management and storage is managed by the underlying Servlet container or
+     *         {@code false} if managed by Shiro directly (called 'native' sessions).
+     */
+    boolean isServletContainerSessions();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/session/package-info.java b/web/src/main/java/org/apache/shiro/web/session/package-info.java
new file mode 100644
index 0000000..abd2727
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/session/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Components supporting Session management in web-enabled applications.
+ */
+package org.apache.shiro.web.session;
diff --git a/web/src/main/java/org/apache/shiro/web/subject/WebSubject.java b/web/src/main/java/org/apache/shiro/web/subject/WebSubject.java
new file mode 100644
index 0000000..fb5adfc
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/WebSubject.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.subject;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.subject.support.DefaultWebSubjectContext;
+import org.apache.shiro.web.util.RequestPairSource;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code WebSubject} represents a Subject instance that was acquired as a result of an incoming
+ * {@link ServletRequest}.
+ *
+ * @since 1.0
+ */
+public interface WebSubject extends Subject, RequestPairSource {
+
+    /**
+     * Returns the {@code ServletRequest} accessible when the Subject instance was created.
+     *
+     * @return the {@code ServletRequest} accessible when the Subject instance was created.
+     */
+    ServletRequest getServletRequest();
+
+    /**
+     * Returns the {@code ServletResponse} accessible when the Subject instance was created.
+     *
+     * @return the {@code ServletResponse} accessible when the Subject instance was created.
+     */
+    ServletResponse getServletResponse();
+
+    /**
+     * A {@code WebSubject.Builder} performs the same function as a {@link Subject.Builder Subject.Builder}, but
+     * additionally ensures that the Servlet request/response pair that is triggering the Subject instance's creation
+     * is retained for use by internal Shiro components as necessary.
+     */
+    public static class Builder extends Subject.Builder {
+
+        /**
+         * Constructs a new {@code Web.Builder} instance using the {@link SecurityManager SecurityManager} obtained by
+         * calling {@code SecurityUtils.}{@link SecurityUtils#getSecurityManager() getSecurityManager()}.  If you want
+         * to specify your own SecurityManager instance, use the
+         * {@link #Builder(SecurityManager, ServletRequest, ServletResponse)} constructor instead.
+         *
+         * @param request  the incoming ServletRequest that will be associated with the built {@code WebSubject} instance.
+         * @param response the outgoing ServletRequest paired with the ServletRequest that will be associated with the
+         *                 built {@code WebSubject} instance.
+         */
+        public Builder(ServletRequest request, ServletResponse response) {
+            this(SecurityUtils.getSecurityManager(), request, response);
+        }
+
+        /**
+         * Constructs a new {@code Web.Builder} instance using the specified {@code SecurityManager} instance to
+         * create the {@link WebSubject WebSubject} instance.
+         *
+         * @param securityManager the {@code SecurityManager SecurityManager} instance to use to build the
+         *                        {@code WebSubject} instance.
+         * @param request         the incoming ServletRequest that will be associated with the built {@code WebSubject}
+         *                        instance.
+         * @param response        the outgoing ServletRequest paired with the ServletRequest that will be associated
+         *                        with the built {@code WebSubject} instance.
+         */
+        public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
+            super(securityManager);
+            if (request == null) {
+                throw new IllegalArgumentException("ServletRequest argument cannot be null.");
+            }
+            if (response == null) {
+                throw new IllegalArgumentException("ServletResponse argument cannot be null.");
+            }
+            setRequest(request);
+            setResponse(response);
+        }
+
+        /**
+         * Overrides the parent implementation to return a new instance of a
+         * {@link DefaultWebSubjectContext DefaultWebSubjectContext} to account for the additional request/response
+         * pair.
+         *
+         * @return a new instance of a {@link DefaultWebSubjectContext DefaultWebSubjectContext} to account for the
+         *         additional request/response pair.
+         */
+        @Override
+        protected SubjectContext newSubjectContextInstance() {
+            return new DefaultWebSubjectContext();
+        }
+
+        /**
+         * Called by the {@code WebSubject.Builder} constructor, this method places the request object in the
+         * context map for later retrieval.
+         *
+         * @param request the incoming ServletRequest that triggered the creation of the {@code WebSubject} instance.
+         * @return 'this' for method chaining.
+         */
+        protected Builder setRequest(ServletRequest request) {
+            if (request != null) {
+                ((WebSubjectContext) getSubjectContext()).setServletRequest(request);
+            }
+            return this;
+        }
+
+        /**
+         * Called by the {@code WebSubject.Builder} constructor, this method places the response object in the
+         * context map for later retrieval.
+         *
+         * @param response the outgoing ServletRequest paired with the ServletRequest that triggered the creation of
+         *                 the {@code WebSubject} instance.
+         * @return 'this' for method chaining.
+         */
+        protected Builder setResponse(ServletResponse response) {
+            if (response != null) {
+                ((WebSubjectContext) getSubjectContext()).setServletResponse(response);
+            }
+            return this;
+        }
+
+        /**
+         * Returns {@link #buildSubject() super.buildSubject()}, but additionally ensures that the returned instance
+         * is an {@code instanceof} {@link WebSubject WebSubject} and to support a type-safe method so a caller
+         * does not have to cast.   Per the parent class's method JavaDoc, this method will return a new instance
+         * each time it is called.
+         *
+         * @return a new {@link WebSubject WebSubject} instance built by this {@code Builder}.
+         */
+        public WebSubject buildWebSubject() {
+            Subject subject = super.buildSubject();
+            if (!(subject instanceof WebSubject)) {
+                String msg = "Subject implementation returned from the SecurityManager was not a " +
+                        WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager " +
+                        "has been configured and made available to this builder.";
+                throw new IllegalStateException(msg);
+            }
+            return (WebSubject) subject;
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/subject/WebSubjectContext.java b/web/src/main/java/org/apache/shiro/web/subject/WebSubjectContext.java
new file mode 100644
index 0000000..c2a7cf1
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/WebSubjectContext.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.subject;
+
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.util.RequestPairSource;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code WebSubjectContext} is a {@link SubjectContext} that additionally provides for type-safe
+ * methods to set and retrieve a {@link ServletRequest} and {@link ServletResponse}.
+ *
+ * @since 1.0
+ */
+public interface WebSubjectContext extends SubjectContext, RequestPairSource {
+
+    /**
+     * Returns the {@code ServletRequest} received by the servlet container triggering the creation of the
+     * {@code Subject} instance.
+     *
+     * @return the {@code ServletRequest} received by the servlet container triggering the creation of the
+     *         {@code Subject} instance.
+     */
+    ServletRequest getServletRequest();
+
+    /**
+     * Sets the {@code ServletRequest} received by the servlet container triggering the creation of the
+     * {@code Subject} instance.
+     *
+     * @param request the {@code ServletRequest} received by the servlet container triggering the creation of the
+     *                {@code Subject} instance.
+     */
+    void setServletRequest(ServletRequest request);
+
+    ServletRequest resolveServletRequest();
+
+    /**
+     * The paired {@code ServletResponse} corresponding to the associated {@link #getServletRequest servletRequest}.
+     *
+     * @return the paired {@code ServletResponse} corresponding to the associated
+     *         {@link #getServletRequest servletRequest}.
+     */
+    ServletResponse getServletResponse();
+
+    /**
+     * Sets the paired {@code ServletResponse} corresponding to the associated {@link #getServletRequest servletRequest}.
+     *
+     * @param response The paired {@code ServletResponse} corresponding to the associated
+     *                 {@link #getServletRequest servletRequest}.
+     */
+    void setServletResponse(ServletResponse response);
+
+    ServletResponse resolveServletResponse();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/subject/package-info.java b/web/src/main/java/org/apache/shiro/web/subject/package-info.java
new file mode 100644
index 0000000..4ffd90d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Web-specific {@link org.apache.shiro.subject.Subject Subject} interfaces to enable {@code Subject} use in web
+ * environments.
+ *
+ * @see WebSubject
+ */
+package org.apache.shiro.web.subject;
diff --git a/web/src/main/java/org/apache/shiro/web/subject/support/DefaultWebSubjectContext.java b/web/src/main/java/org/apache/shiro/web/subject/support/DefaultWebSubjectContext.java
new file mode 100644
index 0000000..2f0eddf
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/support/DefaultWebSubjectContext.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.subject.support;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Default {@code WebSubjectContext} implementation that provides for additional storage and retrieval of
+ * a {@link ServletRequest} and {@link ServletResponse}.
+ *
+ * @since 1.0
+ */
+public class DefaultWebSubjectContext extends DefaultSubjectContext implements WebSubjectContext {
+
+    private static final long serialVersionUID = 8188555355305827739L;
+
+    private static final String SERVLET_REQUEST = DefaultWebSubjectContext.class.getName() + ".SERVLET_REQUEST";
+    private static final String SERVLET_RESPONSE = DefaultWebSubjectContext.class.getName() + ".SERVLET_RESPONSE";
+
+    public DefaultWebSubjectContext() {
+    }
+
+    public DefaultWebSubjectContext(WebSubjectContext context) {
+        super(context);
+    }
+
+    @Override
+    public String resolveHost() {
+        String host = super.resolveHost();
+        if (host == null) {
+            ServletRequest request = resolveServletRequest();
+            if (request != null) {
+                host = request.getRemoteHost();
+            }
+        }
+        return host;
+    }
+
+    public ServletRequest getServletRequest() {
+        return getTypedValue(SERVLET_REQUEST, ServletRequest.class);
+    }
+
+    public void setServletRequest(ServletRequest request) {
+        if (request != null) {
+            put(SERVLET_REQUEST, request);
+        }
+    }
+
+    public ServletRequest resolveServletRequest() {
+
+        ServletRequest request = getServletRequest();
+
+        //fall back on existing subject instance if it exists:
+        if (request == null) {
+            Subject existing = getSubject();
+            if (existing instanceof WebSubject) {
+                request = ((WebSubject) existing).getServletRequest();
+            }
+        }
+
+        return request;
+    }
+
+    public ServletResponse getServletResponse() {
+        return getTypedValue(SERVLET_RESPONSE, ServletResponse.class);
+    }
+
+    public void setServletResponse(ServletResponse response) {
+        if (response != null) {
+            put(SERVLET_RESPONSE, response);
+        }
+    }
+
+    public ServletResponse resolveServletResponse() {
+
+        ServletResponse response = getServletResponse();
+
+        //fall back on existing subject instance if it exists:
+        if (response == null) {
+            Subject existing = getSubject();
+            if (existing instanceof WebSubject) {
+                response = ((WebSubject) existing).getServletResponse();
+            }
+        }
+
+        return response;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java b/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java
new file mode 100644
index 0000000..16c1db9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/support/WebDelegatingSubject.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.subject.support;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.session.mgt.DefaultWebSessionContext;
+import org.apache.shiro.web.session.mgt.WebSessionContext;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Default {@link WebSubject WebSubject} implementation that additional ensures the ability to retain a
+ * servlet request/response pair to be used by internal shiro components as necessary during the request execution.
+ *
+ * @since 1.0
+ */
+public class WebDelegatingSubject extends DelegatingSubject implements WebSubject {
+
+    private static final long serialVersionUID = -1655724323350159250L;
+
+    private final ServletRequest servletRequest;
+    private final ServletResponse servletResponse;
+
+    public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
+                                String host, Session session,
+                                ServletRequest request, ServletResponse response,
+                                SecurityManager securityManager) {
+        this(principals, authenticated, host, session, true, request, response, securityManager);
+    }
+
+    //since 1.2
+    public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
+                                String host, Session session, boolean sessionEnabled,
+                                ServletRequest request, ServletResponse response,
+                                SecurityManager securityManager) {
+        super(principals, authenticated, host, session, sessionEnabled, securityManager);
+        this.servletRequest = request;
+        this.servletResponse = response;
+    }
+
+    public ServletRequest getServletRequest() {
+        return servletRequest;
+    }
+
+    public ServletResponse getServletResponse() {
+        return servletResponse;
+    }
+
+    /**
+     * Returns {@code true} if session creation is allowed  (as determined by the super class's
+     * {@link super#isSessionCreationEnabled()} value and no request-specific override has disabled sessions for this subject,
+     * {@code false} otherwise.
+     * <p/>
+     * This means session creation is disabled if the super {@link super#isSessionCreationEnabled()} property is {@code false}
+     * or if a request attribute is discovered that turns off sessions for the current request.
+     *
+     * @return {@code true} if session creation is allowed  (as determined by the super class's
+     *         {@link super#isSessionCreationEnabled()} value and no request-specific override has disabled sessions for this
+     *         subject, {@code false} otherwise.
+     * @since 1.2
+     */
+    @Override
+    protected boolean isSessionCreationEnabled() {
+        boolean enabled = super.isSessionCreationEnabled();
+        return enabled && WebUtils._isSessionCreationEnabled(this);
+    }
+
+    @Override
+    protected SessionContext createSessionContext() {
+        WebSessionContext wsc = new DefaultWebSessionContext();
+        String host = getHost();
+        if (StringUtils.hasText(host)) {
+            wsc.setHost(host);
+        }
+        wsc.setServletRequest(this.servletRequest);
+        wsc.setServletResponse(this.servletResponse);
+        return wsc;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/subject/support/package-info.java b/web/src/main/java/org/apache/shiro/web/subject/support/package-info.java
new file mode 100644
index 0000000..de9fde0
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/subject/support/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Supporting implementations of {@code org.apache.shiro.web.subject} package interfaces.
+ *
+ * @see WebDelegatingSubject
+ */
+package org.apache.shiro.web.subject.support;
diff --git a/web/src/main/java/org/apache/shiro/web/tags/AuthenticatedTag.java b/web/src/main/java/org/apache/shiro/web/tags/AuthenticatedTag.java
new file mode 100644
index 0000000..d532241
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/AuthenticatedTag.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * JSP tag that renders the tag body only if the current user has executed a <b>successful</b> authentication attempt
+ * <em>during their current session</em>.
+ *
+ * <p>This is more restrictive than the {@link UserTag}, which only
+ * ensures the current user is known to the system, either via a current login or from Remember Me services,
+ * which only makes the assumption that the current user is who they say they are, and does not guarantee it like
+ * this tag does.
+ *
+ * <p>The logically opposite tag of this one is the {@link NotAuthenticatedTag}
+ *
+ * @since 0.2
+ */
+public class AuthenticatedTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(AuthenticatedTag.class);
+
+    public int onDoStartTag() throws JspException {
+        if (getSubject() != null && getSubject().isAuthenticated()) {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject exists and is authenticated.  Tag body will be evaluated.");
+            }
+            return TagSupport.EVAL_BODY_INCLUDE;
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject does not exist or is not authenticated.  Tag body will not be evaluated.");
+            }
+            return TagSupport.SKIP_BODY;
+        }
+    }
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/tags/GuestTag.java b/web/src/main/java/org/apache/shiro/web/tags/GuestTag.java
new file mode 100644
index 0000000..4a309e9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/GuestTag.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * JSP tag that renders the tag body if the current user <em>is not</em> known to the system, either because they
+ * haven't logged in yet, or because they have no 'RememberMe' identity.
+ *
+ * <p>The logically opposite tag of this one is the {@link UserTag}.  Please read that class's JavaDoc as it explains
+ * more about the differences between Authenticated/Unauthenticated and User/Guest semantic differences.
+ *
+ * @since 0.9
+ */
+public class GuestTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(GuestTag.class);
+
+    public int onDoStartTag() throws JspException {
+        if (getSubject() == null || getSubject().getPrincipal() == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject does not exist or does not have a known identity (aka 'principal').  " +
+                        "Tag body will be evaluated.");
+            }
+            return TagSupport.EVAL_BODY_INCLUDE;
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject exists or has a known identity (aka 'principal').  " +
+                        "Tag body will not be evaluated.");
+            }
+            return TagSupport.SKIP_BODY;
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/HasAnyRolesTag.java b/web/src/main/java/org/apache/shiro/web/tags/HasAnyRolesTag.java
new file mode 100644
index 0000000..882b4cc
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/HasAnyRolesTag.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import org.apache.shiro.subject.Subject;
+
+
+/**
+ * Displays body content if the current user has any of the roles specified.
+ *
+ * @since 0.2
+ */
+public class HasAnyRolesTag extends RoleTag {
+
+    //TODO - complete JavaDoc
+
+    // Delimeter that separates role names in tag attribute
+    private static final String ROLE_NAMES_DELIMETER = ",";
+
+    public HasAnyRolesTag() {
+    }
+
+    protected boolean showTagBody(String roleNames) {
+        boolean hasAnyRole = false;
+
+        Subject subject = getSubject();
+
+        if (subject != null) {
+
+            // Iterate through roles and check to see if the user has one of the roles
+            for (String role : roleNames.split(ROLE_NAMES_DELIMETER)) {
+
+                if (subject.hasRole(role.trim())) {
+                    hasAnyRole = true;
+                    break;
+                }
+
+            }
+
+        }
+
+        return hasAnyRole;
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/HasPermissionTag.java b/web/src/main/java/org/apache/shiro/web/tags/HasPermissionTag.java
new file mode 100644
index 0000000..1d3d8ab
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/HasPermissionTag.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+/**
+ * @since 0.1
+ */
+public class HasPermissionTag extends PermissionTag {
+
+    //TODO - complete JavaDoc
+
+    public HasPermissionTag() {
+    }
+
+    protected boolean showTagBody(String p) {
+        return isPermitted(p);
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/HasRoleTag.java b/web/src/main/java/org/apache/shiro/web/tags/HasRoleTag.java
new file mode 100644
index 0000000..15f345f
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/HasRoleTag.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+/**
+ * @since 0.1
+ */
+public class HasRoleTag extends RoleTag {
+
+    //TODO - complete JavaDoc
+
+    public HasRoleTag() {
+    }
+
+    protected boolean showTagBody(String roleName) {
+        return getSubject() != null && getSubject().hasRole(roleName);
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/LacksPermissionTag.java b/web/src/main/java/org/apache/shiro/web/tags/LacksPermissionTag.java
new file mode 100644
index 0000000..a3007a0
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/LacksPermissionTag.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+/**
+ * @since 0.1
+ */
+public class LacksPermissionTag extends PermissionTag {
+
+    //TODO - complete JavaDoc
+
+    public LacksPermissionTag() {
+    }
+
+    protected boolean showTagBody(String p) {
+        return !isPermitted(p);
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/LacksRoleTag.java b/web/src/main/java/org/apache/shiro/web/tags/LacksRoleTag.java
new file mode 100644
index 0000000..ec31251
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/LacksRoleTag.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+/**
+ * @since 0.1
+ */
+public class LacksRoleTag extends RoleTag {
+
+    //TODO - complete JavaDoc
+
+    public LacksRoleTag() {
+    }
+
+    protected boolean showTagBody(String roleName) {
+        boolean hasRole = getSubject() != null && getSubject().hasRole(roleName);
+        return !hasRole;
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/NotAuthenticatedTag.java b/web/src/main/java/org/apache/shiro/web/tags/NotAuthenticatedTag.java
new file mode 100644
index 0000000..ba08e1e
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/NotAuthenticatedTag.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * JSP tag that renders the tag body only if the current user has <em>not</em> executed a successful authentication
+ * attempt <em>during their current session</em>.
+ *
+ * <p>The logically opposite tag of this one is the {@link org.apache.shiro.web.tags.AuthenticatedTag}.
+ *
+ * @since 0.2
+ */
+public class NotAuthenticatedTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(NotAuthenticatedTag.class);
+
+    public int onDoStartTag() throws JspException {
+        if (getSubject() == null || !getSubject().isAuthenticated()) {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject does not exist or is not authenticated.  Tag body will be evaluated.");
+            }
+            return TagSupport.EVAL_BODY_INCLUDE;
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject exists and is authenticated.  Tag body will not be evaluated.");
+            }
+            return TagSupport.SKIP_BODY;
+        }
+    }
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/tags/PermissionTag.java b/web/src/main/java/org/apache/shiro/web/tags/PermissionTag.java
new file mode 100644
index 0000000..68bec54
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/PermissionTag.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * @since 0.1
+ */
+public abstract class PermissionTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private String name = null;
+
+    public PermissionTag() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    protected void verifyAttributes() throws JspException {
+        String permission = getName();
+
+        if (permission == null || permission.length() == 0) {
+            String msg = "The 'name' tag attribute must be set.";
+            throw new JspException(msg);
+        }
+    }
+
+    public int onDoStartTag() throws JspException {
+
+        String p = getName();
+
+        boolean show = showTagBody(p);
+        if (show) {
+            return TagSupport.EVAL_BODY_INCLUDE;
+        } else {
+            return TagSupport.SKIP_BODY;
+        }
+    }
+
+    protected boolean isPermitted(String p) {
+        return getSubject() != null && getSubject().isPermitted(p);
+    }
+
+    protected abstract boolean showTagBody(String p);
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/PrincipalTag.java b/web/src/main/java/org/apache/shiro/web/tags/PrincipalTag.java
new file mode 100644
index 0000000..eec9f10
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/PrincipalTag.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspTagException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>Tag used to print out the String value of a user's default principal,
+ * or a specific principal as specified by the tag's attributes.</p>
+ *
+ * <p> If no attributes are specified, the tag prints out the <tt>toString()</tt>
+ * value of the user's default principal.  If the <tt>type</tt> attribute
+ * is specified, the tag looks for a principal with the given type.  If the
+ * <tt>property</tt> attribute is specified, the tag prints the string value of
+ * the specified property of the principal.  If no principal is found or the user
+ * is not authenticated, the tag displays nothing unless a <tt>defaultValue</tt>
+ * is specified.</p>
+ *
+ * @since 0.2
+ */
+public class PrincipalTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Logger log = LoggerFactory.getLogger(PrincipalTag.class);
+
+    /**
+     * The type of principal to be retrieved, or null if the default principal should be used.
+     */
+    private String type;
+
+    /**
+     * The property name to retrieve of the principal, or null if the <tt>toString()</tt> value should be used.
+     */
+    private String property;
+
+    /**
+     * The default value that should be displayed if the user is not authenticated, or no principal is found.
+     */
+    private String defaultValue;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+
+    public String getType() {
+        return type;
+    }
+
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+    public String getProperty() {
+        return property;
+    }
+
+
+    public void setProperty(String property) {
+        this.property = property;
+    }
+
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+
+    @SuppressWarnings({"unchecked"})
+    public int onDoStartTag() throws JspException {
+        String strValue = null;
+
+        if (getSubject() != null) {
+
+            // Get the principal to print out
+            Object principal;
+
+            if (type == null) {
+                principal = getSubject().getPrincipal();
+            } else {
+                principal = getPrincipalFromClassName();
+            }
+
+            // Get the string value of the principal
+            if (principal != null) {
+                if (property == null) {
+                    strValue = principal.toString();
+                } else {
+                    strValue = getPrincipalProperty(principal, property);
+                }
+            }
+
+        }
+
+        // Print out the principal value if not null
+        if (strValue != null) {
+            try {
+                pageContext.getOut().write(strValue);
+            } catch (IOException e) {
+                throw new JspTagException("Error writing [" + strValue + "] to JSP.", e);
+            }
+        }
+
+        return SKIP_BODY;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Object getPrincipalFromClassName() {
+        Object principal = null;
+
+        try {
+            Class cls = Class.forName(type);
+            principal = getSubject().getPrincipals().oneByType(cls);
+        } catch (ClassNotFoundException e) {
+            if (log.isErrorEnabled()) {
+                log.error("Unable to find class for name [" + type + "]");
+            }
+        }
+        return principal;
+    }
+
+
+    private String getPrincipalProperty(Object principal, String property) throws JspTagException {
+        String strValue = null;
+
+        try {
+            BeanInfo bi = Introspector.getBeanInfo(principal.getClass());
+
+            // Loop through the properties to get the string value of the specified property
+            boolean foundProperty = false;
+            for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
+                if (pd.getName().equals(property)) {
+                    Object value = pd.getReadMethod().invoke(principal, (Object[]) null);
+                    strValue = String.valueOf(value);
+                    foundProperty = true;
+                    break;
+                }
+            }
+
+            if (!foundProperty) {
+                final String message = "Property [" + property + "] not found in principal of type [" + principal.getClass().getName() + "]";
+                if (log.isErrorEnabled()) {
+                    log.error(message);
+                }
+                throw new JspTagException(message);
+            }
+
+        } catch (Exception e) {
+            final String message = "Error reading property [" + property + "] from principal of type [" + principal.getClass().getName() + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+            throw new JspTagException(message, e);
+        }
+
+        return strValue;
+    }
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/apache/shiro/web/tags/RoleTag.java b/web/src/main/java/org/apache/shiro/web/tags/RoleTag.java
new file mode 100644
index 0000000..00b8a18
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/RoleTag.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * @since 0.1
+ */
+public abstract class RoleTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private String name = null;
+
+    public RoleTag() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int onDoStartTag() throws JspException {
+        boolean show = showTagBody(getName());
+        if (show) {
+            return TagSupport.EVAL_BODY_INCLUDE;
+        } else {
+            return TagSupport.SKIP_BODY;
+        }
+    }
+
+    protected abstract boolean showTagBody(String roleName);
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/SecureTag.java b/web/src/main/java/org/apache/shiro/web/tags/SecureTag.java
new file mode 100644
index 0000000..a9d5d43
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/SecureTag.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @since 0.1
+ */
+public abstract class SecureTag extends TagSupport {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(SecureTag.class);
+
+    public SecureTag() {
+    }
+
+    protected Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    protected void verifyAttributes() throws JspException {
+    }
+
+    public int doStartTag() throws JspException {
+
+        verifyAttributes();
+
+        return onDoStartTag();
+    }
+
+    public abstract int onDoStartTag() throws JspException;
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/UserTag.java b/web/src/main/java/org/apache/shiro/web/tags/UserTag.java
new file mode 100644
index 0000000..6d72b89
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/UserTag.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.tags;
+
+import javax.servlet.jsp.JspException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * JSP tag that renders the tag body if the current user known to the system, either from a successful login attempt
+ * (not necessarily during the current session) or from 'RememberMe' services.
+ *
+ * <p><b>Note:</b> This is <em>less</em> restrictive than the <code>AuthenticatedTag</code> since it only assumes
+ * the user is who they say they are, either via a current session login <em>or</em> via Remember Me services, which
+ * makes no guarantee the user is who they say they are.  The <code>AuthenticatedTag</code> however
+ * guarantees that the current user has logged in <em>during their current session</em>, proving they really are
+ * who they say they are.
+ *
+ * <p>The logically opposite tag of this one is the {@link org.apache.shiro.web.tags.GuestTag}.
+ *
+ * @since 0.9
+ */
+public class UserTag extends SecureTag {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(UserTag.class);
+
+    public int onDoStartTag() throws JspException {
+        if (getSubject() != null && getSubject().getPrincipal() != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject has known identity (aka 'principal').  " +
+                        "Tag body will be evaluated.");
+            }
+            return EVAL_BODY_INCLUDE;
+        } else {
+            if (log.isTraceEnabled()) {
+                log.trace("Subject does not exist or have a known identity (aka 'principal').  " +
+                        "Tag body will not be evaluated.");
+            }
+            return SKIP_BODY;
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/tags/package-info.java b/web/src/main/java/org/apache/shiro/web/tags/package-info.java
new file mode 100644
index 0000000..6c48e52
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/tags/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Provides the Shiro JSP Tag Library implementations.
+ * <p/>
+ * Shiro JSP Tags can be used to evalute or not evaluate (show or not show) parts of a JSP page based on the
+ * current user's authentication status and/or authorization (access control) abilities.
+ */
+package org.apache.shiro.web.tags;
diff --git a/web/src/main/java/org/apache/shiro/web/util/RedirectView.java b/web/src/main/java/org/apache/shiro/web/util/RedirectView.java
new file mode 100644
index 0000000..8e039b1
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/util/RedirectView.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.util;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Map;
+
+/**
+ * View that redirects to an absolute, context relative, or current request
+ * relative URL, exposing all model attributes as HTTP query parameters.
+ * <p/>
+ * A URL for this view is supposed to be a HTTP redirect URL, i.e.
+ * suitable for HttpServletResponse's <code>sendRedirect</code> method, which
+ * is what actually does the redirect if the HTTP 1.0 flag is on, or via sending
+ * back an HTTP 303 code - if the HTTP 1.0 compatibility flag is off.
+ * <p/>
+ * Note that while the default value for the "contextRelative" flag is off,
+ * you will probably want to almost always set it to true. With the flag off,
+ * URLs starting with "/" are considered relative to the web server root, while
+ * with the flag on, they are considered relative to the web application root.
+ * Since most web apps will never know or care what their context path actually
+ * is, they are much better off setting this flag to true, and submitting paths
+ * which are to be considered relative to the web application root.
+ * <p/>
+ * Note that in a Servlet 2.2 environment, i.e. a servlet container which
+ * is only compliant to the limits of this spec, this class will probably fail
+ * when feeding in URLs which are not fully absolute, or relative to the current
+ * request (no leading "/"), as these are the only two types of URL that
+ * <code>sendRedirect</code> supports in a Servlet 2.2 environment.
+ * <p/>
+ * <em>This class was borrowed from a nearly identical version found in
+ * the <a href="http://www.springframework.org/">Spring Framework</a>, with minor modifications to
+ * avoid a dependency on Spring itself for a very small amount of code - we couldn't have done it better, and
+ * don't want to repeat all of their great effort ;).
+ * The original author names and copyright (Apache 2.0) has been left in place.  A special
+ * thanks to Rod Johnson, Juergen Hoeller, and Colin Sampaleanu for making this available.</em>
+ *
+ * @see #setContextRelative
+ * @see #setHttp10Compatible
+ * @see javax.servlet.http.HttpServletResponse#sendRedirect
+ * @since 0.2
+ */
+public class RedirectView {
+
+    //TODO - complete JavaDoc
+
+    /**
+     * The default encoding scheme: UTF-8
+     */
+    public static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
+
+    private String url;
+
+    private boolean contextRelative = false;
+
+    private boolean http10Compatible = true;
+
+    private String encodingScheme = DEFAULT_ENCODING_SCHEME;
+
+    /**
+     * Constructor for use as a bean.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public RedirectView() {
+    }
+
+    /**
+     * Create a new RedirectView with the given URL.
+     * <p>The given URL will be considered as relative to the web server,
+     * not as relative to the current ServletContext.
+     *
+     * @param url the URL to redirect to
+     * @see #RedirectView(String, boolean)
+     */
+    public RedirectView(String url) {
+        setUrl(url);
+    }
+
+    /**
+     * Create a new RedirectView with the given URL.
+     *
+     * @param url             the URL to redirect to
+     * @param contextRelative whether to interpret the given URL as
+     *                        relative to the current ServletContext
+     */
+    public RedirectView(String url, boolean contextRelative) {
+        this(url);
+        this.contextRelative = contextRelative;
+    }
+
+    /**
+     * Create a new RedirectView with the given URL.
+     *
+     * @param url              the URL to redirect to
+     * @param contextRelative  whether to interpret the given URL as
+     *                         relative to the current ServletContext
+     * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
+     */
+    public RedirectView(String url, boolean contextRelative, boolean http10Compatible) {
+        this(url);
+        this.contextRelative = contextRelative;
+        this.http10Compatible = http10Compatible;
+    }
+
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Set whether to interpret a given URL that starts with a slash ("/")
+     * as relative to the current ServletContext, i.e. as relative to the
+     * web application root.
+     * <p/>
+     * Default is "false": A URL that starts with a slash will be interpreted
+     * as absolute, i.e. taken as-is. If true, the context path will be
+     * prepended to the URL in such a case.
+     *
+     * @param contextRelative whether to interpret a given URL that starts with a slash ("/")
+     *                        as relative to the current ServletContext, i.e. as relative to the
+     *                        web application root.
+     * @see javax.servlet.http.HttpServletRequest#getContextPath
+     */
+    public void setContextRelative(boolean contextRelative) {
+        this.contextRelative = contextRelative;
+    }
+
+    /**
+     * Set whether to stay compatible with HTTP 1.0 clients.
+     * <p>In the default implementation, this will enforce HTTP status code 302
+     * in any case, i.e. delegate to <code>HttpServletResponse.sendRedirect</code>.
+     * Turning this off will send HTTP status code 303, which is the correct
+     * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients.
+     * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any
+     * difference. However, some clients depend on 303 when redirecting
+     * after a POST request; turn this flag off in such a scenario.
+     *
+     * @param http10Compatible whether to stay compatible with HTTP 1.0 clients.
+     * @see javax.servlet.http.HttpServletResponse#sendRedirect
+     */
+    public void setHttp10Compatible(boolean http10Compatible) {
+        this.http10Compatible = http10Compatible;
+    }
+
+    /**
+     * Set the encoding scheme for this view. Default is UTF-8.
+     *
+     * @param encodingScheme the encoding scheme for this view. Default is UTF-8.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void setEncodingScheme(String encodingScheme) {
+        this.encodingScheme = encodingScheme;
+    }
+
+
+    /**
+     * Convert model to request parameters and redirect to the given URL.
+     *
+     * @param model    the model to convert
+     * @param request  the incoming HttpServletRequest
+     * @param response the outgoing HttpServletResponse
+     * @throws java.io.IOException if there is a problem issuing the redirect
+     * @see #appendQueryProperties
+     * @see #sendRedirect
+     */
+    public final void renderMergedOutputModel(
+            Map model, HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+        // Prepare name URL.
+        StringBuilder targetUrl = new StringBuilder();
+        if (this.contextRelative && getUrl().startsWith("/")) {
+            // Do not apply context path to relative URLs.
+            targetUrl.append(request.getContextPath());
+        }
+        targetUrl.append(getUrl());
+        //change the following method to accept a StringBuilder instead of a StringBuilder for Shiro 2.x:
+        appendQueryProperties(targetUrl, model, this.encodingScheme);
+
+        sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
+    }
+
+    /**
+     * Append query properties to the redirect URL.
+     * Stringifies, URL-encodes and formats model attributes as query properties.
+     *
+     * @param targetUrl      the StringBuffer to append the properties to
+     * @param model          Map that contains model attributes
+     * @param encodingScheme the encoding scheme to use
+     * @throws java.io.UnsupportedEncodingException if string encoding failed
+     * @see #urlEncode
+     * @see #queryProperties
+     * @see #urlEncode(String, String)
+     */
+    protected void appendQueryProperties(StringBuilder targetUrl, Map model, String encodingScheme)
+            throws UnsupportedEncodingException {
+
+        // Extract anchor fragment, if any.
+        // The following code does not use JDK 1.4's StringBuffer.indexOf(String)
+        // method to retain JDK 1.3 compatibility.
+        String fragment = null;
+        int anchorIndex = targetUrl.toString().indexOf('#');
+        if (anchorIndex > -1) {
+            fragment = targetUrl.substring(anchorIndex);
+            targetUrl.delete(anchorIndex, targetUrl.length());
+        }
+
+        // If there aren't already some parameters, we need a "?".
+        boolean first = (getUrl().indexOf('?') < 0);
+        Map queryProps = queryProperties(model);
+
+        if (queryProps != null) {
+            for (Object o : queryProps.entrySet()) {
+                if (first) {
+                    targetUrl.append('?');
+                    first = false;
+                } else {
+                    targetUrl.append('&');
+                }
+                Map.Entry entry = (Map.Entry) o;
+                String encodedKey = urlEncode(entry.getKey().toString(), encodingScheme);
+                String encodedValue =
+                        (entry.getValue() != null ? urlEncode(entry.getValue().toString(), encodingScheme) : "");
+                targetUrl.append(encodedKey).append('=').append(encodedValue);
+            }
+        }
+
+        // Append anchor fragment, if any, to end of URL.
+        if (fragment != null) {
+            targetUrl.append(fragment);
+        }
+    }
+
+    /**
+     * URL-encode the given input String with the given encoding scheme, using
+     * {@link URLEncoder#encode(String, String) URLEncoder.encode(input, enc)}.
+     *
+     * @param input          the unencoded input String
+     * @param encodingScheme the encoding scheme
+     * @return the encoded output String
+     * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder
+     * @see java.net.URLEncoder#encode(String, String)
+     * @see java.net.URLEncoder#encode(String)
+     */
+    protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException {
+        return URLEncoder.encode(input, encodingScheme);
+    }
+
+    /**
+     * Determine name-value pairs for query strings, which will be stringified,
+     * URL-encoded and formatted by appendQueryProperties.
+     * <p/>
+     * This implementation returns all model elements as-is.
+     *
+     * @param model the model elements for which to determine name-value pairs.
+     * @return the name-value pairs for query strings.
+     * @see #appendQueryProperties
+     */
+    protected Map queryProperties(Map model) {
+        return model;
+    }
+
+    /**
+     * Send a redirect back to the HTTP client
+     *
+     * @param request          current HTTP request (allows for reacting to request method)
+     * @param response         current HTTP response (for sending response headers)
+     * @param targetUrl        the name URL to redirect to
+     * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
+     * @throws IOException if thrown by response methods
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
+                                String targetUrl, boolean http10Compatible) throws IOException {
+        if (http10Compatible) {
+            // Always send status code 302.
+            response.sendRedirect(response.encodeRedirectURL(targetUrl));
+        } else {
+            // Correct HTTP status code is 303, in particular for POST requests.
+            response.setStatus(303);
+            response.setHeader("Location", response.encodeRedirectURL(targetUrl));
+        }
+    }
+
+}
diff --git a/web/src/main/java/org/apache/shiro/web/util/RequestPairSource.java b/web/src/main/java/org/apache/shiro/web/util/RequestPairSource.java
new file mode 100644
index 0000000..334fb65
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/util/RequestPairSource.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Les Hazlewood
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.shiro.web.util;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A {@code RequestPairSource} is a component that can supply a {@link ServletRequest ServletRequest} and
+ * {@link ServletResponse ServletResponse} pair associated with a currently executing request.  This is used for
+ * framework development support and is rarely used by end-users.
+ *
+ * @since 1.0
+ */
+public interface RequestPairSource {
+
+    /**
+     * Returns the incoming {@link ServletRequest ServletRequest} associated with the component.
+     *
+     * @return the incoming {@link ServletRequest ServletRequest} associated with the component.
+     */
+    ServletRequest getServletRequest();
+
+    /**
+     * Returns the outgoing {@link ServletResponse ServletResponse} paired with the incoming
+     * {@link #getServletRequest() servletRequest}.
+     *
+     * @return the outgoing {@link ServletResponse ServletResponse} paired with the incoming
+     *         {@link #getServletRequest() servletRequest}.
+     */
+    ServletResponse getServletResponse();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/util/SavedRequest.java b/web/src/main/java/org/apache/shiro/web/util/SavedRequest.java
new file mode 100644
index 0000000..175fa9d
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/util/SavedRequest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.Serializable;
+
+/**
+ * Maintains request data for a request that was redirected, so that after authentication
+ * the user can be redirected to the originally requested page.
+ *
+ * @since 0.9
+ */
+public class SavedRequest implements Serializable {
+
+    //TODO - complete JavaDoc
+
+    private String method;
+    private String queryString;
+    private String requestURI;
+
+    /**
+     * Constructs a new instance from the given HTTP request.
+     *
+     * @param request the current request to save.
+     */
+    public SavedRequest(HttpServletRequest request) {
+        this.method = request.getMethod();
+        this.queryString = request.getQueryString();
+        this.requestURI = request.getRequestURI();
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public String getQueryString() {
+        return queryString;
+    }
+
+    public String getRequestURI() {
+        return requestURI;
+    }
+
+    public String getRequestUrl() {
+        StringBuilder requestUrl = new StringBuilder(getRequestURI());
+        if (getQueryString() != null) {
+            requestUrl.append("?").append(getQueryString());
+        }
+        return requestUrl.toString();
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/util/WebUtils.java b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
new file mode 100644
index 0000000..561dae9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
@@ -0,0 +1,674 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.util;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.DefaultSubjectContext;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.env.EnvironmentLoader;
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.AccessControlFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Map;
+
+/**
+ * Simple utility class for operations used across multiple class hierarchies in the web framework code.
+ * <p/>
+ * Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
+ * and in these cases, we have retained all license, copyright and author information.
+ *
+ * @since 0.9
+ */
+public class WebUtils {
+
+    //TODO - complete JavaDoc
+
+    private static final Logger log = LoggerFactory.getLogger(WebUtils.class);
+
+    public static final String SERVLET_REQUEST_KEY = ServletRequest.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
+    public static final String SERVLET_RESPONSE_KEY = ServletResponse.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
+
+    /**
+     * {@link org.apache.shiro.session.Session Session} key used to save a request and later restore it, for example when redirecting to a
+     * requested page after login, equal to {@code shiroSavedRequest}.
+     */
+    public static final String SAVED_REQUEST_KEY = "shiroSavedRequest";
+
+    /**
+     * Standard Servlet 2.3+ spec request attributes for include URI and paths.
+     * <p>If included via a RequestDispatcher, the current resource will see the
+     * originating request. Its own URI and paths are exposed as request attributes.
+     */
+    public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
+    public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
+    public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
+    public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
+    public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
+
+    /**
+     * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
+     * <p>If forwarded to via a RequestDispatcher, the current resource will see its
+     * own URI and paths. The originating URI and paths are exposed as request attributes.
+     */
+    public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
+    public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
+    public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
+    public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
+    public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
+
+    /**
+     * Default character encoding to use when <code>request.getCharacterEncoding</code>
+     * returns <code>null</code>, according to the Servlet spec.
+     *
+     * @see javax.servlet.ServletRequest#getCharacterEncoding
+     */
+    public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
+
+    /**
+     * Return the path within the web application for the given request.
+     * Detects include request URL if called within a RequestDispatcher include.
+     * <p/>
+     * For example, for a request to URL
+     * <p/>
+     * <code>http://www.somehost.com/myapp/my/url.jsp</code>,
+     * <p/>
+     * for an application deployed to <code>/mayapp</code> (the application's context path), this method would return
+     * <p/>
+     * <code>/my/url.jsp</code>.
+     *
+     * @param request current HTTP request
+     * @return the path within the web application
+     */
+    public static String getPathWithinApplication(HttpServletRequest request) {
+        String contextPath = getContextPath(request);
+        String requestUri = getRequestUri(request);
+        if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
+            // Normal case: URI contains context path.
+            String path = requestUri.substring(contextPath.length());
+            return (StringUtils.hasText(path) ? path : "/");
+        } else {
+            // Special case: rather unusual.
+            return requestUri;
+        }
+    }
+
+    /**
+     * Return the request URI for the given request, detecting an include request
+     * URL if called within a RequestDispatcher include.
+     * <p>As the value returned by <code>request.getRequestURI()</code> is <i>not</i>
+     * decoded by the servlet container, this method will decode it.
+     * <p>The URI that the web container resolves <i>should</i> be correct, but some
+     * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
+     * in the URI. This method cuts off such incorrect appendices.
+     *
+     * @param request current HTTP request
+     * @return the request URI
+     */
+    public static String getRequestUri(HttpServletRequest request) {
+        String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
+        if (uri == null) {
+            uri = request.getRequestURI();
+        }
+        return normalize(decodeAndCleanUriString(request, uri));
+    }
+
+    /**
+     * Normalize a relative URI path that may have relative values ("/./",
+     * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
+     * useful only for normalizing application-generated paths.  It does not
+     * try to perform security checks for malicious input.
+     * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
+     * Tomcat trunk, r939305
+     *
+     * @param path Relative path to be normalized
+     * @return normalized path
+     */
+    public static String normalize(String path) {
+        return normalize(path, true);
+    }
+
+    /**
+     * Normalize a relative URI path that may have relative values ("/./",
+     * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
+     * useful only for normalizing application-generated paths.  It does not
+     * try to perform security checks for malicious input.
+     * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
+     * Tomcat trunk, r939305
+     *
+     * @param path             Relative path to be normalized
+     * @param replaceBackSlash Should '\\' be replaced with '/'
+     * @return normalized path
+     */
+    private static String normalize(String path, boolean replaceBackSlash) {
+
+        if (path == null)
+            return null;
+
+        // Create a place for the normalized path
+        String normalized = path;
+
+        if (replaceBackSlash && normalized.indexOf('\\') >= 0)
+            normalized = normalized.replace('\\', '/');
+
+        if (normalized.equals("/."))
+            return "/";
+
+        // Add a leading "/" if necessary
+        if (!normalized.startsWith("/"))
+            normalized = "/" + normalized;
+
+        // Resolve occurrences of "//" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("//");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                    normalized.substring(index + 1);
+        }
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/./");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                    normalized.substring(index + 2);
+        }
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/../");
+            if (index < 0)
+                break;
+            if (index == 0)
+                return (null);  // Trying to go outside our context
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                    normalized.substring(index + 3);
+        }
+
+        // Return the normalized path that we have completed
+        return (normalized);
+
+    }
+
+
+    /**
+     * Decode the supplied URI string and strips any extraneous portion after a ';'.
+     *
+     * @param request the incoming HttpServletRequest
+     * @param uri     the application's URI string
+     * @return the supplied URI string stripped of any extraneous portion after a ';'.
+     */
+    private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
+        uri = decodeRequestString(request, uri);
+        int semicolonIndex = uri.indexOf(';');
+        return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
+    }
+
+    /**
+     * Return the context path for the given request, detecting an include request
+     * URL if called within a RequestDispatcher include.
+     * <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
+     * decoded by the servlet container, this method will decode it.
+     *
+     * @param request current HTTP request
+     * @return the context path
+     */
+    public static String getContextPath(HttpServletRequest request) {
+        String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
+        if (contextPath == null) {
+            contextPath = request.getContextPath();
+        }
+        if ("/".equals(contextPath)) {
+            // Invalid case, but happens for includes on Jetty: silently adapt it.
+            contextPath = "";
+        }
+        return decodeRequestString(request, contextPath);
+    }
+
+    /**
+     * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via the
+     * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
+     * <p/>
+     * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
+     * environment startup and no environment at all.
+     *
+     * @param sc ServletContext to find the web application context for
+     * @return the root WebApplicationContext for this web app
+     * @throws IllegalStateException if the root WebApplicationContext could not be found
+     * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
+     * @since 1.2
+     */
+    public static WebEnvironment getRequiredWebEnvironment(ServletContext sc)
+            throws IllegalStateException {
+
+        WebEnvironment we = getWebEnvironment(sc);
+        if (we == null) {
+            throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?");
+        }
+        return we;
+    }
+
+    /**
+     * Find the Shiro {@link WebEnvironment} for this web application, which is typically loaded via
+     * {@link org.apache.shiro.web.env.EnvironmentLoaderListener}.
+     * <p/>
+     * This implementation rethrows an exception that happened on environment startup to differentiate between a failed
+     * environment startup and no environment at all.
+     *
+     * @param sc ServletContext to find the web application context for
+     * @return the root WebApplicationContext for this web app, or <code>null</code> if none
+     * @see org.apache.shiro.web.env.EnvironmentLoader#ENVIRONMENT_ATTRIBUTE_KEY
+     * @since 1.2
+     */
+    public static WebEnvironment getWebEnvironment(ServletContext sc) {
+        return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY);
+    }
+
+    /**
+     * Find the Shiro {@link WebEnvironment} for this web application.
+     *
+     * @param sc       ServletContext to find the web application context for
+     * @param attrName the name of the ServletContext attribute to look for
+     * @return the desired WebEnvironment for this web app, or <code>null</code> if none
+     * @since 1.2
+     */
+    public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) {
+        if (sc == null) {
+            throw new IllegalArgumentException("ServletContext argument must not be null.");
+        }
+        Object attr = sc.getAttribute(attrName);
+        if (attr == null) {
+            return null;
+        }
+        if (attr instanceof RuntimeException) {
+            throw (RuntimeException) attr;
+        }
+        if (attr instanceof Error) {
+            throw (Error) attr;
+        }
+        if (attr instanceof Exception) {
+            throw new IllegalStateException((Exception) attr);
+        }
+        if (!(attr instanceof WebEnvironment)) {
+            throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr);
+        }
+        return (WebEnvironment) attr;
+    }
+
+
+    /**
+     * Decode the given source string with a URLDecoder. The encoding will be taken
+     * from the request, falling back to the default "ISO-8859-1".
+     * <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
+     *
+     * @param request current HTTP request
+     * @param source  the String to decode
+     * @return the decoded String
+     * @see #DEFAULT_CHARACTER_ENCODING
+     * @see javax.servlet.ServletRequest#getCharacterEncoding
+     * @see java.net.URLDecoder#decode(String, String)
+     * @see java.net.URLDecoder#decode(String)
+     */
+    @SuppressWarnings({"deprecation"})
+    public static String decodeRequestString(HttpServletRequest request, String source) {
+        String enc = determineEncoding(request);
+        try {
+            return URLDecoder.decode(source, enc);
+        } catch (UnsupportedEncodingException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("Could not decode request string [" + source + "] with encoding '" + enc +
+                        "': falling back to platform default encoding; exception message: " + ex.getMessage());
+            }
+            return URLDecoder.decode(source);
+        }
+    }
+
+    /**
+     * Determine the encoding for the given request.
+     * Can be overridden in subclasses.
+     * <p>The default implementation checks the request's
+     * {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
+     * <code>null</code>, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
+     *
+     * @param request current HTTP request
+     * @return the encoding for the request (never <code>null</code>)
+     * @see javax.servlet.ServletRequest#getCharacterEncoding()
+     */
+    protected static String determineEncoding(HttpServletRequest request) {
+        String enc = request.getCharacterEncoding();
+        if (enc == null) {
+            enc = DEFAULT_CHARACTER_ENCODING;
+        }
+        return enc;
+    }
+
+    /*
+     * Returns {@code true} IFF the specified {@code SubjectContext}:
+     * <ol>
+     * <li>A {@link WebSubjectContext} instance</li>
+     * <li>The {@code WebSubjectContext}'s request/response pair are not null</li>
+     * <li>The request is an {@link HttpServletRequest} instance</li>
+     * <li>The response is an {@link HttpServletResponse} instance</li>
+     * </ol>
+     *
+     * @param context the SubjectContext to check to see if it is HTTP compatible.
+     * @return {@code true} IFF the specified context has HTTP request/response objects, {@code false} otherwise.
+     * @since 1.0
+     */
+
+    public static boolean isWeb(Object requestPairSource) {
+        return requestPairSource instanceof RequestPairSource && isWeb((RequestPairSource) requestPairSource);
+    }
+
+    public static boolean isHttp(Object requestPairSource) {
+        return requestPairSource instanceof RequestPairSource && isHttp((RequestPairSource) requestPairSource);
+    }
+
+    public static ServletRequest getRequest(Object requestPairSource) {
+        if (requestPairSource instanceof RequestPairSource) {
+            return ((RequestPairSource) requestPairSource).getServletRequest();
+        }
+        return null;
+    }
+
+    public static ServletResponse getResponse(Object requestPairSource) {
+        if (requestPairSource instanceof RequestPairSource) {
+            return ((RequestPairSource) requestPairSource).getServletResponse();
+        }
+        return null;
+    }
+
+    public static HttpServletRequest getHttpRequest(Object requestPairSource) {
+        ServletRequest request = getRequest(requestPairSource);
+        if (request instanceof HttpServletRequest) {
+            return (HttpServletRequest) request;
+        }
+        return null;
+    }
+
+    public static HttpServletResponse getHttpResponse(Object requestPairSource) {
+        ServletResponse response = getResponse(requestPairSource);
+        if (response instanceof HttpServletResponse) {
+            return (HttpServletResponse) response;
+        }
+        return null;
+    }
+
+    private static boolean isWeb(RequestPairSource source) {
+        ServletRequest request = source.getServletRequest();
+        ServletResponse response = source.getServletResponse();
+        return request != null && response != null;
+    }
+
+    private static boolean isHttp(RequestPairSource source) {
+        ServletRequest request = source.getServletRequest();
+        ServletResponse response = source.getServletResponse();
+        return request instanceof HttpServletRequest && response instanceof HttpServletResponse;
+    }
+
+    /**
+     * Returns {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
+     * otherwise.
+     * <p/>
+     * <b>This method exists for Shiro's internal framework needs and should never be called by Shiro end-users.  It
+     * could be changed/removed at any time.</b>
+     *
+     * @param requestPairSource a {@link RequestPairSource} instance, almost always a
+     *                          {@link org.apache.shiro.web.subject.WebSubject WebSubject} instance.
+     * @return {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
+     *         otherwise.
+     */
+    public static boolean _isSessionCreationEnabled(Object requestPairSource) {
+        if (requestPairSource instanceof RequestPairSource) {
+            RequestPairSource source = (RequestPairSource) requestPairSource;
+            return _isSessionCreationEnabled(source.getServletRequest());
+        }
+        return true; //by default
+    }
+
+    /**
+     * Returns {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
+     * otherwise.
+     * <p/>
+     * <b>This method exists for Shiro's internal framework needs and should never be called by Shiro end-users.  It
+     * could be changed/removed at any time.</b>
+     *
+     * @param request incoming servlet request.
+     * @return {@code true} if a session is allowed to be created for a subject-associated request, {@code false}
+     *         otherwise.
+     */
+    public static boolean _isSessionCreationEnabled(ServletRequest request) {
+        if (request != null) {
+            Object val = request.getAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED);
+            if (val != null && val instanceof Boolean) {
+                return (Boolean) val;
+            }
+        }
+        return true; //by default
+    }
+
+    /**
+     * A convenience method that merely casts the incoming <code>ServletRequest</code> to an
+     * <code>HttpServletRequest</code>:
+     * <p/>
+     * <code>return (HttpServletRequest)request;</code>
+     * <p/>
+     * Logic could be changed in the future for logging or throwing an meaningful exception in
+     * non HTTP request environments (e.g. Portlet API).
+     *
+     * @param request the incoming ServletRequest
+     * @return the <code>request</code> argument casted to an <code>HttpServletRequest</code>.
+     */
+    public static HttpServletRequest toHttp(ServletRequest request) {
+        return (HttpServletRequest) request;
+    }
+
+    /**
+     * A convenience method that merely casts the incoming <code>ServletResponse</code> to an
+     * <code>HttpServletResponse</code>:
+     * <p/>
+     * <code>return (HttpServletResponse)response;</code>
+     * <p/>
+     * Logic could be changed in the future for logging or throwing an meaningful exception in
+     * non HTTP request environments (e.g. Portlet API).
+     *
+     * @param response the outgoing ServletResponse
+     * @return the <code>response</code> argument casted to an <code>HttpServletResponse</code>.
+     */
+    public static HttpServletResponse toHttp(ServletResponse response) {
+        return (HttpServletResponse) response;
+    }
+
+    /**
+     * Redirects the current request to a new URL based on the given parameters.
+     *
+     * @param request          the servlet request.
+     * @param response         the servlet response.
+     * @param url              the URL to redirect the user to.
+     * @param queryParams      a map of parameters that should be set as request parameters for the new request.
+     * @param contextRelative  true if the URL is relative to the servlet context path, or false if the URL is absolute.
+     * @param http10Compatible whether to stay compatible with HTTP 1.0 clients.
+     * @throws java.io.IOException if thrown by response methods.
+     */
+    public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
+        RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
+        view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
+    }
+
+    /**
+     * Redirects the current request to a new URL based on the given parameters and default values
+     * for unspecified parameters.
+     *
+     * @param request  the servlet request.
+     * @param response the servlet response.
+     * @param url      the URL to redirect the user to.
+     * @throws java.io.IOException if thrown by response methods.
+     */
+    public static void issueRedirect(ServletRequest request, ServletResponse response, String url) throws IOException {
+        issueRedirect(request, response, url, null, true, true);
+    }
+
+    /**
+     * Redirects the current request to a new URL based on the given parameters and default values
+     * for unspecified parameters.
+     *
+     * @param request     the servlet request.
+     * @param response    the servlet response.
+     * @param url         the URL to redirect the user to.
+     * @param queryParams a map of parameters that should be set as request parameters for the new request.
+     * @throws java.io.IOException if thrown by response methods.
+     */
+    public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams) throws IOException {
+        issueRedirect(request, response, url, queryParams, true, true);
+    }
+
+    /**
+     * Redirects the current request to a new URL based on the given parameters and default values
+     * for unspecified parameters.
+     *
+     * @param request         the servlet request.
+     * @param response        the servlet response.
+     * @param url             the URL to redirect the user to.
+     * @param queryParams     a map of parameters that should be set as request parameters for the new request.
+     * @param contextRelative true if the URL is relative to the servlet context path, or false if the URL is absolute.
+     * @throws java.io.IOException if thrown by response methods.
+     */
+    public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative) throws IOException {
+        issueRedirect(request, response, url, queryParams, contextRelative, true);
+    }
+
+    /**
+     * <p>Checks to see if a request param is considered true using a loose matching strategy for
+     * general values that indicate that something is true or enabled, etc.</p>
+     * <p/>
+     * <p>Values that are considered "true" include (case-insensitive): true, t, 1, enabled, y, yes, on.</p>
+     *
+     * @param request   the servlet request
+     * @param paramName @return true if the param value is considered true or false if it isn't.
+     * @return true if the given parameter is considered "true" - false otherwise.
+     */
+    public static boolean isTrue(ServletRequest request, String paramName) {
+        String value = getCleanParam(request, paramName);
+        return value != null &&
+                (value.equalsIgnoreCase("true") ||
+                        value.equalsIgnoreCase("t") ||
+                        value.equalsIgnoreCase("1") ||
+                        value.equalsIgnoreCase("enabled") ||
+                        value.equalsIgnoreCase("y") ||
+                        value.equalsIgnoreCase("yes") ||
+                        value.equalsIgnoreCase("on"));
+    }
+
+    /**
+     * Convenience method that returns a request parameter value, first running it through
+     * {@link StringUtils#clean(String)}.
+     *
+     * @param request   the servlet request.
+     * @param paramName the parameter name.
+     * @return the clean param value, or null if the param does not exist or is empty.
+     */
+    public static String getCleanParam(ServletRequest request, String paramName) {
+        return StringUtils.clean(request.getParameter(paramName));
+    }
+
+    public static void saveRequest(ServletRequest request) {
+        Subject subject = SecurityUtils.getSubject();
+        Session session = subject.getSession();
+        HttpServletRequest httpRequest = toHttp(request);
+        SavedRequest savedRequest = new SavedRequest(httpRequest);
+        session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
+    }
+
+    public static SavedRequest getAndClearSavedRequest(ServletRequest request) {
+        SavedRequest savedRequest = getSavedRequest(request);
+        if (savedRequest != null) {
+            Subject subject = SecurityUtils.getSubject();
+            Session session = subject.getSession();
+            session.removeAttribute(SAVED_REQUEST_KEY);
+        }
+        return savedRequest;
+    }
+
+    public static SavedRequest getSavedRequest(ServletRequest request) {
+        SavedRequest savedRequest = null;
+        Subject subject = SecurityUtils.getSubject();
+        Session session = subject.getSession(false);
+        if (session != null) {
+            savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_KEY);
+        }
+        return savedRequest;
+    }
+
+    /**
+     * Redirects the to the request url from a previously
+     * {@link #saveRequest(javax.servlet.ServletRequest) saved} request, or if there is no saved request, redirects the
+     * end user to the specified {@code fallbackUrl}.  If there is no saved request or fallback url, this method
+     * throws an {@link IllegalStateException}.
+     * <p/>
+     * This method is primarily used to support a common login scenario - if an unauthenticated user accesses a
+     * page that requires authentication, it is expected that request is
+     * {@link #saveRequest(javax.servlet.ServletRequest) saved} first and then redirected to the login page. Then,
+     * after a successful login, this method can be called to redirect them back to their originally requested URL, a
+     * nice usability feature.
+     *
+     * @param request     the incoming request
+     * @param response    the outgoing response
+     * @param fallbackUrl the fallback url to redirect to if there is no saved request available.
+     * @throws IllegalStateException if there is no saved request and the {@code fallbackUrl} is {@code null}.
+     * @throws IOException           if there is an error redirecting
+     * @since 1.0
+     */
+    public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
+            throws IOException {
+        String successUrl = null;
+        boolean contextRelative = true;
+        SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
+        if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
+            successUrl = savedRequest.getRequestUrl();
+            contextRelative = false;
+        }
+
+        if (successUrl == null) {
+            successUrl = fallbackUrl;
+        }
+
+        if (successUrl == null) {
+            throw new IllegalStateException("Success URL not available via saved request or via the " +
+                    "successUrlFallback method parameter. One of these must be non-null for " +
+                    "issueSuccessRedirect() to work.");
+        }
+
+        WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
+    }
+
+}
diff --git a/web/src/main/resources/META-INF/shiro.tld b/web/src/main/resources/META-INF/shiro.tld
new file mode 100644
index 0000000..1501357
--- /dev/null
+++ b/web/src/main/resources/META-INF/shiro.tld
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+
+<taglib>
+
+  <tlib-version>1.1.2</tlib-version>
+
+  <jsp-version>1.2</jsp-version>
+
+  <short-name>Apache Shiro</short-name>
+
+  <uri>http://shiro.apache.org/tags</uri>
+
+  <description>Apache Shiro JSP Tag Library.</description>
+
+  <tag>
+    <name>hasPermission</name>
+    <tag-class>org.apache.shiro.web.tags.HasPermissionTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current Subject (user)
+      'has' (implies) the specified permission (i.e the user has the specified ability).
+    </description>
+    <attribute>
+      <name>name</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <name>lacksPermission</name>
+    <tag-class>org.apache.shiro.web.tags.LacksPermissionTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current Subject (user) does
+      NOT have (not imply) the specified permission (i.e. the user lacks the specified ability)
+    </description>
+    <attribute>
+      <name>name</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <name>hasRole</name>
+    <tag-class>org.apache.shiro.web.tags.HasRoleTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current user has the specified role.</description>
+    <attribute>
+      <name>name</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+
+  <tag>
+    <name>hasAnyRoles</name>
+    <tag-class>org.apache.shiro.web.tags.HasAnyRolesTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current user has one of the specified roles from a
+      comma-separated list of role names.
+    </description>
+    <attribute>
+      <name>name</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <name>lacksRole</name>
+    <tag-class>org.apache.shiro.web.tags.LacksRoleTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current user does NOT have the specified role
+      (i.e. they explicitly lack the specified role)
+    </description>
+    <attribute>
+      <name>name</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <name>authenticated</name>
+    <tag-class>org.apache.shiro.web.tags.AuthenticatedTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current user has successfully authenticated
+      _during their current session_. It is more restrictive than the 'user' tag.
+      It is logically opposite to the 'notAuthenticated' tag.
+    </description>
+  </tag>
+
+  <tag>
+    <name>notAuthenticated</name>
+    <tag-class>org.apache.shiro.web.tags.NotAuthenticatedTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current user has NOT succesfully authenticated
+      _during their current session_. It is logically opposite to the 'authenticated' tag.
+    </description>
+  </tag>
+
+  <tag>
+    <name>user</name>
+    <tag-class>org.apache.shiro.web.tags.UserTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current Subject has a known identity, either
+      from a previous login or from 'RememberMe' services. Note that this is semantically different
+      from the 'authenticated' tag, which is more restrictive. It is logically
+      opposite to the 'guest' tag.
+    </description>
+  </tag>
+
+  <tag>
+    <name>guest</name>
+    <tag-class>org.apache.shiro.web.tags.GuestTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays body content only if the current Subject IS NOT known to the system, either
+      because they have not logged in or they have no corresponding 'RememberMe' identity. It is logically
+      opposite to the 'user' tag.
+    </description>
+  </tag>
+
+  <tag>
+    <name>principal</name>
+    <tag-class>org.apache.shiro.web.tags.PrincipalTag</tag-class>
+    <body-content>JSP</body-content>
+    <description>Displays the user's principal or a property of the user's principal.</description>
+    <attribute>
+      <name>type</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>property</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>defaultValue</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+</taglib>
diff --git a/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy b/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy
new file mode 100644
index 0000000..24653bc
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.config
+
+import javax.servlet.Filter
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import org.apache.shiro.config.Ini
+import org.apache.shiro.web.filter.authc.FormAuthenticationFilter
+import org.apache.shiro.web.filter.authz.SslFilter
+import org.apache.shiro.web.filter.mgt.FilterChainResolver
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link IniFilterChainResolverFactory} implementation.
+ *
+ * @since 1.2
+ */
+class IniFilterChainResolverFactoryTest extends GroovyTestCase {
+
+    private IniFilterChainResolverFactory factory;
+
+    protected FilterConfig createNiceMockFilterConfig() {
+        FilterConfig mock = createNiceMock(FilterConfig)
+        ServletContext mockServletContext = createNiceMock(ServletContext)
+        expect(mock.servletContext).andReturn(mockServletContext)
+        return mock
+    }
+
+    void setUp() {
+        this.factory = new IniFilterChainResolverFactory()
+    }
+
+    void testNewInstance() {
+        assertNull factory.filterConfig
+        factory.filterConfig = null
+        assertNull factory.filterConfig
+    }
+
+    void testGetInstanceNoIni() {
+        assertNotNull factory.getInstance()
+    }
+
+    void testNewInstanceWithIni() {
+        Ini ini = new Ini()
+        ini.load("""
+        [urls]
+        /index.html = anon
+        """)
+        factory = new IniFilterChainResolverFactory(ini)
+        FilterChainResolver resolver = factory.getInstance()
+        assertNotNull resolver
+    }
+
+    void testGetFiltersWithNullOrEmptySection() {
+        Map<String, Filter> filters = factory.getFilters(null, null);
+        assertNull(filters);
+    }
+
+    void testCreateChainsWithNullUrlsSection() {
+        //should do nothing (return immediately, no exceptions):
+        factory.createChains(null, null);
+    }
+
+    void testNewInstanceWithNonFilter() {
+        Ini ini = new Ini()
+        ini.load("""
+        [filters]
+        # any non filter will do:
+        test = org.apache.shiro.web.servlet.SimpleCookie
+        [urls]
+        /index.html = anon
+        """)
+        factory = new IniFilterChainResolverFactory(ini)
+        assertNotNull factory.getInstance()
+    }
+
+    void testNewInstanceWithFilterConfig() {
+        Ini ini = new Ini()
+        ini.load("""
+        [urls]
+        /index.html = anon
+        """)
+        factory = new IniFilterChainResolverFactory(ini)
+        FilterConfig config = createNiceMockFilterConfig()
+        factory.setFilterConfig(config)
+        
+        replay config
+        
+        FilterChainResolver resolver = factory.getInstance();
+        
+        assertNotNull resolver
+        
+        verify config
+    }
+
+    //asserts SHIRO-306
+    void testGetFilters() {
+        def extractedFilters = factory.getFilters(null, null)
+        assertNull extractedFilters
+    }
+
+    //asserts SHIRO-306
+    void testGetFiltersWithoutSectionWithDefaults() {
+        def factory = new IniFilterChainResolverFactory()
+
+        def defaults = ['filter': new FormAuthenticationFilter()]
+
+        def extractedFilters = factory.getFilters(null, defaults)
+        
+        assertNotNull extractedFilters
+        assertEquals 1, extractedFilters.size()
+        assertTrue extractedFilters['filter'] instanceof FormAuthenticationFilter
+    }
+
+    //asserts SHIRO-306
+    void testGetFiltersWithSectionWithoutDefaults() {
+        def factory = new IniFilterChainResolverFactory()
+
+        def section = ['filter': FormAuthenticationFilter.class.name]
+
+        def extractedFilters = factory.getFilters(section, null)
+
+        assertNotNull extractedFilters
+        assertEquals 1, extractedFilters.size()
+        assertTrue extractedFilters['filter'] instanceof FormAuthenticationFilter
+    }
+
+    //asserts SHIRO-306
+    void testGetFiltersWithSectionAndDefaults() {
+        def factory = new IniFilterChainResolverFactory()
+
+        def section = ['filtersSectionFilter': SslFilter.class.name]
+
+        def defaults = ['mainSectionFilter': new FormAuthenticationFilter()]
+
+        def extractedFilters = factory.getFilters(section, defaults)
+
+        assertNotNull extractedFilters
+        assertEquals 2, extractedFilters.size()
+        assertTrue extractedFilters['filtersSectionFilter'] instanceof SslFilter
+        assertTrue extractedFilters['mainSectionFilter'] instanceof FormAuthenticationFilter
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy b/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy
new file mode 100644
index 0000000..4fdf54e
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.env
+
+import org.apache.shiro.config.Ini
+import org.apache.shiro.web.filter.mgt.DefaultFilter
+
+/**
+ * Unit tests for the {@link IniWebEnvironment} implementation.
+ * 
+ * @since 1.2
+ */
+class IniWebEnvironmentTest extends GroovyTestCase {
+    
+    
+    //asserts SHIRO-306
+    void testObjectsAfterSecurityManagerCreation() {
+        
+        def ini = new Ini()
+        ini.load("""
+        [main]
+        compositeBean = org.apache.shiro.config.CompositeBean
+        """)
+        
+        def env = new IniWebEnvironment(ini:  ini)
+        env.init()
+
+        assertNotNull env.objects
+        //asserts that the objects size = securityManager (1) + num custom objects + num default filters
+        def expectedSize = 2 + DefaultFilter.values().length
+        assertEquals expectedSize, env.objects.size()
+        assertNotNull env.objects['securityManager']
+        assertNotNull env.objects['compositeBean']
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
new file mode 100644
index 0000000..974ef7a
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt
+
+import javax.servlet.Filter
+import javax.servlet.FilterChain
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import org.apache.shiro.config.ConfigurationException
+import org.apache.shiro.web.filter.authz.SslFilter
+import org.apache.shiro.web.servlet.ShiroFilter
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link DefaultFilterChainManager} implementation.
+ */
+class DefaultFilterChainManagerTest extends GroovyTestCase {
+
+    DefaultFilterChainManager manager;
+
+    void setUp() {
+        this.manager = new DefaultFilterChainManager();
+    }
+
+    //SHIRO-205
+    void testToNameConfigPairNoBrackets() {
+        def token = "foo"
+
+        String[] pair = manager.toNameConfigPair(token);
+
+        assertNotNull pair
+        assertEquals 2, pair.length
+        assertEquals "foo", pair[0]
+        assertNull pair[1]
+    }
+
+    //SHIRO-205
+    void testToNameConfigPairWithEmptyBrackets() {
+        def token = "foo[]"
+
+        String[] pair = manager.toNameConfigPair(token);
+
+        assertNotNull pair
+        assertEquals 2, pair.length
+        assertEquals "foo", pair[0]
+        assertNull pair[1]
+    }
+
+    //SHIRO-205
+    void testToNameConfigPairWithPopulatedBrackets() {
+        def token = "foo[bar, baz]"
+
+        String[] pair = manager.toNameConfigPair(token);
+
+        assertNotNull pair
+        assertEquals 2, pair.length
+        assertEquals "foo", pair[0]
+        assertEquals "bar, baz", pair[1]
+    }
+
+    //SHIRO-205 - asserts backwards compatibility before SHIRO-205 was implemented:
+    void testToNameConfigPairWithNestedQuotesInBrackets() {
+        def token = 'roles["guest, admin"]'
+
+        String[] pair = manager.toNameConfigPair(token);
+
+        assertNotNull pair
+        assertEquals 2, pair.length
+        assertEquals "roles", pair[0]
+        assertEquals "guest, admin", pair[1]
+    }
+
+    //SHIRO-205 - asserts backwards compatibility before SHIRO-205 was implemented:
+    //@since 1.2.2
+    void testToNameConfigPairWithIndividualNestedQuotesInBrackets() {
+        def token = 'roles["guest", "admin"]'
+
+        String[] pair = manager.toNameConfigPair(token);
+
+        assertNotNull pair
+        assertEquals 2, pair.length
+        assertEquals "roles", pair[0]
+        assertEquals '"guest", "admin"', pair[1]
+    }
+    
+    //SHIRO-205
+    void testFilterChainConfigWithNestedCommas() {
+        def chain = "a, b[c], d[e, f], g[h, i, j], k"
+
+        String[] tokens = manager.splitChainDefinition(chain);
+        
+        assertNotNull tokens
+        assertEquals 5, tokens.length
+        assertEquals "a", tokens[0]
+        assertEquals "b[c]", tokens[1]
+        assertEquals "d[e, f]", tokens[2]
+        assertEquals "g[h, i, j]", tokens[3]
+        assertEquals "k", tokens[4]
+    }
+
+    //SHIRO-205
+    void testFilterChainConfigWithNestedQuotedCommas() {
+        def chain = "a, b[c], d[e, f], g[h, i, j], k"
+
+        String[] tokens = manager.splitChainDefinition(chain);
+
+        assertNotNull tokens
+        assertEquals 5, tokens.length
+        assertEquals "a", tokens[0]
+        assertEquals "b[c]", tokens[1]
+        assertEquals "d[e, f]", tokens[2]
+        assertEquals "g[h, i, j]", tokens[3]
+        assertEquals "k", tokens[4]
+    }
+
+    void testNewInstanceDefaultFilters() {
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            assertNotNull(manager.getFilter(defaultFilter.name()));
+        }
+        assertFalse(manager.hasChains());
+    }
+
+    protected FilterConfig createNiceMockFilterConfig() {
+        FilterConfig mock = createNiceMock(FilterConfig.class);
+        ServletContext mockServletContext = createNiceMock(ServletContext.class);
+        expect(mock.getServletContext()).andReturn(mockServletContext);
+        return mock;
+    }
+
+    void testNewInstanceWithFilterConfig() {
+        FilterConfig mock = createNiceMockFilterConfig();
+        replay(mock);
+        this.manager = new DefaultFilterChainManager(mock);
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            assertNotNull(manager.getFilter(defaultFilter.name()));
+        }
+        assertFalse(manager.hasChains());
+        verify(mock);
+    }
+
+    void testCreateChain() {
+        try {
+            manager.createChain(null, null);
+        } catch (NullPointerException expected) {
+        }
+        try {
+            manager.createChain("test", null);
+        } catch (NullPointerException expected) {
+        }
+
+        manager.createChain("test", "authc, roles[manager], perms[\"user:read,write:12345\"");
+
+        assertTrue(manager.hasChains());
+
+        Set<String> chainNames = manager.getChainNames();
+        assertNotNull(chainNames);
+        assertEquals(1, chainNames.size());
+        assertTrue(chainNames.contains("test"));
+
+        Map<String, NamedFilterList> chains = manager.getFilterChains();
+        assertEquals(1, chains.size());
+        assertTrue(chains.containsKey("test"));
+        manager.setFilterChains(chains);
+
+        NamedFilterList chain = manager.getChain("test");
+        assertNotNull(chain);
+
+        Filter filter = chain.get(0);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.authc.getFilterClass(), filter.getClass());
+
+        filter = chain.get(1);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.roles.getFilterClass(), filter.getClass());
+
+        filter = chain.get(2);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.perms.getFilterClass(), filter.getClass());
+    }
+
+    /**
+     * Helps assert <a href="https://issues.apache.org/jira/browse/SHIRO-429">SHIRO-429</a>
+     * @since 1.2.2
+     */
+    void testCreateChainWithQuotedInstanceConfig() {
+
+        manager.createChain("test", 'authc, perms["perm1", "perm2"]');
+
+        assertTrue(manager.hasChains());
+
+        Set<String> chainNames = manager.getChainNames();
+        assertNotNull(chainNames);
+        assertEquals(1, chainNames.size());
+        assertTrue(chainNames.contains("test"));
+
+        Map<String, NamedFilterList> chains = manager.getFilterChains();
+        assertEquals(1, chains.size());
+        assertTrue(chains.containsKey("test"));
+        manager.setFilterChains(chains);
+
+        NamedFilterList chain = manager.getChain("test");
+        assertNotNull(chain);
+
+        Filter filter = chain.get(0);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.authc.getFilterClass(), filter.getClass());
+
+        filter = chain.get(1);
+        assertNotNull(filter);
+        assertEquals(DefaultFilter.perms.getFilterClass(), filter.getClass());
+    }
+
+    void testBeanMethods() {
+        Map<String, Filter> filters = manager.getFilters();
+        assertEquals(filters.size(), DefaultFilter.values().length);
+        manager.setFilters(filters);
+    }
+
+    void testAddFilter() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+        manager.addFilter("test", new SslFilter());
+        Filter filter = manager.getFilter("test");
+        assertNotNull(filter);
+        assertEquals(SslFilter.class, filter.getClass());
+        verify(mockFilterConfig);
+    }
+
+    void testAddFilterNoInit() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        Filter mockFilter = createNiceMock(Filter.class);
+
+        replay mockFilterConfig, mockFilter
+
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        this.manager.addFilter("blah", mockFilter);
+
+        assertNotNull this.manager.filters['blah']
+        assertSame this.manager.filters['blah'], mockFilter
+
+        verify mockFilterConfig, mockFilter
+    }
+
+    void testAddFilterNoFilterConfig() {
+        SslFilter filter = new SslFilter();
+        manager.addFilter("test", filter);
+        assertNotNull manager.filters['test']
+        assertSame manager.filters['test'], filter
+    }
+
+    void testAddToChain() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        manager.addFilter("testSsl", new SslFilter());
+        manager.createChain("test", "anon");
+
+        try {
+            manager.addToChain("test", null);
+            fail "Should have thrown an IllegalArgumentException"
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            manager.addToChain(null, "testSsl");
+            fail "Should have thrown an IllegalArgumentException"
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    void testAddToChainNotPathProcessor() {
+        FilterConfig mockFilterConfig = createNiceMockFilterConfig();
+        replay(mockFilterConfig);
+        this.manager = new DefaultFilterChainManager(mockFilterConfig);
+
+        manager.addFilter("nonPathProcessor", new ShiroFilter());
+        manager.createChain("test", "nonPathProcessor");
+
+        try {
+            manager.addToChain("test", "nonPathProcessor", "dummyConfig");
+            fail "Should have thrown a ConfigurationException"
+        } catch (ConfigurationException expected) {
+        }
+    }
+
+    void testProxy() {
+        FilterChain mock = createNiceMock(FilterChain.class);
+        replay(mock);
+        manager.createChain("test", "anon");
+        this.manager.proxy(mock, "test");
+        verify(mock);
+    }
+
+    void testProxyNoChain() {
+        FilterChain mock = createNiceMock(FilterChain.class);
+        replay(mock);
+        try {
+            this.manager.proxy(mock, "blah");
+            fail "Should have thrown an IllegalArgumentException"
+        } catch (IllegalArgumentException expected) {
+        }
+        verify(mock);
+    }
+
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/filter/session/NoSessionCreationFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/filter/session/NoSessionCreationFilterTest.groovy
new file mode 100644
index 0000000..3611b29
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/session/NoSessionCreationFilterTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.session
+
+import javax.servlet.ServletRequest
+import javax.servlet.ServletResponse
+import org.apache.shiro.subject.support.DefaultSubjectContext
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link NoSessionCreationFilter} implementation.
+ *
+ * @since 1.2
+ */
+class NoSessionCreationFilterTest extends GroovyTestCase {
+
+    void testDefault() {
+        NoSessionCreationFilter filter = new NoSessionCreationFilter();
+
+        def request = createStrictMock(ServletRequest)
+        def response = createStrictMock(ServletResponse)
+
+        request.setAttribute(eq(DefaultSubjectContext.SESSION_CREATION_ENABLED), eq(Boolean.FALSE))
+
+        replay request, response
+
+        assertTrue filter.onPreHandle(request, response, null)
+
+        verify request, response
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluatorTest.groovy b/web/src/test/groovy/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluatorTest.groovy
new file mode 100644
index 0000000..2a86030
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/mgt/DefaultWebSessionStorageEvaluatorTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt
+
+import javax.servlet.ServletRequest
+import javax.servlet.ServletResponse
+import org.apache.shiro.session.Session
+import org.apache.shiro.subject.Subject
+import org.apache.shiro.subject.support.DefaultSubjectContext
+import org.apache.shiro.web.subject.WebSubject
+import org.apache.shiro.web.util.RequestPairSource
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link DefaultWebSessionStorageEvaluator} implementation.
+ *
+ * @since 1.2
+ */
+class DefaultWebSessionStorageEvaluatorTest extends GroovyTestCase {
+
+    void testWithSession() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+
+        def subject = createStrictMock(Subject)
+        def session = createStrictMock(Session)
+
+        expect(subject.getSession(false)).andReturn session
+
+        replay subject, session
+
+        assertTrue evaluator.isSessionStorageEnabled(subject)
+
+        verify subject, session
+    }
+
+    void testWithoutSessionAndNonWebSubject() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+
+        def subject = createStrictMock(Subject)
+
+        expect(subject.getSession(false)).andReturn null
+
+        replay subject
+
+        assertTrue evaluator.isSessionStorageEnabled(subject)
+
+        verify subject
+    }
+
+    void testWithoutSessionAndGenerallyDisabled() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+        evaluator.sessionStorageEnabled = false
+
+        def subject = createStrictMock(Subject)
+
+        expect(subject.getSession(false)).andReturn null
+
+        replay subject
+
+        assertFalse evaluator.isSessionStorageEnabled(subject)
+
+        verify subject
+    }
+
+    void testWebSubjectWithoutSessionAndGenerallyEnabled() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+
+        def subject = createStrictMock(RequestPairWebSubject)
+        def request = createMock(ServletRequest)
+        def response = createMock(ServletResponse)
+
+        expect(subject.getSession(false)).andReturn null
+        expect(subject.getServletRequest()).andReturn request
+        expect(request.getAttribute(eq(DefaultSubjectContext.SESSION_CREATION_ENABLED))).andReturn null
+
+        replay subject, request, response
+
+        assertTrue evaluator.isSessionStorageEnabled(subject)
+
+        verify subject, request, response
+    }
+
+    void testWebSubjectWithoutSessionAndGenerallyEnabledButRequestDisabled() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+
+        def subject = createStrictMock(RequestPairWebSubject)
+        def request = createMock(ServletRequest)
+        def response = createMock(ServletResponse)
+
+        expect(subject.getSession(false)).andReturn null
+        expect(subject.getServletRequest()).andReturn request
+        expect(request.getAttribute(eq(DefaultSubjectContext.SESSION_CREATION_ENABLED))).andReturn Boolean.FALSE
+
+        replay subject, request, response
+
+        assertFalse evaluator.isSessionStorageEnabled(subject)
+
+        verify subject, request, response
+    }
+
+    void testWebSubjectWithoutSessionAndGenerallyEnabledWithNonBooleanRequestAttribute() {
+
+        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator()
+
+        def subject = createStrictMock(RequestPairWebSubject)
+        def request = createMock(ServletRequest)
+        def response = createMock(ServletResponse)
+
+        expect(subject.getSession(false)).andReturn null
+        expect(subject.getServletRequest()).andReturn request
+        expect(request.getAttribute(eq(DefaultSubjectContext.SESSION_CREATION_ENABLED))).andReturn new Object()
+
+        replay subject, request, response
+
+        assertTrue evaluator.isSessionStorageEnabled(subject)
+
+        verify subject, request, response
+    }
+
+    private interface RequestPairWebSubject extends RequestPairSource, WebSubject {
+
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy
new file mode 100644
index 0000000..876d1c3
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/servlet/AbstractShiroFilterTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet
+
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import org.apache.shiro.SecurityUtils
+import org.apache.shiro.UnavailableSecurityManagerException
+import org.apache.shiro.web.mgt.WebSecurityManager
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link AbstractShiroFilter} implementation.
+ */
+class AbstractShiroFilterTest extends GroovyTestCase {
+
+    void testInit() {
+
+        SecurityUtils.securityManager = null
+
+        def securityManager = createStrictMock(WebSecurityManager)
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+
+        expect(filterConfig.servletContext).andReturn servletContext
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+
+        replay securityManager, filterConfig, servletContext
+
+        AbstractShiroFilter filter = new AbstractShiroFilter() {}
+        filter.securityManager = securityManager
+
+        filter.init(filterConfig)
+
+        try {
+            SecurityUtils.getSecurityManager()
+            fail "AbstractShiroFilter initialization should not have resulted in a static SecurityManager reference."
+        } catch (UnavailableSecurityManagerException expected) {
+        }
+
+        verify securityManager, filterConfig, servletContext
+    }
+
+    void testInitWithStaticReference() {
+
+        SecurityUtils.securityManager = null
+
+        def securityManager = createStrictMock(WebSecurityManager)
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+
+        expect(filterConfig.servletContext).andReturn servletContext
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn "true"
+
+        replay securityManager, filterConfig, servletContext
+
+        AbstractShiroFilter filter = new AbstractShiroFilter(){}
+        filter.securityManager = securityManager
+
+        try {
+            filter.init(filterConfig)
+
+            assertSame securityManager, SecurityUtils.securityManager
+
+            verify securityManager, filterConfig, servletContext
+        } finally {
+            SecurityUtils.securityManager = null
+        }
+    }
+
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy
new file mode 100644
index 0000000..c039ec0
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet
+
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import javax.servlet.ServletException
+import org.apache.shiro.io.ResourceUtils
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link IniShiroFilter} implementation.
+ */
+class IniShiroFilterTest extends GroovyTestCase {
+
+    void testDefaultWebInfConfig() {
+        def filterConfig = createMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        InputStream inputStream = ResourceUtils.getInputStreamForPath("classpath:IniShiroFilterTest.ini")
+        assertNotNull inputStream
+
+        expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn null
+        //simulate the servlet context resource of /WEB-INF/shiro.ini to be our test file above:
+        expect(servletContext.getResourceAsStream(eq(IniShiroFilter.DEFAULT_WEB_INI_RESOURCE_PATH))).andReturn(inputStream)
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
+
+        verify filterConfig, servletContext
+    }
+
+    void testResourceConfig() {
+        def filterConfig = createMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+
+        expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn "classpath:IniShiroFilterTest.ini"
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
+
+        verify filterConfig, servletContext
+    }
+
+    void testResourceConfigWithoutResource() {
+        def filterConfig = createMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        def nonExistentResource = "/WEB-INF/foo.ini"
+
+        expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn nonExistentResource
+        expect(servletContext.getResourceAsStream(eq(nonExistentResource))).andReturn(null)
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        try {
+            filter.init(filterConfig)
+            fail "Filter init should have failed due to specified nonexisting resource path."
+        } catch (ServletException expected) {
+        }
+
+        verify filterConfig, servletContext
+    }
+
+    void testDefaultClasspathConfig() {
+
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+
+        expect(filterConfig.getServletContext()).andReturn servletContext
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_INIT_PARAM_NAME)).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME)).andReturn null
+        expect(servletContext.getResourceAsStream(IniShiroFilter.DEFAULT_WEB_INI_RESOURCE_PATH)).andReturn null
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
+
+        verify filterConfig, servletContext
+    }
+
+
+    void testSimpleConfig() {
+        def config = """
+        [filters]
+        authc.successUrl = /index.jsp
+        """
+        def filterConfig = createMock(FilterConfig)
+        def servletContext = createMock(ServletContext)
+
+        expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn config
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn null
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
+
+        verify filterConfig, servletContext
+    }
+
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
new file mode 100644
index 0000000..c9922a7
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet
+
+import javax.servlet.FilterConfig
+import javax.servlet.ServletContext
+import org.apache.shiro.web.env.EnvironmentLoader
+import org.apache.shiro.web.env.WebEnvironment
+import org.apache.shiro.web.filter.mgt.FilterChainResolver
+import org.apache.shiro.web.mgt.WebSecurityManager
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for {@link ShiroFilter}.
+ */
+class ShiroFilterTest extends GroovyTestCase {
+
+    void testInit() {
+
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        def webEnvironment = createStrictMock(WebEnvironment)
+        def webSecurityManager = createStrictMock(WebSecurityManager)
+        def filterChainResolver = createStrictMock(FilterChainResolver)
+
+        expect(filterConfig.servletContext).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(servletContext.getAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY))).andReturn webEnvironment
+        expect(webEnvironment.webSecurityManager).andReturn webSecurityManager
+        expect(webEnvironment.filterChainResolver).andReturn filterChainResolver
+
+        replay filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver
+
+        ShiroFilter filter = new ShiroFilter()
+
+        filter.init(filterConfig)
+
+        assertSame filter.securityManager, webSecurityManager
+        assertSame filter.filterChainResolver, filterChainResolver
+
+        verify filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver
+
+    }
+
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
new file mode 100644
index 0000000..a5de7b7
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt
+
+import org.apache.shiro.session.mgt.SimpleSession
+import org.apache.shiro.util.ThreadContext
+import org.apache.shiro.web.servlet.Cookie
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest
+import org.apache.shiro.web.servlet.ShiroHttpSession
+import org.apache.shiro.web.servlet.SimpleCookie
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+import javax.servlet.ServletRequest
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+/**
+ * Test cases for the {@link DefaultWebSessionManager} implementation.
+ *
+ * @since 1.0
+ */
+public class DefaultWebSessionManagerTest {
+
+
+    DefaultWebSessionManager mgr;
+
+    @Before
+    void setUp() {
+        this.mgr = new DefaultWebSessionManager()
+    }
+
+    @After
+    public void clearThread() {
+        ThreadContext.remove();
+    }
+
+    @Test
+    public void testOnStart() {
+        Cookie cookie = createMock(Cookie.class);
+        mgr.setSessionIdCookie(cookie);
+
+        SimpleSession session = new SimpleSession();
+        session.setId("12345");
+
+        WebSessionContext wsc = new DefaultWebSessionContext();
+        wsc.setServletRequest(createMock(HttpServletRequest.class));
+        wsc.setServletResponse(createMock(HttpServletResponse.class));
+
+        //test that the cookie template is being used:
+        expect(cookie.getValue()).andReturn("blah");
+        expect(cookie.getComment()).andReturn("comment");
+        expect(cookie.getDomain()).andReturn("domain");
+        expect(cookie.getMaxAge()).andReturn(SimpleCookie.DEFAULT_MAX_AGE);
+        expect(cookie.getName()).andReturn(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        expect(cookie.getPath()).andReturn("/");
+        expect(cookie.getVersion()).andReturn(SimpleCookie.DEFAULT_VERSION);
+        expect(cookie.isSecure()).andReturn(true);
+        expect(cookie.isHttpOnly()).andReturn(true);
+
+        replay(cookie);
+
+        mgr.onStart(session, wsc);
+
+        verify(cookie);
+    }
+
+    @Test
+    public void testOnStartWithSessionIdCookieDisabled() {
+
+        Cookie cookie = createMock(Cookie.class);
+        mgr.setSessionIdCookie(cookie);
+        mgr.setSessionIdCookieEnabled(false);
+
+        //we should not have any reads from the cookie fields - if we do, this test case will fail.
+
+        SimpleSession session = new SimpleSession();
+        session.setId("12345");
+
+        WebSessionContext wsc = new DefaultWebSessionContext();
+        wsc.setServletRequest(createMock(HttpServletRequest.class));
+        wsc.setServletResponse(createMock(HttpServletResponse.class));
+
+        replay(cookie);
+
+        mgr.onStart(session, wsc);
+
+        verify(cookie);
+    }
+
+    @Test
+    public void testGetSessionIdWithSessionIdCookieEnabled() {
+        Cookie cookie = createMock(Cookie.class);
+        mgr.setSessionIdCookie(cookie);
+
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+
+        String id = "12345";
+
+        expect(cookie.readValue(request, response)).andReturn(id);
+
+        //expect that state attributes are set correctly
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+
+        replay(cookie);
+        replay(request);
+        replay(response);
+
+        Serializable sessionId = mgr.getSessionId(request, response);
+        assertEquals(sessionId, id);
+
+        verify(cookie);
+        verify(request);
+        verify(response);
+    }
+
+    @Test
+    public void testGetSessionIdWithSessionIdCookieDisabled() {
+
+        Cookie cookie = createMock(Cookie.class);
+        mgr.setSessionIdCookie(cookie);
+        mgr.setSessionIdCookieEnabled(false);
+
+        //we should not have any reads from the cookie fields - if we do, this test case will fail.
+
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+
+        String id = "12345";
+
+        expect(cookie.getName()).andReturn(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        expect(request.getRequestURI()).andReturn("/foo/bar?JSESSIONID=$id" as String)
+        expect(request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME)).andReturn(id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+
+        replay(cookie);
+        replay(request);
+        replay(response);
+
+        Serializable sessionId = mgr.getSessionId(request, response);
+        assertEquals(sessionId, id);
+
+        verify(cookie);
+        verify(request);
+        verify(response);
+    }
+
+    @Test
+    public void testGetSessionIdWithSessionIdCookieDisabledAndLowercaseRequestParam() {
+
+        Cookie cookie = createMock(Cookie.class);
+        mgr.setSessionIdCookie(cookie);
+        mgr.setSessionIdCookieEnabled(false);
+
+        //we should not have any reads from the cookie fields - if we do, this test case will fail.
+
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+
+        String id = "12345";
+
+        expect(cookie.getName()).andReturn(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        expect(request.getRequestURI()).andReturn("/foo/bar?JSESSIONID=$id" as String)
+        expect(request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME)).andReturn(null);
+        expect(request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME.toLowerCase())).andReturn(id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
+                ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+
+        replay(cookie);
+        replay(request);
+        replay(response);
+
+        Serializable sessionId = mgr.getSessionId(request, response);
+        assertEquals(sessionId, id);
+
+        verify(cookie);
+        verify(request);
+        verify(response);
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    public void testGetSessionIdFromRequestUriPathSegmentParam() {
+
+        mgr.setSessionIdCookieEnabled(false);
+
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+
+        String id = "12345";
+
+        expect(request.getRequestURI()).andReturn("/foo/bar.html;JSESSIONID=$id;key2=value2?key3=value3" as String)
+
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+
+        replay(request);
+        replay(response);
+
+        Serializable sessionId = mgr.getSessionId(request, response);
+        assertEquals(sessionId, id);
+
+        verify(request);
+        verify(response);
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    void testSessionIDRequestPathParameterWithNonHttpRequest() {
+
+        def request = createMock(ServletRequest)
+
+        replay request
+
+        assertNull mgr.getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME)
+
+        verify request
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    void testSessionIDRequestPathParameterWithoutARequestURI() {
+
+        def request = createMock(HttpServletRequest)
+
+        expect(request.getRequestURI()).andReturn null
+        replay request
+
+        assertNull mgr.getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME)
+
+        verify request
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    void testSessionIDRequestPathParameterWithoutPathParameters() {
+
+        def request = createMock(HttpServletRequest)
+
+        expect(request.getRequestURI()).andReturn '/foo/bar/baz.html'
+        replay request
+
+        assertNull mgr.getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME)
+
+        verify request
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    void testSessionIDRequestPathParameterWithoutJSESSIONID() {
+
+        def request = createMock(HttpServletRequest)
+
+        expect(request.getRequestURI()).andReturn '/foo/bar;key1=key2;a/b/c;blah'
+        replay request
+
+        assertNull mgr.getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME)
+
+        verify request
+    }
+
+    //SHIRO-351:
+    //since 1.2.2
+    @Test
+    void testSessionIDRequestPathParameter() {
+
+        def request = createMock(HttpServletRequest)
+
+        def id = 'baz'
+        def path = "/foo/bar;key1=value1;key3,key4,key5;JSESSIONID=$id;key6=value6?key7=value7&key8=value8"
+
+        expect(request.getRequestURI()).andReturn(path.toString())
+        replay request
+
+        String found = mgr.getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME)
+
+        assertEquals id, found
+
+        verify request
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/session/mgt/ServletContainerSessionManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/session/mgt/ServletContainerSessionManagerTest.groovy
new file mode 100644
index 0000000..dccb4c7
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/session/mgt/ServletContainerSessionManagerTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.session.mgt
+
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import javax.servlet.http.HttpSession
+import org.apache.shiro.session.mgt.SessionContext
+import org.apache.shiro.session.mgt.SessionKey
+import org.apache.shiro.web.session.HttpServletSession
+import static org.easymock.EasyMock.*
+
+/**
+ * Unit tests for the {@link ServletContainerSessionManager} implementation.
+ */
+class ServletContainerSessionManagerTest extends GroovyTestCase {
+
+    void testStartWithNonWebSessionContext() {
+
+        def sessionContext = createStrictMock(SessionContext)
+
+        replay sessionContext
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        try {
+            mgr.start sessionContext
+            fail "Start should have failed with a non-web SessionContext"
+        } catch (IllegalArgumentException expected) {
+        }
+
+        verify sessionContext
+    }
+
+    void testStartWithContextHostValue() {
+
+        def host = "host.somecompany.com"
+
+        def request = createStrictMock(HttpServletRequest)
+        def response = createStrictMock(HttpServletResponse)
+        def httpSession = createStrictMock(HttpSession)
+        def context = new DefaultWebSessionContext()
+        context.servletRequest = request
+        context.servletResponse = response
+        context.host = host
+
+        expect(request.session).andReturn httpSession
+
+        httpSession.setAttribute(eq(HttpServletSession.HOST_SESSION_KEY), eq(host))
+        expect(httpSession.getAttribute(eq(HttpServletSession.HOST_SESSION_KEY))).andReturn host
+
+        replay request, response, httpSession
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        def startedSession = mgr.start(context)
+
+        assertTrue startedSession instanceof HttpServletSession
+        assertEquals host, startedSession.host
+        assertSame httpSession, startedSession.httpSession
+
+        verify request, response, httpSession
+    }
+
+    void testStartWithoutContextHostValue() {
+
+        def host = "host.somecompany.com"
+
+        def request = createStrictMock(HttpServletRequest)
+        def response = createStrictMock(HttpServletResponse)
+        def httpSession = createStrictMock(HttpSession)
+        def context = new DefaultWebSessionContext()
+        context.servletRequest = request
+        context.servletResponse = response
+
+        expect(request.session).andReturn httpSession
+        expect(request.remoteHost).andReturn host
+
+        httpSession.setAttribute(eq(HttpServletSession.HOST_SESSION_KEY), eq(host))
+        expect(httpSession.getAttribute(eq(HttpServletSession.HOST_SESSION_KEY))).andReturn host
+
+        replay request, response, httpSession
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        def startedSession = mgr.start(context)
+
+        assertTrue startedSession instanceof HttpServletSession
+        assertEquals host, startedSession.host
+        assertSame httpSession, startedSession.httpSession
+
+        verify request, response, httpSession
+    }
+
+    void testGetSessionWithNonWebSessionKey() {
+
+        def key = createStrictMock(SessionKey)
+
+        replay key
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        try {
+            mgr.getSession(key)
+            fail "getSession should have failed with a non-web SessionKey"
+        } catch (IllegalArgumentException expected) {
+        }
+
+        verify key
+    }
+
+    void testGetSessionWithExistingRequestSession() {
+
+        String host = "www.company.com"
+
+        def request = createStrictMock(HttpServletRequest)
+        def response = createStrictMock(HttpServletResponse)
+        def httpSession = createStrictMock(HttpSession)
+
+        expect(request.getSession(false)).andReturn httpSession
+        expect(request.remoteHost).andReturn host
+        httpSession.setAttribute(eq(HttpServletSession.HOST_SESSION_KEY), eq(host))
+        expect(httpSession.getAttribute(eq(HttpServletSession.HOST_SESSION_KEY))).andReturn host
+
+        def key = new WebSessionKey(request, response)
+
+        replay request, response, httpSession
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        def session = mgr.getSession(key)
+
+        assertTrue session instanceof HttpServletSession
+        assertEquals host, session.host
+        assertSame httpSession, session.httpSession
+
+        verify request, response, httpSession
+    }
+
+    void testGetSessionWithoutExistingRequestSession() {
+
+        def request = createStrictMock(HttpServletRequest)
+        def response = createStrictMock(HttpServletResponse)
+        def httpSession = createStrictMock(HttpSession)
+
+        expect(request.getSession(false)).andReturn null
+
+        def key = new WebSessionKey(request, response)
+
+        replay request, response, httpSession
+
+        ServletContainerSessionManager mgr = new ServletContainerSessionManager()
+        def session = mgr.getSession(key)
+
+        assertNull session
+
+        verify request, response, httpSession
+    }
+
+
+
+
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/WebTest.java b/web/src/test/java/org/apache/shiro/web/WebTest.java
new file mode 100644
index 0000000..22eeacc
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/WebTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * @since 1.0
+ */
+public abstract class WebTest {
+
+    protected FilterConfig createNiceMockFilterConfig() {
+        FilterConfig mock = createNiceMock(FilterConfig.class);
+        ServletContext mockServletContext = createNiceMock(ServletContext.class);
+        expect(mock.getServletContext()).andReturn(mockServletContext);
+        return mock;
+    }
+
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/config/WebIniSecurityManagerFactoryTest.java b/web/src/test/java/org/apache/shiro/web/config/WebIniSecurityManagerFactoryTest.java
new file mode 100644
index 0000000..47370ef
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/config/WebIniSecurityManagerFactoryTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.config;
+
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.web.filter.mgt.DefaultFilter;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import java.util.Map;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @since May 8, 2010 4:21:10 PM
+ */
+public class WebIniSecurityManagerFactoryTest {
+
+
+    /**
+     * Test that ensures the WebIniSecurityManagerFactory will automatically add the default
+     * filters to the pool of beans before the INI configuration is interpreted.
+     */
+    @Test
+    public void testDefaultFiltersPresent() {
+        Ini ini = new Ini();
+        //just a normal configuration line in the MAIN section for any of the default filtes should work
+        //out of the box.  So, create the main section and just config one of them:
+        Ini.Section section = ini.addSection(IniSecurityManagerFactory.MAIN_SECTION_NAME);
+        section.put("authc.loginUrl", "/login.jsp");
+
+        WebIniSecurityManagerFactory factory = new WebIniSecurityManagerFactory(ini);
+        org.apache.shiro.mgt.SecurityManager sm = factory.getInstance();
+        assertNotNull(sm);
+        assertTrue(sm instanceof DefaultWebSecurityManager);
+
+        //now assert that all of the default filters exist:
+        Map<String, ?> beans = factory.getBeans();
+        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
+            Filter filter = (Filter) beans.get(defaultFilter.name());
+            assertNotNull(filter);
+            assertTrue(defaultFilter.getFilterClass().isAssignableFrom(filter.getClass()));
+        }
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/PathMatchingFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/PathMatchingFilterTest.java
new file mode 100644
index 0000000..d5e89a8
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/PathMatchingFilterTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the {@link PathMatchingFilter} implementation.
+ */
+public class PathMatchingFilterTest {
+
+    private static final String CONTEXT_PATH = "/";
+    private static final String ENABLED_PATH = CONTEXT_PATH + "enabled";
+    private static final String DISABLED_PATH = CONTEXT_PATH + "disabled";
+
+    HttpServletRequest request;
+    ServletResponse response;
+    PathMatchingFilter filter;
+
+    @Before
+    public void setUp() {
+        request = createNiceMock(HttpServletRequest.class);
+        response = createNiceMock(ServletResponse.class);
+        filter = createTestInstance();
+    }
+
+    private PathMatchingFilter createTestInstance() {
+        final String NAME = "pathMatchingFilter";
+
+        PathMatchingFilter filter = new PathMatchingFilter() {
+            @Override
+            protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue) throws Exception {
+                return !path.equals(DISABLED_PATH);
+            }
+
+            @Override
+            protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+                //simulate a subclass that handles the response itself (A 'false' return value indicates that the
+                //FilterChain should not continue to be executed)
+                //
+                //This method should only be called if the filter is enabled, so we know if the return value is
+                //false, then the filter was enabled.  A true return value from 'onPreHandle' indicates this test
+                //filter was disabled or a path wasn't matched.
+                return false;
+            }
+        };
+        filter.setName(NAME);
+
+        return filter;
+    }
+
+    /**
+     * Test asserting <a href="https://issues.apache.org/jira/browse/SHIRO-221">SHIRO-221<a/>.
+     */
+    @SuppressWarnings({"JavaDoc"})
+    @Test
+    public void testDisabledBasedOnPath() throws Exception {
+        filter.processPathConfig(DISABLED_PATH, null);
+
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        ServletResponse response = createNiceMock(ServletResponse.class);
+
+        expect(request.getContextPath()).andReturn(CONTEXT_PATH).anyTimes();
+        expect(request.getRequestURI()).andReturn(DISABLED_PATH).anyTimes();
+        replay(request);
+
+        boolean continueFilterChain = filter.preHandle(request, response);
+
+        assertTrue("FilterChain should continue.", continueFilterChain);
+
+        verify(request);
+    }
+
+    /**
+     * Test asserting <a href="https://issues.apache.org/jira/browse/SHIRO-221">SHIRO-221<a/>.
+     */
+    @SuppressWarnings({"JavaDoc"})
+    @Test
+    public void testEnabled() throws Exception {
+        //Configure the filter to reflect 2 configured paths.  This test will simulate a request to the
+        //enabled path
+        filter.processPathConfig(DISABLED_PATH, null);
+        filter.processPathConfig(ENABLED_PATH, null);
+
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        ServletResponse response = createNiceMock(ServletResponse.class);
+
+        expect(request.getContextPath()).andReturn(CONTEXT_PATH).anyTimes();
+        expect(request.getRequestURI()).andReturn(ENABLED_PATH).anyTimes();
+        replay(request);
+
+        boolean continueFilterChain = filter.preHandle(request, response);
+
+        assertFalse("FilterChain should NOT continue.", continueFilterChain);
+
+        verify(request);
+    }
+
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authc/AnonymousFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authc/AnonymousFilterTest.java
new file mode 100644
index 0000000..7507ea6
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authc/AnonymousFilterTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+/**
+ * Test for {@link org.apache.shiro.web.filter.authc.AnonymousFilter}.
+ *
+ * @since 1.0
+ */
+public class AnonymousFilterTest {
+
+    @Test
+    public void test() {
+        AnonymousFilter filter = new AnonymousFilter();
+        boolean allow = filter.onPreHandle(null, null, null);
+        assertTrue(allow);
+    }
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authc/BasicHttpFilterAuthenticationTest.java b/web/src/test/java/org/apache/shiro/web/filter/authc/BasicHttpFilterAuthenticationTest.java
new file mode 100644
index 0000000..72a42cd
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authc/BasicHttpFilterAuthenticationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authc;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.codec.Base64;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Test case for {@link BasicHttpAuthenticationFilter}.
+ * @since 1.0
+ */
+public class BasicHttpFilterAuthenticationTest {
+
+    BasicHttpAuthenticationFilter testFilter;
+
+    @Before
+    public void setUp() {
+    }
+
+    @Test
+    public void createTokenNoAuthorizationHeader() throws Exception {
+        testFilter = new BasicHttpAuthenticationFilter();
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        expect(request.getHeader("Authorization")).andReturn(null);
+        expect(request.getRemoteHost()).andReturn("localhost");
+        
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+        
+        replay(request);
+        replay(response);
+        
+		AuthenticationToken token = testFilter.createToken(request, response);
+		assertNotNull(token);
+		assertTrue("Token is not a username and password token.", token instanceof UsernamePasswordToken);
+		assertEquals("", token.getPrincipal());
+		
+		verify(request);
+		verify(response);
+    }
+
+    @Test
+    public void createTokenNoUsername() throws Exception {
+        testFilter = new BasicHttpAuthenticationFilter();
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        expect(request.getHeader("Authorization")).andReturn(createAuthorizationHeader("", ""));
+        expect(request.getRemoteHost()).andReturn("localhost");
+        
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+        
+        replay(request);
+        replay(response);
+        
+		AuthenticationToken token = testFilter.createToken(request, response);
+		assertNotNull(token);
+		assertTrue("Token is not a username and password token.", token instanceof UsernamePasswordToken);
+		assertEquals("", token.getPrincipal());
+		
+		verify(request);
+		verify(response);
+    }
+
+    @Test
+    public void createTokenNoPassword() throws Exception {
+        testFilter = new BasicHttpAuthenticationFilter();
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        expect(request.getHeader("Authorization")).andReturn(createAuthorizationHeader("pedro", ""));
+        expect(request.getRemoteHost()).andReturn("localhost");
+        
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+        
+        replay(request);
+        replay(response);
+        
+		AuthenticationToken token = testFilter.createToken(request, response);
+		assertNotNull(token);
+		assertTrue("Token is not a username and password token.", token instanceof UsernamePasswordToken);
+		
+		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+		assertEquals("pedro", upToken.getUsername());
+		assertEquals("Password is not empty.", 0, upToken.getPassword().length);
+		
+		verify(request);
+		verify(response);
+    }
+
+    @Test
+    public void createTokenColonInPassword() throws Exception {
+        testFilter = new BasicHttpAuthenticationFilter();
+        HttpServletRequest request = createMock(HttpServletRequest.class);
+        expect(request.getHeader("Authorization")).andReturn(createAuthorizationHeader("pedro", "pass:word"));
+        expect(request.getRemoteHost()).andReturn("localhost");
+
+        HttpServletResponse response = createMock(HttpServletResponse.class);
+
+        replay(request);
+        replay(response);
+
+		AuthenticationToken token = testFilter.createToken(request, response);
+		assertNotNull(token);
+		assertTrue("Token is not a username and password token.", token instanceof UsernamePasswordToken);
+
+		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+		assertEquals("pedro", upToken.getUsername());
+		assertEquals("pass:word", new String(upToken.getPassword()));
+
+		verify(request);
+		verify(response);
+    }
+
+    private String createAuthorizationHeader(String username, String password) {
+    	return "Basic " + new String(Base64.encode((username + ":" + password).getBytes()));
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/AuthorizationFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/AuthorizationFilterTest.java
new file mode 100644
index 0000000..dba7cb5
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/AuthorizationFilterTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.easymock.EasyMock.*;
+
+/**
+ * Test cases for the {@link AuthorizationFilter} class.
+ */
+public class AuthorizationFilterTest extends SecurityManagerTestSupport {
+
+    @Test
+    public void testUserOnAccessDeniedWithResponseError() throws IOException {
+        // Tests when a user (known identity) is denied access and no unauthorizedUrl has been configured.
+        // This should trigger an HTTP response error code.
+
+        //log in the user using the account provided by the superclass for tests:
+        SecurityUtils.getSubject().login(new UsernamePasswordToken("test", "test"));
+        
+        AuthorizationFilter filter = new AuthorizationFilter() {
+            @Override
+            protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
+                    throws Exception {
+                return false; //for this test case
+            }
+        };
+
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        replay(response);
+        filter.onAccessDenied(request, response);
+        verify(response);
+    }
+
+    @Test
+    public void testUserOnAccessDeniedWithRedirect() throws IOException {
+        // Tests when a user (known identity) is denied access and an unauthorizedUrl *has* been configured.
+        // This should trigger an HTTP redirect
+
+        //log in the user using the account provided by the superclass for tests:
+        SecurityUtils.getSubject().login(new UsernamePasswordToken("test", "test"));
+
+        String unauthorizedUrl = "unauthorized.jsp";
+
+        AuthorizationFilter filter = new AuthorizationFilter() {
+            @Override
+            protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
+                    throws Exception {
+                return false; //for this test case
+            }
+        };
+        filter.setUnauthorizedUrl(unauthorizedUrl);
+
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+
+        expect(request.getContextPath()).andReturn("/").anyTimes();
+
+        String encoded = "/" + unauthorizedUrl;
+        expect(response.encodeRedirectURL(unauthorizedUrl)).andReturn(encoded);
+        response.sendRedirect(encoded);
+        replay(request);
+        replay(response);
+
+        filter.onAccessDenied(request, response);
+
+        verify(request);
+        verify(response);
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/HostFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/HostFilterTest.java
new file mode 100644
index 0000000..8781590
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/HostFilterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.regex.Pattern;
+
+/** @since 1.0 */
+public class HostFilterTest {
+
+    @Test
+    public void testPrivateClassC() {
+        Pattern p = Pattern.compile(HostFilter.PRIVATE_CLASS_C_REGEX);
+
+        String base = "192.168.";
+
+        for (int i = 0; i < 256; i++) {
+            String ibase = base + i;
+            for (int j = 0; j < 256; j++) {
+                String ip = ibase + "." + j;
+                assertTrue(p.matcher(ip).matches());
+            }
+        }
+    }
+
+    @Test
+    public void testPrivateClassB() {
+        Pattern p = Pattern.compile(HostFilter.PRIVATE_CLASS_B_REGEX);
+
+        String base = "172.";
+
+        for (int i = 16; i < 32; i++) {
+            String ibase = base + i;
+            for (int j = 0; j < 256; j++) {
+                String jBase = ibase + "." + j;
+                for (int k = 0; k < 256; k++) {
+                    String ip = jBase + "." + k;
+                    assertTrue(p.matcher(ip).matches());
+                }
+            }
+        }
+    }
+
+    /* Takes a long time (20+ seconds?) - only enable when testing explicitly:
+    @Test
+    public void testPrivateClassA() {
+        Pattern p = Pattern.compile(HostFilter.PRIVATE_CLASS_A_REGEX);
+
+        String base = "10.";
+
+        for (int i = 0; i < 256; i++) {
+            String ibase = base + i;
+            for (int j = 0; j < 256; j++) {
+                String jBase = ibase + "." + j;
+                for (int k = 0; k < 256; k++) {
+                    String ip = jBase + "." + k;
+                    assertTrue(p.matcher(ip).matches());
+                }
+            }
+        }
+    } */
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilterTest.java
new file mode 100644
index 0000000..1737b19
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+
+public class HttpMethodPermissionFilterTest {
+
+    @Test
+    public void testPermisisonMapping() {
+        // Testing the isAccessAllowed would be easier, but would need to mock out the servlet request
+
+        HttpMethodPermissionFilter filter = new HttpMethodPermissionFilter();
+
+        String[] permsBefore = {"foo", "bar"};
+
+        String[] permsAfter = filter.buildPermissions(permsBefore, filter.getHttpMethodAction("get"));
+        Assert.assertEquals(2, permsAfter.length);
+        Assert.assertEquals("foo:read", permsAfter[0]);
+        Assert.assertEquals("bar:read", permsAfter[1]);
+
+        Assert.assertEquals("foo:read", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("head"))[0]);
+        Assert.assertEquals("foo:update", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("put"))[0]);
+        Assert.assertEquals("foo:create", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("post"))[0]);
+        Assert.assertEquals("foo:create", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("mkcol"))[0]);
+        Assert.assertEquals("foo:delete", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("delete"))[0]);
+        Assert.assertEquals("foo:read", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("options"))[0]);
+        Assert.assertEquals("foo:read", filter.buildPermissions(permsBefore, filter.getHttpMethodAction("trace"))[0]);
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/PortFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/PortFilterTest.java
new file mode 100644
index 0000000..98691d5
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/PortFilterTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.authz;
+
+import org.junit.Test;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link PortFilter} class.
+ *
+ * @since 1.1
+ */
+public class PortFilterTest {
+
+    protected HttpServletRequest createBaseMockRequest() {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getScheme()).andReturn("http");
+        expect(request.getServerName()).andReturn("localhost");
+        expect(request.getRequestURI()).andReturn("/");
+        return request;
+    }
+
+    @Test
+    public void testDefault() throws Exception {
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        HttpServletRequest request = createBaseMockRequest();
+
+        expect(response.encodeRedirectURL(eq("http://localhost/"))).andReturn("http://localhost/");
+        replay(request);
+        replay(response);
+
+        PortFilter filter = new PortFilter();
+        boolean result = filter.onAccessDenied(request, response, null);
+
+        verify(request);
+        verify(response);
+        assertFalse(result);
+    }
+
+    /**
+     * This tests the case where the client (e.g. browser) specifies a simple request to http://localhost/
+     * (i.e. http scheme with the implied port of 80). The redirectURL should reflect the configured port (8080) instead
+     * of the implied port 80.
+     *
+     * @throws Exception if there is a test failure
+     */
+    @Test
+    public void testConfiguredPort() throws Exception {
+        int port = 8080;
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        HttpServletRequest request = createBaseMockRequest();
+
+        String expected = "http://localhost:" + port + "/";
+        expect(response.encodeRedirectURL(eq(expected))).andReturn(expected);
+        replay(request);
+        replay(response);
+
+        PortFilter filter = new PortFilter();
+        filter.setPort(port);
+        boolean result = filter.onAccessDenied(request, response, null);
+
+        verify(request);
+        verify(response);
+        assertFalse(result);
+    }
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java b/web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java
new file mode 100644
index 0000000..49dc90c
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.util.AntPathMatcher;
+import org.apache.shiro.web.WebTest;
+import org.apache.shiro.web.util.WebUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver}.
+ *
+ * @since 1.0
+ */
+public class PathMatchingFilterChainResolverTest extends WebTest {
+
+    private PathMatchingFilterChainResolver resolver;
+
+    @Before
+    public void setUp() {
+        resolver = new PathMatchingFilterChainResolver();
+    }
+
+    @Test
+    public void testNewInstance() {
+        assertNotNull(resolver.getPathMatcher());
+        assertTrue(resolver.getPathMatcher() instanceof AntPathMatcher);
+        assertNotNull(resolver.getFilterChainManager());
+        assertTrue(resolver.getFilterChainManager() instanceof DefaultFilterChainManager);
+    }
+
+    @Test
+    public void testNewInstanceWithFilterConfig() {
+        FilterConfig mock = createNiceMockFilterConfig();
+        replay(mock);
+        resolver = new PathMatchingFilterChainResolver(mock);
+        assertNotNull(resolver.getPathMatcher());
+        assertTrue(resolver.getPathMatcher() instanceof AntPathMatcher);
+        assertNotNull(resolver.getFilterChainManager());
+        assertTrue(resolver.getFilterChainManager() instanceof DefaultFilterChainManager);
+        assertEquals(((DefaultFilterChainManager) resolver.getFilterChainManager()).getFilterConfig(), mock);
+        verify(mock);
+    }
+
+    @Test
+    public void testSetters() {
+        resolver.setPathMatcher(new AntPathMatcher());
+        assertNotNull(resolver.getPathMatcher());
+        assertTrue(resolver.getPathMatcher() instanceof AntPathMatcher);
+        resolver.setFilterChainManager(new DefaultFilterChainManager());
+        assertNotNull(resolver.getFilterChainManager());
+        assertTrue(resolver.getFilterChainManager() instanceof DefaultFilterChainManager);
+    }
+
+    @Test
+    public void testGetChainsWithoutChains() {
+        ServletRequest request = createNiceMock(HttpServletRequest.class);
+        ServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNull(resolved);
+    }
+
+    @Test
+    public void testGetChainsWithMatch() {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+
+        //ensure at least one chain is defined:
+        resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+        expect(request.getContextPath()).andReturn("");
+        expect(request.getRequestURI()).andReturn("/index.html");
+        replay(request);
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNotNull(resolved);
+        verify(request);
+    }
+    
+    @Test
+    public void testPathTraversalWithDot() {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+
+        //ensure at least one chain is defined:
+        resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+        expect(request.getContextPath()).andReturn("");
+        expect(request.getRequestURI()).andReturn("/./index.html");
+        replay(request);
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNotNull(resolved);
+        verify(request);
+    }
+    
+    @Test
+    public void testPathTraversalWithDotDot() {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+
+        //ensure at least one chain is defined:
+        resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+        expect(request.getContextPath()).andReturn("");
+        expect(request.getRequestURI()).andReturn("/public/../index.html");
+        replay(request);
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNotNull(resolved);
+        verify(request);
+    }
+
+    @Test
+    public void testGetChainsWithoutMatch() {
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse response = createNiceMock(HttpServletResponse.class);
+        FilterChain chain = createNiceMock(FilterChain.class);
+
+        //ensure at least one chain is defined:
+        resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+        expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+        expect(request.getContextPath()).andReturn("");
+        expect(request.getRequestURI()).andReturn("/");
+        replay(request);
+
+        FilterChain resolved = resolver.getChain(request, response, chain);
+        assertNull(resolved);
+        verify(request);
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterListTest.java b/web/src/test/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterListTest.java
new file mode 100644
index 0000000..a62e132
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/mgt/SimpleNamedFilterListTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.filter.mgt;
+
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.web.filter.authc.UserFilter;
+import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
+import org.apache.shiro.web.filter.authz.PortFilter;
+import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
+import org.apache.shiro.web.filter.authz.SslFilter;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import java.util.*;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.junit.Assert.*;
+
+/**
+ * Test case for the {@link SimpleNamedFilterList} implementation.
+ *
+ * @since 1.0
+ */
+public class SimpleNamedFilterListTest {
+
+    @Test
+    public void testNewInstance() {
+        @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
+        SimpleNamedFilterList list = new SimpleNamedFilterList("test");
+        assertNotNull(list.getName());
+        assertEquals("test", list.getName());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNewInstanceNameless() {
+        new SimpleNamedFilterList(null);
+    }
+
+    @Test
+    public void testNewInstanceBackingList() {
+        new SimpleNamedFilterList("test", new ArrayList<Filter>());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNewInstanceNullBackingList() {
+        new SimpleNamedFilterList("test", null);
+    }
+
+    /**
+     * Exists mainly to increase code coverage as the SimpleNamedFilterList
+     * implementation is a direct pass through.
+     */
+    @Test
+    public void testListMethods() {
+        FilterChain mock = createNiceMock(FilterChain.class);
+        Filter filter = createNiceMock(Filter.class);
+
+        NamedFilterList list = new SimpleNamedFilterList("test");
+        list.add(filter);
+        FilterChain chain = list.proxy(mock);
+        assertNotNull(chain);
+        assertNotSame(mock, chain);
+
+        Filter singleFilter = new SslFilter();
+        List<? extends Filter> multipleFilters = CollectionUtils.asList(new PortFilter(), new UserFilter());
+
+        list.add(0, singleFilter);
+        assertEquals(2, list.size());
+        assertTrue(list.get(0) instanceof SslFilter);
+        assertTrue(Arrays.equals(list.toArray(), new Object[]{singleFilter, filter}));
+
+        list.addAll(multipleFilters);
+        assertEquals(4, list.size());
+        assertTrue(list.get(2) instanceof PortFilter);
+        assertTrue(list.get(3) instanceof UserFilter);
+
+        list.addAll(0, CollectionUtils.asList(new PermissionsAuthorizationFilter(), new RolesAuthorizationFilter()));
+        assertEquals(6, list.size());
+        assertTrue(list.get(0) instanceof PermissionsAuthorizationFilter);
+        assertTrue(list.get(1) instanceof RolesAuthorizationFilter);
+        assertEquals(2, list.indexOf(singleFilter));
+        assertEquals(multipleFilters, list.subList(4, list.size()));
+
+        assertTrue(list.contains(singleFilter));
+        assertTrue(list.containsAll(multipleFilters));
+
+        assertFalse(list.isEmpty());
+        list.clear();
+        assertTrue(list.isEmpty());
+
+        list.add(singleFilter);
+        Iterator i = list.iterator();
+        assertTrue(i.hasNext());
+        assertEquals(i.next(), singleFilter);
+
+        ListIterator li = list.listIterator();
+        assertTrue(li.hasNext());
+        assertEquals(li.next(), singleFilter);
+
+        li = list.listIterator(0);
+        assertTrue(li.hasNext());
+        assertEquals(li.next(), singleFilter);
+
+        list.set(0, singleFilter);
+        assertEquals(list.get(0), singleFilter);
+
+        Filter[] filters = new Filter[list.size()];
+        filters = list.toArray(filters);
+        assertEquals(1, filters.length);
+        assertEquals(filters[0], singleFilter);
+
+        assertEquals(0, list.lastIndexOf(singleFilter));
+
+        list.remove(singleFilter);
+        assertTrue(list.isEmpty());
+
+        list.add(singleFilter);
+        list.remove(0);
+        assertTrue(list.isEmpty());
+
+        list.add(singleFilter);
+        list.addAll(multipleFilters);
+        assertEquals(3, list.size());
+        list.removeAll(multipleFilters);
+        assertEquals(1, list.size());
+        assertEquals(list.get(0), singleFilter);
+
+        list.addAll(multipleFilters);
+        assertEquals(3, list.size());
+        list.retainAll(multipleFilters);
+        assertEquals(2, list.size());
+        //noinspection unchecked
+        assertEquals(new ArrayList(list), multipleFilters);
+    }
+
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/mgt/AbstractWebSecurityManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/AbstractWebSecurityManagerTest.java
new file mode 100644
index 0000000..8d08041
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/mgt/AbstractWebSecurityManagerTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.util.ThreadContext;
+import org.junit.After;
+
+/**
+ * @since 1.0
+ */
+public abstract class AbstractWebSecurityManagerTest {
+
+    @After
+    public void tearDown() {
+        ThreadContext.remove();
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
new file mode 100644
index 0000000..1144575
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.crypto.CryptoException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+import org.apache.shiro.web.subject.support.DefaultWebSubjectContext;
+import org.junit.Test;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for the {@link CookieRememberMeManager} implementation.
+ *
+ * @since 1.0
+ */
+public class CookieRememberMeManagerTest {
+
+    @Test
+    public void onSuccessfulLogin() {
+
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        org.apache.shiro.web.servlet.Cookie cookie = createMock(org.apache.shiro.web.servlet.Cookie.class);
+        mgr.setCookie(cookie);
+
+        //first remove any previous cookie
+        cookie.removeFrom(isA(HttpServletRequest.class), isA(HttpServletResponse.class));
+
+        //then ensure a new cookie is created by reading the template's attributes:
+        expect(cookie.getName()).andReturn("rememberMe");
+        expect(cookie.getValue()).andReturn(null);
+        expect(cookie.getComment()).andReturn(null);
+        expect(cookie.getDomain()).andReturn(null);
+        expect(cookie.getPath()).andReturn(null);
+        expect(cookie.getMaxAge()).andReturn(SimpleCookie.DEFAULT_MAX_AGE);
+        expect(cookie.getVersion()).andReturn(SimpleCookie.DEFAULT_VERSION);
+        expect(cookie.isSecure()).andReturn(false);
+        expect(cookie.isHttpOnly()).andReturn(true);
+
+        UsernamePasswordToken token = new UsernamePasswordToken("user", "secret");
+        token.setRememberMe(true);
+        AuthenticationInfo account = new SimpleAuthenticationInfo("user", "secret", "test");
+
+        replay(mockSubject);
+        replay(mockRequest);
+        replay(cookie);
+
+        mgr.onSuccessfulLogin(mockSubject, token, account);
+
+        verify(mockRequest);
+        verify(mockSubject);
+        verify(cookie);
+    }
+    
+    // SHIRO-183
+    @Test
+    public void getRememberedSerializedIdentityReturnsNullForDeletedCookie() {
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, org.apache.shiro.web.servlet.Cookie.DELETED_COOKIE_VALUE)
+        };
+
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        replay(mockRequest);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        assertNull(mgr.getRememberedSerializedIdentity(context));
+    }
+    
+
+    // SHIRO-69
+    @Test
+    public void getRememberedPrincipals() {
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+
+        //The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test.
+        //This will have to change any time the PrincipalCollection implementation changes:
+        final String userPCAesBase64 = "WlD5MLzzZznN3dQ1lPJO/eScSuY245k29aECNmjUs31o7Yu478hWhaM5Sj" +
+                "jmoe900/72JNu3hcJaPG6Q17Vuz4F8x0kBjbFnPVx4PqzsZYT6yreeS2jwO6OwfI+efqXOKyB2a5KPtnr" +
+                "7jt5kZsyH38XJISb81cf6xqTGUru8zC+kNqJFz7E5RpO0kraBofS5jhMm45gDVjDRkjgPJAzocVWMtrza" +
+                "zy67P8eb+kMSBCqGI251JTNAGboVgQ28KjfaAJ/6LXRJUj7kB7CGia7mgRk+hxzEJGDs81at5VOPqODJr" +
+                "xb8tcIdemFUFIkiYVP9bGs4dP3ECtmw7aNrCzv+84sx3vRFUrd5DbDYpEuE12hF2Y9owDK9sxStbXoF0y" +
+                "A32dhfGDIqS+agsass0sWn8WX2TM9i8SxrUjiFbxqyIG49HbqGrZp5QLM9IuIwO+TzGfF1FzumQGdwmWT" +
+                "xkVapw5UESl34YvA615cb+82ue1I=";
+
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)
+        };
+
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        replay(mockRequest);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        PrincipalCollection collection = mgr.getRememberedPrincipals(context);
+
+        verify(mockRequest);
+
+        assertTrue(collection != null);
+        //noinspection ConstantConditions
+        assertTrue(collection.iterator().next().equals("user"));
+    }
+
+    // SHIRO-69
+
+    @Test
+    public void getRememberedPrincipalsDecryptionError() {
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+
+        // Simulate a bad return value here (for example if this was encrypted with a different key
+        final String userPCAesBase64 = "garbage";
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)
+        };
+
+        expect(mockRequest.getCookies()).andReturn(cookies).anyTimes();
+        replay(mockRequest);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        try {
+            mgr.getRememberedPrincipals(context);
+        } catch (CryptoException expected) {
+            return;
+        }
+        fail("CryptoException was expected to be thrown");
+    }
+
+    @Test
+    public void onLogout() {
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        org.apache.shiro.web.servlet.Cookie cookie = createMock(org.apache.shiro.web.servlet.Cookie.class);
+        mgr.setCookie(cookie);
+
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
+        expect(mockRequest.getContextPath()).andReturn(null).anyTimes();
+
+        cookie.removeFrom(isA(HttpServletRequest.class), isA(HttpServletResponse.class));
+
+        replay(mockRequest);
+        replay(mockResponse);
+        replay(mockSubject);
+        replay(cookie);
+
+        mgr.onLogout(mockSubject);
+
+        verify(mockSubject);
+        verify(mockRequest);
+        verify(mockResponse);
+        verify(cookie);
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/mgt/DefaultWebSecurityManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/DefaultWebSecurityManagerTest.java
new file mode 100644
index 0000000..cfa86e5
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/mgt/DefaultWebSecurityManagerTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.mgt;
+
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.session.ExpiredSessionException;
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.mgt.AbstractSessionManager;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.LifecycleUtils;
+import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
+import org.apache.shiro.web.servlet.ShiroHttpSession;
+import org.apache.shiro.web.session.mgt.WebSessionManager;
+import org.apache.shiro.web.subject.WebSubject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+/**
+ * @since 0.9
+ */
+public class DefaultWebSecurityManagerTest extends AbstractWebSecurityManagerTest {
+
+    private DefaultWebSecurityManager sm;
+
+    @Before
+    public void setup() {
+        sm = new DefaultWebSecurityManager();
+        sm.setSessionMode(DefaultWebSecurityManager.NATIVE_SESSION_MODE);
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        section.put("lonestarr", "vespa");
+        sm.setRealm(new IniRealm(ini));
+    }
+
+    @After
+    public void tearDown() {
+        sm.destroy();
+        super.tearDown();
+    }
+
+    protected Subject newSubject(ServletRequest request, ServletResponse response) {
+        return new WebSubject.Builder(sm, request, response).buildSubject();
+    }
+
+	@Test
+	public void checkSessionManagerDeterminesContainerSessionMode() {
+		sm.setSessionMode(DefaultWebSecurityManager.NATIVE_SESSION_MODE);
+		WebSessionManager sessionManager = createMock(WebSessionManager.class);
+
+		expect(sessionManager.isServletContainerSessions()).andReturn(true).anyTimes();
+
+		replay(sessionManager);
+
+		sm.setSessionManager(sessionManager);
+
+		assertTrue("The set SessionManager is not being used to determine isHttpSessionMode.", sm.isHttpSessionMode());
+
+		verify(sessionManager);
+	}
+
+    @Test
+    public void shiroSessionModeInit() {
+        sm.setSessionMode(DefaultWebSecurityManager.NATIVE_SESSION_MODE);
+    }
+
+    protected void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Test
+    public void testLogin() {
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        expect(mockRequest.getCookies()).andReturn(null);
+        expect(mockRequest.getContextPath()).andReturn("/");
+
+        replay(mockRequest);
+
+        Subject subject = newSubject(mockRequest, mockResponse);
+
+        assertFalse(subject.isAuthenticated());
+
+        subject.login(new UsernamePasswordToken("lonestarr", "vespa"));
+
+        assertTrue(subject.isAuthenticated());
+        assertNotNull(subject.getPrincipal());
+        assertTrue(subject.getPrincipal().equals("lonestarr"));
+    }
+
+    @Test
+    public void testSessionTimeout() {
+        shiroSessionModeInit();
+        long globalTimeout = 100;
+        ((AbstractSessionManager) sm.getSessionManager()).setGlobalSessionTimeout(globalTimeout);
+
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        expect(mockRequest.getCookies()).andReturn(null);
+        expect(mockRequest.getContextPath()).andReturn("/");
+
+        replay(mockRequest);
+
+        Subject subject = newSubject(mockRequest, mockResponse);
+
+        Session session = subject.getSession();
+        assertEquals(session.getTimeout(), globalTimeout);
+        session.setTimeout(125);
+        assertEquals(session.getTimeout(), 125);
+        sleep(200);
+        try {
+            session.getTimeout();
+            fail("Session should have expired.");
+        } catch (ExpiredSessionException expected) {
+        }
+    }
+
+    @Test
+    public void testGetSubjectByRequestResponsePair() {
+        shiroSessionModeInit();
+
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        expect(mockRequest.getCookies()).andReturn(null);
+
+        replay(mockRequest);
+        replay(mockResponse);
+
+        Subject subject = newSubject(mockRequest, mockResponse);
+
+        verify(mockRequest);
+        verify(mockResponse);
+
+        assertNotNull(subject);
+        assertTrue(subject.getPrincipals() == null || subject.getPrincipals().isEmpty());
+        assertTrue(subject.getSession(false) == null);
+        assertFalse(subject.isAuthenticated());
+    }
+
+    @Test
+    public void testGetSubjectByRequestSessionId() {
+
+        shiroSessionModeInit();
+
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        replay(mockRequest);
+        replay(mockResponse);
+
+        Subject subject = newSubject(mockRequest, mockResponse);
+
+        Session session = subject.getSession();
+        Serializable sessionId = session.getId();
+
+        assertNotNull(sessionId);
+
+        verify(mockRequest);
+        verify(mockResponse);
+
+        mockRequest = createNiceMock(HttpServletRequest.class);
+        mockResponse = createNiceMock(HttpServletResponse.class);
+        //now simulate the cookie going with the request and the Subject should be acquired based on that:
+        Cookie[] cookies = new Cookie[]{new Cookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME, sessionId.toString())};
+        expect(mockRequest.getCookies()).andReturn(cookies).anyTimes();
+        expect(mockRequest.getParameter(isA(String.class))).andReturn(null).anyTimes();
+
+        replay(mockRequest);
+        replay(mockResponse);
+
+        subject = newSubject(mockRequest, mockResponse);
+
+        session = subject.getSession(false);
+        assertNotNull(session);
+        assertEquals(sessionId, session.getId());
+
+        verify(mockRequest);
+        verify(mockResponse);
+    }
+
+    /**
+     * Asserts fix for <a href="https://issues.apache.org/jira/browse/SHIRO-350">SHIRO-350</a>.
+     */
+    @Test
+    public void testBuildNonWebSubjectWithDefaultServletContainerSessionManager() {
+
+        Ini ini = new Ini();
+        Ini.Section section = ini.addSection(IniRealm.USERS_SECTION_NAME);
+        section.put("user1", "user1");
+
+        WebIniSecurityManagerFactory factory = new WebIniSecurityManagerFactory(ini);
+
+        WebSecurityManager securityManager = (WebSecurityManager)factory.getInstance();
+
+        PrincipalCollection principals = new SimplePrincipalCollection("user1", "iniRealm");
+        Subject subject = new Subject.Builder(securityManager).principals(principals).buildSubject();
+
+        assertNotNull(subject);
+        assertEquals("user1", subject.getPrincipal());
+    }
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java b/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java
new file mode 100644
index 0000000..8fdc034
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the {@link OncePerRequestFilter} implementation.
+ *
+ * @since 1.2
+ */
+public class OncePerRequestFilterTest {
+
+    private static final boolean[] FILTERED = new boolean[1];
+    private static final String NAME = "oncePerRequestFilter";
+    private static final String ATTR_NAME = NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX;
+
+    private OncePerRequestFilter filter;
+    private FilterChain chain;
+    private ServletRequest request;
+    private ServletResponse response;
+
+    @Before
+    public void setUp() {
+        FILTERED[0] = false;
+        filter = createTestInstance();
+        chain = createNiceMock(FilterChain.class);
+        request = createNiceMock(ServletRequest.class);
+        response = createNiceMock(ServletResponse.class);
+    }
+
+    private OncePerRequestFilter createTestInstance() {
+        OncePerRequestFilter filter = new OncePerRequestFilter() {
+            @Override
+            protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws ServletException, IOException {
+                FILTERED[0] = true;
+            }
+        };
+        filter.setName(NAME);
+
+        return filter;
+    }
+
+    /**
+     * Test asserting <a href="https://issues.apache.org/jira/browse/SHIRO-221">SHIRO-221<a/>.
+     */
+    @SuppressWarnings({"JavaDoc"})
+    @Test
+    public void testEnabled() throws IOException, ServletException {
+        expect(request.getAttribute(ATTR_NAME)).andReturn(null).anyTimes();
+        replay(request);
+
+        filter.doFilter(request, response, chain);
+
+        verify(request);
+        assertTrue("Filter should have executed", FILTERED[0]);
+    }
+
+    /**
+     * Test asserting <a href="https://issues.apache.org/jira/browse/SHIRO-221">SHIRO-221<a/>.
+     */
+    @SuppressWarnings({"JavaDoc"})
+    @Test
+    public void testDisabled() throws IOException, ServletException {
+        filter.setEnabled(false); //test disabled
+
+        expect(request.getAttribute(ATTR_NAME)).andReturn(null).anyTimes();
+        replay(request);
+
+        filter.doFilter(request, response, chain);
+
+        verify(request);
+        assertFalse("Filter should NOT have executed", FILTERED[0]);
+    }
+
+}
diff --git a/web/src/test/java/org/apache/shiro/web/servlet/SimpleCookieTest.java b/web/src/test/java/org/apache/shiro/web/servlet/SimpleCookieTest.java
new file mode 100644
index 0000000..79d88e8
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/servlet/SimpleCookieTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.servlet;
+
+import junit.framework.TestCase;
+import org.easymock.IArgumentMatcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @since Apr 22, 2010 9:40:47 PM
+ */
+public class SimpleCookieTest extends TestCase {
+
+    private SimpleCookie cookie;
+
+    private HttpServletRequest mockRequest;
+    private HttpServletResponse mockResponse;
+
+    @Before
+    public void setUp() throws Exception {
+        this.mockRequest = createMock(HttpServletRequest.class);
+        this.mockResponse = createMock(HttpServletResponse.class);
+        this.cookie = new SimpleCookie("test");
+    }
+
+    @Test
+    //Verifies fix for JSEC-94
+    public void testRemoveValue() throws Exception {
+
+        //verify that the cookie header starts with what we want
+        //we can't verify the exact date format string that is appended, so we resort to just
+        //simple 'startsWith' matching, which is good enough:
+        String name = "test";
+        String value = "deleteMe";
+        String path = "/somepath";
+
+        String headerValue = this.cookie.buildHeaderValue(name, value, null, null, path,
+                0, SimpleCookie.DEFAULT_VERSION, false, false);
+
+        String expectedStart = new StringBuilder()
+                .append(name).append(SimpleCookie.NAME_VALUE_DELIMITER).append(value)
+                .append(SimpleCookie.ATTRIBUTE_DELIMITER)
+                .append(SimpleCookie.PATH_ATTRIBUTE_NAME).append(SimpleCookie.NAME_VALUE_DELIMITER).append(path)
+                .toString();
+
+        assertTrue(headerValue.startsWith(expectedStart));
+
+        expect(mockRequest.getContextPath()).andReturn(path).times(1);
+        mockResponse.addHeader(eq(SimpleCookie.COOKIE_HEADER_NAME), isA(String.class)); //can't calculate the date format in the test
+        replay(mockRequest);
+        replay(mockResponse);
+
+        this.cookie.removeFrom(mockRequest, mockResponse);
+
+        verify(mockRequest);
+        verify(mockResponse);
+    }
+
+    private void testRootContextPath(String contextPath) {
+        this.cookie.setValue("blah");
+
+        String expectedCookieValue = new StringBuilder()
+                .append("test").append(SimpleCookie.NAME_VALUE_DELIMITER).append("blah")
+                .append(SimpleCookie.ATTRIBUTE_DELIMITER)
+                .append(SimpleCookie.PATH_ATTRIBUTE_NAME).append(SimpleCookie.NAME_VALUE_DELIMITER).append(Cookie.ROOT_PATH)
+                .append(SimpleCookie.ATTRIBUTE_DELIMITER)
+                .append(SimpleCookie.HTTP_ONLY_ATTRIBUTE_NAME)
+                .toString();
+
+        expect(mockRequest.getContextPath()).andReturn(contextPath);
+        mockResponse.addHeader(SimpleCookie.COOKIE_HEADER_NAME, expectedCookieValue);
+
+        replay(mockRequest);
+        replay(mockResponse);
+
+        this.cookie.saveTo(mockRequest, mockResponse);
+
+        verify(mockRequest);
+        verify(mockResponse);
+    }
+
+    @Test
+    /** Verifies fix for <a href="http://issues.apache.org/jira/browse/JSEC-34">JSEC-34</a> (1 of 2)*/
+    public void testEmptyContextPath() throws Exception {
+        testRootContextPath("");
+    }
+
+
+    @Test
+    /** Verifies fix for <a href="http://issues.apache.org/jira/browse/JSEC-34">JSEC-34</a> (2 of 2)*/
+    public void testNullContextPath() throws Exception {
+        testRootContextPath(null);
+    }
+
+    private static <T extends javax.servlet.http.Cookie> T eqCookie(final T in) {
+        reportMatcher(new IArgumentMatcher() {
+            public boolean matches(Object o) {
+                javax.servlet.http.Cookie c = (javax.servlet.http.Cookie) o;
+                return c.getName().equals(in.getName()) &&
+                        c.getValue().equals(in.getValue()) &&
+                        c.getPath().equals(in.getPath()) &&
+                        c.getMaxAge() == in.getMaxAge() &&
+                        c.getSecure() == in.getSecure() &&
+                        c.getValue().equals(in.getValue());
+            }
+
+            public void appendTo(StringBuffer sb) {
+                sb.append("eqCookie(");
+                sb.append(in.getClass().getName());
+                sb.append(")");
+
+            }
+        });
+        return null;
+    }
+
+}
diff --git a/web/src/test/resources/IniShiroFilterTest.ini b/web/src/test/resources/IniShiroFilterTest.ini
new file mode 100644
index 0000000..b95945d
--- /dev/null
+++ b/web/src/test/resources/IniShiroFilterTest.ini
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+[filters]
+authc.successUrl = /index.jsp
+
diff --git a/web/src/test/resources/log4j.properties b/web/src/test/resources/log4j.properties
new file mode 100644
index 0000000..0462fd8
--- /dev/null
+++ b/web/src/test/resources/log4j.properties
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+log4j.rootLogger=TRACE, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
+
+# Pattern to output: date priority [category] - message
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
+log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
+
+# Spring logging level is WARN
+log4j.logger.org.springframework=WARN
+
+# General Apache libraries is WARN
+log4j.logger.org.apache=WARN
+
+log4j.logger.net.sf.ehcache=WARN
+
+log4j.logger.org.apache.shiro=TRACE
+log4j.logger.org.apache.shiro.util.ThreadContext=WARN
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/shiro.git



More information about the pkg-java-commits mailing list