SpringOne 2GX Sample Apps – Spring Security Form Login
This is the first in a series of posts making the demo applications that I used for my SpringOne 2GX presentations available. I’ll describe here how to create a standard Grails
application using the Spring Security plugin that authenticates users from a database. This was used in the Demystifying Spring Security in Grails
talk (you can download the presentation here
)
Also refer to the plugin documentation for other tutorials here.
To create a standard application that loads users from a database, run
cd springone2gx
To make classpath management simpler in Eclipse/STS I create a grails-app/conf/BuildConfig.groovy
(in Grails 1.1 apps; in 1.2 this is done for you) with the line
grails.project.plugins.dir='plugins'
to keep plugins in the project root like in 1.0.x but this is optional.
Next install the plugin:
Run the create-auth-domains
script to generate the person, authority, and request map domain classes and also grails-app/conf/SecurityConfig.groovy
:
The other two scripts that the plugin provides are optional and create CRUD pages (generate-manager
) and basic user registration (generate-registration
). It’s a good idea to run generate-manager
; run generate-registration
if it’s useful to you.
There are three methods for securing URLs in the plugin and for this demo we’ll use controller annotations, so we’ll need to configure that. We can delete the request map class, controller, and CRUD pages since they won’t be needed. The plugin scripts currently assume you’ll be using request maps, so we have to run
generate-manager
and generate-registration
before deleting these.
- delete
grails-app/domain/com/burtbeckwith/springone2gx/Requestmap.groovy
- delete
grails-app/controller/RequestmapController.groovy
- delete the
grails-app/views/requestmap
directory and its GSPs - remove the
com.burtbeckwith.springone2gx.Requestmap
import fromgrails-app/controller/RoleController.groovy
- in
grails-app/conf/SecurityConfig.groovy
, disable requestmaps (useRequestMapDomainClass = false
) and enable annotations (useControllerAnnotations = true
), and remove therequestMapClass
property:security { active = true loginUserDomainClass = "com.burtbeckwith.springone2gx.User" authorityDomainClass = "com.burtbeckwith.springone2gx.Role" useRequestMapDomainClass = false useControllerAnnotations = true }
The current plugin is monolithic – it has support for several authentication mechanisms (OpenID, Facebook, etc.) If you’re not using these you can delete that code and associated jar files. This is completely optional, and you can always add them back by extracting them from the plugin zip.
If you’re not using OpenID, here are the steps to remove that code:
- delete the
org.codehaus.groovy.grails.plugins.springsecurity.openid
package: PLUGIN_DIR/src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/openid - delete these jars
- openid4java-0.9.2.jar
- spring-security-openid-2.0.4.jar
- xmlsec-1.3.0.jar
- htmlparser-1.6.jar
- commons-httpclient-3.0.1.jar
- openxri-client.jar
- openxri-syntax.jar
- in
LoginController.groovy
- delete
def openIDConsumer
- delete
def openIDAuthenticationProcessingFilter
- delete the
openIdAuthenticate()
action - remove the
config.useOpenId
part in theauth()
action
- delete
If you’re not using Facebook, here are the steps to remove that code:
- delete the
org.codehaus.groovy.grails.plugins.springsecurity.facebook package
: PLUGIN_DIR/src/java/org/codehaus/groovy/grails/plugins/springsecurity/facebook - delete these jars
- facebook-java-api-2.0.4.jar
- json-20070829.jar
- in
LoginController.groovy
- remove the
config.useFacebook
part in theauth()
action
- remove the
If you’re not using Kerberos, here are the steps to remove that code:
- delete the
org.codehaus.groovy.grails.plugins.springsecurity.kerberos
package: PLUGIN_DIR/src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/kerberos
If you’re not using LDAP, here are the steps to remove that code:
- delete the
org.codehaus.groovy.grails.plugins.springsecurity.ldap
package: PLUGIN_DIR/src/groovy/org/codehaus/groovy/grails/plugins/springsecurity/ldap and PLUGIN_DIR/src/java/org/codehaus/groovy/grails/plugins/springsecurity/ldap - delete these jars
- spring-ldap-1.2.1.jar
- spring-ldap-tiger-1.2.1.jar
- in
LoginController.groovy
- remove the
config.useFacebook
part in theauth()
action
- remove the
If you’re not using CAS, here are the steps to remove that code:
-
delete these jars
- cas-client-core-3.1.1.jar
- spring-security-cas-client-2.0.4.jar
Even if you don’t use NTLM it’s best to leave it in – the jars and code aren’t large and it’s more complicated to remove than just deleting.
In Eclipse or STS the steps to configure the classpath are:
- add
PLUGIN_DIR/src/groovy
as a source folder - add
PLUGIN_DIR/src/java
as a source folder - add
PLUGIN_DIR/grails-app/services
as a source folder - add
PLUGIN_DIR/lib/spring-security-core-2.0.4.jar
- add
PLUGIN_DIR/lib/spring-security-core-tiger-2.0.4.jar
- add
PLUGIN_DIR/lib/spring-security-ntlm-2.0.4.jar
- add
PLUGIN_DIR/lib/jcifs-1.2.25.jar
Having done all that, let’s create a secured controller to test annotations:
and add the import for the annotation, and annotate at the class level that you must be an admin to access this controller:
import org.codehaus.groovy.grails.plugins.springsecurity.Secured @Secured(['ROLE_ADMIN']) class SecureController { def index = { redirect action: foo } def foo = { render 'OK' } def bar = { render 'also OK' } }
The controller has a redirect from the default action and a second action so we can test that all methods inherit the class-level annotation.
Hitting this controller will require a login, so lets add code to BootStrap to create a user:
import com.burtbeckwith.springone2gx.Role import com.burtbeckwith.springone2gx.User class BootStrap { def passwordEncoder def init = { servletContext -> def adminRole = new Role(description: 'Admin role', authority: 'ROLE_ADMIN').save() String password = passwordEncoder.encodePassword('p4ssw0rd', null) def me = new User(username: 'admin', passwd: password, enabled: true).save() adminRole.addToPeople(me) adminRole.save(flush: true) } def destroy = {} }
Start the app using
and open http://localhost:8080/springone2gx/secure/ in a browser and it should prompt you to login – use the username and password from the user created in BootStrap.
After successful login it’ll redirect to http://localhost:8080/springone2gx/secure/foo – verify that http://localhost:8080/springone2gx/secure/bar
is also secured by going to that in your browser.
The ‘secure’ controller blocks access, but anyone can access the role or user controller – open http://localhost:8080/springone2gx/user/list to verify that. So let’s finish locking down the app.
Add the same annotation to UserController and RoleController that you added to SecureController:
import org.codehaus.groovy.grails.plugins.springsecurity.Secured @Secured(['ROLE_ADMIN']) class UserController { ... }
import org.codehaus.groovy.grails.plugins.springsecurity.Secured @Secured(['ROLE_ADMIN']) class RoleController { ... }
Up to now we’ve assumed that all access should be allowed unless it’s explicitly blocked. This is appropriate for many applications, but you might want to take the pessimistic approach of denying access unless it’s allowed. That’s simple to do.
Set controllerAnnotationsRejectIfNoRule
to true
in grails-app/conf/SecurityConfig.groovy
, and since we can’t annotate CSS/JavaScript/images/etc., we’ll need to allow access to those using the controllerAnnotationStaticRules
property:
security { active = true loginUserDomainClass = 'com.burtbeckwith.springone2gx.User' authorityDomainClass = 'com.burtbeckwith.springone2gx.Role' useRequestMapDomainClass = false useControllerAnnotations = true controllerAnnotationsRejectIfNoRule = true controllerAnnotationStaticRules = [ '/**/js/**': ['IS_AUTHENTICATED_ANONYMOUSLY'], '/**/css/**': ['IS_AUTHENTICATED_ANONYMOUSLY'], '/**/images/**': ['IS_AUTHENTICATED_ANONYMOUSLY'], '/*': ['IS_AUTHENTICATED_ANONYMOUSLY'] ] }
Users won’t be able to log in now, since you’re now allowing access to LoginController. That’s easy to do: add an annotation:
import org.codehaus.groovy.grails.plugins.springsecurity.Secured @Secured(['IS_AUTHENTICATED_ANONYMOUSLY']) class LoginController { ... }
IS_AUTHENTICATED_ANONYMOUSLY
means that any access is allowed – “anonymous” logins as well as real logins. Do the same for LogoutController, and CaptchaController and RegisterController if you’re using the registration features of the plugin.
Using this approach, new controllers will be inaccessible until you annotate them. If you need the pessimistic approach, this is preferred to the alternative that controllers accidentally allow full access because you forgot to lock them down.
You can download a finished application based on this discussion here
[…] This post was Twitted by tyama […]
Burt,
Thanks for writing such an informative blog. I have implemented the “SpringOne 2GX Sample Apps – Spring Security Form Login” example. It works perfectly with the Jetty container and when deployed to Tomcat 6.x. But when I deploy to WebLogic 10.0, I consistently get the following ERROR:java.lang.NoClassDefFoundError: Class bytes found but defineClass() failed for: ‘org.codehaus.groovy.grails.plugins.springsecurity.GrailsAccessDeniedHandlerImpl’
I followed your example to the letter. If I explode the war file, the class, GrailsAccessDeniedHandlerImpl, is in the WEB-INF/class directory.
What am I missing? Is there supposed to be a method named defineClass() inGrailsAccessDeniedHandlerImpl?
I am running Windows XP, Java 1.5_22, Grails 1.1.1, and acegi plugin 0.5.1.
Thanks.
Phil
One other thing. I included a WEB-INF/weblogic.xml that contains:
true