Using Spring Security with Apache Directory Server

This recipe extends on the previous recipe and shows a fully-functional Flight Booking application developed using Spring Web MVC that makes use of Spring Security to implement web request and method-level security. We'll look at modifications or additions that we need to make to configurations and artifacts generated by the security setup command to create a security-aware Flight Booking application.

Let's first take a quick look at the security requirements of the Flight Booking application before we delve into the details of how these requirements are met using Spring Security.

Flight Booking application requirements

The Flight Booking application users are authenticated against Apache Directory Server, which contains application users, details and their role information. An authenticated user of the Flight Booking application can either have the role of ROLE_ADMIN_USER or ROLE_APP_USER. Access to application functionality is granted or restricted based on the authenticated user's role.

Web request security requirement of Flight Booking application restricts unauthorized access to menu options. The following screenshot shows the main menu of the Flight Booking application:

Flight Booking application requirements

The following table defines the access permissions for each menu option (shown in the given screenshot) based on role:

Menu option

Accessible to role

Create new FlightDescription

List all Flight Descriptions

Find by Destination And Origin

Create new Flight

List all Flights

ROLE_ADMIN_USER

Create new Booking

List all Bookings

ROLE_APP_USER

ROLE_ADMIN_USER

As the given table shows, an application user with the ROLE_ADMIN_USER role can access web pages for Flight, FlightDescription, and Booking JPA entities. An application user with the ROLE_APP_USER role can only access web pages corresponding to the Booking JPA entity.

Even though the Create new Booking and List all Bookings links are accessible to both ROLE_APP_USER and ROLE_ADMIN_USER roles (as shown in the preceding table), the following security requirements (which will eventually translate into method-level security requirements) must also be met by the application:

  • A user with ROLE_APP_USER role can create a new Booking instance, but can't edit or remove an existing Booking instance
  • A user with ROLE_ADMIN_USER role can edit or remove an existing Booking instance, but can't create a new Booking instance

Getting ready

Extract the contents of the ch06-ldap-security.zip file into the C: oo-cookbook directory. This will create the ch06-ldap-security directory in C: oo-cookbook. The ch06-ldap-security directory contains a flightapp-web web project that represents the security-aware Flight Booking application. This flightapp-web project is an extension of the flightapp-web project that we created in the previous recipe. It contains modifications to Spring Security generated artifacts, and a couple of additional changes to the address web request and method-level security requirements of the Flight Booking application.

If you are using a different database than MySQL or your connection settings are different than what is specified in database.properties file of flightapp-web project, then modify the database.properties file accordingly.

Open the command prompt and go to the C: oo-cookbookch06-ldap-security directory.

How to do it...

To configure security settings with the Spring application, follow the steps given here:

  1. Deploy the flightapp-web project as a dynamic web application in an embedded Tomcat instance:
    ..ch06-ldap-security> mvn tomcat:run
    

    This will download the dependencies defined in the pom.xml file of the flightapp-web project. Now, you can access the flightapp-web application by accessing the following URL:http://localhost:8080/flightapp-web

    If you see the following web page, then it means you have successfully deployed the flightapp-web application on the embedded Tomcat instance:

    How to do it...
  2. Select the Create new Flight Description menu option, which will show you the login screen of the Flight Booking application, as shown here:
    How to do it...
  3. Enter admin in the Name labeled field, admin in the Password labeled field, and click the Submit button to log in to the application. The admin user has ROLE_ADMIN_USER role.

    The admin user is associated with the ROLE_ADMIN_USER role; therefore, the admin user is shown the form for creating a new FlightDescription instance, as shown here:

    How to do it...
  4. Enter flight description details as shown in the screenshot and click the Save button to create a new FlightDescription instance.
  5. Now, select the Create new Flight menu option to view the form for creating a new Flight instance, as shown here:
    How to do it...
  6. Set the date in the Arrival Date and Departure Date fields, and select the newly created FlightDescription from the combo box labeled Flight Description. Set the value of the Flight Number field to MYFLT-101. Now, click the Save button to create the new Flight instance in the database.
  7. In the given form, you may notice that in the Roo-generated Flight Booking Spring Web MVC application we can set arrival and departure dates but can't set time of arrival or departure of flights. This is because Roo-generated Spring Web MVC applications make use of the dijit.form.DateTextBox component of the Dijit library to render java.util.Date type fields of a JPA entity in JSPX views. You can modify this behavior by either modifying Roo-installed datetime.tagx tag (refer to the WEB-INF/tags/form/fields directory of flightapp-web) or by creating your own custom tag that renders a java.util.Date JPA field as a form field, which makes use of both dijit.form.DateTextBox (for selecting date) and diji.form.TimeTextBox (for selecting time). This allows users to select both date and time values for the field.
  8. As we have already created the FlightDescription instance and associated Flight instance, it's time to create a booking on the MYFLT-101 flight. Select the Create new Booking menu option to view the form for creating a new Booking instance, as shown here:
    How to do it...
  9. Select MYFLT-101 flight number from Flight field and enter a name in the Booked By field. If you now click the Save button to save the Booking instance, you'll receive an Access denied to admin message, as shown here:
    How to do it...

    The access denied message is shown because a user in the ROLE_ADMIN_USER role doesn't have access to invoke the persist method of the Booking JPA entity. Select the Logout hyperlink to log out from the Flight Booking application.

  10. Now, select the Create new Booking option from the menu option. The Flight Booking application will ask you to log in because all menu options are accessible only to authenticated users. Log in with name as ashish and password as ashish. The user ashish has the ROLE_APP_USER role.
  11. Create a new Booking instance, as described in the fifth step. This time the Booking instance is created successfully because ROLE_APP_USER has the permission to invoke the persist method of the Booking JPA entity.
  12. Now, select the Create new Flight Description menu option. This will show the Access denied to ashish message, as shown here:
    How to do it...

    The access denied message is displayed because web request security of Flight Booking application restricts users from accessing menu options related to Flight and FlightDescription JPA entities. Also, if you are logged in as ashish and attempt to modify or delete an existing Booking instance, then you'll be denied access by the application. The reason for this is that the permission to invoke merge and remove methods of the Booking JPA entity is only with users with ROLE_ADMIN_USER role.

How it works...

In the Configuring Spring security for your application recipe, we discussed Spring Security configuration generated by the security setup command. As the security setup command created configuration was only helpful in getting us started with adding security to our application, this recipe extends the configuration created by security setup command to demonstrate how authentication and authorization can be quickly incorporated into Roo-generated web applications. In this section, we'll look at what modifications or additions we made to configurations and artifacts generated by security setup command to create a security-aware Flight Booking application.

Let's start with how we set up Apache Directory Server as the authentication source for the Flight Booking application.

Setting up embedded Apache Directory Server

Spring Security namespace provides an <ldap-server> element that configures the location of an external LDAP server against which authentication is to be performed. It can also be used to create an embedded Apache Directory Server instance. If the url attribute of the <ldap-server> element is specified, then it means that an external LDAP server is being used for authentication. And, if the url attribute is not specified, then an embedded instance of Apache Directory Server is created.

The applicationContext-security.xml file of the flightapp-web project configures embedded Apache Directory Server instances, as shown here:

<ldap-server ldif="classpath:application_users.ldif" 
      root="dc=sample,dc=com" />

The ldif attribute specifies the location of the LDIF (LDAP Data Interchange Format) file, which contains user information loaded by the embedded LDAP server. You'll find the application_users.ldif file in the WEB-INF/classes directory of the flightapp-web project. The root attribute specifies the root of the LDAP directory tree.

The following figure shows the LDAP directory tree defined by the application_users.ldif file:

Setting up embedded Apache Directory Server

