Simple web application with Spring Security: Part 5

In part 5 of the series we will continue to add more behavior to our web application based on the following specifications:

User Story 7: Create common navigation that all secure pages will contain.
Note: There will be links to home, admin pages and a logout link

User Story 8: Add support for users to logout. When a user logs out they should go back to the login page. A message should inform the user that they have successfully logged out

User Story 9: A user should be only able to log on to the application once. Concurrent sessions should not be allowed. Inform user when login fails due to concurrent login problem.

Create common navigation

We want a simple navigation that is common across all secure pages. It will contain links to home, admin and a logout link to allow users to logout.

Step 1: Create failing acceptance tests

first off we are going to create a convenience class that will help clear up the acceptances.

Convenience class:

package com.heraclitus.springsecuritywebapp.dsl;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

/**
 * I contain convenience methods for navigating the spring security web app.
 */
public class NavigatingSpringSecurityWebApp {

    public static void login(final WebDriver driver) {
        // login on login page
        final WebElement usernameField = driver.findElement(By
                .name("j_username"));
        usernameField.sendKeys("username");

        final WebElement passwordField = driver.findElement(By
                .name("j_password"));
        passwordField.sendKeys("password");

        passwordField.submit();
    }

}

now create the test(s):

@Test
    public void shouldBeAbleToSeeCommonNavigationOnAdminPage() {

        // try to access admin
        driver.get("http://localhost:8080/springsecuritywebapp/admin.htm");

        login(driver);

        // verify
        assertThat(driver.getTitle(),
                is("Admin: Spring Security Web Application"));
        assertNotNull(driver.findElement(By.linkText("Home")));
        assertNotNull(driver.findElement(By.linkText("Admin")));
        assertNotNull(driver.findElement(By.linkText("Logout")));

    }

    @Test
    public void shouldBeAbleToSeeCommonNavigationOnHomePage() {

        // try to access admin
        driver.get("http://localhost:8080/springsecuritywebapp/home.htm");

        login(driver);

        // verify
        assertThat(driver.getTitle(),
                is("Home: Spring Security Web Application"));
        assertNotNull(driver.findElement(By.linkText("Home")));
        assertNotNull(driver.findElement(By.linkText("Admin")));
        assertNotNull(driver.findElement(By.linkText("Logout")));

    }

Step 2: create navigation.jsp

<div id="navcontainer">
<ul id="navlist">
    <li class="home"><a href="home.htm">Home</a></li>
    <li><a href="admin.htm">Admin</a></li>
    <li><a href="logout.htm">Logout</a></li>
</ul>
</div>

Step 3: include navigation.jsp in home.jsp and admin.jsp like so:

<body>
<%@ include file="/WEB-INF/jsp/navigation.jsp" %>
.
.

Build, deploy, run tests and verify all are passing.

Add logout support to application

As described by:

User Story 8: Add support for users to logout. When a user logs out they should go back to the login page. A message should inform the user that they have successfully logged out

Step 1: Create failing acceptance test

@Test
    public void shouldBeAbleToLogoutOfApplicationAndSeeMessageThatConfirmsThis() {

        // try to access admin
        driver.get("http://localhost:8080/springsecuritywebapp/home.htm");

        login(driver);

        // verify
        final WebElement logoutLink = driver.findElement(By.linkText("Logout"));
        logoutLink.click();

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

        final WebElement informationMessageSection = driver.findElement(By
                .id("infomessage"));
        assertThat(informationMessageSection.getText(),
                containsString("You have been successfully logged out."));
    }

Step 2: edit applicationContext-security.xml

<http auto-config="true">
	    <intercept-url pattern="/login.jsp" filters="none" />
		<intercept-url pattern="/**" access="ROLE_USER" />
		<form-login login-page="/login.jsp" default-target-url="/home.htm"
		always-use-default-target="false" authentication-failure-url="/login.jsp?authfailed=true" />
		<logout invalidate-session="true" logout-url="/logout.htm" logout-success-url="/login.jsp?loggedout=true"/>
.
.

Step 3: update login.jsp to display logout message:

