Securing a Grails application using Spring and Acegi Security

Since a Grails application uses Spring, it’s possible to configure an application using Grails to use Acegi Security. This example shows what is required to provide role-based URL security for a Grails application.

web.xml config

I found that the ContextLoaderServlet did not work with Acegi Security – it didn’t find the applicationContext file. Replacing it with the Listener configuration did work:

  • Comment out the ContextLoaderServlet in web-app/web.template.xml
  • Add the following lines:
    <code>
    &lt;context-param&gt;
      &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
      &lt;param-value&gt;/WEB-INF/applicationContext.xml&lt;/param-value&gt;
    &lt;/context-param&gt;
    
    &lt;listener&gt;
      &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
    &lt;/listener&gt;
    </code>
    

Add the following filter configuration and mapping to wire in the Acegi Security beans to the web app:

<code>
&lt;filter&gt;
    &lt;filter-name&gt;Acegi Filter Chain Proxy&lt;/filter-name&gt;
    &lt;filter-class&gt;
	  org.acegisecurity.util.FilterToBeanProxy
    &lt;/filter-class&gt;
    &lt;init-param&gt;
	  &lt;param-name&gt;targetClass&lt;/param-name&gt;
	  &lt;param-value&gt;
		org.acegisecurity.util.FilterChainProxy
	  &lt;/param-value&gt;
    &lt;/init-param&gt;
&lt;/filter&gt;
      
&lt;filter-mapping&gt;
  &lt;filter-name&gt;Acegi Filter Chain Proxy&lt;/filter-name&gt;
  &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;

</code>

