Simple web application with Spring Security: Part 2

In part 1, we concentrated on creating the project structure and in providing build automation for all the files and code that will be contained in the end artifact (the WAR file).

In part 2 we will create another project that is responsible for containing all the acceptance (functional) tests for the application. We will create our first end-to-end failing test and implement enough code to get it to pass.

Create acceptance tests project On Eclipse

create a java project named: SpringSecurityWebApplicationAcceptanceTests

create structure as shown below:
acctestprojectstructure

Add dependencies to acceptance tests project

  • add junit-4.5.jar to lib/test/junit
  • add webdriver jars (json-20080701.jar, webdriver-common.jar, webdriver-firefox.jar) to lib/test/webdriver

springsecurity_dependencies_part2

Write first failing test for user story 1

Using JUnit and WebDriver, we are going to write a test that simulates user interaction with the browser (in this case firefox).

Remembering the behavior we are trying to add:

User Story 1: A user that is not logged in should be forced to authenticate themselves through a login form when they visit a secure page.

We create a test class called UserStory1AcceptanceTest:

package com.heraclitus.springsecuritywebapp;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;

import org.junit.After;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

/**
 * I contain end-to-end acceptance/functional tests for the simple web
 * application with spring security.
 *
 * I specifically contain tests that verify the behavior of UserStory1.
 */
public class UserStory1AcceptanceTest {

    private final WebDriver driver = new FirefoxDriver();

    @After
    public void aTearDownMethodForEachTest() {
        // close firefox browser that was launch after each test
        driver.close();
    }

    @Test
    public void shouldBeAskedToAuthenticateWhenTryingToVisitASecurePageAndNotLoggedIn() {

        driver.get("http://localhost:8080/springsecuritywebapp/home.htm");

        assertThat(driver.getTitle(),
                is(not("Home: Spring Security Web Application")));
        assertThat(driver.getTitle(),
                is("Login: Spring Security Web Application"));
    }
}

When we execute the test (from within the eclipse IDE Alt + shift + x, then t) we see that it fails with the following:

java.lang.AssertionError:
Expected: is "Login: Spring Security Web Application"
     got: "Apache Tomcat/6.0.18 - Error report"

So instead of arriving on the login page which is what we want, we are getting a tomcat error report because the url of the home page resource doesn’t exist yet. So now we need to implement enough code to get this test passing.

Add spring dependencies to WAR project

  • from the spring download add the following jars to the SpringSecurityWebApplicationWAR/lib folder
    • jstl.jar from spring-framework-2.5.6\lib\j2ee
    • standard.jar from spring-framework-2.5.6\lib\jarakta-tablibs
    • spring.jar from spring-framework-2.5.6\dist
    • spring-mvc.jar from spring-framework-2.5.6\dist\modules
    • commons-logging.jar from spring-framework-2.5.6\lib\jakarta-commons
    • aspectjrt.jar from spring-framework-2.5.6\lib\aspectj
  • from the spring-security download add the following jars to the SpringSecurityWebApplicationWAR/lib folder
    • spring-security-core-2.0.4.jar from spring-security-2.0.4\dist

springsecurity_war_dependencies_part2

Get spring-mvc working

First up we need to create a simple page to represent the home page. We create a new file named home.jsp.

<%@ page session="true"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>

<head>
<title>Home: Spring Security Web Application</title>

</head>

<body>

home page: only logged in users should see this page.

</body>

</html>

This created under the web/WEB-INF/jsp folder. We do this cause it stops users being able to directly access the jsp pages. Instead we will use spring-mvc and create a mapping between the url we use for the home page and the actual jsp file.

To get this to work, we must first edit the web.xml file and configure it to load up spring-mvc’s DispatcherServlet.

Add the following to our existing web.xml

<!--
		Provides core MVC application controller. See
		springsecuritywebapp-servlet.xml.
	-->
	<servlet>
		<servlet-name>springsecuritywebapp</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springsecuritywebapp</servlet-name>
		<url-pattern>*.htm</url-pattern>
	</servlet-mapping>

Any request to the servlet container that matches the url pattern (*.htm) will now be routed through spring mvc’s DispatcherServlet. As mentioned in the comments, the configuration within springsecuritywebapp-servlet.xml will be responsible for how spring mvc interprets this request.

So next we create the springsecuritywebapp-servlet.xml file under web/WEB-INF/