<%@ 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>Login: Spring Security Web Application</title>

<style TYPE="text/css">
.errormessage {
   color:red;
}

.successmessage {
}
</style>
</head>

<body onload='document.loginForm.j_username.focus();'>

<form id="loginForm" name="loginForm" action="j_spring_security_check" method="post">
<c:if test="${not empty param.authfailed}">
    <span id="infomessage" class="errormessage" >
    Login failed due to: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
    </span>
</c:if>
<c:if test="${not empty param.loggedout}">
    <span id="infomessage" class="successmessage">
    You have been successfully logged out.
    </span>
</c:if>
        <table>
          <tr><td>Username</td><td><input id="usernameField" type="text" name="j_username" value="<c:out value="${SPRING_SECURITY_LAST_USERNAME}"/>"/></td></tr>
          <tr><td>Password</td><td><input id="passwordField" type="password" name="j_password" /></td></tr>

          <tr><td colspan="2" align="right"><input type="submit" value="Login" /></td></tr>
        </table>
</form>

</body>

</html>

Build, deploy and run all tests to verify our new test passes and all tests are still working as expected.

Add support for concurrent session control to application

As described by:

User Story 9: A user should be only able to log on to the application once. Concurrent sessions should not be allowed. Inform user when login fails due to concurrent login problem.

Spring security allows us to easily had concurrent session control to our application. We just need to update our applicationContext-security.xml file like so:

<http auto-config="true">
	    <intercept-url pattern="/login.jsp" filters="none" />
		<intercept-url pattern="/**" access="ROLE_USER" />
		<form-login login-page="/login.jsp" default-target-url="/home.htm"
		always-use-default-target="false" authentication-failure-url="/login.jsp?authfailed=true" />
		<logout invalidate-session="true" logout-url="/logout.htm" logout-success-url="/login.jsp?loggedout=true"/>

		<concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
	</http>

modify your web.xml to register the following listener:

<listener>
		<listener-class>
			org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
	</listener>

to test this we have to simulate a browser being opened, user logging in successfully using a users credentials, then we close the browser and open a new one and try to login again using the same user details, it should detect that this user already has a session and reject the login attempt.

First lets run all tests to verify this change doesn’t break anything. A good number of our tests have failed because those that required a logged in user never had to worry about logging out before. We need to separate out those acceptance tests that require no login and have a setup and teardown method handle logging in and logging out.

Acceptance test for concurrent session

@Test
    public void shouldOnlyAllowOneConcurrentSessionPerUser() throws Exception {

        driver.get("http://localhost:8080/springsecuritywebapp/login.jsp");

        // login on login page
        final WebElement usernameField = driver.findElement(By
                .name("j_username"));
        usernameField.sendKeys("test");

        final WebElement passwordField = driver.findElement(By
                .name("j_password"));
        passwordField.sendKeys("test");

        passwordField.submit();

        // close browser session
        driver.close();

        driver = new FirefoxDriver();
        // try to log in again.
        driver.get("http://localhost:8080/springsecuritywebapp/login.jsp");

        // login on login page
        final WebElement usernameField2 = driver.findElement(By
                .name("j_username"));
        usernameField2.sendKeys("test");

        final WebElement passwordField2 = driver.findElement(By
                .name("j_password"));
        passwordField2.sendKeys("test");

        passwordField2.submit();

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

        final WebElement informationMessageSection = driver.findElement(By
                .id("infomessage"));
        assertThat(
                informationMessageSection.getText(),
                containsString("Login failed due to: Maximum sessions of 1 for this principal exceeded."));
}

Getting the code

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

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

3 Responses to “Simple web application with Spring Security: Part 5”

  1. Chris Huynh Says:

    How do I detect if the user currently have an active session so that I can display a warning message and provide option for user to open a new session or close the current active session. Please advise.

    Regards,
    Chris Huynh

  2. jack Says:

    it is the same to fail in logining.

  3. peliz77 Says:

    I get this messge : Login failed due to: Maximun sessions of {0} for this principal exceeded.

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


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: