Spring Security in the wild with Maven +

security
Security is always important when you need protected or user specific areas in your application. The easiest way for a basic security is the configuration of a security constraint in your web.xml file. If you are using Tomcat all you need is a Realm. This works well for static users in a file or if you are beginning your application with the standard database layout for the Tomcat JDBC Realm. If you have a look at the database layout this won’t fit well for most of the applications. You need more options, but Tomcat is very limited in this point.

If you need more options you should have a look at Spring Security (formerly known as Acegi Security). There are a lot of options in Spring Security, but I will give you an example, not a list of features copied from the documentation.

Requirements

  • easy configuration
  • working together with Struts2
  • URL based
  • getting user details from a web service

When I started using Spring Security I found a lot of examples. There is also a good documentation available, but you need some hours to sort everything. Because I couldn’t find an example for exactly my problem, I will share my solution.

Before we start

At the beginning of your application you should plan which areas need a security layer. You don’t need to include it from the beginning, but please take the time to think about it. This will reduce the configuration time afterwards. If you know that some services will be secured, create a special namespace for this (e.g. /secure/*). For your URL based security you just need to include the namespace. It is a waste of time to create a lot of actions in a base folder and write 100 lines of suspect security configuration. If you forget an action, it could be very dangerous. If you need a more fine grained security you can use a good URL layout as a starting point and reduce the configuration overhead.

Let’s get real

Maven configuration

<project>

  ...

  <dependencies>
    <!--  Struts 2 -->
    <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-sitemesh-plugin</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-spring-plugin</artifactId>
    </dependency>

    <!-- Security -->
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core-tiger</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-acl</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-taglibs</artifactId>
    </dependency>

    <!-- Servlet & Jsp -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
      <scope>provided</scope>
    </dependency>
</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
         version="2.4">
  <display-name>Name</display-name>

  <!-- path of applicationContexts used by spring -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      /WEB-INF/applicationContext.xml
      /WEB-INF/applicationContext-security.xml
    </param-value>
  </context-param>

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter>
    <filter-name>action2-cleanup</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
  </filter>
  <filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
  </filter>
  <filter>
    <filter-name>action2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>action2-cleanup</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>action2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- Listeners -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- Welcome file lists -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

applicationContext.xml

<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-2.0.xsd" default-autowire="autodetect">

  <!-- UserService for Spring Security -->
  <bean id="customUserService" class="com.agimatec.ostium.security.CustomUserDetailsService"/>
</beans>

applicationContext-security.xml

<!--
- Spring namespace-based configuration
-->
<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-2.0.xsd

http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

  <!--
  - Secure the page per the URL pattern
  -->
  <http auto-config="true">
    <!-- we need a ROLE_ prefix in here -->
    <intercept-url pattern="/update/**" access="ROLE_OSTIUM-User"/>

    <!-- use own login page rather than the default one -->
    <form-login login-page="/login/login.jsp"/>
  </http>

  <!--
    Use with DummyDAO
  -->
  <!--<authentication-provider>
    <user-service>
      <user name="12345" password="12345" authorities="ROLE_USER"/>
      <user name="12346" password="12346" authorities="ROLE_USER"/>
    </user-service>
  </authentication-provider> -->

  <!--
    Use with JDBC
  -->
  <!--<authentication-provider>
    <jdbc-user-service
        data-source-ref="dataSource"
        users-by-username-query="SELECT ...;"
        authorities-by-username-query="SELECT ..."/>
  </authentication-provider> -->

  <authentication-provider user-service-ref="customUserService"/>
</beans:beans>

CustomUserDetailsService.java

package com.agimatec.ostium.security;

import com.agimatec.connecta.model.XFireUser;
import com.agimatec.connecta.xfire.WebXFireDAO;
import com.agimatec.nucleus.exceptions.CodedException;
import com.agimatec.nucleus.common.model.UserState;
import com.agimatec.usermanagement.service.XFireUserManagement;
import org.springframework.dao.DataAccessException;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.User;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;

public class CustomUserDetailsService extends WebXFireDAO implements UserDetailsService {
    public CustomUserDetailsService() {
        super(XFireUserManagement.class);
    }

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        try {
            // get the user from the XFire webservice
            XFireUser user = this.getService().getUserByIdentification(username);

            // extract all important parts
            String password = user.getPassword();
            String role = user.getRole();
            boolean enabled = user.isEnabled();

            // create a new Spring Security User
            return new User(username,password,enabled,true,true,true,new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_"+role)});
        } catch (CodedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

2 Responses to “Spring Security in the wild with Maven”

  1. a description of your concerns and how this solution addresses them, would of helped alot!
    rather than, “oh, nothing matched what i needed… but this worked for me…”

    doesnt really help…

  2. Hello Zynasis,

    I was searching for a solution to authenticate the user against a web service. There are a lot of examples for file based or JDBC based Spring Security. For me an example what to do, to plug in custom logic was missing. Maybe there are examples out there, but as I said, I could’t find one.

    Because I think it could save a lot of time, if anyone is searching how to do it and just implement UserDetailService and configure it.

    Hope that helps ;)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>