A peek into WebEngage's security layer - super cool use of Java annotations

A peek into WebEngage's security layer - super cool use of Java annotations
Note: If you are new to attribute programming, we recommend giving Attribute based programming or Java Annotations a read first.

To begin with, let us show you a small code snippet from one of our controllers. The saveSurveyResponse method underneath gets called every time some user takes an in-site survey on a customer’s website.

/**
 * This method is invoked everytime someone takes a survey on a customer's website.
 * It performs two main tasks -
 * 1. saves the user's response in our database
 * 2. refreshes the stats and data graphs for the corresponding survey
 */
public ... saveSurveyResponse(...) throws IOException {
  //mundane code here to compute a response object "surveyResponseDto"

  //first save the response in database
  this.publisherBc.saveSurveyResponse(licenseCode, surveyResponseDto);

  //then, refresh all the analytics associated with this survey
  this.publisherBc.refreshSurveyStatusOnResponse(licenseCode, surveyId);
}

As you can see above, the code performs two major tasks – #1. save the response in database and #2. refresh the analytics associated with the corresponding survey. We pre-compute a lot of data graphs and collate the information to create a bunch of Maps, so that the stats for our customers can be presented in real-time, in its true essence. That’s a lot of computing work. The method refreshSurveyStatusOnResponse usually takes about half a second (and much more at times) to complete. We can’t keep the end user on a third party website waiting because (s)he took a simple survey and we got in to some crazy computing business!

Solution? Simple – make that method call asynchronous.
Yes, one would either use a batch process or make that call asynchronous. However, it is a much bigger problem to address. There are several such methods in any application stack which should be executed asynchronously to keep the user experience intact. E.g. in any action that needs to send out an email, the sending email part can easily be made asynchronous because SMTP relays can be painful at times leading to huge lag; in the process, it keeps your end user waiting for a response.

Implementation? We created a cool Java annotation called Asynch.
We first created an annotation interface called Asynch and then implemented a method interceptor to look for this annotation on the callee. If the annotation were present, we’d execute it further in a new thread. Snippet from the implementation below.

/**
 * Defining the Asynch interface
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface Asynch {}

/**
 * Implementation of the Asynch interface. Every method in our controllers
 * goes through this interceptor. If the Asynch annotation is present,
 * this implementation invokes a new Thread to execute the method. Simple!
 */
public class AsynchInterceptor implements MethodInterceptor {
  public Object invoke(final MethodInvocation invocation) throws Throwable {
    Method method = invocation.getMethod();
    Annotation[] declaredAnnotations = method.getDeclaredAnnotations(); 
    if(declaredAnnotations != null && declaredAnnotations.length > 0) {
      for (Annotation annotation : declaredAnnotations) {
        if(annotation instanceof Asynch) {
          //start the requested task in a new thread and immediately
          //return back control to the caller
          new Thread(invocation.getMethod().getName()) {
            public void execute() {
              invocation.proceed();
            }
          }.start();
          return null;
        }
      }
    }
    return invocation.proceed();
  }
}

Done? Just that much?
Oh yes, pretty much. Here’s how the declaration of heavyweight method, refreshSurveyStatusOnResponse, looks like –

/**
 * So, earlier we had a simple method in our interface which we later
 * annotated with the Asynch @interface. Bang! The caller doesn't need
 * to worry about it now. This method (no matter who the caller is)
 * gets executed asynchronously. Ain't that awesome? 
 */
@Asynch
public void refreshSurveyStatusOnResponse(String licenseCode, Integer surveyId);

How did we use annotations to build our security layer?
Now that we gave you a fair idea of how we are using Annotations to our advantage, let’s dive a bit deeper. You are about to see how we built our entire Authorization and Authentication stacks using Java Annotation.

We are security freaks. Be it the web layer or data exchange layer, access to all our code is protected. The caller of a method is denied entry to the method if it does not have right privileges. Underneath is a small snippet from one of our web layer controllers. This method is invoked when someone tries to edit a survey from the WebEngage dashboard.

/**
 * This is a public method invoked via a URL on the site. Once a user on the site
 * tries to reach this method, the "rules" specified below (via annotations) are
 * evaluated. If it matches with the criterion specified for the UserAuth class,
 * the user is allowed an entry into the method; otherwise is shown the exit door!
 *
 * In the specific example below, only "signed-in" users who are "authorized 
 * publishers" (our terminology for WebEngage customers) AND have access to 
 * "survey configuration" related features, are allowed entry into this method.
 */
@UserAuth (
  userTypes = {
     UserType.SIGNED, 
     UserType.AUTHORIZED_PUBLISHER
  },
  publisherUserFeatures = {
     Feature.SURVEY_CONFIGURATION
  }
)
public ... edit(...) throws IOException{

}

Pretty nice. Right? So just by annotating the edit method with UserAuth, we made sure that survey edit URL’s returns a sweet nothing to those who are not supposed to use those URL’s. Where’s the beauty? This piece of annotation is reusable; we use it in a variety of ways on pretty much all the code that needs to be protected behind the concepts of user and their corresponding roles.

Of-course there’s a lot of application specific code behind understanding the annotation UserAuth. However, that’s one time and we have managed to reuse it in a much more sophisticated manner inside our Business Layer as well. Take a look at this usage in one of the methods below:

/**
 * This is our business component method for customers who intend to change
 * the styling of their surveys to match the CSS with their site's look and
 * feel [oh, we have a sexy CSS editor inside dashboard for them to do so ;)]
 *
 * This is a nested annotation. A list of @Authorize methods can be specified
 * as rules. Each of them specifying the method to be called (for authorization)
 * and the argument to be passed to it. For the caller to get access, these
 * values should meet the AuthRules criteria. If it does not, an 
 * AuthorizationException is thrown.
 */
@AuthRules(
  authRules = {
    @Authorize(
      method = "hasPublisherUserFeatureAccess", 
      sargs = {"$0", "SURVEY_STYLING"}
    ), 
    @Authorize(
      method = "hasPublisherFeatureAccess", 
      sargs = {"$0", "WE_SURVEY_CUSTOM_CSS"}
    )
  }
)
public void saveSurveyStyleCss(
  Integer publisherId, 
  AdhocAttributeName cssAdhocAttributeName, 
  String css
) throws AuthorizationException;

This explains how powerful annotations can get. At WebEngage, we use them to the fullest. Hope this article helps you build some cool stuff with attributes. Do let us know!

Note: Use the Asynch annotation idea with care. Spawning new threads without being in control can be fatal. If you plan to use it, make sure the threads are fetched from a pre-created thread pool.

Stay tuned. We love you!

  • Created: 12 Mar 2012
  • Last Updated: 18 Nov 2019

asdf

Author

Avlesh Singh Co-founder and CEO, WebEngage

Avlesh is the co-founder and CEO at WebEngage. He occasionally writes here and on his personal blog.


Liked our article? Give us your feedback by rating it.
Total Rating : 0 , Average Rating : 0