Annotation for checking required session fields

21 / Sep / 2011 by Uday Pratap Singh 2 comments

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

def books = {
   User user = User.get(session.userId)
   Project project = Project.get(session.projectId)
   ......
   .....
}  

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

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
}

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

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;
                    }
                }
            }

        }
    }
}


And its all done. Now we just annotate our controller and actions accordingly.

@RequiredSession(exclude = ["registration", "joinProject"])
class UserController {
      def edit ={}
      def update = {}
      def list ={}
      def save ={}

      def registration ={}
      def joinProject = {}
}

In above example registration and joinProject action will bypass the session fields check.

@RequiredSession
class ItemController {
        def index={}
        def buy ={}
        def save ={}
} 

All the action of above examples can be accessed only when user is logged in.

class HomeController {
      def index ={}
      def aboutUs={}
      @RequiredSession
      def dashboard = {}
}

Actions other than dashboard are public actions which can be accessed without login.

class UserController {
  @RequiredSession(fields = ["loggedInUserId"])
   def updatePassword = {
    
   }
}

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

FOUND THIS USEFUL? SHARE IT

comments (2)

  1. Alberto Vilches

    Hey, it’s really cool! Anyway, I prefer to store my session variables in a service with session scope. What do you think? :)

    Reply

Leave a comment -