| [[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:[<==>] 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. |