The given figure shows that user groups administrator and appuser are defined under ou=groups, and application users admin and ashish are defined under ou=users. The DN (Distinguished Name) of user ashish is uid=ashish,ou=users,dc=sample,dc=com, and DN of user admin is uid=admin,ou=users,dc=sample,dc=com. DN of administrator group entry is cn=administrator,ou=groups,dc=sample,dc=com, and DN of appuser group entry is cn=appuser,ou=groups,dc=sample,dc=com.

The uniqueMember attribute(s) of an entry defined under ou=groups identifies the application user who belongs to that group. For instance, in the cn=administrator entry, the uniqueMember attribute value is uid=admin,ou=users,dc=sample,dc=com (DN of user admin), which means that the admin user belongs to administrator group. Similarly, ashish belongs to appuser group.

The businessCategory attribute of an entry under ou=groups identifies the role of the users belonging to that group. As the given figure shows, the role of user admin is admin_user and the role of user ashish is app_user.

As Flight Booking application makes use of the embedded Apache Directory Server, the following JAR dependencies have been added to the pom.xml file of the flightapp-web project:

  • apacheds-protocol-shared
  • apacheds-protocol-ldap
  • apacheds-core-entry
  • apacheds-core
  • apacheds-server-jndi
  • shared-ldap

Note

As Spring Security supports version 1.5.5 of embedded Apache Directory Server, all the given JAR files belong to version 1.5.5.

As we are using Spring Security's LDAP support, spring-security-ldap JAR dependency is also added to the pom.xml file.

Let's now look at configuration, which instructs Spring Security to authenticate against the embedded LDAP server.

Authenticating against the LDAP server

To authenticate against the embedded LDAP server, the following configuration has been added to the applicationContext-security.xml file:

<authentication-manager>
  <authentication-provider>
    <ldap-user-service group-search-filter="uniqueMember={0}"
       group-search-base="ou=groups" 
       user-search-base="ou=users" 
       user-search-filter="uid={0}" 
       group-role-attribute="businessCategory" />
  </authentication-provider>
</authentication-manager>

The <ldap-user-service> configures LdapUserDetailsService (an implementation of UserDetailsService that we discussed earlier), which loads user details containing username, password, and roles from the LDAP server. Authentication is performed by comparing the user entered password with the user details loaded by the LdapUserDetailsService instance.

The <ldap-user-service> element accepts the following attributes:

  • user-search-base: Specifies the part of the directory tree under which search for users is performed. The value ou=users means that the search will be performed on entries that are defined under DN, which are ou=users, dc=sample, dc=com.
  • user-search-filter: It specifies the filter criteria used for searching users in the directory tree. The value uid={0} means that search is made for the user entry where value of uid attribute is equal to username entered by the user in the login form. The value {0} is replaced by the username entered by the user in the login form.
  • group-search-base: It specifies the part of the directory tree under which search for groups is performed. The value ou=groups means that the search will be performed on entries that are defined under DN, which are ou=groups,dc=sample, and dc=com.
  • group-search-filter: It specifies the filter criteria used for searching groups in the directory tree. The value uniqueMember={0} means that a search is made for group entries, where the value of the uniqueMember attribute is equal to the DN of the user.

    If user ashish attempts to log in to the Flight Booking application, then the value of the user-search-base and user-search-filter attributes will be used for searching the user. This will result in returning the entry whose DN is uid=ashish,ou=users,dc=sample,dc=com. Now, the value of the group-search-filter and group-search-base attributes will be used to search for the group entry whose uniqueMember attribute value is uid=ashish,ou=users,dc=sample,dc=com. This will return the cn=appuser,ou=groups,dc=sample,dc=com entry because it contains the uniqueMember attribute with value uid=ashish,ou=users,dc=sample,dc=com.

  • group-role-attribute: It specifies the attribute of the group entry, which is used as the role name. As the value of group-role-attribute is businessCategory, if the group entry returned for the authenticating user is cn=administrator,ou=groups,dc=sample,dc=com, then the role of the user is admin_user—the value of the businessCategory attribute of the entry.

It is important to note at this point that Spring Security's LDAP authentication mechanism by default prepends ROLE_ to the name of the role returned after authentication. So, if the admin user authenticates with the Flight Booking application, then instead of admin_user role, it gets the role ROLE_ADMIN_USER. Similarly, user ashish gets the 'ROLE_APP_USER' role.

Let's now look at how web request security is configured for the Flight Booking application.

Configuring web request security

The following <http> element of security namespace shows how web request security is configured in the applicationContext-security.xml file of the flightapp-web project:

<http auto-config="true" use-expressions="true">
  <access-denied-handler error-page="/accessdenied" />
  <form-login .../>
  <logout logout-url="/resources/j_spring_security_logout" />

  <intercept-url pattern="/flights/**" 
    access="hasRole('ROLE_ADMIN_USER')" />

  <intercept-url pattern="/flightdescriptions/**" 
    access="hasRole('ROLE_ADMIN_USER')" />

  <intercept-url pattern="/bookings/**"
    access="hasAnyRole('ROLE_APP_USER','ROLE_ADMIN_USER')" />

  <intercept-url pattern="/accessdenied/**"
    access="hasAnyRole('ROLE_APP_USER', 'ROLE_ADMIN_USER')" />
 </http>

As in the previous recipe, we discussed the <http> element of the Roo-generated applicationContext-security.xml file; but here we'll only focus on configuration elements specifically added for meeting flightapp-web application's web security requirements. In the given code, the following are the elements that we added to configure web request security for the Flight Booking application:

  • <access-denied-handler>: It configures the error page that is shown to the user if access is denied to the requested page. The error-page attribute specifies the URL of the error page. The <mvc:view-controller path="/accessdenied"/> entry in webmvc-config.xml configures ParameterizableViewController, responsible for rendering the access denied page—accessdenied.jspx in the /WEB-INF/views directory.
  • <intercept-url>: It defines the URL pattern and the corresponding access permissions for the Flight Booking application. Let's look at each of the <intercept-url> elements defined for the Flight Booking application:
    <intercept-url pattern="/flights/**" 
        access="hasRole('ROLE_ADMIN_USER')" />
    • The pattern /flights/** refers to all the web pages that are specific to managing Flight JPA entity instances. The hasRole('ROLE_ADMIN_USER') expression specifies that pages specific to managing Flight JPA entity instances are accessible only to users with the ROLE_ADMIN_USER role.
      <intercept-url pattern="/flightdescriptions/**" 
          access="hasRole('ROLE_ADMIN_USER')" />
    • The pattern /flightdescriptions/** refers to all the web pages that are specific to managing FlightDescription JPA entity instances. The hasRole('ROLE_ADMIN_USER') expression specifies that pages specific to managing FlightDescription JPA entity instances are accessible only to users with the ROLE_ADMIN_USER role.
      <intercept-url pattern="/bookings/**"
          access="hasAnyRole('ROLE_APP_USER','ROLE_ADMIN_USER')" />
    • The pattern /bookings/** refers to all the web pages that are specific to managing Booking JPA entity instances. The hasAnyRole('ROLE_APP_USER','ROLE_ADMIN_USER') expression specifies that pages specific to managing Booking JPA entity instances are accessible only to users with the ROLE_ADMIN_USER or ROLE_APP_USER role.
      <intercept-url pattern="/accessdenied/**"
          access="hasAnyRole('ROLE_APP_USER', 'ROLE_ADMIN_USER')" />
    • The pattern /accessdenied/** refers to the web page that shows Access denied message. The hasAnyRole('ROLE_APP_USER','ROLE_ADMIN_USER') expression specifies that the access denied page is accessible to users with the ROLE_ADMIN_USER or ROLE_APP_USER role. This is important because the access denied page should not be accessible to anonymous users.

    Let's now look at how method-level security is configured in the Flight Booking application.

Configuring method-level security

Method-level security in the Flight Booking application is enabled by the <global-method-security> element in the applicationContext-security.xml file, as shown here:

<global-method-security mode="aspectj" 
   secured-annotations="enabled"/>

The mode attribute value specifies whether Spring AOP (which proxies the target object) or AspectJ (in which Spring's AspectJ security aspect is weaved into the class at load-time or compile-time) is used for securing methods. As Booking JPA entity is created outside the Spring container, to use Spring's @Secured annotation (discussed in the next section) to secure methods defined by Booking JPA entity, you need to use AspectJ. The value aspectj of the mode attribute instructs Spring to weave AnnotationSecurityAspect (available in the Spring Security's spring-security-aspects JAR file) into classes that make use of the @Secured annotation. The secured-annotations attribute specifies if the use of the @Secured annotations is enabled or disabled for the application context. The value enabled means that Spring Security will secure all methods that make use @Secured method-level annotation. Spring also supports using JSR-250 security annotations, security expressions (like hasRole, hasPermission, and so on), and the <protect-pointcut> sub-element of the <global-method-security> element to implement method-level security. You can use a combination of different approaches to implement method-level security in your application.

As we are using AspectJ mode for implementing method-level security in the Flight Booking application, dependency on spring-security-aspects JAR has been added to the pom.xml file, and the AspectJ compiler plugin configuration in the pom.xml file has been updated to include spring-security-aspects, as shown here:

   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    ...
    <configuration>
     <outxml>true</outxml>
     <aspectLibraries>
      <aspectLibrary>
       <groupId>org.springframework</groupId>
       <artifactId>spring-aspects</artifactId>
      </aspectLibrary>
      <aspectLibrary>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-aspects</artifactId>
      </aspectLibrary>
     </aspectLibraries>
     ...
    </configuration>
   </plugin>

The <aspectLibrary> element specifies the JAR files that contain aspects. The spring-aspects JAR contains aspect for weaving @Transactional support and spring-security-aspects JAR contains aspect for weaving @Secured support in classes.

Note

You may also notice that Spring Security version 3.1.0 RC1 has been used in the Flight Booking application because spring-security schema prior to version 3.1 didn't support mode attribute for <global-method-security> element.

Now that we have seen how method-level security is configured for the Flight Booking application, we are ready to annotate Booking JPA entity methods with the @Secured annotation.

Adding @Secured annotation to JPA entity methods

Spring Security's @Secured annotation can be used at method-level to secure methods from unauthorized access. @Secured annotation specifies the user roles that are authorized to invoke the method.

Adding Spring Security's @Secured annotation to JPA entity methods in Roo-generated applications is a bit of an involved process. Roo defines JPA entity methods in the *_Roo_Entity.aj AspectJ ITD file, which is not recommended to be modified by application developers. To add the @Secured annotation to a JPA entity method, perform push-in refactoring (refer to Chapter 7) to move the method to the entity's Java class or simply copy the methods from the AspectJ file to entity's Java class. For instance, in the case of the Booking JPA entity, the persist method is copied from the Booking_Roo_Entity.aj file to the Booking.java file, as shown here:

@RooEntity(identifierColumn = "BOOKING_ID")
public class Booking {
    @PersistenceContext
    transient EntityManager entityManager;
    ...
    @Transactional
    @Secured("ROLE_APP_USER")
    public void persist() {
        if (this.entityManager == null) 
           this.entityManager = entityManager();
        this.entityManager.persist(this);
    }
    
    public static final EntityManager entityManager() {
        EntityManager em = new Booking().entityManager;
        ...
        return em;
    }
}

The given code shows that the persist method is copied from Booking_Roo_Entity.aj to the Booking.java file. The @Secured annotation is added to the persist method to make it secure.

It is important to note that copying a method from AspectJ ITD to a Java file doesn't require moving dependent methods and attributes also. For instance, moving persist method from Booking_Roo_Entity.aj to Booking.java doesn't require moving entityManager attribute and entityManager() method, as shown in the code. It has been done to simplify understanding the code.

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

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