So, I'm on a small project which has some multi-tenancy. Simply put; user A can see the bananas on his trees, and user B can see the bananas on his own trees... but they can't see each others.
But it's restfull, so ideally, you'd call something like /tree/{tree-id}/bananas
And since we know who's executing the call (since it's authenticated), we can verify that it's user A calling us, and then check which trees he can see.
If he's trying to be sneaky, and does a restcall with a treeId of B, a security violation should occur.
Okay, so how do we do that?
Well, the application has a controller for that, and we'd want to secure it there.
So, assume we have the following code:
public List<Banana> getBananasOfTree(String tree)
Since it's supposed to be annotated, we'd use something like this:
@RequestMapping("/tree/{tree-id}/bananas")
@Secured // or some other requirement
public List<Banana> getBananasOfTree(@PathVariable("tree-id") String tree)
In spring, we have several options. The first one I tried was a HandlerInterceptor.
It's an interceptor which executes just before the method is called, in spring's handlerchain. We can make it intercept on specific annotations, so... we just add an annotation, and we're groovy.
Well, almost. The annotation needs to be on the method, so we can't put the annotation on the pathvariable. That's okay, we can reference it, right? Well, not quite. We can't bind by name, since the parametername is not available at runtime, it's stripped away (the signature of the method is (String arg0, User arg1)) That's annoying, so we'd have to do it either by type, or by parameter index, so our (method)annotation should have an index signifying which element we'd want to check. It's getting uglier by the second.
And sadly, that's not all. The HandlerInterceptor is executed before the actual parameter resolution. Since it's a webrequest, the actual binding of the tree-id parameter hasn't occured yet. And it's not something which is easily achieved in Spring to (redo) it's binding to get the actual value.
#sosad.
So, we'll have to resort to plain AOP.
How? Well, we define a pointcut, like this:execution(public * * (.., @MyAnnotation (*), ..))
(in other words, on the execution of a public method which contains MyAnnotation on a parameter)
That allows us both to have an annotation to trigger the aspect, and we can use that same annotation to signify the parameter which should be checked.
Great!
Of course, it's not done yet.
What happens in the aspect is a lot of reflection... The JoinPoint signifies the method, from the method we can retrieve the actual method (i.e. public List<Banana> getBananaOfTrees(...)) and then we still need to figure out which argument we want to have, so we loop over all parameters, find the first one having our nice annotation, save it's parameterindex, and from the actual arguments which we retrieve from the JoinPoint, we get the argument which contains the tree-id. And then, only then, can we verify if the currently logged in user (which can be retrieved through the securitycontext) is allowed to see the tree in question. *sigh*
Of course, the securitycontext doesn't hold the actual user... it only holds the name or principal. So we'll have to do a database lookup for it.
Yeah, that's kinda sucky. So... what do we need from the user? Well, we need his tree-id. That could be put on the security context easily. So once we do that, we'll have a 'moderately' clean solution.
And there was much rejoicing...
But it's restfull, so ideally, you'd call something like /tree/{tree-id}/bananas
And since we know who's executing the call (since it's authenticated), we can verify that it's user A calling us, and then check which trees he can see.
If he's trying to be sneaky, and does a restcall with a treeId of B, a security violation should occur.
Okay, so how do we do that?
Well, the application has a controller for that, and we'd want to secure it there.
So, assume we have the following code:
public List<Banana> getBananasOfTree(String tree)
Since it's supposed to be annotated, we'd use something like this:
@RequestMapping("/tree/{tree-id}/bananas")
@Secured // or some other requirement
public List<Banana> getBananasOfTree(@PathVariable("tree-id") String tree)
In spring, we have several options. The first one I tried was a HandlerInterceptor.
It's an interceptor which executes just before the method is called, in spring's handlerchain. We can make it intercept on specific annotations, so... we just add an annotation, and we're groovy.
Well, almost. The annotation needs to be on the method, so we can't put the annotation on the pathvariable. That's okay, we can reference it, right? Well, not quite. We can't bind by name, since the parametername is not available at runtime, it's stripped away (the signature of the method is (String arg0, User arg1)) That's annoying, so we'd have to do it either by type, or by parameter index, so our (method)annotation should have an index signifying which element we'd want to check. It's getting uglier by the second.
And sadly, that's not all. The HandlerInterceptor is executed before the actual parameter resolution. Since it's a webrequest, the actual binding of the tree-id parameter hasn't occured yet. And it's not something which is easily achieved in Spring to (redo) it's binding to get the actual value.
#sosad.
So, we'll have to resort to plain AOP.
How? Well, we define a pointcut, like this:execution(public * * (.., @MyAnnotation (*), ..))
(in other words, on the execution of a public method which contains MyAnnotation on a parameter)
That allows us both to have an annotation to trigger the aspect, and we can use that same annotation to signify the parameter which should be checked.
Great!
Of course, it's not done yet.
What happens in the aspect is a lot of reflection... The JoinPoint signifies the method, from the method we can retrieve the actual method (i.e. public List<Banana> getBananaOfTrees(...)) and then we still need to figure out which argument we want to have, so we loop over all parameters, find the first one having our nice annotation, save it's parameterindex, and from the actual arguments which we retrieve from the JoinPoint, we get the argument which contains the tree-id. And then, only then, can we verify if the currently logged in user (which can be retrieved through the securitycontext) is allowed to see the tree in question. *sigh*
Of course, the securitycontext doesn't hold the actual user... it only holds the name or principal. So we'll have to do a database lookup for it.
Yeah, that's kinda sucky. So... what do we need from the user? Well, we need his tree-id. That could be put on the security context easily. So once we do that, we'll have a 'moderately' clean solution.
And there was much rejoicing...
Also, with all that reflection jazz going on, on *every* call to that tree method... it may become slow. Something to consider...
BeantwoordenVerwijderen