Merge pull request #239 from fpapon/SHIRO-780

[SHIRO-780] NOTICE files of shiro components don't match NOTICE in so…
diff --git a/config/ogdl/src/main/java/org/apache/shiro/config/ogdl/ReflectionBuilder.java b/config/ogdl/src/main/java/org/apache/shiro/config/ogdl/ReflectionBuilder.java
index 77a1db6..8ff214d 100644
--- a/config/ogdl/src/main/java/org/apache/shiro/config/ogdl/ReflectionBuilder.java
+++ b/config/ogdl/src/main/java/org/apache/shiro/config/ogdl/ReflectionBuilder.java
@@ -287,10 +287,15 @@
             }
 
             processor.execute();
-        }
 
-        //SHIRO-413: init method must be called for constructed objects that are Initializable
-        LifecycleUtils.init(objects.values());
+            //SHIRO-778: onInit method on AuthenticatingRealm is called twice
+            objects.keySet().stream()
+                    .filter(key -> !kvPairs.containsKey(key))
+                    .forEach(key -> LifecycleUtils.init(objects.get(key)));
+        } else {
+            //SHIRO-413: init method must be called for constructed objects that are Initializable
+            LifecycleUtils.init(objects.values());
+        }
 
         return objects;
     }
diff --git a/config/ogdl/src/test/groovy/org/apache/shiro/config/ogdl/ReflectionBuilderTest.groovy b/config/ogdl/src/test/groovy/org/apache/shiro/config/ogdl/ReflectionBuilderTest.groovy
index 5fd0950..8bc4a4f 100644
--- a/config/ogdl/src/test/groovy/org/apache/shiro/config/ogdl/ReflectionBuilderTest.groovy
+++ b/config/ogdl/src/test/groovy/org/apache/shiro/config/ogdl/ReflectionBuilderTest.groovy
@@ -18,6 +18,7 @@
  */
 package org.apache.shiro.config.ogdl
 
+import org.apache.shiro.config.ogdl.beans.InitCountBean
 import org.apache.shiro.lang.codec.Base64
 import org.apache.shiro.lang.codec.CodecSupport
 import org.apache.shiro.lang.codec.Hex
@@ -26,6 +27,8 @@
 import org.apache.shiro.config.ogdl.event.BeanEvent
 import org.junit.Test
 
+import java.util.concurrent.ConcurrentHashMap
+
 import static org.junit.Assert.*
 import static org.hamcrest.Matchers.*
 
@@ -640,6 +643,37 @@
         assertNotNull(beanMap.get("two"))
     }
 
+    @Test
+    void testNotMultipleInitialization() {
+        // given
+        Map<String, String> defs = new ConcurrentHashMap<>()
+        defs.put("initcountbean", InitCountBean.getCanonicalName())
+        ReflectionBuilder builder = new ReflectionBuilder()
+
+        // when
+        builder.buildObjects(defs)
+
+        // then
+        assertEquals(1, InitCountBean.getInitCount())
+        InitCountBean.resetCount()
+    }
+
+    @Test
+    void testNotMultipleInitializationWithNullFirst() {
+        // given
+        Map<String, String> defs = new ConcurrentHashMap<>()
+        defs.put("initcountbean", InitCountBean.getCanonicalName())
+        ReflectionBuilder builder = new ReflectionBuilder()
+
+        // when
+        builder.buildObjects(null)
+        builder.buildObjects(defs)
+
+        // then
+        assertEquals(1, InitCountBean.getInitCount())
+        InitCountBean.resetCount()
+    }
+
     void assertInstantiatedEvents(String name, Map<String, ?> objects, int expected) {
         def bean = objects.get(name) as RecordingBeanListener
         def events = bean.getInstantiatedEvents()
diff --git a/config/ogdl/src/test/java/org/apache/shiro/config/ogdl/beans/InitCountBean.java b/config/ogdl/src/test/java/org/apache/shiro/config/ogdl/beans/InitCountBean.java
new file mode 100644
index 0000000..1dccf0a
--- /dev/null
+++ b/config/ogdl/src/test/java/org/apache/shiro/config/ogdl/beans/InitCountBean.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.config.ogdl.beans;
+
+import org.apache.shiro.lang.ShiroException;
+import org.apache.shiro.lang.util.Initializable;
+
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.LongAdder;
+
+public class InitCountBean implements Initializable {
+    private static final LongAdder INIT_COUNT = new LongAdder();
+
+    public InitCountBean() {
+        super();
+    }
+
+    public static long getInitCount() {
+        return INIT_COUNT.longValue();
+    }
+
+    public static void resetCount() {
+        INIT_COUNT.reset();
+    }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", InitCountBean.class.getSimpleName() + "[", "]")
+                .add("INIT_COUNT=" + getInitCount())
+                .toString();
+    }
+
+    @Override
+    public void init() throws ShiroException {
+        INIT_COUNT.increment();
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java b/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
index 43523a0..160e58f 100644
--- a/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
+++ b/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
@@ -146,7 +146,7 @@
             return null;
         }
         if (!requiredType.isInstance(o)) {
-            String msg = "Object named '" + name + "' is not of required type [" + requiredType.getName() + "].";
+            String msg = "Object named '" + name + "' (of type [" + o.getClass().getName() + "]) is not of required type [" + requiredType.getName() + "].";
             throw new RequiredTypeException(msg);
         }
         return (T)o;
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
index e6a1bb3..8991bba 100644
--- a/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
+++ b/core/src/main/java/org/apache/shiro/session/mgt/AbstractValidatingSessionManager.java
@@ -213,7 +213,7 @@
             log.debug("No sessionValidationScheduler set.  Attempting to create default instance.");
         }
         scheduler = new ExecutorServiceSessionValidationScheduler(this);
