Spring Security 3 Hello World Example

Security is of great concern in any web application. If you are looking for a proven and industry standard solution to secure your Java/J2ee based application, then widely used and highly customizable authentication and access control framework - Spring Security is well worth considering.

This post will show all the steps to setup, configure and integrate Spring Security 3 to protect your web application from being breached using a simple hello world example.

Tools and Technologies used in this article

  1. Spring Framework 3.1.4
  2. Spring Security 3.1.4
  3. Spring Tool Suite 3.2
  4. JDK 1.6
  5. Tomcat 7

Note : Spring 3 requires at least JDK 5. So, make sure you have JDK 5 or above.

1. Initial Spring 3 MVC Web Application

We'll start with creating (Refer Spring 3 MVC Framework Based Hello World Web Application Example) a simple Spring 3 MVC project (say SpringSecurityHelloWorld). There will be two pages (say public.jsp and mypage.jsp), one controller (SpringSecurityHelloController) with two handler methods and Spring Configuration File (dispatcher-servlet.xml).
File: WEB-INF/pages/public.jsp

<html>
<title>Public Page</title>
<body>
<h4>${message}</h4>
</body>
</html>

File: WEB-INF/pages/secured/mypage.jsp

<html>
<title>My Secured Page</title>
<body>
<h2>Hello World!</h2>
<h4>${message}</h4>
</body>
</html>

File: com/srccodes/spring/controller/SpringSecurityHelloController.java

package com.srccodes.spring.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
 
/**
 * @author Abhijit Ghosh
 * @version 1.0
 */
 
@Controller
public class SpringSecurityHelloController {
 
    @RequestMapping("/public")
    public String accessPublicPage(Model model) {
        model.addAttribute("message", "This page is publicly accessible. No authentication is required to view.");
 
        return "public";
    }
     
    @RequestMapping("/secured/mypage")
    public String accessSecuredPage(Model model) {
        model.addAttribute("message", "Only you are authenticated and authorized to view this page.");
 
        return "/secured/mypage";
    }
}

File: WEB-INF/dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<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"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
 
    <context:component-scan base-package="com.srccodes.spring.controller" />
    <mvc:annotation-driven />
     
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

Find below the screenshot of the project structure of our initial Spring 3 MVC Web Application

So far there is no security and anybody can access both public and secured pages without login. We'll integrate Spring Security with our initial web application so that page 'public.jsp' remains publicly accessible but to access the secured page 'mypage.jsp', user needs to login.

2. Add Spring Security Maven Dependencies

Add Spring Security Maven Dependencies in Maven pom.xml.

File: pom.xml

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.srccodes.spring</groupId>
  <artifactId>SpringSecurityHelloWorld</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringSecurityHelloWorld</name>
  <url>http://maven.apache.org</url>
   
  <properties>
        <org.springframework.version>3.1.4.RELEASE</org.springframework.version>
        <spring-security.version>3.1.4.RELEASE</spring-security.version>
    </properties>
 
    <dependencies>
        <!-- Spring MVC depends on spring-core, spring-beans, spring-context, spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
         
        <!-- Spring Security Dependencies -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security.version}</version>
         </dependency> 
         <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security.version}</version>
          </dependency> 
          <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security.version}</version>
          </dependency>
    </dependencies>
 
    <build>
        <finalName>SpringSecurityHelloWorld</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Note: If you use Spring Framework 3.2.x and Spring Security 3.1.x (or less), then you may encounter Spring Asm Dependency Issue: java.lang.IncompatibleClassChangeError.

3. Spring Security configuration

Create a separate spring security xml and add following configuration to enable Spring security.

File: WEB-INF/spring-security.xml

<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-3.2.xsd
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-3.1.xsd">
           
    <http auto-config='true'>
      <intercept-url pattern="/secured/*" access="ROLE_USER" />
    </http>
       
    <authentication-manager>
      <authentication-provider>
        <user-service>
          <user name="srccodes" password="password" authorities="ROLE_USER" />
        </user-service>
      </authentication-provider>
    </authentication-manager>   
           
</beans:beans>

Note:
defines a pattern for request URLs which need to be secured. Attribute access defines roles of an user who is authorised to see requested URLs matching with that pattern. auto-config='true' automatically enables form based login, basic authentication and logout mechanism.

handles authentication of requests and uses the mechanism provided by to authenticate an user. To make the example simple, I have defined one hardcoded user with username as "srccodes", password as "password" and authorities as "ROLE_USER". authorities can take comma separated list of roles assigned to the particular user.


4. Integration of Spring Security

Spring Security is entirely based on servlet filter. We need to declare a filter called DelegatingFilterProxy in web.xml.
File: WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
 
<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">
 
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
     
    <!-- Spring context files to be loaded -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/dispatcher-servlet.xml,
            /WEB-INF/spring-security.xml
        </param-value>
    </context-param>
     
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     
     
    <!-- filter declaration for Spring Security -->
    <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>
</web-app>   

DelegatingFilterProxy is actually a filter proxy which delegates filter's methods to a Spring managed bean (by default named as "springSecurityFilterChain") which implements javax.servlet.Filter. Name of this bean must same with the in web.xml. Otherwise you may get following error during start up of the web application. Lets say is 'XXXXX'.

Server Console

Jul 7, 2013 9:53:14 PM org.springframework.security.web.DefaultSecurityFilterChain <init>
INFO: Creating filter chain: org.springframework.security.web.util.AnyRequestMatcher@1, [org.springframework.security.web.context.SecurityContextPersistenceFilter@34cf935a, org.springframework.security.web.authentication.logout.LogoutFilter@27d314cc, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@12fa7181, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@7831d5e2, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@fd12614, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6c1af328, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5d51fe8a, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@321e45e3, org.springframework.security.web.session.SessionManagementFilter@617e53c9, org.springframework.security.web.access.ExceptionTranslationFilter@2caee320, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@dc160cb]
Jul 7, 2013 9:53:14 PM org.springframework.security.config.http.DefaultFilterChainValidator checkLoginPageIsntProtected
INFO: Checking whether login URL '/spring_security_login' is accessible with your configuration
Jul 7, 2013 9:53:14 PM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 13612 ms
Jul 7, 2013 9:53:14 PM org.apache.catalina.core.StandardContext filterStart
SEVERE: Exception starting filter XXXXX
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'XXXXX' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:549)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1109)
    at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:326)
    at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:236)
    at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:194)
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:281)    

Note : In web.xml, filter init-param "targetBeanName" can be used to specify the name of the target Spring bean defined in the application context.

5. Overall Project Structure

6. Demo

Now we'll test what we have achieved so far. Start the server and deploy the web application. Open the url http://:/SpringSecurityHelloWorld/public. You'll be able to see the publicly accessible "public.jsp" page.

So far so good. Now we'll try to open our secured / protected page (mypage.jsp) at http://:/SpringSecurityHelloWorld**/secured/mypage**. Oops!!!. We have been intercepted by DelegatingFilterProxy and redirected to spring defined login form at http://:/SpringSecurityHelloWorld**/spring_security_login**.

Note: If user defined login form is not supplied, then Spring will automatically create one for authentication.

Supposedly we should not be able to view the secured page without valid username and password. So, let's first try with wrong credentials.

Oops!!!. Spring security has caught us again. It is also showing error message "secured page without valid username and password." with "Reason: Bad credentials". Now we have no option left but to try with correct username (srccodes) and password (password). But this time Spring security will redirect us to the initially requested URL and we'll be able to view the content of the secured page.

Download SrcCodes

All code samples shown in this post are available on GitHub.

References