Chapter 5. Spring Security

In this chapter, you will learn how to secure applications using the Spring Security framework, a subproject of the Spring framework. Spring Security was initially known as Acegi Security, but its name has been changed since joining with the Spring Portfolio projects. Spring Security can be used to secure any Java application, but it's mostly used for web-based applications. Web applications, especially those that can be accessed through the Internet, are vulnerable to hacker attacks if they are not secured properly.

If you've already used Spring Security, be advised there have been several changes introduced in the 3.0 release of the Spring Security framework, which was released almost in tandem with the 3.0 release of the Spring framework (i.e., core). These changes include feature enhancements like support for annotations and OpenID, as well as a restructuring that includes class name changes and package partitioning (i.e., multiple JARs). This is especially important if you are running Spring Security 2.x code, on which the first edition of the book was based.

If, on the other hand, you've never handled security in an application, there are several terms and concepts that you must understand first. Authentication is the process of verifying a principal's identity against what it claims to be. A principal can be a user, a device, or a system, but most typically, it's a user. A principal has to provide evidence of identity to be authenticated. This evidence is called a credential, which is usually a password when the target principal is a user.

Authorization is the process of granting authorities to an authenticated user so that this user is allowed to access particular resources of the target application. The authorization process must be performed after the authentication process. Typically, authorities are granted in terms of roles.

Access control means controlling access to an application's resources. It entails making a decision on whether a user is allowed to access a resource. This decision is called an access control decision, and it's made by comparing the resource's access attributes with the user's granted authorities or other characteristics.

After finishing this chapter, you will understand basic security concepts and know how to secure your web applications at the URL access level, the method invocation level, the view-rendering level, and the domain object level.

Securing URL Access

Problem

Many web applications have some particular URLs that are critically important and private. You must secure these URLs by preventing unauthorized access to them.

Solution

Spring Security enables you to secure a web application's URL access in a declarative way through simple configuration. It handles security by applying servlet filters to HTTP requests. You can configure these filters in Spring's bean configuration files using XML elements defined in the Spring Security schema. However, as servlet filters must be registered in the web deployment descriptor to take effect, you have to register a DelegatingFilterProxy instance in the web deployment descriptor, which is a servlet filter that delegates request filtering to a filter in Spring's application context.

Spring Security allows you to configure web application security through the <http> element. If your web application's security requirements are straightforward and typical, you can set this element's auto-config attribute to true so that Spring Security will automatically register and configure several basic security services, including the following:

  • Form-based login service: This provides a default page that contains a login form for users to log into this application.

  • Logout service: This provides a handler mapped with a URL for users to log out of this application.

  • HTTP Basic authentication: This can process the Basic authentication credentials presented in HTTP request headers. It can also be used for authenticating requests made with remoting protocols and web services.

  • Anonymous login: This assigns a principal and grants authorities to an anonymous user so that you can handle an anonymous user like a normal user.

  • Remember-me support: This can remember a user's identity across multiple browser sessions, usually by storing a cookie in the user's browser.

  • Servlet API integration: This allows you to access security information in your web application via standard Servlet APIs, such as HttpServletRequest.isUserInRole() and HttpServletRequest.getUserPrincipal().

With these security services registered, you can specify the URL patterns that require particular authorities to access. Spring Security will perform security checks according to your configurations. A user must log into an application before accessing the secure URLs, unless these URLs are opened for anonymous access. Spring Security provides a set of authentication providers for you to choose from. An authentication provider authenticates a user and returns the authorities granted to this user.

How It Works

Suppose you are going to develop an online message board application for users to post their messages on. First, you create the domain class Message with three properties: author, title, and body:

package com.apress.springrecipes.board.domain;

public class Message {

    private Long id;
    private String author;
    private String title;
    private String body;

    // Getters and Setters
    ...
}

Next, you define the operations of your message board in a service interface, including listing all messages, posting a message, deleting a message, and finding a message by its ID:

package com.apress.springrecipes.board.service;
...
public interface MessageBoardService {

    public List<Message> listMessages();
    public void postMessage(Message message);
    public void deleteMessage(Message message);
    public Message findMessageById(Long messageId);
}

For testing purposes, let's implement this interface by using a list to store the posted messages. You can use the message posting time (in milliseconds) as a message's identifier. You also have to declare the postMessage() and deleteMessage() method as synchronized to make them thread-safe.

package com.apress.springrecipes.board.service;
...
public class MessageBoardServiceImpl implements MessageBoardService {

    private Map<Long, Message> messages = new LinkedHashMap<Long, Message>();

    public List<Message> listMessages() {
        return new ArrayList<Message>(messages.values());
    }

    public synchronized void postMessage(Message message) {
        message.setId(System.currentTimeMillis());
        messages.put(message.getId(), message);
    }
public synchronized void deleteMessage(Message message) {
        messages.remove(message.getId());
    }

    public Message findMessageById(Long messageId) {
        return messages.get(messageId);
    }
}

Setting Up a Spring MVC Application That Uses Spring Security

To develop this application using Spring MVC as the web framework and Spring Security as the security framework, you first create the following directory structure for your web application.

Note

Before using Spring Security, you have to have the relevant Spring Security jars on your classpath. If you are using Maven, add the following dependencies to your Maven project. We include here a few extra dependencies that you will need on a case-by-case basis, including LDAP support and ACL support. In this book, we have used a variable - ${spring.security.version} to extract the version out. In this book, we are building against 3.0.2.RELEASE.

<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-ldap</artifactId>
   <version>${spring.security.version}</version>
 </dependency>

 <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-config</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-taglibs</artifactId>
   <version>${spring.security.version}</version>
 </dependency>

 <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-acl</artifactId>
   <version>${spring.security.version}</version>
 </dependency>

The Spring configurations for this application are separated into three different files: board-security.xml, board-service.xml, and board-servlet.xml. Each of them configures a particular layer.

Creating the Configuration Files

In the web deployment descriptor (i.e., web.xml), you register ContextLoaderListener to load the root application context at startup and Spring MVC's DispatcherServlet to dispatch requests:

<web-app 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">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/board-service.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
<servlet>
        <servlet-name>board</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>board</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

If the root application context's configuration file doesn't have the default name (i.e., applicationContext.xml), or if you configure it with multiple configuration files, you'll have to specify the file locations in the contextConfigLocation context parameter. Also note that you have mapped the URL pattern / to DispatcherServlet, meaning everything under the application's root directory will be handled by this servlet

In the web layer configuration file (i.e., board-servlet.xml), you define a view resolver to resolve view names into JSP files located in the /WEB-INF/jsp/ directory. Later, you will have to configure your controllers in this file.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 <context:component-scan base-package="com.apress.springrecipes.board.web" />

    <bean class="org.springframework.web.servlet.view.
Creating the Configuration Files
InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> </beans>

In the service layer configuration file (i.e., board-service.xml), you have to declare only the message board service:

<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="messageBoardService"
        class="com.apress.springrecipes.board.service.MessageBoardServiceImpl" />
</beans>

Creating the Controllers and Page Views

Suppose you have to implement a function for listing all messages posted on the message board. The first step is to create the following controller.

package com.apress.springrecipes.board.web;
...

@Controller
@RequestMapping("/messageList*")
public class MessageListController  {

    private MessageBoardService messageBoardService;

    @Autowired
    public MessageListController(MessageBoardService messageBoardService) {
        this.messageBoardService = messageBoardService;
    }


    @RequestMapping(method = RequestMethod.GET)
    public String generateList(Model model) {
         List<Message> messages = java.util.Collections.emptyList();
         messages = messageBoardService.listMessages();
         model.addAttribute("messages",messages);
         return "messageList";
    }
}

The controller is mapped to a URL in the form /messageList. The controller's main method—generateList()—obtains a list of messages from the messageBoardService, saves it to the model object under the messages named, and returns control to a logical view named messageList. In accordance with Spring MVC conventions, this last logical view is mapped to the JSP /WEB-INF/jsp/messageList.jsp showing all the messages passed from the controller:

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

<html>
<head>
<title>Message List</title>
</head>

<body>
<c:forEach items="${messages}" var="message">
<table>
  <tr>
    <td>Author</td>
    <td>${message.author}</td>
  </tr>
  <tr>
    <td>Title</td>
    <td>${message.title}</td>
  </tr>
  <tr>
    <td>Body</td>
    <td>${message.body}</td>
  </tr>
  <tr>
    <td colspan="2">
      <a href="messageDelete?messageId=${message.id}">Delete</a>
    </td>
  </tr>
</table>
<hr />
</c:forEach>
<a href="messagePost.htm">Post</a>
</body>
</html>

Another function you have to implement is for users to post messages on the message board. You create the following form controller for this purpose:

package com.apress.springrecipes.board.web;
...

