Security
Application managed security can be implemented using Controller hierarchies and lifecycle features.
Introduction
All aspects of
web application security
of course also pertain to Civilian applications, as do established security techniques and solutions.
In addition we present techniques how to implement application managed security based on features of the Controller class.
Container and application managed security
Authentication and access control are two main aspects of application security.
In the context of web applications they translate into questions such as
- how to authenticate clients which make a request?
- how to present a login form?
- how to implement session management?
- how to restrict access to resources?
- how to create responses tailored to access rights?
- etc.
Container managed security is a solution to enforce authentication and access control
at the container level whereas the application does not need to take care of these tasks.
In a servlet environment this is done using declarative definitions, e.g. in the web.xml.
But more complex applications often require application managed security –
security measures are implemented and enforced by the application.
Here are two use cases which cannot be addressed by declarative definitions in a servlet container:
- Access to a resource is always granted but the returned representation depends
on access rights of the user who is making the request.
E.g. in a returned HTML page specific menu items are only included for specific user roles,
page parts are different, etc.
- A resource displays data which is identified by a request parameter. Access control restricts an
user to certain partitions of the whole data space. Therefore requests are either
processed or rejected based on the request parameter. For instance (for a path parameter encoding a customer id)
a user may access the resource
/customers/1345
but not
/customers/7890
.
Libraries like Spring Security or Apache Shiro can help you to implement application managed
security. In the following we discuss additional possibilities to implement authentication
and access control in your application using Controller features.
Use controller architecture for security
Requests to dynamic resources are processed by
Controllers.
The interplay of controller inheritance
and
invocation
allows for elegant solutions how to implement and enforce authentication and access control
within your application.
The CRM sample is used to exemplify the discussion.
Controller inheritance
Controller classes are derived from
Controller, but you can also
introduce base controller classes derived from Controller specific to your application.
Why would you want to do this? Of course to inherit functionality from base classes.
Now to an important observation: If the resource URLs exposed by your application form a well designed
URL tree it will most likely align well with a controller hierarchy:
Controller class |
maps to resource |
abstract CrmController
|
|
+- abstract SecuredController
|
|
+- IndexController
|
/
|
+- abstract users.UsersController
|
|
+- userse.IndexController
|
/users
|
+- abstract users.id.UserController
|
|
+- users.id.IndedxController
|
/users/{:userId}
|
(using +-
as inheritance relation and omitting the package prefix org.civilian.samples.crm.web.root
)
We will use this feature to inherit security related functionality.
Controller invocation
Controllers have a defined lifecycle and
invocation:
A controller instance is created to process a single request. Controllers define action
methods (for different RESTful scenarios) which are the entry points for processing.
Before and after a controller action method is invoked, initialization and cleanup methods
defined by the
Controller base class are called.
Our strategy to implement authentication and access control is based on the initialization method
Controller.checkAccess() which is called before the negotiated Controller action method.
If checkAccess()
commits the response, e.g. by sending a redirect or an error, the controller
action method is not invoked.
Prevent unauthenticated access using Controller.checkAccess()
Class
org.civilian.samples.crm.web.root.CrmSecuredController of the CRM sample
is the common base class for
all controllers whose resource is behind the login wall.
It implements
checkAccess()
and
- tests if the request is made by an authenticated user (testing the existence of a session attribute).
- If not, it sends a 401 unauthorized error (for ajax requests) or a redirect to the
/login
resource (for non-ajax requests).
- If yes, it initializes a SessionUser property which is available to all derived classes.
CrmSecuredController.checkAccess()
is declared final, therefore it is guaranteed that no resource
behind the login wall can be accessed without proper authentication.
Implement and enforce any access control using Controller.checkAccess()
Naturally this technique can be used to implement any access control. As a second example the CRM sample allows
access to resources
/users/*
only to users with the administrator role.
Again a common Controller base class
org.civilian.samples.crm.web.root.users.UsersController
for these resources exists.
It inherits from
CrmSecuredResource
– that already guarantees only authenticated
access to resources
/users/*
.
But
CrmSecuredResource.checkAccess()
is declared final (to have a strong guarantee). To allow for additional
access control checks,
CrmSecuredResource
declares the method
checkCrmAccess()
which is called
when the request is properly authenticated.
Now,
UsersController
uses this method to implement its check for the admin role.
If derived classes would require another
more specific access check, we could apply the technique again and provide for instance a method
checkUserAccess()
.