Access function parameter by annotation in AOP (AspectJ) - spring-boot

I have a function under class MyController:
#RestController
#RequestMapping(value = "/api/service")
public class MyController {
#PostMapping(value = "add_person")
public MyResponse addPerson(#RequestBody Person person) {
// ...
}
#PostMapping(value = "add_person_2")
public MyResponse addPerson(#PathVariable(value = "person_age") Int age, #RequestBody Person person) {
// ...
}
}
I have setup AspectJ in my project to have a AOP logic to run whenever those two addPerson(...) method above is called:
#Around("execution(public MyResponse addPerson(..))")
public void around(ProceedingJoinPoint joinPoint) {
// NO matter which addPerson(...) is executing, I am only interested in the
// parameter value annotated with #RequestBody.
// How can I access the parameter that passed in addPerson(...) & is annotated with
// #RequestBody through ProceedingJoinPoint ?
}
My question is mentioned in above code comment. I wonder how can I access the parameter annotated with #RequestBody in my AOP function? I don't want to check parameter type or name, but interested to know how to access parameter by checking the annotation through ProceedingJoinPoint. Is it possible?

I do not want to mark this question as a duplicate because it is no exact duplicate, but my answer here should answer the question about how to
match an annotated parameter at any position,
get the annotation + the parameter value itself.
The linked answer uses a #Before advice. If you want to somehow replace the value by another one in an #Around advice when calling proceed() this is also possible, but was not asked here and my request for seeing more of the advice method body was also ignored.
If you want to limit to annotated Person parameters, you would have to use the fully qualified class name my.package.Person instead of the * inside (*) and do the corresponding cast after accessing the parameter in the advice body.
In my comment I also asked if the parameter has a fixed relative position in the parameter list such as first, last or second/third from left/right. If the OP would have confirmed such a fixed relative position, reflection would not be necessary and the corresponding parameter could be bound to an advice method parameter directly via args() pointcut designator. This would be quite elegant and eliminate the need to loop over getArgs() or over a two-dimensional array of parameter annotations.

Related

Spring AOP: execute code before validation in a controller

I have the following code:
class OrderController {
#AllowedScopes({ORDER_CREATE})
#PostMapping("/create")
public CreateOrderResponse createOrder(#Valid #RequestBody OrderRequest request){
}
}
#Aspect
#Component
public class AllowedScopeAspect {
#Pointcut("#annotation(allowedScopes)")
private void callAtAllowedScopes(AllowedScopes allowedScopes) {
// just a pointcut signature
}
#Before(value = "callAtAllowedScopes(allowedScopes)", argNames = "jp,allowedScopes")
public void validateScope(JoinPoint jp, AllowedScopes allowedScopes) {
...
}
}
Aspect code validates if user have required scope.
The problem is Aspect code is executed after request body validation. If validation is not OKAY, it is returning validation error. if passes, returning 403 error.
How can I execute aspect code before data binding and validation or control handler stage?
You seem to misunderstand how the JVM works. Method parameters always need to be evaluated before calling the method, otherwise the JVM cannot put the parameters on the stack for the method to get access to them. Therefore, also validation takes place before calling the method.
Spring AOP can only intercept method execution, i.e. an aspect is necessarily triggered after validation. The whole point of parameter validation is to not execute the corresponding method, if any parameter is invalid. But if the method is not executed in the first place, there is nothing to intercept for the aspect. 😉

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

Custom #RequestMapping annotation

I have few methods in my spring controllers which are mapped on the same path, example.
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
I was wondering if there is a way to create an annotation that will automatically have set value and method, to have something like this:
#RequestMappingGetByID
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
Have a nice day everyone
Update
The goal of this is the following
all my controllers (eg. user, order, client) extends a parametrized BaseController that includes a base set of function (get by id, save, update, delete, etc) All the logic is on the BaseController, but in order to map the value I have to add the annotation on the specific controller.
Instead of writing all the time {id} and post I would like to annotate the methods with a custom interface that already includes those values
The following works for Spring 4.1.x that I tested:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#interface RequestMappingGetByID {
}
Then you can use
#RequestMappingGetByID
protected ResourceDTO getById(#PathVariable int id) {
return super.getById(id);
}
like you mention.
This kind of annotation is was Spring calls a meta-annotation. Check out this part of the documentation
I am not sure if this meta-annotation would work in versions of Spring prior to 4.x, but it's definitely possible since Spring had some meta-annotation handling capabilities in the 3.x line
If you where using Groovy, you could also take advantage of the #AnnotationCollector AST, which in effect would keep the duplication out of your source code, but would push the regular #RequestMapping annotation into the produced bytecode. Check out this for more details.
The benefit in this case would be that Spring need not have to be equipped with the meta-annotation reading capabilities, and there for the solution possibly works on older Spring versions

Spring model attribute declaration

What is the difference between declaring #ModelAttribute as a method parameter and using model.asMap()?
Example:
public void method1(#ModelAttribute("attr") MyObject myObj) {
...
}
public void method2(Model model) {
MyObject myObj = (MyObject) model.asMap().get("attr");
}
These are not the same if that's what your thinking. I tried to get an attribute from the model doing it the first way but didn't work so ended up doing it the second way.
Thanks
EDIT: What I mean by "it didn't work" is that when I used #ModelAttribute, it was confusing the values of the variables in that object to another object of a different type which had the same variable names.
EDIT #2: No it is not a duplicate because he does not address the model.asMap() method. As per the other post, I tried using #ModelAttribute on a #SessionAttributes variable but ran into problems so had to use model.asMap()

Spring Framework validate request parameter or path variable

I know I can validate forms in Spring, but can I apply similar validate to URL parameters? For example, I have a method in my controller as follows:
public String edit(#PathVariable("system") String system,
#RequestParam(value="group") String group,
ModelMap model) throws DAOException {
Can I validate the values of system and group before the method is called, to ensure they are of a certain value or match a certain regex?
Thanks
You may be able to use Spring Asserts for this. The Assert api (http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/util/Assert.html) runs a supplied expression against the specified parameters and if the expression equates to false then it throws an exception.
Ex:
Assert.isTrue(system.equals("ValidSystemName"), "You must supply a valid system");
It also contains functions to check that parameters are not null or are not empty strings, etc.
Create an annotation that marks parameters that should be validated. This annotation needs a #Retention of RUNTIME and a #Target of ElementType.PARAMETER.
Create a validator implemented as an AspectJ Aspect.
Wrap calls to controllers with this validator.
A sample annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
#Documented
public #interface ValidSystemParameter {
}
A sample validator:
#Aspect
public class ValidSystemParameterValidator {
#Pointcut("TODO: write your pointcut expression")
public void controllerMethodWithValidSystemParameter();
#Before(pointcut = "controllerMethodWithValidSystemParameter()")
public void validateSystemParameter(String systemParameter) {
// validate the parameter (throwing an exception)
}
}
To learn about the AspectJ pointcut expression language see: http://www.eclipse.org/aspectj/doc/released/progguide/language-joinPoints.html
To learn about AspectJ integration in Spring see: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html#aop-ataspectj
I might be a little late, but with Spring 3.0 you have the option of using JSR-303 validation with the #Valid annotation. There are also some more specific annotations as #DateTimeFormat and #NumberFormat. More details here: http://static.springsource.org/spring/docs/3.0.5.RELEASE/reference/validation.html#validation-mvc
As I see it you have two options:
Define your request parameters as objects and user JSR-303
validation.
Use the Assert api as mentioned above.
If you just want to make a simple validation on a single value, I would go with the latter (that's what I did when I had simple int values to check for max value).

Resources