@Controller
@RequestMapping("/messagePost*")
public class MessagePostController  {

    private MessageBoardService messageBoardService;


    @Autowired
    public void MessagePostController(MessageBoardService messageBoardService) {
        this.messageBoardService = messageBoardService;
    }
@RequestMapping(method=RequestMethod.GET)
    public String setupForm(Model model) {
       Message message = new Message();
       model.addAttribute("message",message);
       return "messagePost";
    }

     @RequestMapping(method=RequestMethod.POST)
     public String onSubmit(@ModelAttribute("message") 
Creating the Controllers and Page Views
Message message, BindingResult result) { if (result.hasErrors()) { return "messagePost"; } else { messageBoardService.postMessage(message); return "redirect:messageList"; } } }

A user must have logged into the message board before posting a message. You can get a user's login name with the getRemoteUser() method defined in HttpServletRequest. This login name will be used as the message's author name.

You then create the form view /WEB-INF/jsp/messagePost.jsp with Spring's form tags for users to input message contents:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title>Message Post</title>
</head>

<body>
<form:form method="POST" modelAttribute="message">
<table>
  <tr>
    <td>Title</td>
    <td><form:input path="title" /></td>
  </tr>
  <tr>
    <td>Body</td>
    <td><form:textarea path="body" /></td>
  </tr>
  <tr>
    <td colspan="2"><input type="submit" value="Post" /></td>
  </tr>
</table>
</form:form>
</body>
</html>

The last function is to allow a user to delete a posted message by clicking the Delete link on the message list page. You create the following controller for this function:

package com.apress.springrecipes.board.web;
...


@Controller
@RequestMapping("/messageDelete*")
public class MessageDeleteController  {

    private MessageBoardService messageBoardService;

    @Autowired
    public void MessageDeleteController(MessageBoardService messageBoardService) {
        this.messageBoardService = messageBoardService;
    }


    @RequestMapping(method= RequestMethod.GET)
    public String messageDelte(@RequestParam(required = true, 
Creating the Controllers and Page Views
value = "messageId") Long messageId, Model model) { Message message = messageBoardService.findMessageById(messageId); messageBoardService.deleteMessage(message); model.addAttribute("messages", messageBoardService.listMessages()); return "redirect:messageList"; } }

Now, you can deploy this application to a web container (e.g., Apache Tomcat 6.0). By default, Tomcat listens on port 8080, so if you deploy your application to the board context path, you can list all posted messages with the following URL:

http://localhost:8080/board/messageList.htm

Up to this time, you haven't configured any security service for this application, so you can access it directly without logging into it.

Securing URL Access

Now, let's secure this web application's URL access with Spring Security. First, you have to configure a DelegatingFilterProxy instance in web.xml to delegate HTTP request filtering to a filter defined in Spring Security:

<web-app ...>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/board-service.xml
            /WEB-INF/board-security.xml
        </param-value>
    </context-param>
    ...
    <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>

The responsibility of DelegatingFilterProxy is simply to delegate HTTP request filtering to a Spring bean that implements the java.util.logging.Filter interface. By default, it delegates to a bean whose name is the same as its <filter-name> property, but you can override the bean name in its targetBeanName init parameter. As Spring Security will automatically configure a filter chain with the name springSecurityFilterChain when you enable web application security, you can simply use this name for your DelegatingFilterProxy instance.

Although you can configure Spring Security in the same configuration file as the web and service layers, it's better to separate the security configurations in an isolated file (e.g., board-security.xml). Inside web.xml, you have to add the file's location to the contextConfigLocation context parameter for ContextLoaderListener to load it at startup. Then, you create it with the following content:

<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.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <http auto-config="true">
        <intercept-url pattern="/messageList*"
            access="ROLE_USER,ROLE_ANONYMOUS" />
        <intercept-url pattern="/messagePost*" access="ROLE_USER" />
        <intercept-url pattern="/messageDelete*" access="ROLE_ADMIN" />
    </http>
<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="secret"
                authorities="ROLE_ADMIN,ROLE_USER" />
            <user name="user1" password="1111" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
    </authentication-manager>
</beans:beans>

You may find that this file looks a bit different from a normal bean configuration file. Normally, the default namespace of a bean configuration file is beans, so you can use the <bean> and <property> elements without the beans prefix. However, if you use this style to declare the Spring Security services, all security elements must be appended with the security prefix. Because the elements in a security configuration file are mostly Spring Security's, you can define security as the default namespace instead, so you can use them without the security prefix. If you do it this way, however, when you declare normal Spring beans in this file, you have to include the beans prefix for the <bean> and <property> elements.

The <http auto-config="true"> element automatically configures the basic security services that a typical web application needs. You can fine-tune these services with the corresponding subelements inside it.

Inside the <http> configuration element, you can restrict access to particular URLs with one or more <intercept-url> elements. Each <intercept-url> element specifies a URL pattern and a set of access attributes required to access the URLs. Remember that you must always include a wildcard at the end of a URL pattern. Failing to do so will make the URL pattern unable to match a URL that has request parameters. As a result, hackers could easily skip the security check by appending an arbitrary request parameter.

Access attributes are compared with a user's authorities to decide if this user can access the URLs. In most cases, access attributes are defined in terms of roles. For example, users with the ROLE_USER role, or anonymous users, who have the ROLE_ANONYMOUS role by default, are able to access the URL /messageList to list all messages. However, a user must have the ROLE_USER role to post a new message via the URL /messagePost. Only an administrator who has the ROLE_ADMIN role can delete messages via /messageDelete.

You can configure authentication services in the <authentication-provider> element, which is nested inside the <authentication-manager> element. Spring Security supports several ways of authenticating users, including authenticating against a database or an LDAP repository. It also supports defining user details in <user-service> directly for simple security requirements. You can specify a username, a password, and a set of authorities for each user.

Now, you can redeploy this application to test its security configurations. You can enter the request path /messageList to list all posted messages as usual, because it's open to anonymous users. But if you click the link to post a new message, you will be redirected to the default login page generated by Spring Security. You must log into this application with a correct username and password to post a message. Finally, to delete a message, you must log in as an administrator.

Logging In to Web Applications

Problem

A secure application requires its users to log in before they can access certain secure functions. This is especially important for web applications running on the open Internet, because hackers can easily reach them. Most web applications have to provide a way for users to input their credentials to log in.

Solution

Spring Security supports multiple ways for users to log into a web application. It supports form-based login by providing a default web page that contains a login form. You can also provide a custom web page as the login page. In addition, Spring Security supports HTTP Basic authentication by processing the Basic authentication credentials presented in HTTP request headers. HTTP Basic authentication can also be used for authenticating requests made with remoting protocols and web services.

Some parts of your application may allow for anonymous access (e.g., access to the welcome page). Spring Security provides an anonymous login service that can assign a principal and grant authorities to an anonymous user so that you can handle an anonymous user like a normal user when defining security policies.

Spring Security also supports remember-me login, which is able to remember a user's identity across multiple browser sessions so that a user needn't log in again after logging in for the first time.

How It Works

To help you better understand the various login mechanisms in isolation, let's first disable HTTP auto-configuration by removing the auto-config attribute:

<http>
    <intercept-url pattern="/messageList*" access="ROLE_USER,ROLE_ANONYMOUS" />
    <intercept-url pattern="/messagePost*" access="ROLE_USER" />
    <intercept-url pattern="/messageDelete*" access="ROLE_ADMIN" />
</http>

Note that the login services introduced next will be registered automatically if you enable HTTP auto-config. However, if you disable HTTP auto-config or you want to customize these services, you have to configure the corresponding XML elements explicitly.

HTTP Basic Authentication

The HTTP Basic authentication support can be configured via the <http-basic> element. When HTTP Basic authentication is required, a browser will typically display a login dialog or a specific login page for users to log in.

<http>
    ...
    <http-basic />
</http>

Note that when HTTP Basic authentication and form-based login are enabled at the same time, the latter will be used. So, if you want your web application users to log in with this authentication type, you should not enable form-based login.

Form-Based Login

The form-based login service will render a web page that contains a login form for users to input their login details and process the login form submission. It's configured via the <form-login> element:

<http>
    ...
    <form-login />
</http>

By default, Spring Security automatically creates a login page and maps it to the URL /spring_security_login. So, you can add a link to your application (e.g., in messageList.jsp) referring to this URL for login:

<a href="<c:url value="/spring_security_login" />">Login</a>

If you don't prefer the default login page, you can provide a custom login page of your own. For example, you can create the following login.jsp file in the root directory of the web application. Note that you shouldn't put this file inside WEB-INF, which would prevent users from accessing it directly.

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

<html>
<head>
<title>Login</title>
</head>

