Hacking Delhi
Tuesday, January 19th, 2016I did a talk at GR8Conf India on Saturday called “Fun With Spring Security” where I presented a few sample applications demonstrating various non-standard techniques for authentication, restricting access to URLs, etc. The test applications are available at GitHub
and each has a
README.adoc
but I wanted to summarize the applications in a combined blog post.
All of the apps use Grails 3 and spring-security-core 3.x but would be easily converted to Grails 2 and version 2.x of the plugin since nothing depends on the newer versions.
Some items that are common to all or most of the apps:
- when you start the app the index.gsp page has some links to test the various actions
- debug/trace logging for the plugin and Spring Security is configured but commented out in
logback.groovy
- most of the applications are intentionally stripped-down:
- no static resources
- the GSPs are very minimal
- unused attributes were removed from the
grails.plugin.springsecurity
block inapplication.groovy
autorole
The first example is the “autorole” application. It demonstrates how to infer one or more roles from persisted user information without explicitly storing roles in the traditional many-to-many format. In this example there isn’t even a Role or UserRole domain class, only the User class; all of the work is done in autorole.AutoRoleUserDetailsService
, the custom UserDetailsService
implementation.
A more realistic implementation would probably use a hybrid approach, storing some role information in the database and inferring the rest, with the UserDetailsService
merging the “real” and “virtual” roles as needed.
Items of note:
test.User
is the user domain class generated by thes2-quickstart
script with one modification, adding aboolean admin
property- the
Role
andUserRole
classes generated by thes2-quickstart
script were deleted since they’re not used - two users are created in
BootStrap.groovy
; user “admin” (password “password”) has theadmin
boolean set totrue
and will be auto-grantedROLE_ADMIN
, and user “user” (password “password”) has the default value foradmin
(false
) which will result in a grant ofROLE_USER
autorole.AutoRoleUserDetailsService
is registered in grails-app/conf/spring/resources.groovy as theuserDetailsService
beansecured.SecureController
has two annotated actions;/secure
requiresROLE_USER
(orROLE_ADMIN
since hierarchical roles are configured) and/secure/admin
requiresROLE_ADMIN
noroles
The next example is the “noroles” application. It shows how to use expressions to guard access when access rules are simple and roles aren’t required. In this example there isn’t even a Role or UserRole domain class, only the User class.
Items of note:
-
test.User
is the user domain class generated by thes2-quickstart
script with a few modifications:- a
final authorities = []
property soGormUserDetailsService
works correctly, but doesn’t grant any roles since there aren’t any (for demo purposes here, since there is a customUserDetailsService
) - a
UserType userType
property - a
String businessUnit
property - a
boolean developer
property
- a
- the Role and UserRole classes generated by the
s2-quickstart
script were deleted since they’re not used - four users are created in
BootStrap.groovy
, all with password “password”:- admin1 has UserType
admin
, businessUnit: ‘group1’ - admin2 has UserType
admin
, businessUnit: ‘group2’ - salesdude has UserType
sales
, businessUnit: ‘group1’ - codemonkey has UserType
other
, businessUnit: ‘it’, developertrue
- admin1 has UserType
-
secured.SecureController
has several annotated actions using expressions to guard access - a custom
UserDetailsService
creates an extendedUserDetails
instance to cache nonstandard user properties for use in expressions
hacking_newdelhi
This is an update of the demo app from Greach 2015 (“hacking_madrid”) which was an update of the demo app from GGX 2011 (“hacking_london”). I updated it to Grails 3 and spring-security-core 3.0.3.
It adds an “organization” drop-down to the login page in addition to username and password, and a customized Authentication
class, servlet filter, and AuthenticationProvider
.
Items of note:
-
grails-app/views/login/auth.gsp
is the same asauth.gsp
from the core plugin, with the addition of a
to select the user’s Organization during login -
hacking.extralogin.OrganizationAuthentication
extendsUsernamePasswordAuthenticationToken
to add aString organizationName
property -
hacking.extralogin.ui.OrganizationFilter
extends the core plugin’sGrailsUsernamePasswordAuthenticationFilter
and is registered as theauthenticationProcessingFilter
bean to process form logins; it createsOrganizationAuthentication
instances from POST parameters for authentication -
hacking.extralogin.auth.OrganizationAuthenticationProvider
uses the data inOrganizationAuthentication
to authenticate- It’s based on
DaoAuthenticationProvider
but directly accesses the database using GORM instead of delegating to aUserDetailsService
- It’s based on
- two new domain classes,
Organization
andOrgUser
are used to persist the user/organization relationship- OrgUser is the many-to-many join class which uses two 1-to-many relationships instead of the traditional GORM
static hasMany
mapped collections
- OrgUser is the many-to-many join class which uses two 1-to-many relationships instead of the traditional GORM
- BootStrap.groovy creates test data:
- two
Organization
instances, “Org1” and “Org2” - a user with
ROLE_ADMIN
(“admin”/”password”) in “Org1” - a user with
ROLE_USER
(“user”/”password”) in “Org2” - a user with
ROLE_USER
andenabled
set to false (“disabled”/”password”) in “Org1”
- two
- rather than copying and pasting the entire bean definitions into
resources.groovy
to override the bean class for theauthenticationProcessingFilter
anddaoAuthenticationProvider
beans,hacking.HackingBeanFactoryPostProcessor
is registered inresources.groovy
and updates the bean class in the bean definition. This approach retains all of the dependency injections and configuration updates and helps prevent the app from breaking if updated to a newer version of the plugin that has different dependencies and/or config options for the beans -
NoStackBadCredentialsException
is thrown as needed instead ofBadCredentialsException
; it’s similar to the core plugin’sNoStackUsernameNotFoundException
which avoids filling in the stack trace to reduce creation cost -
secured.SecureController
has two annotated actions;/secure
requiresROLE_USER
(orROLE_ADMIN
since hierarchical roles are configured) and/secure/admin
requiresROLE_ADMIN
lockout
This application shows how to use events to lock a user account after a fixed number of failed login attempts.
Items of note:
-
lockout.FailureEventListener
is registered to listen forAuthenticationFailureBadCredentialsEvent
-
lockout.SuccessEventListener
is registered to listen forAuthenticationSuccessEvent
- the User domain class has
int badCredentialsCount
to track failed logins -
UserService
increments badCredentialsCount for failures and resets to 0 on success -
secured.SecureController
has two annotated actions;/secure
requiresROLE_USER
(orROLE_ADMIN
since hierarchical roles are configured) and/secure/admin
requiresROLE_ADMIN
x509
This application showing how to use X.509 browser certificates to authenticate.
Items of note:
- X.509 is enabled by adding
useX509 = true
inapplication.groovy
- two users (“dianne” and “scott”) are created in
BootStrap.groovy
, both with password “not_used” since it’s unused with certificate authentication - add the
dianne.p12
and/orscott.p12
certificate to your browser to authenticate as that person - you must use SSL with X.509 authentication; I tested by building a WAR file and deploying it to Tomcat 8, and configuring
run-app
similarly is left as an exercise for the reader- To test, run `grails war` and copy build/libs/x509-0.1.war to the Tomcat webapps folder, renaming the war to ROOT.war so it uses the default context
- be sure to access the application with SSL URLs, e.g. https://localhost:8443/secure/index
- configure
server.jks
as the keystore and truststore;server.xml
is an example Tomcat 8 config file that does this, expecting thatserver.jks
is in theconf
directory -
secured.SecureController
has two annotated actions;/secure
requiresROLE_USER
(orROLE_ADMIN
since hierarchical roles are configured) and/secure/admin
requiresROLE_ADMIN
x509chained
This last application shows how to use X.509 browser certificates and a second authentication provider to authenticate.
Note that this has been only lightly tested and should be used with caution. I have no idea if there are gaps in the implementation. Test anything based on this approach and/or code extensively before using in a real application. If you find problems with the approach or code let me know so I can update the code.
Items of note:
- X.509 is enabled by adding
useX509 = true
inapplication.groovy
- two users (“dianne” and “scott”) are created in
BootStrap.groovy
, both with password “password” since the password is needed for the second form-auth phase - add the
dianne.p12
and/orscott.p12
certificate to your browser to authenticate as that person - you must use SSL with X.509 authentication; I tested by building a WAR file and deploying it to Tomcat 8, and configuring
run-app
similarly is left as an exercise for the reader- To test, run `grails war` and copy build/libs/x509chained-0.1.war to the Tomcat webapps folder, renaming the war to ROOT.war so it uses the default context
- be sure to access the application with SSL URLs, e.g. https://localhost:8443/secure/index
- configure
server.jks
as the keystore and truststore;server.xml
is an example Tomcat 8 config file that does this, expecting thatserver.jks
is in theconf
directory -
x509chained.LoginController
extends the plugin’sLoginController
to not redirect tosuccessHandler.defaultTargetUrl
if authenticated. This is needed because the chained authentication happens in two requests with a redirect. If the first phase (X.509) succeeds, there will be an active authentication, but it’s incomplete and cannot be used yet. Filter chain processing must be allowed to happen to allow the second authentication phase to run. -
x509chained.ChainedX509AuthenticationFilter
extends the standardX509AuthenticationFilter
to replace theAuthentication
insuccessfulAuthentication
with one with all of the real roles replaced withROLE_CHAINED_X509
as a marker to indicate that the first authentication phase succeeded. The second authentication phase will create a standardAuthentication
with the real roles. -
x509chained.ChainedAuthenticationProcessingFilter
extends the plugin’s form authentication filter (GrailsUsernamePasswordAuthenticationFilter
). It detects that the X.509 phase has occurred and redirects to the login page, replacing the credentials (since they’re unused by X.509) with a marker string so downstream processing is aware of the current state in the workflow. -
secured.SecureController
has two annotated actions;/secure
requiresROLE_USER
(orROLE_ADMIN
since hierarchical roles are configured) and/secure/admin
requiresROLE_ADMIN