Developer's Diary
Software development, with Terry Ebdon
12-Jun-2020 Grails Security

Spring

Yesterday I added spring-security-core:4.0.0 to my Grails app. I hit a couple of problems:

  1. The logout screen doesn't work.
  2. User roles aren't being saved.

Logout

The logout issue is probably something silly that I've done. Not a big deal, this is just demo-ware and I don't need to demo log outs. When I get a minute I'll create a test project to see what I've screwed up.

User Roles

The user roles problem is weird. Grails created three persistent classes: User, Role and UserRole. I then reversed my previous User implementation into the new class.

I've protected the app with a static rule, in grails-app/conf/application.groovy:

[pattern: '/**', access: ['isAuthenticated()']]

That works great, you now have to login to do anything. I'll be using role based authorisation, so need to create create Role instances, in sample data, and assign users to those roles.

Create roles

adminRole = new Role(authority: 'ROLE_ADMIN').
save(failOnError: true)
coachRole = new Role(authority: 'ROLE_COACH').
save(failOnError: true)
athleteRole = new Role(authority: 'ROLE_ATHLETE').
save(failOnError: true)

Create users

I use a helper function to create adult users. This forces the date of birth to be 1-JAN-1970, generates a unique email address, sets the password to 'fred' and returns the saved User instance.

User admin = addAdult( 'Adam', 'Admin' )
User coach1 = addAdult( 'Charlie','Curtis' )

Assign users to roles

This is where things go horribly wrong.

def adminUserRole = UserRole.create( admin, adminRole ).
save(failOnError: true)
def coach1UserRole = UserRole.create( coach1, coachRole ).
save(failOnError: true)
Note
The .save() shouldn't be required. See below.

UserRole.create() creates a non-persistent object. Fair enough. But calling the object's save() method doesn't work, i.e. the object's id property is null after .save( failOnError: true ). This snippet reveals the problem:

[adminUserRole, coach1UserRole].each { ur ->
print "Created UserRole id: ${ur.id}, "
print "username: ${ur.user.username}, "
print "authority: ${ur.role.authority}"
print '\n'
}

The UserRole's id is null in the terminal log.

************************
* Creating sample data *
************************
Generating new password
Generate email for Adam Admin
Created user AdminAdam with id 1, password {bcrypt}...
Generating new password
Generate email for Charlie Curtis
Created user CurtisCharlie with id 2, password {bcrypt}...
Created UserRole id: null, username: AdminAdam, authority: ROLE_ADMIN
Created UserRole id: null, username: CurtisCharlie, authority: ROLE_COACH

I then check the UserRole mapping with this code:

printRoles admin
printRoles coach1

where printRoles is defined as:

private printRoles( def user ) {
[adminRole, coachRole, athleteRole].each { role ->
long userId = user.id
long roleId = role.id
println "\nuserId: $userId, roleId: $roleId"
def hasRole = UserRole.exists( userId, roleId )
println "${user.username} == ${role.authority} : ${hasRole}"
println UserRole.get( userId, roleId )
}
}

The terminal log confirms that no user roles have been assigned.

userId: 1, roleId: 1
AdminAdam == ROLE_ADMIN : false
null
userId: 1, roleId: 2
AdminAdam == ROLE_COACH : false
null
userId: 1, roleId: 3
AdminAdam == ROLE_ATHLETE : false
null
userId: 2, roleId: 1
CurtisCharlie == ROLE_ADMIN : false
null
userId: 2, roleId: 2
CurtisCharlie == ROLE_COACH : false
null
userId: 2, roleId: 3
CurtisCharlie == ROLE_ATHLETE : false
null

The log is consistent with the UserRole instances not being saved. But why don't I see the .save() failing, due to the failOnError: true argument? It doesn't add up. I did some digging into Spring Security Core reference documentation. This shows the User class implementing an authorities property. Sure enough, it's defined in my User class. OK, let's add that to the mix:

private printRoles( def user ) {
// ... same code as before ...
println "Authorities: ${user.authorities}"
}

Running with this just confirms what I already know; the UserRole objects aren't being saved.

userId: 2, roleId: 3
CurtisCharlie == ROLE_ATHLETE : false
null
Authorities: []

Don't save

I looked at the sample code for the book Grails in Action, 2nd ed. The only significant difference is that it doesn't call .save() on the created UserRole. So that work-around shouldn't be required. A peek at the generated UserRole.create() method confirms this.

static UserRole create(User user, Role role, boolean flush = false) {
def instance = new UserRole(user: user, role: role)
instance.save(flush: flush)
instance
}

Interesting that it explicitly returns the instance, doesn't .save() return the UserRole? I changed UserRole.create() to pass failOnError: true and removed the explicit saves from my bootstrap class. That made no difference. I need to recreate the problem in a throw-away project. Hopefully the throw-away will work, then I can diff the projects to find the error.

11-JUN-2020 👈 Top of page 👉 13-JUN-2020

© 2020 Terry Ebdon.

Find me coding on GitHub, networking on LinkedIn, answering questions on Stack Exchange and hanging out on twitter.