I gave a talk at the Groovy & Grails Exchange in London called “Hacking the Grails Spring Security Plugin”. I didn’t want to spend a lot of time discussing the sample app code since there was a lot of material to cover, so I’m making the code available here with a brief discussion of the implementation.
To support a custom login where the user’s organization must be specified in addition to the standard username and password, there’s a custom
hacking.extralogin.auth.OrganizationAuthenticationProvider) which processes a subclass of
hacking.extralogin.OrganizationAuthentication) that adds an
organizationName property, and a filter (
hacking.extralogin.ui.OrganizationFilter) that creates the authentication from the request and initiates authentication.
In this example all authentication uses this approach, so the filter replaces the standard
"authenticationProcessingFilter" bean (and subclasses the plugin’s
RequestHolderAuthenticationFilter to maintain its functionality) and the provider replaces the
"daoAuthenticationProvider" bean. The provider directly implements the
AuthenticationProvider interface since using GORM directly is simple enough to not need to delegate to a
UserDetailsService or other helper classes.
You can see the bean registrations for the filter and provider in
Note that the auth provider and filter are in separate packages to reinforce the idea that auth providers shouldn’t be aware of the UI. The filters that call the auth providers create an
Authentication instance with all of the information that’s needed to authenticate, getting most or all of the data from the HTTP request. This keeps the auth providers modular and reusable outside of a web application.
No changes are required for the generated
UserRole classes, but a new domain class
Organization is needed to store the organization names, and
OrgUser is needed to provide a link between users and organizations.
auth.gsp has an extra input, a
<select> box with all available
If you request http://localhost:8080/hacking_london/secure you should see the text “not secured” since the index action is not guarded. But navigating to http://localhost:8080/hacking_london/secure/admin requires a user with
ROLE_ADMIN, http://localhost:8080/hacking_london/secure/user requires a user with
ROLE_USER, and http://localhost:8080/hacking_london/secure/adminOrUser requires a user with either
ROLE_USER. You can use one of the users created in
There’s an extra user (“disabled”) with a disabled account to test that login fails with a correct username, org name, and password.
We also need to tweak an error message. The plugin’s i18n message bundle will display the error “Sorry, we were not able to find a user with that username and password” if the username, password, or organization are wrong. But we should include the organization in the message to indicate that it might have been wrong. To fix this, add this line to your application’s
springSecurity.errors.login.fail=Sorry, we were not able to find a user with that username, organization, and password.
To test this, log in as user ‘user’ with password ‘password’ but leave the organization name selected as ‘Org1’.
Note that since we’re not using the plugin’s
UserDetailsService or Spring Security’s
DaoAuthenticationProvider we don’t need the
grails.plugins.springsecurity.authority.className properties added to
Config.groovy by the
s2-quickstart script. They’re commented out so you can switch back to the standard authentication approach by removing or commenting out the bean overrides in
resources.groovy and the organization select box in
The other significant customization I discussed was doing a custom post-logout redirect. It is possible to specify a
spring-security-redirect request parameter when logging out, but this is too coarse an approach in general. If you need to use logic specific to the user, or something about the current authentication state, you need more control. So the sample application subclasses the default implementation of
hacking.logout.CustomLogoutSuccessHandler and registers it as the
logoutSuccessHandler bean in
The logic is contrived; if you’re in
Organization ‘Org1’ you’re redirected to ‘http://yahoo.com’ and if you’re in
Organization ‘Org2’ you’re redirected to ‘http://google.com’. Otherwise you’re redirected to the default location (‘/’ unless you’ve customized it with the
grails.plugins.springsecurity.successHandler.defaultTargetUrl config attribute). But it shows an example of how you could use your own business logic to make a similar decision.
There’s one wrinkle here though; the only parameters for the overridden
determineTargetUrl method are the
HttpServletResponse, but not the
Authentication. And since this is the last step of the logout process, the user has already been logged out and the
Authentication isn’t available from the request,
SecurityContextHolder, etc. But the public method (
onLogoutSuccess) that calls this method has a parameter for the
Authentication, so we save it in a
ThreadLocal so it’s available for our override.