-        scheduler.setInterval(getSessionValidationInterval());
+        scheduler.setSessionValidationInterval(getSessionValidationInterval());
         if (log.isTraceEnabled()) {
             log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
         }
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
index 657a377..fc10de7 100644
--- a/core/src/main/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.java
+++ b/core/src/main/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationScheduler.java
@@ -31,7 +31,7 @@
 /**
  * SessionValidationScheduler implementation that uses a
  * {@link ScheduledExecutorService} to call {@link ValidatingSessionManager#validateSessions()} every
- * <em>{@link #getInterval interval}</em> milliseconds.
+ * <em>{@link #getSessionValidationInterval sessionValidationInterval}</em> milliseconds.
  *
  * @since 0.9
  */
@@ -44,7 +44,7 @@
 
     ValidatingSessionManager sessionManager;
     private ScheduledExecutorService service;
-    private long interval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
+    private long sessionValidationInterval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
     private boolean enabled = false;
     private String threadNamePrefix = "SessionValidationThread-";
 
@@ -64,12 +64,12 @@
         this.sessionManager = sessionManager;
     }
 
-    public long getInterval() {
-        return interval;
+    public long getSessionValidationInterval() {
+        return sessionValidationInterval;
     }
 
-    public void setInterval(long interval) {
-        this.interval = interval;
+    public void setSessionValidationInterval(long sessionValidationInterval) {
+        this.sessionValidationInterval = sessionValidationInterval;
     }
 
     public boolean isEnabled() {
@@ -91,7 +91,7 @@
     //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) {
+        if (this.sessionValidationInterval > 0l) {
             this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {  
 	            private final AtomicInteger count = new AtomicInteger(1);
 
@@ -102,7 +102,8 @@
 	                return thread;  
 	            }  
             });                  
-            this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
+            this.service.scheduleAtFixedRate(this, sessionValidationInterval,
+                sessionValidationInterval, TimeUnit.MILLISECONDS);
         }
         this.enabled = true;
     }
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
index 5581b7a..d3040d1 100644
--- a/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
+++ b/core/src/main/java/org/apache/shiro/subject/support/DelegatingSubject.java
@@ -41,6 +41,7 @@
 
 import java.util.Collection;
 import java.util.List;
+import java.util.StringJoiner;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -515,4 +516,16 @@
 
         return popped;
     }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", "DelegatingSubject{", "}")
+            .add("principals=" + principals)
+            .add("authenticated=" + authenticated)
+            .add("host='******")
+            .add("session='******'")
+            .add("sessionCreationEnabled=" + sessionCreationEnabled)
+            .add("securityManager=" + securityManager)
+            .toString();
+    }
 }
diff --git a/core/src/test/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationSchedulerTest.java b/core/src/test/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationSchedulerTest.java
index bb5ba64..8ae53a6 100644
--- a/core/src/test/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationSchedulerTest.java
+++ b/core/src/test/java/org/apache/shiro/session/mgt/ExecutorServiceSessionValidationSchedulerTest.java
@@ -36,7 +36,7 @@
         executorServiceSessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
         executorServiceSessionValidationScheduler.setSessionManager(defaultSessionManager);
         executorServiceSessionValidationScheduler.setThreadNamePrefix("test-");
-        executorServiceSessionValidationScheduler.setInterval(1000L);
+        executorServiceSessionValidationScheduler.setSessionValidationInterval(1000L);
         executorServiceSessionValidationScheduler.enableSessionValidation();
     }
 
@@ -81,7 +81,7 @@
         executorServiceSessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
         executorServiceSessionValidationScheduler.setSessionManager(defaultSessionManager);
         executorServiceSessionValidationScheduler.setThreadNamePrefix("test-");
-        executorServiceSessionValidationScheduler.setInterval(1000L);
+        executorServiceSessionValidationScheduler.setSessionValidationInterval(1000L);
         executorServiceSessionValidationScheduler.enableSessionValidation();
         defaultSessionManager.create(session);
         Thread.sleep(2000L);
@@ -101,4 +101,4 @@
             throw new RuntimeException("Session test exception");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java
index ee04e6d..2bc0bbe 100644
--- a/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java
+++ b/core/src/test/java/org/apache/shiro/subject/DelegatingSubjectTest.java
@@ -216,4 +216,25 @@
 
         LifecycleUtils.destroy(sm);
     }
+
+    @Test
+    public void testToString() {
+        // given
+        String username = "jsmith";
+
+        DefaultSecurityManager securityManager = new DefaultSecurityManager();
+        PrincipalCollection identity = new SimplePrincipalCollection(username, "testRealm");
+        final String hostname = "localhost";
+        final DelegatingSubject sourceSubject = new DelegatingSubject(identity, true, hostname, null, securityManager);
+
+        // when
+        final String subjectToString = sourceSubject.toString();
+
+        // then
+        final Session session = sourceSubject.getSession(true);
+        String sesionId = (String) session.getId();
+        assertFalse("toString must not leak sessionId", subjectToString.contains(sesionId));
+        assertFalse("toString must not leak host", subjectToString.contains(hostname));
+    }
+
 }
diff --git a/pom.xml b/pom.xml
index 086f931..d8f3311 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,8 +102,8 @@
         <slf4j.version>1.7.26</slf4j.version>
         <logback.version>1.2.3</logback.version>
         <log4j.version>1.2.17</log4j.version>
-        <spring.version>5.2.5.RELEASE</spring.version>
-        <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
+        <spring.version>5.2.8.RELEASE</spring.version>
+        <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
         <guice.version>4.2.2</guice.version>
         <jaxrs.api.version>2.1.6</jaxrs.api.version>
         <htmlunit.version>2.39.0</htmlunit.version>
@@ -264,7 +264,7 @@
                 <plugin>
                     <groupId>org.apache.felix</groupId>
                     <artifactId>maven-bundle-plugin</artifactId>
-                    <version>4.2.0</version>
+                    <version>5.1.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
diff --git a/support/guice/pom.xml b/support/guice/pom.xml
index 66ade20..7b2c50e 100644
--- a/support/guice/pom.xml
+++ b/support/guice/pom.xml
@@ -46,6 +46,10 @@
             <optional>true</optional>
         </dependency>
         <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.inject</groupId>
             <artifactId>guice</artifactId>
         </dependency>
@@ -54,10 +58,6 @@
             <artifactId>guice-multibindings</artifactId>
         </dependency>
         <dependency>
-            <groupId>javax.annotation</groupId>
-            <artifactId>javax.annotation-api</artifactId>
-        </dependency>
-        <dependency>
             <groupId>com.google.inject.extensions</groupId>
             <artifactId>guice-servlet</artifactId>
             <optional>true</optional>
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
index e15d50d..e2ff32f 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
@@ -38,7 +38,7 @@
     @Autowired
     protected ShiroFilterChainDefinition shiroFilterChainDefinition;
 
-    @Autowired
+    @Autowired(required = false)
     protected Map<String, Filter> filterMap;
 
     @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
@@ -59,7 +59,10 @@
 
         filterFactoryBean.setSecurityManager(securityManager);
         filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
-        filterFactoryBean.setFilters(filterMap);
+
+        if (filterMap != null) {
+            filterFactoryBean.setFilters(filterMap);
+        }
 
         return filterFactoryBean;
     }
diff --git a/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebConfigurationTest.groovy b/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebConfigurationTest.groovy
new file mode 100644
index 0000000..5105293
--- /dev/null
+++ b/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebConfigurationTest.groovy
@@ -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.spring.config
+
+import org.apache.shiro.authc.UsernamePasswordToken
+import org.apache.shiro.authz.ModularRealmAuthorizer
+import org.apache.shiro.event.EventBus
+import org.apache.shiro.mgt.DefaultSecurityManager
+import org.apache.shiro.mgt.SecurityManager
+import org.apache.shiro.realm.text.TextConfigurationRealm
+import org.apache.shiro.spring.testconfig.RealmTestConfiguration
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean
+import org.apache.shiro.spring.web.config.ShiroWebConfiguration
+import org.apache.shiro.spring.web.config.ShiroWebFilterConfiguration
+import org.apache.shiro.subject.Subject
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @since 1.4.0
+ */
+@ContextConfiguration(classes = [RealmTestConfiguration, ShiroConfiguration, ShiroWebConfiguration, ShiroWebFilterConfiguration])
+public class ShiroWebConfigurationTest extends AbstractJUnit4SpringContextTests {
+
+    @Autowired
+    private SecurityManager securityManager
+
+    @Autowired
+    private EventBus eventBus;
+
+    @Autowired
+    private ShiroFilterFactoryBean shiroFilterFactoryBean
+
+    @Test
+    public void testMinimalConfiguration() {
+
+        // first do a quick check of the injected objects
+        assertNotNull securityManager
+        assertThat securityManager.realms, allOf(hasSize(1), hasItem(instanceOf(TextConfigurationRealm)))
+        assertNull securityManager.cacheManager
+
+        assertNotNull shiroFilterFactoryBean
+        assertThat shiroFilterFactoryBean.filters, anEmptyMap()
+
+        assertSame(((DefaultSecurityManager)securityManager).getEventBus(), eventBus)
+
+        def defaultSecurityManager = (DefaultSecurityManager) securityManager
+        def authorizor = (ModularRealmAuthorizer) defaultSecurityManager.getAuthorizer();
+        assertNull authorizor.rolePermissionResolver
+        assertNull authorizor.permissionResolver
+
+        // now lets do a couple quick permission tests to make sure everything has been initialized correctly.
+        Subject joeCoder = new Subject.Builder(securityManager).buildSubject()
+        joeCoder.login(new UsernamePasswordToken("joe.coder", "password"))
+        joeCoder.checkPermission("read")
+        assertTrue joeCoder.hasRole("user")
+        assertFalse joeCoder.hasRole("admin")
+        joeCoder.logout()
+    }
+
+}
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
index 9d6219e..81e9f11 100644
--- a/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
+++ b/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
@@ -263,20 +263,13 @@
         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:
-
-                Factory<FilterChainResolver> factory = (Factory<FilterChainResolver>) this.objects.get(FILTER_CHAIN_RESOLVER_NAME);
-                if (factory instanceof IniFactorySupport) {
-                    IniFactorySupport iniFactory = (IniFactorySupport) factory;
-                    iniFactory.setIni(ini);
-                    iniFactory.setDefaults(this.objects);
-                }
-                resolver = factory.getInstance();
+            Factory<FilterChainResolver> factory = (Factory<FilterChainResolver>) this.objects.get(FILTER_CHAIN_RESOLVER_NAME);
+            if (factory instanceof IniFactorySupport) {
+                IniFactorySupport iniFactory = (IniFactorySupport) factory;
+                iniFactory.setIni(ini);
+                iniFactory.setDefaults(this.objects);
             }
+            resolver = factory.getInstance();
         }
 
         return resolver;