Spring is a great application framework extensively used in Java applications. Spring Security is yet another open source product from the same company that provides extensive security features going beyond what is in the Java Enterprise Edition specifications (Servlets, EJB). Interestingly, you can use Spring Security in any application server you like without modifications to your application. So your security configuration is not going to be different in different environments, it becomes portable too. Here you find how to enable Spring Security in a simple web application and illustrates a simple use case: protecting access to a method in an object. See the bottom of this page to download the sample applications.
Spring Security supports multiple authentication modes (separately usable or even multiple modes simultaneously used in a single application with fallback configurations). It supports multiple authorization techniques as well. For example, you can protect access to web pages based on URLs. Or else, you can protect access to methods in objects (defined as Spring beans). Yet another authorization technique is to control access to specific business objects (for example access to John's account details in a banking application).
The sample applications were developed and deployed in the environment described below:
- JDK 1.6.11
- JBoss Application Server 5.1.0
- Spring Framework 3.0.3
- Spring Security 3.0.3
- Eclipse IDE 3.5 (Galileo)
- JavaServer Faces 1.2 (JSF) – No separate implementations were used other than what's found with JBoss 5.1.0
Even though I am using JSF as the UI implementation framework, you can use any other framework (like Struts or Spring MVC for example) or even code with plain JSP and Servlets. Spring and Spring Security can be used with all these approaches. In case non-JSF approaches are used, Spring defined beans are accessible though the WebApplicationContext (You use WebApplicationContextUtils class to access that).
First, no security JSF application
The above diagram illustrates the application that we will secure using Spring Security.
Here's the annotated web.xml file:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>JSFSpringNoSecurityWebApp</display-name> <!-- Spring configuration file location --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-business.xml </param-value> </context-param> <!-- To start/stop Spring framework automatically. --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> </web-app>
In the web.xml file above, we see Spring Framework is loaded using the ContextLoaderListener approach. And the FacesServlet is declared since we use JSF. The associated Spring configuration file (applicationContext-business.xml) is as follows:
<?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-3.0.xsd"> <bean id="calculatorBean" class="org.swview.springsecuritytestapp.logic.CalculatorIpml"> </bean> </beans>
In our simple application, we have defined a Spring bean that represents a calculator. Let it represent a class that represent 'business logic' of a typical application. We are interested in protecting access to the methods.
Here is the JSF configuration (faces-config.xml):
<?xml version="1.0" encoding="UTF-8"?> <faces-config 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-facesconfig_1_2.xsd" version="1.2"> <application> <el-resolver> org.springframework.web.jsf.el.SpringBeanFacesELResolver </el-resolver> </application> <managed-bean> <managed-bean-name>calculatorController</managed-bean-name> <managed-bean-class> org.swview.springsecuritytestapp.jsf.CalculatorController </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>calculator</property-name> <value>#{calculatorBean}</value> </managed-property> </managed-bean> </faces-config>
In the above configuration file, we use <el-resolver> tag (el - Expression Language). This facilitates the availability of Spring beans in JSF. You see that the id of the Spring bean (calculatorBean) is used here to specify a managed property of the JSF managed bean calculatorController. calculatorController is a backing bean. It is there to support the calculator.jsp which is a JSF view. By using '#{calculatorBean}' as the value of the managed property, we inject the Spring defined bean to this backing bean. Note that this injection happens for each request since calculatorController is 'request' scoped and hense new bean instances are created with each page request that accesses it.
Here's the calculator.jsp file:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%> <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Calculator</title> </head> <body> <f:view> <h:form> <h:panelGrid border="1" columns="3"> <h:outputLabel value="Number 1:"></h:outputLabel> <h:inputText value="#{calculatorController.number1}" id="number1Field"> <f:convertNumber /> </h:inputText> <h:message for="number1Field"></h:message> <h:outputText value="Number 2:"></h:outputText> <h:inputText value="#{calculatorController.number2}" id="number2Field"> <f:convertNumber /> </h:inputText> <h:message for="number2Field"></h:message> <h:outputLabel value="Sum:"></h:outputLabel> <h:outputLabel value="#{calculatorController.results}"></h:outputLabel> </h:panelGrid> <h:commandButton value="Add Again" action="#{calculatorController.add}"></h:commandButton> </h:form> </f:view> </body> </html>
The above produces the following view (default values will be zero):
Based on the JSF view code, the add() method of the calculatorController (JSF) backing bean is executed when the 'Add Again' button is clicked. Before that, a new bean is created and the number1 and number2 properties are set. add() method of the backing bean simply invokes the add method of the JSF injected Calculator instance (which is in fact the Spring bean created from the CalculatorImpl class. This sets the 'result' property of the backing bean. Which is used to display the sum within the same JSF view (or anywhere else if you like after accessing the calculatorController backing bean).
Here's the code of the CalculatorController:
package org.swview.springsecuritytestapp.jsf; import org.swview.springsecuritytestapp.logic.Calculator; public class CalculatorController { private double number1; private double number2; private double results; private Calculator calculator; public void setCalculator(Calculator calculator) { this.calculator = calculator; } public double getNumber1() { return number1; } public void setNumber1(double number1) { this.number1 = number1; } public double getNumber2() { return number2; } public void setNumber2(double number2) { this.number2 = number2; } public double getResults() { return results; } public void setResults(double results) { this.results = results; } public String add() { results = calculator.add(number1, number2); return "success"; } }
And here's the code of the Spring bean CalculatorImpl which representats a typical class that implements business logic in an application:
package org.swview.springsecuritytestapp.logic; public class CalculatorIpml implements Calculator { public double add(double a, double b) { return a + b; } public double subtract(double a, double b) { return a - b; } }
Spring Secured JSF application
Let's look at securing the above application. We will define two user roles (ROLE_GENERAL_USER and ROLE_SPECIAL_USER) and grant access to the pages of the application. Let's make the users having the ROLE_SPECIAL_USER role perform the 'add' operation. That is let a request from such a user successfully invoke the add() method of the Spring bean. Put another way, we want to control access to a certain business method.
Spring environment is shown by the green background (life cycle of the DelegatingFilterProxy is managed by the Servlet container, but links with the Spring context). Here's the typical request processing cycle:
- A user visits the calculator.jsp page.
- Request is intercepted by the Spring Security provided DelegatingFilterProxy. If authentication is enabled, this filter will do the needful including displaying a form (which you can customize) and actual authentication based on a configured authentication provider.
- If already authenticated, the request is then handled by JSF. A new instance of the CalculatorContrller is created (since it is request scoped) and JSF will request an instance of the Calculator from Spring.
- Spring will pass the Calculator instance to JSF. However, it will not be the actual instance of CalculatorImpl, but a Spring generated proxy instance. This proxy 'Calculator' is what is used by JSF to initialize the dependancy of the CalculatorController.
- Assuming that Spring returned a Calculator, CalculatorController's add() method invokes the add() method of the injected Calculator. That's where the Spring provided proxy object's add() method is invoked. Spring Security get's a chance again. Based on the security context established at the very beginning by the DelegatingFilterProxy, Spring goes about checking the authorization requirements. If satisfied, the proxy goes about invoking the add() method of the actual Calculator bean (CalculatorImpl). If not, the proxy will prevent access to the method. In return, the DelegatingFilterProxy will generate a response page display an error message to the user saying that the access was denied.
Let's go about enabling security only by modifying the xml files (Java 5 annotations can also be used). Here's the overview:
- Add the Spring Security provided DelegatingFilterProxy to the web.xml file and make it intercept all the requests to the web application.
- Describe our security needs in Spring (we will use a separate xml file for this - the better way). Let's mention that users in the ROLE_GENERAL_USER and ROLE_SPECIAL_USER should be able to access the pages of the application.
- Furthermore, let us metion in the extended Spring configuration that we want add() method of the CalculatorImpl Spring bean be access only by those in the ROLE_SPECIAL_USER role.
Here's the web.xml file of the secured application:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>JSFSpringNoSecurityWebApp</display-name> <!-- Spring configuration file location --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-business.xml /WEB-INF/applicationContext-security.xml </param-value> </context-param> <!-- Let Spring handle all requests coming to the web application through this filter. --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!-- All the requests to be handled by the above filter --> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- To start/stop Spring framework automatically. --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> </web-app>
Note that we have mentioned that two Spring configuration files exists. We have also declared the new filter required and the associated url pattern. No modifications have been done to the previous application's artifacts. The newly added applicationContext-security.xml file is as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <!-- Method based security --> <global-method-security> <protect-pointcut access="ROLE_SPECIAL_USER" expression="execution(* org.swview.springsecuritytestapp.logic.Calculator.add(..))"/> <protect-pointcut access="ROLE_GENERAL_USER" expression="execution(* org.swview.springsecuritytestapp.logic.Calculator.subtract(..))"/> </global-method-security> <!-- URL pattern based security --> <http auto-config="true"> <intercept-url pattern="/**" access="ROLE_GENERAL_USER, ROLE_SPECIAL_USER" /> </http> <!-- Usernames/Passwords are kamal/swview test/spring --> <authentication-manager> <authentication-provider> <password-encoder hash="md5"/> <user-service> <user name="kamal" password="65dc70650690999922d7dcd99dbd4033" authorities="ROLE_SPECIAL_USER" /> <user name="test" password="2a2d595e6ed9a0b24f027f2b63b134d6" authorities="ROLE_GENERAL_USER" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
The <global-method-security> element uses AspectJ pointcut expression language to select the methods that you need to controll access to. Alternative approaches exist like editing the applicationContext-business.xml file (and using some different xml tags with bean declarations) or using annotations in CalculatorImpl class where you may not use AspectJ pointcut expressions. Look at the Spring Security documentation for details. If you want to learn more about the pointcut expressions, look at the aspect oriented programming (AOP) section of the Spring documentation.
<intercept-url> element has been used to configure access requirements bases on a URL pattern. By specifying the pattern to be "/**", we have requested the protection for all URLs.
<authentication-manager> element has been used to configure how the users should authenticate. A simple authentication manager has been provided where security credentials are described using XML within the same file. You will typically maintain security credentials in a database, LDAP directory or the like and accordingly configure the <authentication-manager> section.
That's it. We have not provided custom login forms or error pages. Look at the Spring Security documentation if you are interested in doing so. Look at the calculator.jsp in the sample application to figure out how to provide a logout link.
Here's what you see when you access the application:
Summary
Spring Security is a great open source security provider for Java enterprise applications. It's flexible to use different authentication mechanisms (XML, database, LDAP, Kerberose, OpenID, X.509, ...). In web application you can use it to restrict access to pages based on URL patterns, methods in Spring beans, or even individual business objects managed by the application. More capabilities exist.
Spring Security does not require application server specific configurations. Hence the framework provides a portable security model to your application.
If you have found the Spring Framework to be highly useful, so will be the case with Spring Security.