blob: 7a4c452bb7057b7500fa5bba24de56242ce76edf [file]
[[usingControllerAnnotations]]
=== Using Controller Annotations to Secure URLs
==== 1. Create your Grails application.
....
$ grails create-app bookstore
$ cd bookstore
....
==== 2. "`Install`" the plugin by adding it to build.gradle
[source,groovy]
[subs="attributes"]
----
dependencies {
...
compile 'org.grails.plugins:spring-security-core:{project-version}'
...
}
----
Run the compile command to resolve dependencies and ensure everything is correct:
....
$ grails compile
....
==== 3. Create the User and Role domain classes.
....
$ grails s2-quickstart com.mycompany.myapp User Role
....
You can choose your names for your domain classes and package; these are just examples.
[NOTE]
====
Depending on your database, some domain class names might not be valid, especially those relating to security. Before you create names like "`User`" or "`Group`", make sure they are not reserved keywords in your database, or escape the name with backticks in the `mapping` block, e.g.
[source,groovy]
----
static mapping = {
table '`user`'
}
----
====
If you are using Spring Core version 3.1.2 or later and GORM 6.0.10 or later, the script creates this User class:
[source, groovy]
.`grails-app/domain/com/mycompany/myapp/User.groovy`
----
include::../code/s2-quickstart/grails-app/domain/com/mycompany/myapp/User.groovy[]
----
and a password encoder listener to manage password encoding:
[source, groovy]
.`grails-app/conf/spring/resources.groovy`
----
include::../code/s2-quickstart/grails-app/conf/spring/resources.groovy[]
----
[source, groovy]
.`src/main/groovy/com/mycompany/myapp/UserPasswordEncoderListener.groovy`
----
include::../code/s2-quickstart/src/main/groovy/com/mycompany/myapp/UserPasswordEncoderListener.groovy[]
----
Previous versions of the plugin's script manage the password encoding directly in domain class:
[source, groovy]
.`grails-app/domain/com/mycompany/myapp/User.groovy`
----
include::../code/s2-quickstart-old/grails-app/domain/com/mycompany/myapp/User.groovy[]
----
include::../domainClasses/gormAutowire.adoc[]
`s2-quickstart` script generates this Role too:
[source, groovy]
.`Role.groovy`
----
include::../code/s2-quickstart/grails-app/domain/com/mycompany/myapp/Role.groovy[]
----
and a domain class that maps the many-to-many join class, `UserRole`:
[source, groovy]
.`UserRole.groovy`
----
include::../code/s2-quickstart/grails-app/domain/com/mycompany/myapp/UserRole.groovy[]
----
[NOTE]
====
These generated files are not part of the plugin - these are your application files.
They are examples to get you started, so you can edit them as you please.
They contain the minimum needed for the plugin's default implementation of the Spring Security `UserDetailsService`
(which like everything in the plugin is customizable - see <<userDetailsService>>).
====
The script has edited (or created) `grails-app/conf/application.groovy` and added the configuration for your domain classes. Make sure that the changes are correct.
While you're looking at `application.groovy`, add this config override to make the sample app easier to work with:
[source,groovy]
----
grails.plugin.springsecurity.logout.postOnly = false
----
[WARNING]
====
By default only POST requests can be used to logout; this is a very sensible default and shouldn't be changed in most cases. However to keep things simple for this tutorial we'll change it (using the `logout.postOnly` config override above) to avoid having to create a GSP form that POSTs to /logout.
====
The plugin has no support for CRUD actions or GSPs for your domain classes; the `spring-security-ui` plugin supplies a UI for those. So for now you will create roles and users in `grails-app/init/BootStrap.groovy`. (See step 7.)
==== 4. Create a controller that will be restricted by role.
....
$ grails create-controller com.mycompany.myapp.Secure
....
This command creates `grails-app/controllers/com/mycompany/myapp/SecureController.groovy`. Add some output so you can verify that things are working:
[source, groovy]
.`SecureController.groovy`
----
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=packageImport]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=class]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=index]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=classClose]
----
==== 5. Edit grails-app/init/BootStrap.groovy to add a test user.
[source,groovy]
.`BootStrap.groovy`
----
include::../code/s2-quickstart/grails-app/init/com/mycompany/myapp/BootStrap.groovy[]
----
Some things to note about the preceding `BootStrap.groovy`:
* The example does not use a traditional GORM many-to-many mapping for the User pass:[&lt;==&gt;] Role relationship; instead you are mapping the join table with the `UserRole` class. This performance optimization helps significantly when many users have one or more common roles.
* We explicitly flush (using `withSession`) because `BootStrap` does not run in a transaction or OpenSessionInView.
==== 6. Start the server.
....
$ grails run-app
....
==== 7. Verify that you cannot access the page yet.
Before you secure the page, navigate to http://localhost:8080/secure to verify that you cannot access the page yet. You will be redirected to the login page, but after a successful authentication (log in with the username and password you used for the test user in BootStrap.groovy) you will see an error page:
....
Sorry, you're not authorized to view this page.
....
This is because with the default configuration, all URLs are denied unless there is an access rule specified.
==== 8. Apply the annotation.
Edit `grails-app/controllers/com/mycompany/myapp/SecureController.groovy` to import the annotation class and apply the annotation to restrict (and grant) access.
[source, groovy]
.`SecureController.groovy`
----
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=packageImport]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=import]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=class]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=methodAnnotation]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=index]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=classClose]
----
or
[source, groovy]
.`SecureController.groovy`
----
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=packageImport]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=import]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=securedAnnotation]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=class]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=index]
include::../code/s2-quickstart/grails-app/controllers/com/mycompany/myapp/SecureController.groovy[tag=classClose]
----
You can annotate the entire controller or individual actions. In this case you have only one action, so you can do either.
==== 9. Restart.
Shut down the app and run `grails run-app` again, and navigate again to http://localhost:8080/secure.
This time you should again be able to see the secure page after successfully authenticating.
==== 10. Test the Remember Me functionality.
Check the checkbox, and once you've tested the secure page, close your browser and reopen it. Navigate again the the secure page. Because a cookie is stored, you should not need to log in again. Logout at any time by navigating to http://localhost:8080/logout.
==== 11. Create a CRUD UI.
Optionally, create a CRUD UI to work with users and roles.
===== Run grails generate-all for the domain classes:
....
$ grails generate-all com.mycompany.myapp.User
....
....
$ grails generate-all com.mycompany.myapp.Role
....
Since the User domain class handles password hashing, there are no changes required in the generated controllers.
Be sure to add an `@Secured` annotation to both of the generated controllers to make them accessible.