This filter mapping configuration will send all requests through the Acegi filter to check authentication if needed (depending on URL patterns configured in the next section.

Acegi Bean Configuration

Add the following bean definitions to the applicationContext.xml file in web-app/WEB-INF:

<code>
   &lt;bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"&gt;
     &lt;property name="providers"&gt;
       &lt;list&gt;
         &lt;ref bean="daoAuthenticationProvider"/&gt;
       &lt;/list&gt;
     &lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"&gt;
  &lt;property name="userDetailsService"&gt;&lt;ref bean="inMemoryDaoImpl"/&gt;&lt;/property&gt;
&lt;/bean&gt;

   &lt;bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"&gt;
     &lt;property name="userMap"&gt;
       &lt;value&gt;
           admin=password,ROLE_ADMIN
       &lt;/value&gt;
     &lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"&gt;
  &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
  &lt;property name="authenticationFailureUrl"&gt;&lt;value&gt;/acegilogin.jsp?login_error=1&lt;/value&gt;&lt;/property&gt;
  &lt;property name="defaultTargetUrl"&gt;&lt;value&gt;/&lt;/value&gt;&lt;/property&gt;
  &lt;property name="filterProcessesUrl"&gt;&lt;value&gt;/j_acegi_security_check&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;

&lt;!-- prior to ACEGI RC2, was org.acegisecurity.intercept.web.SecurityEnforcementFilter --&gt;
&lt;!--
RC2 notes:
org.acegisecurity.intercept.web.SecurityEnforcementFilter has moved to a new location and name, 
org.acegisecurity.ui.ExceptionTranslationFilter. In addition, the "filterSecurityInterceptor" property on the old 
SecurityEnforcementFilter class has been removed. This is because SecurityEnforcementFilter will no longer 
delegate to FilterSecurityInterceptor as it has in the past. Because this delegation feature has been 
removed (see SEC-144 for a background as to why), please add a new filter definition for FilterSecurityInterceptor 
to the end of your FilterChainProxy. Generally you'll also rename the old SecurityEnforcementFilter entry in your FilterChainProxy 
to ExceptionTranslationFilter, more accurately reflecting its purpose. If you are not using FilterChainProxy 
(although we recommend that you do), you will need to add an additional filter entry to web.xml and use 
FilterToBeanProxy to access the FilterSecurityInterceptor.
--&gt;
&lt;bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"&gt;
    &lt;property name="authenticationEntryPoint"&gt;
        &lt;ref bean="authenticationEntryPoint"/&gt;
    &lt;/property&gt;
&lt;/bean&gt;

      &lt;bean id="httpSessionIntegrationFilter"
            class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"&gt;
            &lt;property name="context"&gt;
                  &lt;value&gt;
                        org.acegisecurity.context.SecurityContextImpl
                  &lt;/value&gt;
            &lt;/property&gt;
      &lt;/bean&gt;
      
&lt;bean id="authenticationEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"&gt;
  &lt;property name="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
  &lt;property name="forceHttps"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;
      
&lt;bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/&gt;

&lt;bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased"&gt;
    &lt;property name="allowIfAllAbstainDecisions"&gt;
        &lt;value&gt;false&lt;/value&gt;
    &lt;/property&gt;
    &lt;property name="decisionVoters"&gt;
        &lt;list&gt;
           &lt;ref local="roleVoter"/&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
    &lt;property name="authenticationManager"&gt;
        &lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
    &lt;property name="accessDecisionManager"&gt;
        &lt;ref bean="accessDecisionManager"/&gt;&lt;/property&gt;
    &lt;property name="objectDefinitionSource"&gt;
        &lt;value&gt;
            CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
           PATTERN_TYPE_APACHE_ANT
            /secure/**=ROLE_ADMIN
            /item/create=ROLE_ADMIN
            /item/delete/*=ROLE_ADMIN
            /item/edit/*=ROLE_ADMIN
            /category/create=ROLE_ADMIN
            /category/delete/*=ROLE_ADMIN
            /category/edit/*=ROLE_ADMIN
            
        &lt;/value&gt;
    &lt;/property&gt;
&lt;/bean&gt;

      &lt;bean id="filterChainProxy"
            class="org.acegisecurity.util.FilterChainProxy"&gt;
            &lt;property name="filterInvocationDefinitionSource"&gt;
                  &lt;value&gt;
                        CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                        PATTERN_TYPE_APACHE_ANT
                        /**=httpSessionIntegrationFilter,authenticationProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
                  &lt;/value&gt;
            &lt;/property&gt;
      &lt;/bean&gt;
</code>

This configuration uses the org.acegisecurity.userdetails.memory.InMemoryDaoImpl ‘in memory’ configuration and defines one user, ‘admin’, with password ‘password’, and a role of ‘ROLE_ADMIN’.

The org.acegisecurity.ui.webapp.AuthenticationProcessingFilter bean defines the HTML form to be used to perform the authentication, using the page acegilogin.jsp.

The FilterSecurityInterceptor bean ties together all the configuration beans, and allows you to specify URL patterns and roles that are allowed to access those URLs. The URLs are relative to the Grails app. In this example, roles are assigned to Item and Category controllers, and various CRUD actions on those controllers.

Example login page

This is the simplest case HTML page with a form for the logon. Create this file, acegilogin.jsp in the web-app directory:

<code>
&lt;html&gt;

&lt;body&gt;
&lt;h3&gt;Logon&lt;/h3&gt;
&lt;form action="j_acegi_security_check" method="POST"&gt;
&lt;p&gt;UserID: &lt;input type="text" name="j_username"&gt;
&lt;p&gt;Password: &lt;input type="text" name="j_password"&gt;
&lt;p&gt;&lt;input type="submit" value="Logon"&gt;
&lt;/form&gt;

&lt;/body&gt;

&lt;/html&gt;
</code>

Step by Step – creating a new webapp using Grails

These steps walk you through creating a new web app using the Grails framework.

Installation

Install Groovy and Grails as per instructions:

App Generation

Generate a new app from grails using grails create-app, and give it a name, say test_app

From the location where you ran the create-app target, an application directory with the name of your new application was created. cd into this directory, and create the entities for the application: grails create-domain-class and specify the entity name when prompted. Repeat for as many entities as you have in your model.

Database setup

To connect the app to a MySQL db, cd into grails-appconf (under your application directory), and edit the ApplicationDataSource.groovy file:

<code>
class ApplicationDataSource {
   @Property boolean pooled = true
   @Property String dbCreate = "create-drop" // one of 'create', 'create-drop','update'
   @Property String url = "jdbc:mysql://localhost/grails_test1"
   @Property String driverClassName = "com.mysql.jdbc.Driver"
   @Property String username = "grails"
   @Property String password = "grailspass"
}
</code>

Connect to your MySQL server with the command line mysql client, and create a new database:
create database grails_test1

Create the user and grant access to this new database:

<code>
grant all on grails_test1 to 'grails' identified by 'grailspass';
flush privileges;
</code>

Drop a copy of the MySQL JDBC connector jar into the lib directory under your application directory.

Generate scaffolding code

Generate the app: grails generate-all. Give the entity name when prompted, and repeat this step for each entity in the application.
Und

Now start the test server: grails run-app

Bring up a browser and point it to http://localhost:8080/APPNAME – where APPNAME is the name of the application you just created.

You’ll be greeted by the application’s default index page, with a list of the Controllers, one per Entity in your application. Click on one and test the sample CRUD pages that were created.

Adding Attributes to your Entities

At this point your Entities only have default properties because we didn’t change them. To be of any use you’ll need to edit them and add additional properties to describe them.

Stop the webserver. I’ve found that modifying the entities while the app is running does not automatically update your tables in your database.

Under your app directory, go into grails-appdomain. You’ll find the source for your Entities here. Assuming you have an Enity called Category, lets add a couple of properties:

<code>
class Category { 
	@Property Long id
	@Property Long version

    String toString() { "${this.class.name} :  $id" }
}	

</code>

Underneath the version property, add a name and description properties:

<code>
	@Property String name
	@Property String description
</code>

Now rebuild with grails generate-all, then restart your app with grails run-app.

At this point go back to your MySQL client and describe your tables to see their structure (desc table_name). Notice that due to the ‘create-drop’ property in your datasource config that we configured earlier, when you start your app Grails is automatically creating your tables for you, based on your Entities (this behavior is configurable).

Building Swing apps with Groovy

I’ve been playing around with Groovy for a couple of daya now, and wanted to try out the markup feature in the language to build data with nested structures (XML, HTML etc. The same Builder concept in Groovy applies to Swing apps as well, as a Swing app is constructed from nested components.

Here is a simple Swing app that is a file editor:

<code>
import groovy.swing.SwingBuilder
import java.awt.BorderLayout

def swing = new SwingBuilder()
def fileName

def widget = swing.frame(title:'GroovyEdit', size:[300,200], defaultCloseOperation:javax.swing.WindowConstants.EXIT_ON_CLOSE) 
{
    menuBar()
    {
        menu(text:'File')
        {
            menuItem(text:'New')
            separator()
            menuItem(text:'Save', actionPerformed:
            {
                if(fileName == null)
                {
                    fileDialog = swing.fileChooser()
       	            fileChoice = fileDialog.showSaveDialog()
	            if(fileChoice == fileDialog.APPROVE_OPTION)
	            {
	                fileName = fileDialog.selectedFile.absolutePath
	                println(fileName)
	            }
	        }
	        new File(fileName).withWriter{ w | w.write(editorText.text) }
	    })
            
            separator()
            menuItem(text:'Exit', actionPerformed:{ System.exit(0) })
        }
    }
    panel(layout: new BorderLayout()) {
          editorText = textArea(constraints: BorderLayout.CENTER)
       }
}
widget.show()

</code>