1

I have to authorize requests based on the scopes it is allowed to access. I have a token-based authorization, which returns me the scopes allowed for the request. If one of the scopes matches with the scopes allowed for my API, then I allow it to access the content of the API. So, I created a custom annotation

 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Authorize{
 String[] scopes() default {""};
}

So, in each API, I just put the annotation above the method and match it with the scopes returned by token authorization.

My Controller1

@PostMapping("/insert")
@Authorize(scopes = {"read", "write"})
public HttpStatus create(){
 // insertion code
}

@GetMapping("/students")
@Authorize(scopes = {"foo", "bar"})
public List<Student> get(){
// Get Code
}

My Controller2

@PostMapping("/insert")
@Authorize(scopes = {"read", "write"})
public HttpStatus create(){
 // insertion code
}

@GetMapping("/classes")
@Authorize(scopes = {"foo", "bar"})
public List<Class> get(){
// Get Code
}

Code where I am trying to access the scopes and match:

private void validateScope(String[] scopes){
// Here 'scopes' is a string list which token authorization returned.
  Method[] methods = GenericController.class.getMethods();
  for(Method m: methods){
    if(m.isAnnotationPresent(Authorize.class)){
       Authorize auth = m.getAnnotation(Authorize.class)
       for(String t: auth.scopes())
         System.out.println(t);
    }
  }
  // once I parse the corresponding scopes allowed by the API properly, then here I will match it with 'scopes' 
}

This just prints out all the scopes applied to the Class. And, also I have to specify a specific Controller. I want to make it generic

How can I achieve this? I want to make the call generic so I can call any controller, and also get the scopes from the specific method, not all of them. I was thinking Google Reflection might help but I did not understand how to use it for my use case.

I have tried manipulating all the answers of Is it possible to read the value of a annotation in java? but none of them work. Any lead will be appreciated. Thanks in advance

Manu Agarwal
  • 47
  • 1
  • 7

1 Answers1

1

As I see it, authorisation is a cross-cutting concern for your API. Your requirement is the perfect candidate for using aspects. I have used Spring Aspects to demonstrate how it can be done. This is not a working code but gives the general idea:

  /**
  * Proxies a controller annotated with {@link Authorise} and checks if the requested scope
  * is in the list of allowed scopes.
  * @param pjp of type sometype
  * @throws {@link AuthorisationBusinessException} is the requested scope is not allowed.
  */
  @Around("@annotation("pathToAnnotation")")
  public Object authorisationAdvice(ProceedingJoinPoint pjp)
      throws Throwable {

    MethodSignature signature = (MethodSignature) pjp.getSignature();

    Authorize annotation = signature.getMethod().getAnnotation(Authorize.class);
    List<String> allowedScopes = annotation.scopes();

    Object arg = pjp.getArgs()[0];    //this will be the argument to your controller. Cast to to the appropriate type and get the requested scope from the token.

    // get the requested scope

   if(annotation.scopes().contains(requestedScope)) {
       return pjp.proceed(); 
   } else {
      //throw exception
   }
}

What this advice basically does is intercept any method annotated with @Authorise. Once the method is proxied, you have the allowed set of scopes, and you the scope that is requested. You can now add whatever checks you want.

You can read on Spring Aspects here: https://docs.spring.io/spring/docs/2.5.x/reference/aop.html

Prashant Pandey
  • 4,332
  • 3
  • 26
  • 44