Friday, June 18, 2010

Using Spring-Security Database in Spring-Roo

Spring Roo is an excellent rapid development tool for Spring Framework based applications.
The current Spring Roo (1.1.0.M1) Security support is very basic and does not support databases out-of-the-box.
To integrate Spring Security with a database backed user and authorization storage, you currently need to set it up yourself.

For this to work, it is required that you already configured your persistence layer in Roo.

Step1: Add the Entities for Spring-Security
Note that in my approach, I avoid reserved SQL keywords for tables and columns.
Roo statements:

// domain.Roles (Spring Security Authorities)
entity --class ~.domain.Roles --testAutomatically
field string --fieldName nameDa --notNull --sizeMax 50 --class ~.domain.Roles

// domain.Users
entity --class ~.domain.Users --testAutomatically
field string --fieldName usernameDa --notNull --sizeMin 3 --sizeMax 30
field string --fieldName passwordDa --sizeMax 100
field boolean --fieldName enabledDa --notNull true
field set --fieldName roles --type ~.domain.Roles --cardinality MANY_TO_MANY --class ~.domain.Users

Step2: Create Web Tier Controllers (to manage roles and users)
controller scaffold --entity ~.domain.Users --class ~.web.UsersController
controller scaffold --entity ~.domain.Roles --class ~.web.RolesController

Step3: Setup security in Roo
Within the roo shell, type "security setup". This creates the security config file and adds needed dependencies to the main pom.xml file.

Step4: Configure Spring Security
File: applicationContext-security
Replace the authentication-manager with a dao based one.
Note the queries to check the user and to get the Authorities which reflects the non-standard table and column names:

<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
<!-- DAO Based Security -->
<authentication-provider>
<password-encoder hash="md5"/>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT U.username_da AS username, U.password_da as password, U.enabled_da as enabled FROM users U where U.username_da=?"
authorities-by-username-query="SELECT U.username_da as username, A.name_da as authority FROM users U left join users_roles UA on U.id=UA.users left join roles A on UA.roles = A.id WHERE U.username_da=?" />
</authentication-provider>
</authentication-manager>

<!-- Security event logging -->
<beans:bean id="loggerListener"
class="org.springframework.security.authentication.event.LoggerListener" />


Step5: Initial Users and Roles
I recommend to start the application now, create your needed roles (e.g. "ROLE_ADMIN", "ROLE_SUPERVISOR") and at least an admin user with ROLE_ADMIN and ROLE_SUPERVISOR assigned. Afterwards secure the roles and users resources in the applicationContext-security.xml (so that only admins can access these controllers):

<intercept-url pattern="/users**" access="hasRole('ROLE_ADMIN')"/>

<intercept-url pattern="/roles**" access="hasRole('ROLE_ADMIN')"/>

7 comments:

  1. Robert,
    Thanks for this post!
    A roo question -
    Where would you do the md5 encode of the passwords? In the Entity code? or maybe in hibernate layer...?

    Thanks
    Gary

    ReplyDelete
  2. I encode the pw In the entity.

    For password match validation, see http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303

    ReplyDelete
  3. Updated the post. The authorities-by-username-query was wrong and returned all authorities. Now it returns only those from the user_roles table for the user.

    ReplyDelete
  4. field set --fieldName roles --element ~.domain.Roles --cardinality MANY_TO_MANY --class ~.domain.Users

    gives the following error in roo 1.1.2

    The entity which will be contained within the Set; no default value

    ReplyDelete
  5. field set --fieldName roles --type ~.domain.Roles --cardinality MANY_TO_MANY --class ~.domain.Users

    ReplyDelete
  6. Is this method still being used or is there a better way? If i am using another UI framework and not JSP to create my front end how can i pass the authenticator to another java file for execution?

    ReplyDelete
  7. I recommend to NOT use MD5 or SHA-1 anymore. Use bCrypt or any other modern, sophisticated hashing algorithms.

    ReplyDelete

Due to the high amount of Spam, you must solve a word verification.