<body>
<form method="POST" action="<c:url value="/j_spring_security_check" />">
<table>
  <tr>
    <td align="right">Username</td>
    <td><input type="text" name="j_username" /></td>
  </tr>
  <tr>
    <td align="right">Password</td>
    <td><input type="password" name="j_password" /></td>
  </tr>
<tr>
    <td align="right">Remember me</td>
    <td><input type="checkbox" name="_spring_security_remember_me" /></td>
  </tr>
  <tr>
    <td colspan="2" align="right">
      <input type="submit" value="Login" />
      <input type="reset" value="Reset" />
    </td>
  </tr>
</table>
</form>
</body>
</html>

Note that the form action URL and the input field names are Spring Security–specific. However, the action URL can be customized with the login-url attribute of <form-login>.

Now, you have to change the previous login link (i.e., messageList.jsp) to refer to this URL for login:

<a href="<c:url value="/login.jsp" />">Login</a>

In order for Spring Security to display your custom login page when a login is requested, you have to specify its URL in the login-page attribute:

<http>
    ...
    <form-login login-page="/login.jsp" />
</http>

If the login page is displayed by Spring Security when a user requests a secure URL, the user will be redirected to the target URL once the login succeeds. However, if the user requests the login page directly via its URL, by default the user will be redirected to the context path's root (i.e., http://localhost:8080/board/) after a successful login. If you have not defined a welcome page in your web deployment descriptor, you may wish to redirect the user to a default target URL when the login succeeds:

<http>
    ....
    <form-login login-page="/login.jsp" default-target-url="/messageList" />
</http>

If you use the default login page created by Spring Security, then when a login fails, Spring Security will render the login page again with the error message. However, if you specify a custom login page, you will have to configure the authentication-failure-url attribute to specify which URL to redirect to on login error. For example, you can redirect to the custom login page again with the error request parameter:

<http>
    ....
    <form-login login-page="/login.jsp" default-target-url="/messageList"
        authentication-failure-url="/login.jsp?error=true" />
</http>

Then your login page should test whether the error request parameter is present. If an error has occurred, you will have to display the error message by accessing the session scope attribute SPRING_SECURITY_LAST_EXCEPTION, which stores the last exception for the current user.

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

<html>
<head>
<title>Login</title>
</head>

<body>
<c:if test="${not empty param.error}">
  <font color="red">
  Login error. <br />
  Reason : ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
  </font>
</c:if>
...
</body>
</html>

The Logout Service

The logout service provides a handler to handle logout requests. It can be configured via the <logout> element:

<http>
    ...
    <logout />
</http>

By default, it's mapped to the URL /j_spring_security_logout, so you can add a link to a page referring to this URL for logout. Note that this URL can be customized with the logout-url attribute of <logout>.

<a href="<c:url value="/j_spring_security_logout" />">Logout</a>

By default, a user will be redirected to the context path's root when the logout succeeds, but sometimes, you may wish to direct the user to another URL, which you can do as follows:

<http>
    ...
    <logout logout-success-url="/login.jsp" />
</http>

Anonymous Login

The anonymous login service can be configured via the <anonymous> element, where you can customize the username and authorities of an anonymous user, whose default values are anonymousUser and ROLE_ANONYMOUS:

<http>
    <intercept-url pattern="/messageList*" access="ROLE_USER,ROLE_GUEST" />
    <intercept-url pattern="/messagePost*" access="ROLE_USER" />
    <intercept-url pattern="/messageDelete*" access="ROLE_ADMIN" />
    ...
    <anonymous username="guest" granted-authority="ROLE_GUEST" />
</http>

Remember-Me Support

Remember-me support can be configured via the <remember-me> element. By default, it encodes the username, password, remember-me expiration time, and a private key as a token, and stores it as a cookie in the user's browser. The next time the user accesses the same web application, this token will be detected so that the user can log in automatically.

<http>
    ...
    <remember-me />
</http>

However, static remember-me tokens can cause security issues, because they may be captured by hackers. Spring Security supports rolling tokens for more advanced security needs, but this requires a database to persist the tokens. For details about rolling remember-me token deployment, please refer to the Spring Security reference documentation.

Authenticating Users

Problem

When a user attempts to log into your application to access its secure resources, you have to authenticate the user's principal and grant authorities to this user.

Solution

In Spring Security, authentication is performed by one or more authentication providers, connected as a chain. If any of these providers authenticates a user successfully, that user will be able to log into the application. If any provider reports that the user is disabled or locked or that the credential is incorrect, or if no provider can authenticate the user, then the user will be unable to log into this application.

Spring Security supports multiple ways of authenticating users and includes built-in provider implementations for them. You can easily configure these providers with the built-in XML elements. Most common authentication providers authenticate users against a user repository storing user details (e.g., in an application's memory, a relational database, or an LDAP repository).

When storing user details in a repository, you should avoid storing user passwords in clear text, because that makes them vulnerable to hackers. Instead, you should always store encrypted passwords in your repository. A typical way of encrypting passwords is to use a one-way hash function to encode the passwords. When a user enters a password to log in, you apply the same hash function to this password and compare the result with the one stored in the repository. Spring Security supports several algorithms for encoding passwords (including MD5 and SHA) and provides built-in password encoders for these algorithms.

If you retrieve a user's details from a user repository every time a user attempts to log in, your application may incur a performance impact. This is because a user repository is usually stored remotely, and it has to perform some kinds of queries in response to a request. For this reason, Spring Security supports caching user details in local memory and storage to save you the overhead of performing remote queries.

How It Works

Authenticating Users with In-Memory Definitions

If you have only a few users in your application and you seldom modify their details, you can consider defining the user details in Spring Security's configuration file so that they will be loaded into your application's memory:

<authentication-manager>
  <authentication-provider>
    <user-service>
        <user name="admin" password="secret" authorities="ROLE_ADMIN,ROLE_USER" />
        <user name="user1" password="1111" authorities="ROLE_USER" />
        <user name="user2" password="2222" disabled="true" authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>
</authentication-manager>

You can define user details in <user-service> with multiple <user> elements. For each user, you can specify a username, a password, a disabled status, and a set of granted authorities. A disabled user cannot log into an application.

Spring Security also allows you to externalize user details in a properties file, such as /WEB-INF/users.properties:

<authentication-manager>
 <authentication-provider>
    <user-service properties="/WEB-INF/users.properties" />
 </authentication-provider>
</authentication-manager>

Then, you can create the specified properties file and define the user details in the form of properties:

admin=secret,ROLE_ADMIN,ROLE_USER
user1=1111,ROLE_USER
user2=2222,disabled,ROLE_USER

Each property in this file represents a user's details. The property key is the username, and the property value is divided into several parts separated by commas. The first part is the password, and the second part is the enabled status, which is optional; and the default status is enabled. The following parts are the authorities granted to the user.

Authenticating Users Against a Database

More typically, user details should be stored in a database for easy maintenance. Spring Security has built-in support for querying user details from a database. By default, it queries user details, including authorities, with the following SQL statements:

SELECT username, password, enabled
FROM   users
WHERE  username = ?

SELECT username, authority
FROM   authorities
WHERE  username = ?

In order for Spring Security to query user details with these SQL statements, you have to create the corresponding tables in your database. For example, you can create them in the board schema of Apache Derby with the following SQL statements:

CREATE TABLE USERS (
    USERNAME    VARCHAR(10)    NOT NULL,
    PASSWORD    VARCHAR(32)    NOT NULL,
    ENABLED     SMALLINT,
    PRIMARY KEY (USERNAME)
);

CREATE TABLE AUTHORITIES (
    USERNAME    VARCHAR(10)    NOT NULL,
    AUTHORITY   VARCHAR(10)    NOT NULL,
    FOREIGN KEY (USERNAME) REFERENCES USERS
);

Next, you can input some user details into these tables for testing purposes. The data for these two tables is shown in Tables 5-1 and 5-2.

Table 5.1. Testing User Data for the USERS Table

USERNAME

PASSWORD

ENABLED

Admin

Secret

1

user1

1111

1

user2

2222

0

Table 5.2. Testing User Data for the AUTHORITIES Table

USERNAME

AUTHORITY

Admin

ROLE_ADMIN

Admin

ROLE_USER

user1

ROLE_USER

user2

ROLE_USER

In order for Spring Security to access these tables, you have to declare a data source (e.g., in board-service.xml) for creating connections to this database.

Note

To connect to a database in the Apache Derby server, you need the Derby client .jars, as well as the Spring JDBC support. If you are using Apache Maven, add the following dependencies to your project:

<dependency>
  <groupId>org.apache.derby</groupId>
  <artifactId>derbyclient</artifactId>
  <version>10.4.2.0</version>
 </dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>${spring.version}</version>
</dependency>
<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName"
        value="org.apache.derby.jdbc.ClientDriver" />
    <property name="url"
        value="jdbc:derby://localhost:1527/board;create=true" />
    <property name="username" value="app" />
    <property name="password" value="app" />
</bean>

The final step is to configure an authentication provider that queries this database for user details. You can achieve this simply by using the <jdbc-user-service> element with a data source reference:

<authentication-manager>
  <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource" />
  </authentication-provider>
</authentication-manager>

However, in some cases, you may already have your own user repository defined in a legacy database. For example, suppose that the tables are created with the following SQL statements, and that all users in the MEMBER table have the enabled status:

CREATE TABLE MEMBER (
    ID          BIGINT         NOT NULL,
    USERNAME    VARCHAR(10)    NOT NULL,
    PASSWORD    VARCHAR(32)    NOT NULL,
    PRIMARY KEY (ID)
);

CREATE TABLE MEMBER_ROLE (
    MEMBER_ID    BIGINT         NOT NULL,
    ROLE         VARCHAR(10)    NOT NULL,
    FOREIGN KEY (MEMBER_ID) REFERENCES MEMBER
);

Suppose you have the legacy user data stored in these tables as shown in Tables 5-3 and 5-4.

Table 5.3. Legacy User Data in the MEMBER Table

ID

USERNAME

PASSWORD

1

Admin

Secret

2

user1

1111

Table 5.4. Legacy User Data in the MEMBER_ROLE Table

MEMBER_ID

ROLE

1

ROLE_ADMIN

1

ROLE_USER

2

ROLE_USER

Fortunately, Spring Security also supports using custom SQL statements to query a legacy database for user details. You can specify the statements for querying a user's information and authorities in the users-by-username-query and authorities-by-username-query aauthorities-by-username-query attributes:

<jdbc-user-service data-source-ref="dataSource"
    users-by-username-query=
        "SELECT username, password, 'true' as enabled
Legacy User Data in the MEMBER_ROLE Table
FROM member
Legacy User Data in the MEMBER_ROLE Table
WHERE username = ?" authorities-by-username-query= "SELECT member.username, member_role.role as authorities
Legacy User Data in the MEMBER_ROLE Table
FROM member, member_role
Legacy User Data in the MEMBER_ROLE Table
WHERE member.username = ? AND member.id = member_role.member_id" />

Encrypting Passwords

Until now, you have been storing user details with clear-text passwords. But this approach is vulnerable to hacker attacks, so you should encrypt the passwords before storing them. Spring Security supports several algorithms for encrypting passwords. For example, you can choose MD5 (Message-Digest algorithm 5), a one-way hash algorithm, to encrypt your passwords.

Note

You may need a utility to calculate MD5 digests for your passwords. One such utility is Jacksum, which you can download from http://sourceforge.net/projects/jacksum/ and extract to a directory of your choice. Then execute the following command to calculate a digest for a text:

java -jar jacksum.jar -a md5 -q "txt:secret"

Now, you can store the encrypted passwords in your user repository. For example, if you are using in-memory user definitions, you can specify the encrypted passwords in the password attributes. Then, you can configure a <password-encoder> element with a hashing algorithm specified in the hash attribute.

<authentication-manager>
   <authentication-provider>
    <password-encoder hash="md5" />
    <user-service>
        <user name="admin" password="5ebe2294ecd0e0f08eab7690d2a6ee69"
            authorities="ROLE_ADMIN, ROLE_USER" />
        <user name="user1" password="b59c67bf196a4758191e42f76670ceba"
            authorities="ROLE_USER" />
        <user name="user2" password="934b535800b1cba8f96a5d72f72f1611"
            disabled="true" authorities="ROLE_USER" />
    </user-service>
  </authentication-provider>
</authentication-manager>

A password encoder is also applicable to a user repository stored in a database:

<authentication-manager>
   <authentication-provider>
    <password-encoder hash="md5" />
    <jdbc-user-service data-source-ref="dataSource" />
  </authentication-provider>
</authentication-manager>

Of course, you have to store the encrypted passwords in the database tables, instead of the clear-text passwords, as shown in Table 5-5.

Table 5.5. Testing User Data with Encrypted Passwords for the USERS Table

USERNAME

PASSWORD

ENABLED

Admin

5ebe2294ecd0e0f08eab7690d2a6ee69

1

user1

b59c67bf196a4758191e42f76670ceba

1

user2

934b535800b1cba8f96a5d72f72f1611

0

Authenticating Users Against an LDAP Repository

Spring Security also supports accessing an LDAP repository for authenticating users. First, you have to prepare some user data for populating the LDAP repository. Let's prepare the user data in the LDAP Data Interchange Format (LDIF), a standard plain-text data format for importing and exporting LDAP directory data. For example, create the users.ldif file containing the following contents:

dn: dc=springrecipes,dc=com
objectClass: top
objectClass: domain
dc: springrecipes

dn: ou=groups,dc=springrecipes,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springrecipes,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springrecipes,dc=com
objectclass: top
objectclass: uidObject
objectclass: person
uid: admin
cn: admin
sn: admin
userPassword: secret
dn: uid=user1,ou=people,dc=springrecipes,dc=com
objectclass: top
objectclass: uidObject
objectclass: person
uid: user1
cn: user1
sn: user1
userPassword: 1111

dn: cn=admin,ou=groups,dc=springrecipes,dc=com
objectclass: top
objectclass: groupOfNames
cn: admin
member: uid=admin,ou=people,dc=springrecipes,dc=com

dn: cn=user,ou=groups,dc=springrecipes,dc=com
objectclass: top
objectclass: groupOfNames
cn: user
member: uid=admin,ou=people,dc=springrecipes,dc=com
member: uid=user1,ou=people,dc=springrecipes,dc=com

Don't worry if you don't understand this LDIF file very well. You probably won't need to use this file format to define LDAP data often, because most LDAP servers support GUI-based configuration. This users.ldif file includes the following contents:

  • The default LDAP domain, dc=springrecipes,dc=com

  • The groups and people organization units for storing groups and users

  • The admin and user1 users with the passwords secret and 1111

  • The admin group (including the admin user) and the user group (including the admin and user1 users)

For testing purposes, you can install an LDAP server on your local machine to host this user repository. For the sake of easy installation and configuration, we recommend installing OpenDS (http://www.opends.org/), a Java-based open source directory service engine that supports LDAP.

Note

OpenDS supports two types of installation interfaces: command line and GUI. This example uses the command-line interface, so you have to download the ZIP distribution and extract it to an arbitrary directory (e.g., C:OpenDS-2.2.0), and then execute the setup script from the root of this directory.

C:OpenDS-2.2.0>setup --cli

OpenDS Directory Server 2.2.0
Please wait while the setup program initializes...

What would you like to use as the initial root user DN for the Directory
Server? [cn=Directory Manager]:
Please provide the password to use for the initial root user: ldap
Please re-enter the password for confirmation: ldap

On which port would you like the Directory Server to accept connections from
LDAP clients? [1389]:

On which port would you like the Administration Connector to accept
connections? [4444]:

What do you wish to use as the base DN for the directory data?
[dc=example,dc=com]:dc=springrecipes,dc=com
Options for populating the database:

    1)  Only create the base entry
    2)  Leave the database empty
    3)  Import data from an LDIF file
    4)  Load automatically-generated sample data

Enter choice [1]: 3

Please specify the path to the LDIF file containing the data to import: users.ldif

Do you want to enable SSL? (yes / no) [no]:

Do you want to enable Start TLS? (yes / no) [no]:

Do you want to start the server when the configuration is completed? (yes /
no) [yes]:

Enable OpenDS to run as a Windows Service? (yes / no) [no]:

Do you want to start the server when the configuration is completed? (yes /
no) [yes]:

What would you like to do?
    1)  Setup the server with the parameters above
    2)  Provide the setup parameters again
    3)  Cancel the setup

Enter choice [1]:

Configuring Directory Server ..... Done.
Importing LDIF file users.ldif ....... Done.
Starting Directory Server ........ Done.

Note that the root user and password for this LDAP server are cn=Directory Manager and ldap, respectively. Later, you will have to use this user to connect to this server.

After the LDAP server has started up, you can configure Spring Security to authenticate users against its repository.

Note

To authenticate users against an LDAP repository, you have to have the Spring LDAP project on your CLASSPATH. If you are using Maven, add the following dependency to your Maven project:

<dependency>
   <groupId>org.springframework.ldap</groupId>
   <artifactId>spring-ldap</artifactId>
   <version>1.3.0.RELEASE</version>
 </dependency>
<beans:beans ...>
    ...
    <authentication-manager>
      <authentication-provider>
        <password-encoder hash="{sha}" />
        <ldap-user-service server-ref="ldapServer"
            user-search-filter="uid={0}" user-search-base="ou=people"
            group-search-filter="member={0}" group-search-base="ou=groups" />
      </authentication-provider>
    </authentication-manager>

    <ldap-server id="ldapServer"
        url="ldap://localhost:389/dc=springrecipes,dc=com"
        manager-dn="cn=Directory Manager" manager-password="ldap" />