<?xml version="1.0" encoding="UTF-8"?>
<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">

	<!--
		no 'id' required, HandlerMapping beans are automatically detected by
		the DispatcherServlet
	-->
	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<value>
				/*.htm=urlController
            </value>
		</property>
	</bean>

	<bean id="urlController"
		class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />

	<!--
		as we have our jsp in an internal place forcing all requests through
		spring, use viewResolver to save us making reference to internal
		structure everywhere e.g. /WEB-INF/jsp/
	-->
	<bean id="viewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass"
			value="org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="WEB-INF/jsp/" />
		<property name="suffix" value=".jsp" />
	</bean>
</beans>

The above configuration will basically take any url request that matches *.htm pattern and map it to the jsp file. So for home.htm, it will be mapped to home.jsp using the UrlFilenameViewController and InternalResourceViewResolver provided by spring.

If we go back to our build.xml file and deploy the application to tomcat (may need to undeploy first if it is already deployed) it should deploy successfully. Now we can run out automated acceptance test again and see if it is passing now (we don’t expect it to yet).

Again we see if fails but this time we don’t get a tomcat error report we get:

java.lang.AssertionError: 
Expected: is not "Home: Spring Security Web Application"
     got: "Home: Spring Security Web Application"

So the url we are using http://localhost:8080/springsecuritywebapp/home.htm is being mapped to the home.jsp file we have created (good our spring mvc configuration is working correctly). But we are not being asked to authenticate ourselves as expected. This functionality has to be added and we do that next using spring-security (formerly acegi).

Get spring-security working

Spring security uses a filters approach to enforcing security. We integrate spring security into our application, we must edit our web.xml file to use the springSecurityFilterChain and tell our application context to load the security specific configuration which will be created in a seperate configuration file.

Add the following to our existing web.xml file

<!--
		- 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>

We then need to create our applicationContext-security.xml under web/WEB-INF folder.

applicationContext-security.xml

<?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">

	<global-method-security secured-annotations="disabled">
	</global-method-security>

	<http auto-config="true">
		<intercept-url pattern="/**" access="ROLE_USER" />
	</http>

	<authentication-provider>
		<user-service id="userDetailsService">
			<user name="username" password="password" authorities="ROLE_USER, ROLE_ADMIN" />
			<user name="test" password="test" authorities="ROLE_USER" />
		</user-service>
	</authentication-provider>
</beans:beans>

NOTE: we have configured spring security to require that a user be authenticated and have the ROLE_USER authority for all urls/pages on the web application. Thus we should be forced to authenticate ourselves when visiting the home page (home.jsp)

NOTE: we are creating two users, username and test using the authentication-provider and user-service configuration. This is a useful feature for rapid development of simple applications. We can add a database backend to this later in the series.

Build and deploy the code and hopefully our test will pass (not quiet yet!)

Check the test is passing

So the test is still not passing. It is failing with:

java.lang.AssertionError: 
Expected: is "Login: Spring Security Web Application"
     got: "Login Page"

So the title is different. What is happening is that spring security is asking the user to authenticate using its own login page that it generates (we still haven’t created one). Lets modify the test to expect ‘Login Page’ instead

@Test
    public void shouldBeAskedToAuthenticateBySpringLoginPageWhenNotLoggedInAndTryingToVisitASecurePage() {
        
        driver.get("http://localhost:8080/springsecuritywebapp/home.htm");
        
        assertThat(driver.getTitle(),
                is(not("Home: Spring Security Web Application")));
        assertThat(driver.getTitle(), is("Login Page"));
    }

I have updated our assertions and the name of the test to better describe the intended behavior being tested. Running the test again we see the green bar.

Summary

In summary, we have created the infrastructure that allows us to develop our application in a test-driven manner. We started by writing a failing acceptance test based on our user story 1 description. We added the desired behavior using spring mvc and spring security. At the end we have an automated test that describes (to some extent) and verifies the intended behavior.

In the next series we will tackle more of the user stories that describe the desired application behavior.

The code for this part is tagged and available for viewing online at: http://code.google.com/p/spring-security-series/source/browse/#svn/tags/SpringSecuritySeriesWAR-Part2

SVN Url: https://spring-security-series.googlecode.com/svn/tags/SpringSecuritySeriesWAR-Part2

Advertisements

One Response to “Simple web application with Spring Security: Part 2”

  1. Learner Says:

    webAppRootKey
    exploringspringsecurity_root

    What this for guys? If I dont use it, is there any problem?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: