Annotation for checking required session fields
Recently I worked on a project where I used spring security plugin. Its a very wonderful plugin for making your application secured from unauthorized users. It gives you a simple annotation @Secured to add security to your action and controller. Thats the first time I got to know the real use case of annotation. So I started reading about annotation and few days later I found the use case to implement my own annotation.
All the projects I have worked on had login functionality where we put the userId and projectId into the session. Then in my code I use to get the user from session.userId. Something like
[java]
def books = {
User user = User.get(session.userId)
Project project = Project.get(session.projectId)
……
…..
}
[/java]
The above code fails when user directly hits this action because there is no check to verify the user is not null. The simple answer for this problem is either use beforeInterceptor or filters. So we started checking the session.userId in filters. But again there are cases where you dont want to check this session value or you can say there are public urls as well. Now we have to put few if else statements in filter.
Here I got my use case to implement an annotation for controllers and actions which checks for the required fields in the session before getting into the action. So I created an annotation in src/groovy folder
[java]
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Target([ElementType.FIELD, ElementType.TYPE]) // Annotation is for actions as well as controller so target is field and for class
@Retention(RetentionPolicy.RUNTIME) // We need it at run time to identify the annotated controller and action
@interface RequiredSession {
String[] exclude() default [] // To exclude some of the actions of controller
String[] fields() default ["userId","projectId"] // The default value is set to userId and projectId that can be overridden while using the annotation on controller or action.
String onFailController() default "home" // Default controller when the field not in session is set to index page
String onFailAction() default "index" // Default action when the field not in session is set to index page
}
[/java]
Now I created a ApplicationFilters and before redirected the request to any action I check for the condition of session fields if the requested action or controller is annotated. The code in the filter is something like
[java]
class ApplicationFilters {
def filters = {
validateSession(controller: ‘*’, action: ‘*’) {
before = {
if (controllerName) {
//Get the instance of controller class from string value i.e; controllerName
def controllerClass = grailsApplication.controllerClasses.find {it.logicalPropertyName == controllerName}
//Read the RequiredSession annotation from controller class
def annotation = controllerClass.clazz.getAnnotation(RequiredSession)
//Get the current action from actionName otherwise read default action of controller
String currentAction = actionName ?: controllerClass.defaultActionName
//Look for the annotation on action if controller is not annotated or the action name is excluded
if (!annotation || currentAction in annotation.exclude()) {
//Get the action field from string value i.e; currentAction
def action = applicationContext.getBean(controllerClass.fullName).class.declaredFields.find { field -> field.name == currentAction }
//If action is found get the annotation else set it to null
annotation = action ? action.getAnnotation(RequiredSession) : null
}
//Check for the field in session whether the are null or not if any of the field is null loginFailed is true
boolean loginFailed = annotation ? (annotation.fields().any {session[it] == null}) : false
if (loginFailed) {
// If login is failed user redirected to on fail action and controller
redirect(action: annotation.onFailAction() , controller: annotation.onFailController())
return false;
}
}
}
}
}
}
[/java]
And its all done. Now we just annotate our controller and actions accordingly.
[java]
@RequiredSession(exclude = ["registration", "joinProject"])
class UserController {
def edit ={}
def update = {}
def list ={}
def save ={}
def registration ={}
def joinProject = {}
}
[/java]
In above example registration and joinProject action will bypass the session fields check.
[java]
@RequiredSession
class ItemController {
def index={}
def buy ={}
def save ={}
}
[/java]
All the action of above examples can be accessed only when user is logged in.
[java]
class HomeController {
def index ={}
def aboutUs={}
@RequiredSession
def dashboard = {}
}
[/java]
Actions other than dashboard are public actions which can be accessed without login.
[java]
class UserController {
@RequiredSession(fields = ["loggedInUserId"])
def updatePassword = {
}
}
[/java]
For updating password user dont need to have some project into session so we specified the fields to be checked in session.
Hope it helps
Uday Pratap Singh
uday@intelligrape.com
Everyone would benfiet from reading this post
Hey, it’s really cool! Anyway, I prefer to store my session variables in a service with session scope. What do you think? 🙂