</beans:beans>

You have to configure an <ldap-user-service> element to define how to search users from an LDAP repository. You can specify the search filters and search bases for searching users and groups via several attributes, whose values must be consistent with the repository's directory structure. With the preceding attribute values, Spring Security will search a user from the people organization unit with a particular user ID and search a user's groups from the groups organization unit. Spring Security will automatically insert the ROLE_ prefix to each group as an authority.

As OpenDS uses SSHA (Salted Secure Hash Algorithm) to encode user passwords by default, you have to specify {sha} as the hash algorithm in <password-encoder>. Note that this value is different from sha, as it's specific to LDAP password encoding.

Finally, <ldap-user-service> has to refer to an LDAP server definition, which defines how to create connections to an LDAP server. You can specify the root user's username and password to connect to the LDAP server running on localhost.

Caching User Details

Both <jdbc-user-service> and <ldap-user-service> support caching user details. First of all, you have to choose a cache implementation that provides a caching service. As Spring and Spring Security have built-in support for Ehcache (http://ehcache.sourceforge.net/), you can choose it as your cache implementation and create a configuration file for it (e.g., ehcache.xml in the classpath root) with the following contents:

<ehcache>
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
        maxElementsInMemory="1000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <cache name="userCache"
        maxElementsInMemory="100"
        eternal="false"
        timeToIdleSeconds="600"
        timeToLiveSeconds="3600"
        overflowToDisk="true"
        />
</ehcache>

Note

To use Ehcache to cache objects, you have to have the Ehcache 1.7.2 library on your CLASSPATH. If you are using Maven, add the following dependency to your Maven project:

<dependency>
   <groupId>net.sf.ehcache</groupId>
   <version>1.7.2</version>
   <artifactId>ehcache-core</artifactId>
 </dependency>

This Ehcache configuration file defines two types of cache configurations. One is for the default, and the other is for caching user details. If the user cache configuration is used, a cache instance will cache the details of at most 100 users in memory. The cached users will overflow to disk when this limit is exceeded. A cached user will expire if it has been idle for 10 minutes or live for 1 hour after its creation.

To enable caching user details in Spring Security, you can set the cache-ref attribute of either <jdbc-user-service> or <ldap-user-service> to refer to a UserCache object. For Ehcache, Spring Security comes with a UserCache implementation, EhCacheBasedUserCache, which has to refer to an Ehcache instance.

<beans:beans ...>
    ...
    <authentication-manager>
       <authentication-provider>
        ...
        <ldap-user-service server-ref="ldapServer"
            user-search-filter="uid={0}" user-search-base="ou=people"
            group-search-filter="member={0}" group-search-base="ou=groups"
            cache-ref="userCache" />
       </authentication-provider>
    </authentication-manager>

    <beans:bean id="userCache" class="org.springframework.security.providers.
Caching User Details
dao.cache.EhCacheBasedUserCache"> <beans:property name="cache" ref="userEhCache" /> </beans:bean> <beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <beans:property name="cacheManager" ref="cacheManager" /> <beans:property name="cacheName" value="userCache" /> </beans:bean> </beans:beans>

In Spring, an Ehcache instance can be created via EhCacheFactoryBean by providing a cache manager and a cache name. Spring also provides EhCacheManagerFactoryBean for you to create an Ehcache manager by loading a configuration file. By default, it loads ehcache.xml (located in the root of the classpath). As an Ehcache manager may be used by other service components, it should be defined in board-service.xml.

<bean id="cacheManager"
    class="org.springframework.security.core.userdetails.cache.EhCacheManagerFactoryBean" />

Making Access Control Decisions

Problem

In the authentication process, an application will grant a successfully authenticated user a set of authorities. When this user attempts to access a resource in the application, the application has to decide whether the resource is accessible with the granted authorities or other characteristics.

Solution

The decision on whether a user is allowed to access a resource in an application is called an access control decision. It is made based on the user's authentication status, and the resource's nature and access attributes. In Spring Security, access control decisions are made by access decision managers, which have to implement the AccessDecisionManager interface. You are free to create your own access decision managers by implementing this interface, but Spring Security comes with three convenient access decision managers based on the voting approach. They are shown in Table 5-6.

Table 5.6. Access Decision Managers That Come with Spring Security

Access Decision Manager

When to Grant Access

AffirmativeBased

At least one voter votes to grant access.

ConsensusBased

A consensus of voters votes to grant access.

UnanimousBased

All voters vote to abstain or grant access (no voter votes to deny access).

All these access decision managers require a group of voters to be configured for voting on access control decisions. Each voter has to implement the AccessDecisionVoter interface. A voter can vote to grant, abstain, or deny access to a resource. The voting results are represented by the ACCESS_GRANTED, ACCESS_DENIED, and ACCESS_ABSTAIN constant fields defined in the AccessDecisionVoter interface.

By default, if no access decision manager is specified explicitly, Spring Security will automatically configure an AffirmativeBased access decision manager with the following two voters configured:

RoleVoter votes for an access control decision based on a user's role. It will only process access attributes that start with the ROLE_ prefix, but this prefix can be customized. It votes to grant access if the user has the same role as required to access the resource or to deny access if the user lacks any role required to access the resource. If the resource does not have an access attribute starting with ROLE_, it will abstain from voting.

AuthenticatedVoter votes for an access control decision based on a user's authentication level. It will only process the access attributes IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_ANONYMOUSLY. It votes to grant access if the user's authentication level is higher than the required attribute. From highest to lowest, authentication levels are fully authenticated, authentication remembered, and anonymously authenticated.

How It Works

By default, Spring Security will automatically configure an access decision manager if none is specified. This default access decision manager is equivalent to the one defined with the following bean configuration:

<bean id="_accessManager"
    class="org.springframework.security.access.vote.AffirmativeBased">
    <property name="decisionVoters">
        <list>
            <bean class="org.springframework.security.access.vote.RoleVoter" />
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
        </list>
    </property>
</bean>

This default access decision manager and its decision voters should satisfy most typical authorization requirements. However, if they don't satisfy yours, you can create your own. In most cases, you'll only need to create a custom voter. For example, you can create a voter to vote for a decision based on a user's IP address:

package com.apress.springrecipes.board.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.access.ConfigAttribute;

import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.access.AccessDecisionVoter;

import java.util.Collection;

public class IpAddressVoter implements AccessDecisionVoter {

    public static final String IP_PREFIX = "IP_";
    public static final String IP_LOCAL_HOST = "IP_LOCAL_HOST";

    public boolean supports(ConfigAttribute attribute) {
        return attribute.getAttribute() != null
                && attribute.getAttribute().startsWith(IP_PREFIX);
    }

    public boolean supports(Class clazz) {
        return true;
    }

    public int vote(Authentication authentication, Object object,
            Collection<ConfigAttribute> configList) {
        if (!(authentication.getDetails() instanceof WebAuthenticationDetails)) {
            return ACCESS_DENIED;
        }

        WebAuthenticationDetails details =
            (WebAuthenticationDetails) authentication.getDetails();
        String address = details.getRemoteAddress();
int result = ACCESS_ABSTAIN;
        for (ConfigAttribute config : configList) {

               result = ACCESS_DENIED;
                if (IP_LOCAL_HOST.equals(config.getAttribute())) {
                    if (address.equals("127.0.0.1") || address.equals("0:0:0:0:0:0:0:1")) {
                        return ACCESS_GRANTED;
                    }
                }

        }
        return result;
    }
}

Note that this voter will only process the access attributes that start with the IP_ prefix. At the moment, it only supports the IP_LOCAL_HOST access attribute. If the user is a web client whose IP address is equal to 127.0.0.1 or 0:0:0:0:0:0:0:1—the last value being returned by networkless Linux workstations—this voter will vote to grant access. Otherwise, it will vote to deny access. If the resource does not have an access attribute starting with IP_, it will abstain from voting.

Next, you have to define a custom access decision manager that includes this voter. If you define this access decision manager in board-security.xml, you will have to include the beans prefix, because the default schema is security.

<beans:bean id="accessDecisionManager"
    class="org.springframework.security.access.vote.AffirmativeBased">
    <beans:property name="decisionVoters">
        <beans:list>
            <beans:bean
                class="org.springframework.security.access.vote.RoleVoter" />
            <beans:bean
                class="org.springframework.security.acces.vote.AuthenticatedVoter" />
            <beans:bean
                class="com.apress.springrecipes.board.
How It Works
security.IpAddressVoter" /> </beans:list> </beans:property> </beans:bean>

Now, suppose you would like to allow users of the machine running the web container (i.e., the server administrators) to delete messages without logging in. You have to refer to this access decision manager from the <http> configuration element and add the access attribute IP_LOCAL_HOST to the URL pattern /messageDelete.htm*:

<http access-decision-manager-ref="accessDecisionManager">
    <intercept-url pattern="/messageList*" access="ROLE_USER,ROLE_GUEST" />
    <intercept-url pattern="/messagePost*" access="ROLE_USER" />
    <intercept-url pattern="/messageDelete*"
        access="ROLE_ADMIN,IP_LOCAL_HOST" />
    ...
</http>

Then, if you access this message board application from localhost, you needn't log in as an administrator to delete a posted message.

Securing Method Invocations

Problem

As an alternative or a complement to securing URL access in the web layer, sometimes you may need to secure method invocations in the service layer. For example, in the case that a single controller has to invoke multiple methods in the service layer, you may wish to enforce fine-grained security controls on these methods.

Solution

Spring Security enables you to secure method invocations in a declarative way. First, you can embed a <security:intercept-methods> element in a bean definition to secure its methods. Alternatively, you can configure a global <global-method-security> element to secure multiple methods matched with AspectJ pointcut expressions. You can also annotate methods declared in a bean interface or an implementation class with the @Secured annotation and then enable security for them in <<global-method-security>.

How It Works

Securing Methods by Embedding a Security Interceptor

First, you can secure a bean's methods by embedding a <security:intercept-methods> element in the bean definition. For example, you can secure the methods of the messageBoardService bean defined in board-service.xml. As this element is defined in the security schema, you have to import it beforehand.

<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-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.0..xsd">
<bean id="messageBoardService"
        class="com.apress.springrecipes.board.service.MessageBoardServiceImpl">
        <security:intercept-methods
            access-decision-manager-ref="accessDecisionManager">
            <security:protect
                method="com.apress.springrecipes.board.service.
Securing Methods by Embedding a Security Interceptor
MessageBoardService.listMessages" access="ROLE_USER,ROLE_GUEST" /> <security:protect method="com.apress.springrecipes.board.service.
Securing Methods by Embedding a Security Interceptor
MessageBoardService.postMessage" access="ROLE_USER" /> <security:protect method="com.apress.springrecipes.board.service.
Securing Methods by Embedding a Security Interceptor
MessageBoardService.deleteMessage" access="ROLE_ADMIN,IP_LOCAL_HOST" /> <security:protect method="com.apress.springrecipes.board.service.
Securing Methods by Embedding a Security Interceptor
MessageBoardService.findMessageById" access="ROLE_USER,ROLE_GUEST" /> </security:intercept-methods> </bean> ... </beans>

In a bean's <security:intercept-methods>, you can specify multiple <security:protect> elements to specify access attributes for this bean's methods. You can match multiple methods by specifying a method name pattern with wildcards. If you would like to use a custom access decision manager, you can specify it in the access-decision-manager-ref a access-decision-manager-ref attribute.

Securing Methods with Pointcuts

Second, you can define global pointcuts in <global-method-security> to secure methods using AspectJ pointcut expressions, instead of embedding a security interceptor in each bean whose methods require security. You should configure the <global-method-security> element in board-security.xml for centralizing security configurations. As the default namespace of this configuration file is security, you needn't specify a prefix for this element explicitly. You can also specify a custom access decision manager in the access-decision-manager-ref attribute.

<global-method-security
    access-decision-manager-ref="accessDecisionManager">
    <protect-pointcut expression=
        "execution(* com.apress.springrecipes.board.service.*Service.list*(..))"
        access="ROLE_USER,ROLE_GUEST" />
    <protect-pointcut expression=
        "execution(* com.apress.springrecipes.board.service.*Service.post*(..))"
        access="ROLE_USER" />
    <protect-pointcut expression=
        "execution(* com.apress.springrecipes.board.service.*Service.delete*(..))"
        access="ROLE_ADMIN,IP_LOCAL_HOST" />
<protect-pointcut expression=
        "execution(* com.apress.springrecipes.board.service.*Service.find*(..))"
        access="ROLE_USER,ROLE_GUEST" />
</global-method-security>

To test this approach, you have to delete the preceding <security:intercept-methods> element.

Securing Methods with Annotations

The third approach to securing methods is by annotating them with @Secured. For example, you can annotate the methods in MessageBoardServiceImpl with the @Secured annotation and specify the access attributes as its value, whose type is String[].

package com.apress.springrecipes.board.service;
...
import org.springframework.security.access.annotation.Secured;

public class MessageBoardServiceImpl implements MessageBoardService {
    ...
    @Secured({"ROLE_USER", "ROLE_GUEST"})
    public List<Message> listMessages() {
        ...
    }

    @Secured("ROLE_USER")
    public synchronized void postMessage(Message message) {
        ...
    }

    @Secured({"ROLE_ADMIN", "IP_LOCAL_HOST"})
    public synchronized void deleteMessage(Message message) {
        ...
    }

    @Secured({"ROLE_USER", "ROLE_GUEST"})
    public Message findMessageById(Long messageId) {
        return messages.get(messageId);
    }
}

Then, in <global-method-security>, you have to enable security for methods annotated with ethods annotated with @Secured.

<global-method-security secured-annotations="enabled"
    access-decision-manager-ref="accessDecisionManager" />

Handling Security in Views

Problem

Sometimes, you may wish to display a user's authentication information, such as the principal name and the granted authorities, in the views of your web application. In addition, you would like to render the view contents conditionally according to the user's authorities.

Solution

Although you can write JSP scriptlets in your JSP files to retrieve authentication and authorization information through the Spring Security API, it's not an efficient solution. Spring Security provides a JSP tag library for you to handle security in JSP views. It includes tags that can display a user's authentication information and render the view contents conditionally according to the user's authorities.

How It Works

Displaying Authentication Information

Suppose you would like to display a user's principal name and granted authorities in the header of the message listing page (i.e., messageList.jsp). First of all, you have to import Spring Security's tag library definition.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

<html>
<head>
<title>Message List</title>
</head>

<body>
<h2>Welcome! <security:authentication property="name" /></h2>

<security:authentication property="authorities" var="authorities" />
<ul>
<c:forEach items="${authorities}" var="authority">
  <li>${authority.authority}</li>
</c:forEach>
</ul>
<hr />
...
</body>
</html>

The <security:authentication> tag exposes the current user's Authentication object for you to render its properties. You can specify a property name or property path in its property attribute. For example, you can render a user's principal name through the name property.

In addition to rendering an authentication property directly, this tag supports storing the property in a JSP variable, whose name is specified in the var attribute. For example, you can store the authorities property, which contains the authorities granted to the user, in the JSP variable authorities, and render them one by one with a <c:forEach> tag. You can further specify the variable scope with the scope a scope attribute.

Rendering View Contents Conditionally

If you would like to render view contents conditionally according to a user's authorities, you can use the <security:authorize> tag. For example, you can decide whether to render the message authors according to the user's authorities:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

<html>
<head>
<title>Message List</title>
</head>

<body>
...
<c:forEach items="${messages}" var="message">
<table>
  <security:authorize ifAllGranted="ROLE_ADMIN,ROLE_USER">
  <tr>
    <td>Author</td>
    <td>${message.author}</td>
  </tr>
  </security:authorize>
  ...
</table>
<hr />
</c:forEach>
...
</body>
</html>

If you want the enclosing content to be rendered only when the user has been granted certain authorities at the same time, you have to specify them in the ifAllGranted attribute. Otherwise, if the enclosing content can be rendered with any of the authorities, you have to specify them in the ifAnyGranted attribute:

<security:authorize ifAnyGranted="ROLE_ADMIN,ROLE_USER">
<tr>
  <td>Author</td>
  <td>${message.author}</td>
</tr>
</security:authorize>

You can also render the enclosing content when a user has not been granted any of the authorities specified in the ifNotGranted attribute:

<security:authorize ifNotGranted="ROLE_GUEST">
<tr>
  <td>Author</td>
  <td>${message.author}</td>
</tr>
</security:authorize>

Handling Domain Object Security

Problem

Sometimes, you may have complicated security requirements that require handling security at the domain object level. That means you have to allow each domain object to have different access attributes for different principals.

Solution

Spring Security provides a module named ACL that allows each domain object to have its own access control list (ACL). An ACL contains a domain object's object identity to associate with the object, and also holds multiple access control entries (ACEs), each of which contains the following two core parts:

  • Permissions: An ACE's permissions are represented by a particular bit mask, with each bit value for a particular type of permission. The BasePermission class predefines five basic permissions as constant values for you to use: READ (bit 0 or integer 1), WRITE (bit 1 or integer 2), CREATE (bit 2 or integer 4), DELETE (bit 3 or integer 8), and ADMINISTRATION (bit 4 or integer 16). You can also define your own using other unused bits.

  • Security Identity (SID): Each ACE contains permissions for a particular SID. An SID can be a principal (PrincipalSid) or an authority (GrantedAuthoritySid) to associate with permissions.

In addition to defining the ACL object model, Spring Security defines APIs for reading and maintaining the model, and provides high-performance JDBC implementations for these APIs. To simplify ACL's usages, Spring Security also provides facilities, such as access decision voters and JSP tags, for you to use ACL consistently with other security facilities in your application.

How It Works

Setting Up an ACL Service

Spring Security provides built-in support for storing ACL data in a relational database and accessing it with JDBC. First of all, you have to create the following tables in your database for storing ACL data:

CREATE TABLE ACL_SID(
    ID         BIGINT        NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    SID        VARCHAR(100)  NOT NULL,
    PRINCIPAL  SMALLINT      NOT NULL,
    PRIMARY KEY (ID),
    UNIQUE (SID, PRINCIPAL)
);

CREATE TABLE ACL_CLASS(
    ID     BIGINT        NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    CLASS  VARCHAR(100)  NOT NULL,
    PRIMARY KEY (ID),
    UNIQUE (CLASS)
);

CREATE TABLE ACL_OBJECT_IDENTITY(
    ID                  BIGINT    NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    OBJECT_ID_CLASS     BIGINT    NOT NULL,
    OBJECT_ID_IDENTITY  BIGINT    NOT NULL,
    PARENT_OBJECT       BIGINT,
    OWNER_SID           BIGINT,
    ENTRIES_INHERITING  SMALLINT  NOT NULL,
    PRIMARY KEY (ID),
    UNIQUE (OBJECT_ID_CLASS, OBJECT_ID_IDENTITY),
    FOREIGN KEY (PARENT_OBJECT)   REFERENCES ACL_OBJECT_IDENTITY,
    FOREIGN KEY (OBJECT_ID_CLASS) REFERENCES ACL_CLASS,
    FOREIGN KEY (OWNER_SID)       REFERENCES ACL_SID
);

CREATE TABLE ACL_ENTRY(
    ID                  BIGINT    NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    ACL_OBJECT_IDENTITY BIGINT    NOT NULL,
    ACE_ORDER           INT       NOT NULL,
    SID                 BIGINT    NOT NULL,
    MASK                INTEGER   NOT NULL,
    GRANTING            SMALLINT  NOT NULL,
    AUDIT_SUCCESS       SMALLINT  NOT NULL,
    AUDIT_FAILURE       SMALLINT  NOT NULL,
    PRIMARY KEY (ID),
    UNIQUE (ACL_OBJECT_IDENTITY, ACE_ORDER),
    FOREIGN KEY (ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY,
    FOREIGN KEY (SID)                 REFERENCES ACL_SID
);

Spring Security defines APIs and provides high-performance JDBC implementations for you to access ACL data stored in these tables, so you'll seldom have a need to access ACL data from the database directly.

As each domain object can have its own ACL, there may be a large number of ACLs in your application. Fortunately, Spring Security supports caching ACL objects. You can continue to use Ehcache as your cache implementation and create a new configuration for ACL caching in ehcache.xml (located in the classpath root).

<ehcache>
    ...
    <cache name="aclCache"
        maxElementsInMemory="1000"
        eternal="false"
        timeToIdleSeconds="600"
        timeToLiveSeconds="3600"
        overflowToDisk="true"
        />
</ehcache>

Next, you have to set up an ACL service for your application. However, as Spring Security doesn't support configuring the ACL module with XML schema-based configurations, you have to configure this module with a group of normal Spring beans. As the default namespace of board-security.xml is security, it's cumbersome to configure an ACL in this file using the standard XML elements in the beans namespace. For this reason, let's create a separate bean configuration file named board-acl.xml, which will store ACL-specific configurations, and add its location in the web deployment descriptor:

<web-app ...>
    ...
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/board-service.xml
            /WEB-INF/board-security.xml
            /WEB-INF/board-acl.xml
        </param-value>
    </context-param>
</web-app>

In an ACL configuration file, the core bean is an ACL service. In Spring Security, there are two interfaces that define operations of an ACL service: AclService and MutableAclService. AclService defines operations for you to read ACLs. MutableAclService is a subinterface of AclService that defines operations for you to create, update, and delete ACLs. If your application only needs to read ACLs, you can simply choose an AclService implementation, such as JdbcAclService. Otherwise, you should choose a MutableAclService implementation, such as JdbcMutableAclService.

<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="aclCache"
        class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
        <constructor-arg ref="aclEhCache" />
    </bean>

    <bean id="aclEhCache"
        class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager" />
        <property name="cacheName" value="aclCache" />
    </bean>

    <bean id="lookupStrategy"
        class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
        <constructor-arg ref="dataSource" />
        <constructor-arg ref="aclCache" />
        <constructor-arg>
            <bean class="org.springframework.security.acls.domain.
Setting Up an ACL Service
AclAuthorizationStrategyImpl"> <constructor-arg> <list> <ref local="adminRole" /> <ref local="adminRole" /> <ref local="adminRole" /> </list> </constructor-arg> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.acls.domain.
Setting Up an ACL Service
ConsoleAuditLogger" /> </constructor-arg> </bean> <bean id="adminRole" class="org.springframework.security.core.authority.GrantedAuthorityImpl"> <constructor-arg value="ROLE_ADMIN" /> </bean> <bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService"> <constructor-arg ref="dataSource" /> <constructor-arg ref="lookupStrategy" /> <constructor-arg ref="aclCache" /> <property name="sidIdentityQuery"
Setting Up an ACL Service
value="values identity_val_local()" /> </bean> </beans>

The core bean definition in this ACL configuration file is the ACL service, which is an instance of JdbcMutableAclService that allows you to maintain ACLs. This class requires three constructor arguments. The first is a data source for creating connections to a database that stores ACL data. You should have a data source defined in board-service.xml beforehand so that you can simply refer to it here (assuming that you have created the ACL tables in the same database). The third constructor argument is a cache instance to use with an ACL, which you can configure using Ehcache as the back-end cache implementation.

The second argument—sidIdentityQuery—is a lookup strategy that performs lookup for an ACL service. Note, if you're using HSQLDB, that the sidIdentityQuery's property is not necessary, because it defaults to this database. If using another database—as in this case for Apache Derby—an explicit value is necessary.

The only implementation that comes with Spring Security is BasicLookupStrategy, which performs basic lookup using standard and compatible SQL statements. If you want to make use of advanced database features to increase lookup performance, you can create your own lookup strategy by implementing the LookupStrategy interface. A BasicLookupStrategy instance also requires a data source and a cache instance. Besides, it requires a constructor argument whose type is AclAuthorizationStrategy. This object determines whether a principal is authorized to change certain properties of an ACL, usually by specifying a required authority for each category of properties. For the preceding configurations, only a user who has the ROLE_ADMIN role can change an ACL's ownership, an ACE's auditing details, or other ACL and ACE details, respectively.

Finally, JdbcMutableAclService embeds standard SQL statements for maintaining ACL data in a relational database. However, those SQL statements may not be compatible with all database products. For example, you have to customize the identity query statement for Apache Derby.

Maintaining ACLs for Domain Objects

In your back-end services and DAOs, you can maintain ACLs for domain objects with the previously defined ACL service via dependency injection. For your message board, you have to create an ACL for a message when it is posted and delete the ACL when this message is deleted:

package com.apress.springrecipes.board.service;
...
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.access.annotation.Secured;
import org.springframework.transaction.annotation.Transactional;

public class MessageBoardServiceImpl implements MessageBoardService {
    ...
    private MutableAclService mutableAclService;

    public void setMutableAclService(MutableAclService mutableAclService) {
        this.mutableAclService = mutableAclService;
    }
@Transactional
    @Secured("ROLE_USER")
    public synchronized void postMessage(Message message) {
        ...
        ObjectIdentity oid =
            new ObjectIdentityImpl(Message.class, message.getId());
        MutableAcl acl = mutableAclService.createAcl(oid);
        acl.insertAce(0, BasePermission.ADMINISTRATION,
                new PrincipalSid(message.getAuthor()), true);
        acl.insertAce(1, BasePermission.DELETE,
                new GrantedAuthoritySid("ROLE_ADMIN"), true);
        acl.insertAce(2, BasePermission.READ,
                new GrantedAuthoritySid("ROLE_USER"), true);
        mutableAclService.updateAcl(acl);
    }

    @Transactional
    @Secured({"ROLE_ADMIN", "IP_LOCAL_HOST"})
    public synchronized void deleteMessage(Message message) {
        ...
        ObjectIdentity oid =
            new ObjectIdentityImpl(Message.class, message.getId());
        mutableAclService.deleteAcl(oid, false);
    }
}

When a user posts a message, you create a new ACL for this message at the same time, using the message ID as the ACL's object identity. When a user deletes a message, you delete the corresponding ACL as well. For a new message, you insert the following three ACEs into its ACL:

  • The message author is permitted to administrate this message.

  • A user who has the ROLE_ADMIN role is permitted to delete this message.

  • A user who has the ROLE_USER role is permitted to read this message.

JdbcMutableAclService requires that the calling methods have transactions enabled so that its SQL statements can run within transactions. So, you annotate the two methods involving ACL maintenance with the @Transactional annotation and then define a transaction manager and <tx:annotation-driven> in board-service.xml. Also, don't forget to inject the ACL service into the message board service for it to maintain ACLs.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    ...
    <tx:annotation-driven />
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="messageBoardService"
        class="com.apress.springrecipes.board.service.MessageBoardServiceImpl">
        <property name="mutableAclService" ref="aclService" />
    </bean>
</beans>

Making Access Control Decisions Based on ACLs

With an ACL for each domain object, you can use an object's ACL to make access control decisions on methods that involve this object. For example, when a user attempts to delete a posted message, you can consult this message's ACL about whether the user is permitted to delete this message.

Spring Security comes with the AclEntryVoter class, which allows you to define a decision voter that votes for decisions based on ACLs. The following ACL voter in board-acl.xml votes for an access control decision if a method has the ACL_MESSAGE_DELETE access attribute and a method argument whose type is Message. If the current user has the ADMINISTRATION or DELETE permissions in the message domain object's ACL, that user will be permitted to delete this message.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
    ...
    <bean id="aclMessageDeleteVoter"
        class="org.springframework.security.acls.AclEntryVoter">
        <constructor-arg ref="aclService" />
        <constructor-arg value="ACL_MESSAGE_DELETE" />
        <constructor-arg>
            <list>
                <util:constant static-field="org.springframework.security.
Making Access Control Decisions Based on ACLs
acls.domain.BasePermission.ADMINISTRATION" /> <util:constant static-field="org.springframework.security.
Making Access Control Decisions Based on ACLs
acls.domain.BasePermission.DELETE" /> </list> </constructor-arg> <property name="processDomainObjectClass" value="com.apress.springrecipes.board.domain.Message" /> </bean>
<bean id="aclAccessDecisionManager"
        class="org.springframework.security.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list>
                <bean class="org.springframework.security.vote.RoleVoter" />
                <ref local="aclMessageDeleteVoter" />
            </list>
        </property>
    </bean>
</beans>

After configuring a voter, you have to include it in an access decision manager for it to vote for decisions. Because an ACL voter cannot vote for HTTP-based access decisions, you can't include it in the global access decision manager, as this manager is used for the <http> element. Instead, you should configure another access decision manager that is specific for method invocations (aclAccessDecisionManager in this case) and include the ACL voter in this manager. In board-security.xml, you have to modify the <global-method-security> element to use this access decision manager for method invocation security:

<global-method-security secured-annotations="enabled"
    access-decision-manager-ref="aclAccessDecisionManager" />

With the voter and access decision manager set up, the last step is to specify the access attribute ACL_MESSAGE_DELETE for the deleteMessage() method:

package com.apress.springrecipes.board.service;
...
import org.springframework.security.access.annotation.Secured;

public class MessageBoardServiceImpl implements MessageBoardService {
    ...
    @Transactional
    @Secured("ACL_MESSAGE_DELETE")
    public synchronized void deleteMessage(Message message) {
        ...
    }
}

With this attribute, only a user who has the ADMINISTRATION permission (by default, the message author) or the DELETE permission (by default, an administrator who has the ROLE_ADMIN role) on the message argument can delete a message.

If you want to hide a message's Delete link when the current user isn't permitted to delete the message, you can wrap the link with the <security:accesscontrollist> tag, whose function is to render its body conditionally according to a domain object's ACL:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

<html>
<head>
<title>Message List</title>
</head>

<body>
...
<c:forEach items="${messages}" var="message">
<table>
  ...
  <security:accesscontrollist domainObject="${message}" hasPermission="8,16">
  <tr>
    <td colspan="2">
      <a href="messageDelete.htm?messageId=${message.id}">Delete</a>
    </td>
  </tr>
  </security:accesscontrollist>
</table>
<hr />
</c:forEach>
...
</body>
</html>

The <security:accesscontrollist> tag consults the specified domain object's ACL to check whether the current user has the specified permissions. This tag will only render its body if the user has one of the required permissions. Note that, in this tag, permissions are defined as integers translated from their bit mask values. The values 8 and 16 represent the DELETE and ADMINISTRATION permissions, respectively.

Handling Domain Objects Returned from Methods

Spring Security can use after invocation providers to handle domain objects returned from methods according to the ACLs of these objects. For methods that return a single domain object, you can register an AclEntryAfterInvocationProvider instance to check whether the current user has specified permissions to access the returned domain object. If the user is not permitted to access the object, this provider will throw an exception to prevent the object from being returned.

On the other hand, for methods that return a collection of domain objects, you can register an AclEntryAfterInvocationCollectionFilteringProvider instance to filter the returned collection according to the ACLs of this collection's domain object elements. The domain objects that the current user doesn't have specified permissions on will be removed from the collection before it's returned to the calling method.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.0.xsd">
    ...
    <bean id="afterAclRead" class="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.afterinvocation.AclEntryAfterInvocationProvider"> <security:custom-after-invocation-provider /> <constructor-arg ref="aclService" /> <constructor-arg> <list> <util:constant static-field="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.domain.BasePermission.ADMINISTRATION" /> <util:constant static-field="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.domain.BasePermission.READ" /> </list> </constructor-arg> </bean> <bean id="afterAclCollectionRead" class="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider"> <security:custom-after-invocation-provider /> <constructor-arg ref="aclService" /> <constructor-arg> <list> <util:constant static-field="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.domain.BasePermission.ADMINISTRATION" /> <util:constant static-field="org.springframework.security.
Handling Domain Objects Returned from Methods
acls.domain.BasePermission.READ" /> </list> </constructor-arg> </bean> </beans>

To register a custom after invocation provider to Spring Security, you can simply embed a <custom-after-invocation-provider> element in the bean definition. This element is defined in the security schema, so you have to import it beforehand.

Now, you can specify the access attributes AFTER_ACL_COLLECTION_READ and AFTER_ACL_READ, which will be handled by the preceding after invocation providers, for the listMessages() and findMessageById() methods.

package com.apress.springrecipes.board.service;
...
import org.springframework.security.access.annotation.Secured;

public class MessageBoardServiceImpl implements MessageBoardService {
    ...
    @Secured({"ROLE_USER", "ROLE_GUEST", "AFTER_ACL_COLLECTION_READ"})
    public List<Message> listMessages() {
        ...
    }

    @Secured({"ROLE_USER", "ROLE_GUEST", "AFTER_ACL_READ"})
    public Message findMessageById(Long messageId) {
        ...
    }
}

Summary

In this chapter, you learned how to secure applications using Spring Security 3.0. It can be used to secure any Java application, but it's mostly used for web applications. The concepts of authentication, authorization, and access control are essential in the security area, so you should have a clear understanding of them.

You often have to secure critical URLs by preventing unauthorized access to them. Spring Security can help you to achieve this in a declarative way. It handles security by applying servlet filters, which can be configured with simple XML elements. If your web application's security requirements are simple and typical, you can enable the HTTP auto-config feature so that Spring Security will automatically configure the basic security services for you.

Spring Security supports multiple ways for users to log into a web application, such as form-based login and HTTP Basic authentication. It also provides an anonymous login service that allows you to handle an anonymous user just like a normal user. Remember-me support allows an application to remember a user's identity across multiple browser sessions.

Spring Security supports multiple ways of authenticating users and has built-in provider implementations for them. For example, it supports authenticating users against in-memory definitions, a relational database, and an LDAP repository. You should always store encrypted passwords in your user repository, because clear-text passwords are vulnerable to hacker attacks. Spring Security also supports caching user details locally to save you the overhead of performing remote queries.

Decisions on whether a user is allowed to access a given resource are made by access decision managers. Spring Security comes with three access decision managers that are based on the voting approach. All of them require a group of voters to be configured for voting on access control decisions.

Spring Security enables you to secure method invocations in a declarative way, either by embedding a security interceptor in a bean definition, or matching multiple methods with AspectJ pointcut expressions or annotations. Spring Security also allows you to display a user's authentication information in JSP views and render view contents conditionally according to a user's authorities.

Spring Security provides an ACL module that allows each domain object to have an ACL for controlling access. You can read and maintain an ACL for each domain object with Spring Security's high-performance APIs, which are implemented with JDBC. Spring Security also provides facilities such as access decision voters and JSP tags for you to use ACLs consistently with other security facilities.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset