Last updated by dhpye 1 year ago
There's a potential security risk due to the way Grails handles URLs with extensions that you should be aware of. If someone requests a URL that ends in a '.' - e.g. /admin/user/list. - a security mapping ofwill allow unauthenticated users to access the action since the URL doesn't match the rule, but will have the dot stripped off for rendering./admin/user/list=ROLE_ADMIN
This only applies to single controller actions and the 'index' action (if it exists), and it only affects the static string approach and Requestmap approach - the annotation handler automatically handles this for you.The fix for single urls is to add a * to the end of the action name:and the fix for controllers that have a mapping for all actions is to double-map them to guard the index action:/admin/user/list*=ROLE_ADMINThis will be fixed in Grails 1.1 but until then you should either use annotations or double-map these entries./admin/user*=ROLE_ADMIN /admin/user/**=ROLE_ADMIN
Securing URLs
There are three ways to configure the request mappings to secure application URLs using the Spring Security plugin. Additionally, @Secured annotations can be used on service methods and work is being done to integrate ACLs, but these won't be discussed here.The goal is to create a mapping of URLs and URL patterns to the roles required to access those URLs.
requestMapString
Typically this is done by defining a string entry in SecurityConfig.groovy, following the form:requestMapString = '''CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/login/**=IS_AUTHENTICATED_ANONYMOUSLY
/admin/**=ROLE_USER
/book/test/**=IS_AUTHENTICATED_FULLY
/book/**=ROLE_SUPERVISOR
'''When using this approach, make sure that you order the rules correctly. The first applicable rule is used, so for example if you have a controller that has one set of rules but an action that has stricter access rules, e.g.
/secure/**=ROLE_ADMIN,ROLE_SUPERUSER /secure/reallysecure/**=ROLE_SUPERUSER
/secure/reallysecure/**=ROLE_SUPERUSER /secure/**=ROLE_ADMIN,ROLE_SUPERUSER
Requestmap
Another supported mechanism stores mapping entries in the database, using the Requestmap domain class. Requestmap has a 'url' property which contains the secured URL pattern and a 'configAttribute' property containing a comma-delimited list of required roles and/or tokens such as IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_ANONYMOUSLY.To use this approach add this to SecurityConfig.groovy:useRequestMapDomainClass = truenew Requestmap(url: '/login/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save() new Requestmap(url: '/admin/**', configAttribute: 'ROLE_USER').save() new Requestmap(url: '/book/test/**', configAttribute: 'IS_AUTHENTICATED_FULLY').save() new Requestmap(url: '/book/**', configAttribute: 'ROLE_SUPERVISOR').save()
Requestmap entries are cached for performance, but this has an impact on runtime configurability. If you create, edit, or delete an instance, the cache must be flushed and repopulated to be consistent with the database. If you use the generated RequestmapController, this is handled for you - "authenticateService.clearCachedRequestmaps()" is called where appropriate. But if you have custom code, you'll need to follow this convention and flush the cache after any update.If you run the generate-manager script (described here) you can use the CRUD GSPs generated at grails-app/views/requestmap to manage Requestmap entries, or you can create your own admin UI.
Annotations
A newer mechanism uses annotations to define what roles are required for various URLs. You can define the annotation at the class level, meaning that the specified roles are required for all actions, or at the action level, or both. If the class and an action are annotated then the action annotation values will be used since they're more specific.For example, given this controller:import org.codehaus.groovy.grails.plugins.springsecurity.Securedclass SecureAnnotatedController { @Secured(['ROLE_ADMIN']) def index = { render 'you have ROLE_ADMIN' } @Secured(['ROLE_ADMIN', 'ROLE_ADMIN2']) def adminEither = { render 'you have ROLE_ADMIN or ROLE_ADMIN2' } def anybody = { render 'anyone can see this' } }
import org.codehaus.groovy.grails.plugins.springsecurity.Secured@Secured(['ROLE_ADMIN'])
class SecureClassAnnotatedController { def index = {
render 'index: you have ROLE_ADMIN'
} def otherAction = {
render 'otherAction: you have ROLE_ADMIN'
} @Secured(['ROLE_ADMIN2'])
def admin2 = {
render 'admin2: you have ROLE_ADMIN2'
}
}useRequestMapDomainClass = false useControllerAnnotations = true
controllerAnnotationStaticRules = ['/js/admin/**': ['ROLE_ADMIN']]
controllerAnnotationsRejectIfNoRule = trueAdvantages/disadvantages:
Each approach has its advantages and disadvantages. The static string is less flexible since it's configured once in the code and can only be updated by restarting the application. In practice this isn't that serious a concern since for most applications, security mappings are unlikely to change at runtime.If you want runtime-configurability then storing Requestmap entries enables this. This allows you to have a core set of rules populated at application startup and to edit, add, and delete them whenever you like. But it separates the security rules from the application code, which is less convenient than having the rules defined in a static string in SecurityConfig.groovy or in the applicable controllers using annotations.Some notes:
- to understand the meaning of IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_ANONYMOUSLY, see the Javadoc for AuthenticatedVoter
- URLs must be mapped in lowercase if using the Requestmap or requestMapString approaches, so for example if you have a FooBarController, its urls will be of the form /fooBar/list, /fooBar/create, etc. but these must be mapped as /foobar/, /foobar/list, /foobar/create. This is handled automatically for you if you use annotations.



