Wednesday, February 10, 2010

Spring security and JAAS provider

Recently, I checked in code (#1318, #1321) to integrate Spring Security into CollectionSpace. As you know, we already had implemented a working JAAS Login Module that acts as a default identity provider. The configuration of that login module is externalized into a JBoss application-policy in the login-config.xml. That is a good thing. Also, that login module inserts tenant information for the user into the security context. We plan to use this info. while making access control decisions in downstream request processing. So, my first task was to integrate that login module with Spring security, if it was possible.

I faced some problems (described at the end) but it was possible. The application-security.xml of our web application looks as follows.

1:  <beans xmlns="http://www.springframework.org/schema/beans"  
2:      xmlns:s="http://www.springframework.org/schema/security"  
3:      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
4:      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
5:      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">  
6:    <s:http use-expressions="true">  
7:      <s:intercept-url pattern="/**" access="permitAll" />  
8:      <s:http-basic />  
9:      <s:logout />  
10:    </s:http>  
11:    <s:authentication-manager>  
12:      <s:authentication-provider ref="jaasAuthenticationProvider" user-service-ref="userDetailsService"/>  
13:    </s:authentication-manager>  
14:    <bean id="jaasAuthenticationProvider"  
15:       class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">  
16:      <property name="loginContextName">  
17:        <value>cspace</value> <!-- value should be same as in application-policy in JBoss login-config.xml -->  
18:      </property>  
19:      <property name="loginConfig">  
20:        <value>/WEB-INF/login.conf</value> <!-- filler, not used at runtime -->  
21:      </property>  
22:      <property name="callbackHandlers">  
23:        <list>  
24:          <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>  
25:          <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>  
26:        </list>  
27:      </property>  
28:      <property name="authorityGranters">  
29:        <list>  
30:          <bean class="org.collectionspace.authentication.spring.CSpaceAuthorityGranter"/>  
31:        </list>  
32:      </property>  
33:    </bean>  
34:    <bean id="userDetailsService" class="org.collectionspace.authentication.spring.CSpaceUserDetailsService">  
35:    </bean>  
36:  </beans>  
Listing 1

As you see, authentication-manager refers to jaasAuthenticationProvider (#12). This JAAS authentication provider is using the same value cspace for loginContextName (#16-18) as the value of the attribute named name (#1 Listing 2) in application-policy of JBoss's login-config.xml as shown below. This is important. This is how Spring security framework finds out the required JAAS login module from the JBoss environment and ties it to its own JAAS authentication provider.

1:  <application-policy name="cspace">  
2:    <authentication>  
3:      <login-module code="org.collectionspace.authentication.jaas.CSpaceJBossDBLoginModule"  
4:             flag="required">  
5:        ...
6:      </login-module>  
7:    </authentication>  
8:  </application-policy>  
Listing 2

From application-security.xml in Listing 1, you may also notice that I have configured the following two entities for CollectionSpace:

  1. CSpaceAuthorityGranter (#30). This class returns a set of strings. Each entry in the set represents a string for a role which is represented as Java Principal by the JAAS provider. Spring uses this string to create a corresponding AuthorityGranter.
  2. CSpaceUserDetailsService (#34). Even if you do not have additional attributes for a user (e.g. user profile), this entity needs to be configured. It is useful to obtain application specific information attached to the user. For example, in CollectionSpace, this service could retrieve information such as first name, last name, contact details, etc. from services such as the Person sevice.
Problem

The CollectionSpace authentication service is packaged as a jar. This jar includes CS database realm classes, JAAS login module and the above mentioned Spring security specific classes. This jar is copied to JBoss's domain lib, e.g. (server/cspace/lib) and is available from system classpath at runtime.


The application-security.xml is packaged with the war file. This war file is built using maven. Maven could package all the Spring specific dependencies when the war is built.

At runtime, Spring framework would complain with the following:

"Cannot convert value of type org.collectionspace.authentication.spring.CSpaceAuthorityGranter to required type org.springframework.security.authentication.jaas.AuthorityGranter for property authorityGranters[0] : no matching editors or conversion strategy found"

To overcome this, I used the following dependency while building the authentication service jar.

1:      <spring.security.version>3.0.1.RELEASE</spring.security.version>  
2:      <dependency>  
3:        <groupId>org.springframework.security</groupId>  
4:        <artifactId>spring-security-core</artifactId>  
5:        <version>${spring.security.version}</version>  
6:        <scope>provided</scope>  
7:      </dependency>  
Listing 3


And the war was build using the following dependencies.

1:    <properties>  
2:      <spring.version>3.0.0.RELEASE</spring.version>  
3:      <spring.security.version>3.0.1.RELEASE</spring.security.version>  
4:    </properties>  
5:      <!-- dependencies on spring security & framework are runtime deps only -->  
6:      <!-- the following list is kept to make sure domain has these packages -->  
7:      <!-- in the cspace/lib directory -->  
8:      <dependency>  
9:        <groupId>org.springframework.security</groupId>  
10:        <artifactId>spring-security-core</artifactId>  
11:        <version>${spring.security.version}</version>  
12:        <scope>provided</scope>  
13:      </dependency>  
14:      <dependency>  
15:        <groupId>org.springframework.security</groupId>  
16:        <artifactId>spring-security-config</artifactId>  
17:        <version>${spring.security.version}</version>  
18:        <scope>provided</scope>  
19:      </dependency>  
20:      <dependency>  
21:        <groupId>org.springframework.security</groupId>  
22:        <artifactId>spring-security-web</artifactId>  
23:        <version>${spring.security.version}</version>  
24:        <scope>provided</scope>  
25:      </dependency>  
26:      <dependency>  
27:        <groupId>org.springframework.security</groupId>  
28:        <artifactId>spring-security-acl</artifactId>  
29:        <version>${spring.security.version}</version>  
30:        <scope>provided</scope>  
31:      </dependency>  
32:      <dependency>  
33:        <groupId>org.springframework</groupId>  
34:        <artifactId>spring-context</artifactId>  
35:        <version>${spring.version}</version>  
36:        <scope>provided</scope>  
37:      </dependency>  
38:      <dependency>  
39:        <groupId>org.springframework</groupId>  
40:        <artifactId>spring-web</artifactId>  
41:        <version>${spring.version}</version>  
42:        <scope>provided</scope>  
43:      </dependency>  
44:      <dependency>  
45:        <groupId>org.springframework</groupId>  
46:        <artifactId>spring-webmvc</artifactId>  
47:        <version>${spring.version}</version>  
48:        <scope>provided</scope>  
49:      </dependency>  
50:      <dependency>  
51:        <groupId>org.springframework</groupId>  
52:        <artifactId>spring-aop</artifactId>  
53:        <version>${spring.version}</version>  
54:        <scope>provided</scope>  
55:      </dependency>  
Listing 4

Note that I have used dependency scope for these dependencies. That means, I have to make these jars available at runtime, usually in the system classpath of JBoss. This is ugly, but if I did not do this, the Spring framework would complain with the error I mentioned earlier. In Spring security 3.0.1.RELEASE, it is coming from line #289 of org.springframework.beans.TypeConverterDelegate.java

My assumption here is that somehow the classes (esp. AuthorityGranter) pulled by maven while building the authentication provider jar and the war, both using default dependency scope, were not the same, even if I was using the same release versions for Spring security in both the poms. At one point I was using CI SNAPSHOTs for Spring security. Perhaps that might be the reason as the repository might be changing at the time. Dunno for sure.

If you know a better way to solve this problem, feel free to comment.

2 comments:

  1. Hi,

    Can you send me source code so i can try and implement the code to check error?

    ReplyDelete
  2. Hi,

    Can you send me source code so i can try and implement the code to check error?

    ReplyDelete