In the last few pages on spring security series, I was about to tackle method-level security and domain-instance security using spring security; But before doing this, I am going to go back to basics and explore the basics of spring security in a deeper way.
Back to basics
We are going to revert to simplest configuration first of all:
In our springsecuritywebapp-servlet.xml file, we remove the configuration for the aclDemoController for now and any reference to it within the file.
In our web.xml:
We configure a listener to load the context of the web application at start up. This will load our applicationContext-security.xml file.
We register a filter named springSecurityFilterChain that delegates all requests matching the url pattern ‘/*’ to a DelegatingFilterProxy.
<!-- - Location of the XML file that defines the root application context - Applied by ContextLoaderListener. --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF\applicationContext-security.xml </param-value> </context-param> <!-- - Loads the root application context of this web app at startup. - The application context is then available via - WebApplicationContextUtils.getWebApplicationContext(servletContext). --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
In our applicationContext-security.xml we leave it empty like so:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="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 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> </beans:beans>
Build and deploy
The application builds ok and deploys to tomcat. However it fails when starting up on tomcat due to:
SEVERE: Error filterStart SEVERE: Context [/springsecuritywebapp] startup failed due to previous errors
Looking in the tomcat logs we see the following error:
23/12/2008 18:33:28 org.apache.catalina.core.StandardContext filterStart SEVERE: Exception starting filter springSecurityFilterChain org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:387) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:971) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:246) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:168) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:884) at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:216) at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:145) at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:179)
Clearly the problem is that we haven’t configured any bean named ‘springSecurityFilterChain’.
Fixing springSecurityFilterChain problem
If we use the <http> configuration element it should auto create a default springSecurityFilterChain.
We update our applicationContext-security.xml to look like so:
<http auto-config="false" session-fixation-protection="none"> </http>
When we build and deploy this we get another problem:
org.apache.catalina.core.StandardContext listenerStart SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: No AuthenticationEntryPoint could be established. Please make sure you have a login mechanism configured through the namespace (such as form-login) or specify a custom AuthenticationEntryPoint with the custom-entry-point-ref attribute Offending resource: ServletContext resource [/WEB-INF/applicationContext-security.xml] at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:72)
So spring security is failing fast as it expects us to have some form of authentication mechanism:
We update our applicationContext-security.xml to look like so:
<http auto-config="false" session-fixation-protection="none"> <form-login login-page="/login.jsp" default-target-url="/home.htm" /> </http>
Building and deploy, we see another problem reported on tomcat:
org.apache.catalina.core.StandardContext listenerStart SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_authenticationManager': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: No authentication providers were found in the application context
So spring security expects an authentication provider to be configured to facilitate the form login via an AuthenticationManager that is being automatically created by spring security and named _authenticationManager.
So we update our applicationContext-security.xml again to add the following snippet:
<authentication-provider> <user-service id="userDetailsService"> <user password="password" name="username" authorities="ROLE_USER" /> </user-service> </authentication-provider>
Building and deploying again, we see from the tomcat logs that the application has started successfully. The authentication through form login (which is all we have configured) works as it should.
How it works behind the scenes
When the applications starts correctly using the configuration laid out as above, see see from the tomcat logs the following details:
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@195afdb: defining beans [ _authenticationManager, _filterChainProxy, _httpSessionContextIntegrationFilter, _filterChainProxyPostProcessor, _filterChainList, _securityContextHolderAwareRequestFilter, _accessManager, _portMapper, _exceptionTranslationFilter, _filterSecurityInterceptor, _formLoginFilter, _formLoginEntryPoint, _entryPointInjectionBeanPostProcessor, _userServiceInjectionPostProcessor, org.springframework.security.providers.dao.DaoAuthenticationProvider#0, userDetailsService, org.springframework.security.config.AuthenticationProviderBeanDefinitionParser$AuthenticationProviderCacheResolver#0 ]; root of factory hierarchy
What is happening is that from that small amount of configuration, spring security is instantiating a number of beans to enable the functionality. Of special interest is the _filterChainProxy. From the logs, we see the FilterChainProxy is configured as:
INFO: FilterChainProxy: FilterChainProxy[ UrlMatcher = org.springframework.security.util.AntUrlPathMatcher[requiresLowerCase='true']; Filter Chains: {/**=[ org.springframework.security.context.HttpSessionContextIntegrationFilter[ order=200; ], org.springframework.security.ui.webapp.AuthenticationProcessingFilter[ order=700; ], org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter[ order=1100; ], org.springframework.security.ui.ExceptionTranslationFilter[ order=1400; ], org.springframework.security.intercept.web.FilterSecurityInterceptor@1cd6a32] }]
Following a HTTP Request
As an example, I try to visit the following page on the application: http://localhost:8080/springsecuritywebapp/login.jsp. This requests starts off its journey navigating through the apache tomcat container:
Apache tomcats ApplicationFilterChain.doInternalChain method retrieves filter configured in our web.xml.
ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.web.filter.DelegatingFilterProxy]
It then uses the filter [ of type DelegatingFilterProxy] and invokes doFilter on this passing the request and response types. As the DelegatingFilterProxy’s javadoc states:
This approach is particularly useful for Filter implementation with complex setup needs, allowing to apply the full Spring bean definition machinery to Filter instances. Alternatively, consider standard Filter setup in combination with looking up service beans from the Spring root application context.
And this is its purpose here, to delegate to a FilterChainProxy for which a bean is already instantiated (_filterChainProxy). This is then responsible for filtering the request through each of filters that are part of its VirtualFilterChain.
From our initial configuration set up for spring security, we have have five filters that are part of FilterChainProxys VirtualFilterChain. These are in order of execution:
-
HttpSessionContextIntegrationFilter
-
AuthenticationProcessingFilter
-
SecurityContextHolderAwareRequestFilter
-
ExceptionTranslationFilter
-
FilterSecurityInterceptor
In the next part I will talk about what happens to our request to reach http://localhost:8080/springsecuritywebapp/login.jsp in each of these filters.