Acegi on Grails
Please note: This tutorial is very outdated. The acegi plugin and grails have changed and matured making the information here misleading.
Here is the current tutorial: http://docs.codehaus.org/display/GRAILS/AcegiSecurity+Plugin+-+Basic+Tutorial (4-2008)
Tutorial to use "grails domain class " from the Acegi security.
You can Download Sample Project for this Tutorial acegilogon.zip based on Grails 0.3 SNAPSHOT
Plugin
- AcegiSecurity Plugin (since Grails-0.4)
Description
Tutorial to use "grails domain class " from the Acegi security. and Securing your applications with "Acegi filters".
I referred the article of Blog "Securing a Grails application using Spring and Acegi Security" and Reference Guide of Acegi.
Implementation overview:
- Domain class to use for authentication and authorization.
- Controller for Login and Logout.
- Class that implements UserDetailsService to use User Domain Class from Grails Application.
- Class that implements FilterInvocationDefinitionSource to use Request Map from Grails Application.
- Abstract class that extends WebApplicationObjectSupport to get GrailsApplication.
- Acegi Configuration.
- web.xml Configuration.
Steps
Note: All paths are relative from your projects home directory.
- run grails create-app to create your application.
- Download and Copy Needed libraries to lib/ see
- run the target grails create-domain-class and create domain classes needed for logins (Person,Authorities,Requestmap). see
- run the target grails create-controller and create Login and Logout Controllers. see
- create auth.gsp grails-app/views/login/auth.gsp . see
- Download Java source and copy to src/java/grails/ . see
grails.GrailsWebApplicationObjectSupport.java
grails.GrailsDaoImpl.java
grails.GrailsFilterInvocationDefinition.java - Copy securityContext.xml to web-app/WEB-INF/ see
securityContext.xml - edit web.xml . see
- Edit grails-app/conf/BootStrap.groovy file to add sample data. see
- run your Grails application
Files Overview
Controller,Domain Class,View
- %PROJECT_HOME%/grails-app/controllers/LoginController.groovy - main controller for login
- %PROJECT_HOME%/grails-app/controllers/LogoutController.groovy - logout controller
- %PROJECT_HOME%/grails-app/domain/Person.groovy - Domain class of Login users
- %PROJECT_HOME%/grails-app/domain/Authorities.groovy - Domain class of Authorities
- %PROJECT_HOME%/grails-app/domain/Requestmap.groovy - Domain class of Request Map
- %PROJECT_HOME%/grails-app/views/login/auth.gsp - Example login page
Needed libraries
- %PROJECT_HOME%/lib/acegi-security-1.0.2.jar http://www.acegisecurity.org/downloads.html
- %PROJECT_HOME%/lib/commons-codec-1.2.jar http://jakarta.apache.org/site/downloads/downloads_commons-codec.cgi
Acegi Bean Configuration file
- %PROJECT_HOME%/web-app/WEB-INF/securityContext.xml
- %PROJECT_HOME%/web-app/WEB-INF/web.template.xml
Java codes that implements the Acegi classes
- %PROJECT_HOME%/src/java/grails/GrailsWebApplicationObjectSupport.java
- %PROJECT_HOME%/src/java/grails/GrailsFilterInvocationDefinition.java
- %PROJECT_HOME%/src/java/grails/GrailsDaoImpl.java
Database & Tables (Domain Class)

class Person {
def Long id;
def Long version;
String username
String passwd
boolean enabled
def constraints = {
username(blank:false,unique:true)
passwd(blank:false)
enabled()
}
}
class Authorities {
def Long id;
def Long version;
String username
String authority
def constraints = {
username(blank:false)
authority(inList:["ROLE_SUPERVISOR", "ROLE_USER"] )
}
}
class Requestmap {
def Long id;
def Long version;
String url
String config_attribute
def constraints = {
url(blank:false,unique:true)
config_attribute(blank:false)
}
}
Controllers and View
import org.acegisecurity.Authentication as Auth import org.acegisecurity.context.SecurityContextHolder as SCH import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken import org.acegisecurity.providers.AbstractAuthenticationToken class LoginController { def index = { def auth = SCH.getContext().getAuthentication(); if (auth != null) { if(auth instanceof AnonymousAuthenticationToken){ redirect(action:"auth") }else if(auth instanceof AbstractAuthenticationToken){ redirect(uri:"/") }else{ redirect(action:"auth") } }else{ redirect(action:"auth") } render(text:"login") } def auth = {} def denied ={ render(text:"access denied") } def authfail = { println params render(text:"authfail") } /** * load user and return domain class called from GrailsFilterInvocationDefinition */ def loadUserByUsername(username){ Person.findByUsername(username) } /** * load authorities by username */ def authoritiesByUsername(username){ Authorities.findAllByUsername(username) } /** * load request map */ def requestMap(hql) { //"from Requestmap where url = '/**' or url ='/book/**' or url = '/book/list/**' order by length(url) desc" Requestmap.findAll(hql) } }
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta name="layout" content="main" /> <title>Login</title> </head> <body> <div class="body"> <form action="../j_acegi_security_check" method="POST"> <p> <label for="person_id">Login ID</label> <input type='text' name='j_username' > </p> <p> <label for="pwd">Password</label> <input type='password' name='j_password'> </p> <p> <input type="checkbox" name="_acegi_security_remember_me">Remember me 2 weeks<br/> <input type="submit" value="Login" /> </p> </form> </div> </body> </html>
For Logout
class LogoutController {
def index = {
redirect(uri:"/j_acegi_logout")
render(text:"")
}
}
Edit web.xml
Add the following line to web.xml file located in the src/templates/war directory. If you don't have this directory you should run the command "grails install-templates" to get it.
add a param-value to the <context-param> with the name "contextConfigLocation" like below:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext.xml /WEB-INF/securityContext.xml </param-value> </context-param>
add a new filter before the existing Sitemesh <filter> (the filter you're adding should be the first filter listed).
<filter>
<filter-name>acegiAuthenticationProcessingFilter</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
add a new filter mapping before the charEncodingFilter filter mapping (the filter mapping you're adding should be the first filter listed).
<filter-mapping>
<filter-name>acegiAuthenticationProcessingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Acegi on WebSphere
The 'FilterToBeanProxy' provided by Acegi does not use the ThreadContext ClassLoader, which is required by WebSphere.
replace the Acegi FilterToBeanProxy with the Spring implementation (which uses the Thread ClassLoader) :
<filter>
<filter-name>filterChainProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>filterChainProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Java codes implements the Acegi classes
Copy those java files to %PROJECT_HOME%/src/java/grails/
grails.GrailsWebApplicationObjectSupport.java - Abstract class that extends WebApplicationObjectSupport to get GrailsApplication.
grails.GrailsDaoImpl.java - Class that implements UserDetailsService to use User Domain Class from Grails Application.
grails.GrailsFilterInvocationDefinition.java - Class that implements FilterInvocationDefinitionSource to use Request Map from Grails Application.
Acegi Bean Configurations
Copy securityContext.xml file to %PROJECT_HOME%/web-app/WEB-INF/
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,logoutFilter, authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter, anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> <!-- fix "/**=httpSessionContextIntegrationFilter" to "filterInvocationInterceptor" in one line --> </property> </bean> <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/> <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter"> <!-- After logged out URL--> <constructor-arg value="/"/> <constructor-arg> <list> <ref bean="rememberMeServices"/> <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/> </list> </constructor-arg> </bean> <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationFailureUrl" value="/login/authfail?login_error=1"/> <property name="defaultTargetUrl" value="/"/> <property name="filterProcessesUrl" value="/j_acegi_security_check"/> <property name="rememberMeServices" ref="rememberMeServices"/> </bean> <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/> <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"> <property name="key" value="foo"/> <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/> </bean> <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <property name="authenticationEntryPoint"> <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <!-- Login form Url --> <property name="loginFormUrl" value="/login/auth"/> <property name="forceHttps" value="false"/> </bean> </property> <property name="accessDeniedHandler"> <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"> <!-- Login denied path --> <property name="errorPage" value="/login/denied"/> </bean> </property> </bean> <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager"/> <property name="accessDecisionManager"> <bean class="org.acegisecurity.vote.AffirmativeBased"> <property name="allowIfAllAbstainDecisions" value="false"/> <property name="decisionVoters"> <list> <bean class="org.acegisecurity.vote.RoleVoter"/> <bean class="org.acegisecurity.vote.AuthenticatedVoter"/> </list> </property> </bean> </property> <property name="objectDefinitionSource" ref="objectDefinitionSource"/> </bean> <bean id="objectDefinitionSource" class="grails.GrailsFilterInvocationDefinition"> <property name="loginControllerName" value="login"/> <property name="loginControllerRequestMapMethod" value="requestMap"/> <property name="requestMapClass" value="Requestmap"/> <property name="requestMapPathFieldMethod" value="getUrl"/> <property name="requestMapConfigAttributeFieldMethod" value="getConfig_attribute"/> <property name="requestMapPathFieldName" value="url"/> </bean> <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="rememberMeServices" ref="rememberMeServices"/> </bean> <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices"> <property name="userDetailsService" ref="userDetailsService"/> <property name="key" value="grailsRocks"/> </bean> <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"> <property name="key" value="foo"/> </bean> <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider"> <property name="key" value="grailsRocks"/> </bean> </list> </property> </bean> <bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/> <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService"/> <property name="passwordEncoder"><ref local="passwordEncoder"/></property> <property name="userCache"> <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"> <property name="cache"> <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager"> <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/> </property> <property name="cacheName" value="userCache"/> </bean> </property> </bean> </property> </bean> <!-- Grails User Details Service --> <bean id="userDetailsService" class="grails.GrailsDaoImpl" > <property name="controllerName" value="login"/> <property name="controllerLoadUserMethod" value="loadUserByUsername"/> <property name="userName" value="getUsername"/> <property name="password" value="getPasswd"/> <property name="enabled" value="getEnabled"/> <property name="controllerLoadAuthoritiesMethod" value="authoritiesByUsername"/> <property name="authority" value="getAuthority"/> </bean> <!-- This bean is optional; it isn't used by any other bean as it only listens and logs --> <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/> </beans>
Sample User Data (ApplicationBootStrap.groovy)
import org.apache.commons.codec.digest.DigestUtils as DU class ApplicationBootStrap { def init = { servletContext -> def pass = DU.md5Hex("pass") new Person(username:"admin",passwd:pass,enabled:true).save() new Person(username:"user1",passwd:pass,enabled:true).save() def supervisor = new Authorities(username:"admin",authority:"ROLE_SUPERVISOR").save() def user = new Authorities(username:"user1",authority:"ROLE_USER").save() new Requestmap(url:"/book/**",config_attribute:"IS_AUTHENTICATED_REMEMBERED").save() new Requestmap(url:"/book/create/**",config_attribute:"ROLE_SUPERVISOR").save() new Requestmap(url:"/book/edit/**",config_attribute:"ROLE_USER").save() new Requestmap(url:"/authorities/**",config_attribute:"ROLE_SUPERVISOR").save() new Requestmap(url:"/person/**",config_attribute:"ROLE_SUPERVISOR").save() new Requestmap(url:"/requestmap/**",config_attribute:"ROLE_SUPERVISOR").save() new Requestmap(url:"/**",config_attribute:"IS_AUTHENTICATED_ANONYMOUSLY").save() } def destroy = { } }

