Simple Spring Security Webapp
Overview
Simple Spring Security 2.0 example securing a webapp based on the
Simple Spring MVC Form Annotation Configuration Webapp. All URLs are restricted to valid users except the login, logoff, and style sheet. Only admins have the ability to delete a record. A non-admin doesn't see the link on the search page to delete a record and also calling the delete method on the DAO is restricted to admins.
View a working demo of the application at
http://www.springbyexample.org/simple-security/.
Web Configuration
/WEB-INF/web.xml
The 'springSecurityFilterChain' Filter needs to be configured to intercept all URLs so Spring Security can control access to them. The filter must be named this to match the default bean it retrieves from the Spring context.
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>simple-security</display-name>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/security-applicationContext.xml,
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<!-- Enables Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>simple-form</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>simple-form</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Spring Configuration
/WEB-INF/security-applicationContext.xml
The
security:global-method-security element configures annotation based security so
@Secured can be used to restrict access to methods.
The
security:http is set to auto-configure basic HTTP security. Inside the the login, logout, and main style sheet are set to the anonymous role (unrestricted access). The rest of the site is restricted to an authenticated user in the user role. The default login and logout configuration is also customized to use custom pages to maintain the sites look & feel.
The authentication is set to use jdbc based user authentication. Only the
DataSource needs to be set on the
security:jdbc-user-service element if the default tables are used. Although other tables can be used by setting custom queries on the element. If you look below the
security:jdbc-user-service element, you will see a static configuration of users which might be more convenient for a small application or during testing.
<?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:security="http://www.springframework.org/schema/security"
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.xsd">
<security:global-method-security secured-annotations="enabled" />
<security:http auto-config="true">
<!-- Restrict URLs based on role -->
<security:intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:intercept-url pattern="/logoutSuccess*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:intercept-url pattern="/css/main.css" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!-- Override default login and logout pages -->
<security:form-login login-page="/login.jsp"
login-processing-url="/loginProcess"
default-target-url="/index.jsp"
authentication-failure-url="/login.jsp?login_error=1" />
<security:logout logout-url="/logout" logout-success-url="/logoutSuccess.jsp" />
</security:http>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource" />
<!--
david:newyork
alex:newjersey
tim:illinois
-->
<!--
<security:password-encoder hash="md5" />
<security:user-service>
<security:user name="david" password="369389d19e24204b4927e30dd7c39efc" authorities="ROLE_USER,ROLE_ADMIN" />
<security:user name="alex" password="847c6f184197dc1545d9891d42814a7d" authorities="ROLE_USER" />
<security:user name="tim" password="0513111ff330e25c631b5d3e9c0a4aae" authorities="ROLE_USER" />
</security:user-service>
-->
</security:authentication-provider>
</beans>
JSP Example
/WEB-INF/jsp/search/person.jsp
The security tag is defined at the top of the page with a prefix of 'sec'. Then around delete link the
sec:authorize tag is configured to only show the link if the user is in the role 'ROLE_ADMIN'. Now, this doesn't actually stop someone from executing a delete query if they know the URL. Below, in the
PersonDao, the
@Secured tag is configured to enforce the rule that only an admin can delete a record.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<h1><fmt:message key="person.search.title"/></h1>
<table class="search">
<tr>
<th><fmt:message key="person.form.firstName"/></th>
<th><fmt:message key="person.form.lastName"/></th>
</tr>
<c:forEach var="person" items="${persons}">
<tr>
<c:url var="editUrl" value="/info/person.html">
<c:param name="id" value="${person.id}" />
</c:url>
<c:url var="deleteUrl" value="/delete/person.html">
<c:param name="id" value="${person.id}" />
</c:url>
<td>${person.firstName}</td>
<td>${person.lastName}</td>
<td>
<a href='<c:out value="${editUrl}"/>'><fmt:message key="button.edit"/></a>
<sec:authorize ifAllGranted="ROLE_ADMIN">
<a href='<c:out value="${deleteUrl}"/>'><fmt:message key="button.delete"/></a>
</sec:authorize>
</td>
</tr>
</c:forEach>
</table>
Code Example
PersonDao
The delete method has access restricted to users in the admin role by putting the
@Secured annotation above it and setting the allowed roles. Which in this case is only the 'ROLE_ADMIN' role. By securing the actual
DAO interface, even if a non-admin user tries to execute the delete URL they will not be able to delete a record. Try to delete a user as a non-admin user to see the request being stopped by Spring Security (ex:
http://www.springbyexample.org/simple-security/delete/person.html?id=1).
You may wonder why the '/delete/person*' URL wasn't restricted. For the current application, this would have been sufficient. But we don't really want to restrict the URL, we want to restrict the actual delete action. Spring Security makes it very easy to restrict access to actual methods. If at some point in the future the another URL is made to also delete a record, our rule will still be enforced. Also, if at some point someone creates a method that calls delete that is accessed by a completely different URL, only admins will be able to execute this new part of the application successfully.
public interface PersonDao {
/**
* Find person by id.
*/
public Person findPersonById(Integer id) throws DataAccessException;
/**
* Find persons.
*/
public Collection<Person> findPersons() throws DataAccessException;
/**
* Find persons by last name.
*/
public Collection<Person> findPersonsByLastName(String lastName) throws DataAccessException;
/**
* Saves person.
*/
public void save(Person person);
/**
* Deletes person.
*/
@Secured ({"ROLE_ADMIN"})
public void delete(Person person);
}
SQL Script
security_schema.sql
SET IGNORECASE TRUE;
CREATE TABLE users (
username VARCHAR(50) NOT NULL PRIMARY KEY,
password VARCHAR(50) NOT NULL,
enabled BIT NOT NULL
);
CREATE TABLE authorities (
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL
);
CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
ALTER TABLE authorities ADD CONSTRAINT fk_authorities_users foreign key (username) REFERENCES users(username);
INSERT INTO users VALUES ('david', 'newyork', true);
INSERT INTO users VALUES ('alex', 'newjersey', true);
INSERT INTO users VALUES ('tim', 'illinois', true);
INSERT INTO authorities VALUES ('david', 'ROLE_USER');
INSERT INTO authorities VALUES ('david', 'ROLE_ADMIN');
INSERT INTO authorities VALUES ('alex', 'ROLE_USER');
INSERT INTO authorities VALUES ('tim', 'ROLE_USER');
Related Links
Project Setup
The project is available to checkout from an anonymous Subversion checkout.
Command Line Example
svn co http://svn.springbyexample.org/simple-spring-security-webapp/trunk simple-spring-security-webapp
General Setup Instructions
General instructions for checking out the project with Eclipse and building with Maven.
Example Project Setup
Comments