Spring Security 5 Overcomplicated

By | September 28, 2018

In this article I will show how to create a Spring MVC web application that uses Spring Security 5 but that does not use SpringBoot.

The method used to configure Spring Security 5 in this article is not something I recommend and really is doing things in an overly complicated manner. It is purely a learning experience and has been a part of the coding exercises I have undertaken when writing my book Core Spring 5 Certification in Detail.

The complete example application is available on GitHub.

Set Up Tomcat

True to my tradition, this will be a complete example and as so, it is necessary to prepare the Apache Tomcat instance that will be used to host the example web application.
In this article, it is assumed that you are running a Tomcat instance on localhost.

  • Download Apache Tomcat 9.
  • Unpack the archive at the location of your choice.
  • Open the file tomcat-users.xml in the conf directory and add the following XML fragment inside the <tomcat-users> element:

  • Start Tomcat.
    Run the startup.sh or startup.bat script in the bin directory.
  • Tail the catalina.out log in the logs directory.
    In a bash or sh shell, this can be accomplished with the command tail -f catalina.out

Create the Project

In your development environment, create an empty Maven project and replace the contents of the pom.xml file in the root of the project with this:

Note that:

  • The first part of the pom.xml file contains the dependencies needed for the project.
    These are Spring, Spring Security, Servlet API and logging dependencies. No Spring Boot dependencies.
  • The contents of the <build> element contains three plug-ins.
  • The first plug-in, the maven-war-plugin, packages the built project into a WAR file which can be deployed to Tomcat, for instance.
  • The second plug-in, the tomcat7-maven-plugin, aids us in deploying the project’s WAR file to Tomcat.
    Note the configuration specifying the URL, user name and password to use when communicating with the Tomcat instance.
  • The last plug-in, the maven-compiler-plugin, makes it possible to specify the Java version that the project will be configured for.

Web Resources

The example application contains the following web resources located in the src/main/webapp directory:

Web resources of the Spring Security 5 Overcomplicated example application.

Web resources of the Spring Security 5 Overcomplicated example application.

Create index.jsp

The index.jsp file contains what will be the only resource in the example application that is accessible without having logged in.

  • Create the index.jsp file in the src/main/webapp directory with the following contents:

Create login.html

The login.html file contains the login form which, when Spring Security has been enabled, will be presented to users not having logged in trying to access a secured resource of the application.

It is necessary to have, what is commonly referred to as, a custom login page in an application that uses Spring Security in order for logging out to work properly.
If the browsers built-in basic authentication login mechanism is used, the user name and password will be stored in the browser and enclosed with each request to the web application thus making it impossible to log out properly.

  • Create the login.html file in the src/main/webapp/static directory with the following contents:

Create test.html

The test.html page is just a static webpage included in the example application in order to show how to secure static contents in a web application.

  • Create the test.html file in the src/main/webapp/static directory with the following contents:

Create the Hello Controller

The hello controller is included in the example application as a representative of non-static web resource. A REST controller could have been used, but I wanted something that can be viewed in a web browser.

  • In the package se.ivankrizsan.spring.web, create the HelloController class implemented like this:

Note that:

  • As the comment says, the HelloController class is not annotated with the @Controller annotation.
    This results in the controller not being autodetected and thus a Spring bean implemented by the HelloController class needs to be explicitly created, as we will see later when we examine the Spring Java configuration files.

MyWebApplicationInitializer Without Security

The web application initializer uses the servlet-3 way of initializing a Java web application using nothing but plain Java code. The first incarnation of the web application initializer in the example program, the version without security, looks like this:

Note that:

  • I have given way to some degree of convenience by using the class AbstractAnnotationConfigDispatcherServletInitializer as superclass to the above configuration class.
    To be even more hardcore in a servlet 3 environment, one can implement the WebApplicationInitializer interface and create the Spring application context as well as the dispatcher servlet.
    This abstract superclass takes care of initializing the dispatcher servlet in a servlet 3 environment and creating the root, and optionally the application, Spring contexts supplying a number of methods that can be overridden to customize the process along the lines of the template method design pattern.
  • The method getRootConfigClasses is overridden and returns a configuration class.
    This configuration class, WebConfiguration, contains the web-related Spring beans of the example application and will be examined in a moment.
    The getRootConfigClasses method is to return the configuration classes which will make up the root Spring application context of the web application.
  • The method getServletConfigClasses is overridden and return null.
    This method is abstract in the parent class so it must be overridden. Null is returned since all the beans of this very small example application are located in the root Spring application context.
  • The method getServletMappings is overridden and return a string array containing only “/”.
    This method is also abstract in the class where it is defined (AbstractDispatcherServletInitializer) so it must be implemented. With “/” as the only mapping, the root path of the web application is mapped to the dispatcher servlet.

WebConfiguration

I have separated the web configuration and the security configuration. The web configuration class is located in the same package as the web application initializer seen above and is implemented like this:

Note that:

  • The configuration class is annotated with @EnableWebMvc.
    Imports the Spring MVC configuration from WebMvcConfigurationSupport.
  • The configuration class implements the WebMvcConfigurer interface.
    The WebMvcConfigurer interface defines a number of callback methods, with default implementations that do nothing, that allows for customizing the Spring MVC configuration when the @EnableWebMvc annotation is used.
  • A Spring bean is created using the HelloController class implemented earlier.
    As before, since the controller class is not annotated with @Controller a bean needs to be created like this in order for the controller to be discovered by Spring MVC.
  • There is a method addResourceHandlers that override a method from the WebMvcConfigurer interface.
    This method allows for adding resource handlers to the supplied resource handler registry.
    In this example a resource handler is added in order for the application to serve static content (HTML pages in this application) located in the webapp/static directory when there is a request to /static/ in the web application or any underlying resource.

First Run – Without Security

The first version of the application, a version without any security, is now ready. To try it out in the local Tomcat instance do the following, assuming that Tomcat is running:

  • Open a terminal window.
  • Go to the root directory of the example project.
  • Build and push the example web application using the following command:
    mvn clean tomcat7:redeploy
    This works both the first time the application is deployed and also for subsequent re-deploys.
  • Open the URL http://localhost:8080/springsecuritynoboot/ in a browser.
    A message saying “This is the Main Page” should appear with three links below it.
  • Click the “Get a Greeting from a web controller” link.
    A greeting message should appear.
  • Press the back button in the browser.
  • Click the “Static contents: test.html” link.
  • A message should appear saying “Hello, this is the static test page!”.
  • Click the “Back to Main Page” link.
  • Finally, click the “Logout” link.
    The result should be a HTTP status 404 – Not Found.

We have now taken the example web application for a spin and can note that all parts of it are accessible without having to login or authenticate in any other way.

Add (Spring) Security

Some parts of adding security to the example web application have already been made, such as adding a login page and the necessary Spring Security dependencies. The time has now come to add some Java configuration as to activate Spring Security.

Add log4j2.xml

Before looking at the security configuration, I’ll add a Log4J2 configuration file that will cause Spring Security to write debug-level logging.

  • In src/main/resources create a file named log4j2.xml with the following contents:

Add Web Security Configuration

Now we’ve come to the core of this article, the part in which I show you how not to use Spring Security 5 – the web security configuration. It is quite extensive, so let’s dive right in.

  • In the package se.ivankrizsan.spring.web create a file named WebSecurityConfiguration.java with this contents:

Note that:

  • The configuration class is long, very long.
    This is due to the fact that I have deliberately avoided using Spring Boot and most means of aid in configuration supplied by Spring Security.
  • The configuration class is annotated with @EnableWebSecurity.
    This annotation enables web security and allow the configuration class to either implement the WebSecurityConfigurer interface or extend the class WebSecurityConfigurerAdapter. The latter class does implement the former interface.
  • The debug attribute of the @EnableWebSecurity annotation is set to true.
    Setting this attribute to true enables debug support in Spring Security.
    This in combination with setting the debug level to DEBUG in the Log4J2 configuration file earlier is done in order to have additional, more detailed, logging from Spring Security when running the example web application.
  • The configuration class extend the WebSecurityConfigurerAdapter class.
    The WebSecurityConfigurerAdapter class sets up a default configuration of Spring Security 5 for a web application and provides a number of methods that can be overridden in a subclass in order to customize the security configuration.
  • There is a default constructor in the configuration class.
    This constructor calls the superclass constructor enclosing a the boolean true that instructs the superclass to disable default configuration. This is in order to forcing this configuration class to have to perform all necessary configuration of Spring Security.
  • There is a method named springSecurityFilterChain that creates a Spring bean with the same name.
    The Spring Security filter chain where all the security filters, all parameters except the HttpFirewall, are brought together. It is a servlet filter through which all requests to the application will pass.
  • A Spring Security filter chain contain one or more filter chains.
    Each filter chain has a request matcher that determines whether the filters in the filter chain are to be applied to a request or not.
  • In the springSecurityFilterChain method there are two filter chains created.
  • The first filter chain has a request matcher configured with the URL pattern “/**”, which matches all requests to the application.
    This filter chain has a minimum set of Spring Security filters configured that will ensure that only logged in users can access the pages/resources in the application.
  • The second filter chain has a request matcher configured with the URL pattern “/static/login.html”, which match only the login page URL.
    This filter chain has no filters configured, which means that it will allow all requests to the login page of the application.
  • In the springSecurityFilterChain method an instance of FilterChainProxy is created.
    A list containing the two filter chains created earlier is passed as parameter to the constructor. The order of the filter chains in the list is significant – the login filter chain is before the default filter chain, otherwise the default filter chain would match all requests and users of the application would never be able to login.
    The FilterChainProxy instance is what will be the bean instance created in this method.
  • In the springSecurityFilterChain method, after the FilterChainProxy instance has been created, a HTTP firewall instance is set on the FilterChainProxy instance. The HttpFirewall instance is a bean that is autowired into this bean method. Details on the HTTP firewall bean will be discussed later.
  • Finally in the springSecurityFilterChain method, the FilterChainProxy instance is wrapped in a DebugFilter instance.
    The debug filter, commonly only used when debugging or during development, generates additional log when requests are received.
  • The next bean is of the type LogoutFilter.
    The logout filter bean contains one logout handler, a CompositeLogoutHandler, which in turn contains two logout handlers that will be invoked when a user logs out of the application. The first logout handler, the SecurityContextLogoutHandler, will invalidate the HTTP session and clears the authentication from the security context. The second logout handler, the CookieClearingLogoutHandler, clears the JSESSIONID cookie.
    In addition to the composite logout handler being supplied to the logout filter, an URL to which the filter will redirect upon a successful logout.
  • The next bean is of the type SecurityContextPersistenceFilter.
    This bean is responsible for retrieving information from a security context repository and storing it in the security context prior to a request being processed and then store the information back in the repository once the request has completed.
  • The next bean is a ExceptionTranslationFilter bean.
    This bean translates security-related exceptions to HTTP responses. A login page URL is configured on the exception translation filter, as to ensure redirection to the login page in the case where the user is not authorized to view a page or access a resource.
  • The filterSecurityInterceptor bean handles security for web pages and resources using the an authentication manager and an access decision manager.
    In addition a security metadata source is created, and set on the FilterSecurityInterceptor, which is configured to allow users in the ADMIN role to access any webpage or resource (matching the URL pattern /**) in the application.
  • The accessDecisionManager bean is responsible for making access control decisions.
    The particular type of access decision manager is a AffirmativeBased access decision manager which holds a number of access decision voters and allows access if any (one) of these voters returns an affirmative response.
  • The ability to log in using a username and a password is provided by the next bean, the usernamePasswordAuthenicationFilter bean.
    On this bean the login URL to which login requests are POSTed is also set. The default is /login but since I have placed the login webpage in webapp/static/login.html, the POST request will be made to /static/login instead of just /login.
  • The authenticationManager bean is responsible for authenticating users.
    It accomplishes this by delegating to one or more authentication providers. In this example, just one single authentication provider is used.
  • The next bean, authenticationProvider, is the single authentication provider in this application.
    The DaoAuthenticationProvider instance allows for setting a password encoder used when encoding and validating passwords. Such a password encoder could calculate the hash of the password, in order to avoid storing the password in plain-text. In this example, the NoOpPasswordEncoder is used. As the name implies, it does nothing, which makes it unsuitable for use in a production environment.
  • The userDetailsService bean is responsible for reading user information into memory.
    Normally, user data, such as login name and password, is stored in a database or such. In this example the user data is only retained in memory by the InMemoryUserDetailsManager. A user with the login name “admin”, the password “secret” and the role “ADMIN” is also created and added to the user details service.
  • The final bean is the customHttpFirewall.
    The reason why a custom HTTP firewall bean needs to be created is in order to allow the use of semicolon in URLs, which Spring MVC normally does not allow.

Update the Web Application Initializer

The web application initializer implemented in the class MyWebApplicationInitializer needs to be updated as to accomplish two more things:

  • Add the web security configuration class to the list of classes that will make up the application root Spring context.
  • Add a servlet filter, implemented by DelegatingFilterProxy, that will delegate web request filtering to the Spring Security filter chain bean.

Since the MyWebApplicationInitializer class is relatively short, the complete updated version of the class is listed here, with the additions highlighted.

Second Run – With Security

The example application is now ready for another test-run.
If you want to see some additional information concerning what is happening behind the scenes when you issue requests to the example application, open a terminal window and tail the catalina.out log.

  • Open a terminal window.
    The terminal in IntelliJ IDEA can also be used.
  • Go to the root directory of the example project.
  • Build and push the example web application using the following command:
    mvn clean tomcat7:redeploy
    This works both the first time the application is deployed and also for subsequent re-deploys.
  • Open the URL http://localhost:8080/springsecuritynoboot/ in a browser.
    A message saying “This is the Main Page” should appear with three links below it.
  • Click the “Get a Greeting from a web controller” link.
    The login page with a message saying “You need to login to access that page” should appear.
  • Enter the user name “admin” and the password “secret”, without quotes, and click the Sign In button.
    The greeting from the web controller should appear.
  • Click the browser’s back button twice.
  • Click the “Static contents: test.html” link.
    A message should appear saying “Hello, this is the static test page!”.
    Since you have logged in, you should be able to access this page too.
  • Click the “Back to Main Page” link.
  • Click the “Logout” link.
    You will be redirected to the Main Page.
  • Click the “Static contents: test.html” link.
    The login page should appear again. Since you logged out, you are no longer authorized to view the test.heml page.

We see that the secured resources and web pages in the example application are now indeed unaccessible without having presented a valid user name and password. Updating the web application initializer and adding the web security configuration does successfully add security to the web application without having alter the application itself.

Final Words

Before leaving this overcomplicated Spring Security 5 example, let’s have a look at the relationships between the Spring beans and additional objects created by the web security configuration and how these are connected to the servlet mechanism:

Spring Security 5 configuration in the example program.

Spring Security 5 configuration in the example program (click to enlarge).

The green rectangles are Spring beans, the blue rectangles are Java objects – both created as a result of the web security configuration.
This concludes this article. I hope you have gained additional understanding of Spring Security 5 and what tremendous aid it supplies when adding security to web applications.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *