I am trying to implement custom annotation and aspect which will insert path variable into request body before validation.
For now it looks like this...
#Aspect
#Component
public class AddParameterToFormAspect {
#Before("#annotation(addParameterToForm)")
public void addParameterToForm(JoinPoint joinPoint, AddParameterToForm addParameterToForm) {
String form = addParameterToForm.form();
String pathVariable = addParameterToForm.pathVariable();
CodeSignature methodSignature = (CodeSignature) joinPoint.getSignature();
List<String> methodParamNames = Arrays.asList(methodSignature.getParameterNames());
int formIndex = 0;
int pathVariableIndex = 0;
for(String s : methodSignature.getParameterNames()) {
if(s.equals(form)) {
formIndex = methodParamNames.indexOf(s);
}
if(s.equals(pathVariable)) {
pathVariableIndex = methodParamNames.indexOf(s);
}
}
Object[] methodArgs = joinPoint.getArgs();
Object formObject = methodArgs[formIndex];
Field pathVariableObject;
try {
pathVariableObject = formObject.getClass().getDeclaredField(pathVariable);
pathVariableObject.setAccessible(true);
pathVariableObject.set(formObject, methodArgs[pathVariableIndex]);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Controller example of working annotation...
#PostMapping("/test/{username}")
#AddParameterToForm(pathVariable = "username", form = "user")
public String test(#PathVariable String username, #RequestBody User user) {
return user.getUsername();
}
Controller example of validation not working...
#PostMapping("/{domainCode}")
#AddParameterToForm(pathVariable = "domainCode", form = "userAddForm")
public ResponseEntity<UserDto> saveUserForDomain(#PathVariable(name="domainCode") String domainCode, #RequestBody #Valid final UserAddForm userAddForm, BindingResult results) {...}
Adding path variable to form works but it seems #Valid no longer works, problem is probably in join point expression... How can I make it to do advice before validation and then validate?
Changing method parameters in a #Before advice is not meant to work. You should use an #Around advice in order to change parameters before calling thisJoinPoint.proceed(). This is because when calling thisJoinPoint.getArgs() you get copies of primitive type parameters, you cannot manipulate the originals in a before-advice. You are lucky that you want to manipulate object types in this case, so that is the reason it works. Using an around-advice would enable you to pass completely new arguments to a method or just manipulate the original objects, you are free to choose.
Furthermore, you should - whenever possible - use args() in order to bind your method arguments of interest to advice parameters in order to be able to interact with them in a non-cryptic and type-safe manner. Creating a local variable and assigning some value to it will not influence the method parameter of the same type at all. Why should it?
Feel free to ask follow-up questions if this explanation is not comprehensive enough for you. Then I could add some sample code for you, too.
Update after question edit:
After having inspected you code a bit more closely and in addition to my remarks earlier today in my comments under your question, disregarding the content of your aspect code, your actual problem is that the validation check cause by #Valid annotations is performed before the method is executed. I.e. what is validated is not the state after the aspect has done its job (populate member fields in your target objects) but the state before the aspect runs. It is actually the same problem discussed in this question, see also M. Deinum's and my suggestions how to solve it:
Maybe you want to try full AspectJ via LTW (load-time weaving) and see if a call() pointcut instead of the implicit execution() pointcut used by Spring AOP solves the problem. You would weave into the calling code (method calls) instead of the callee (method execution) itself. Chances are, that this happens before validation is performed.
A more Spring-like way to solve it is to use a Spring interceptor (M. Deinum mentions HandlerInterceptor) instead of an aspect. There is also a link to an example by someone else.
Having said that, I still recommend to refactor your code so as not to use reflection and matching strings on method parameter names or class member names. I think you could also get rid of your custom annotation by matching your pointcut on methods parameters with #RequestBody and #PathVariable annotations.
Related
In my spring project I have such an aspect class for logging
#Aspect
#Component
public class BaseLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(BaseLoggingAspect.class);
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface NonLoggingField {
}
#Pointcut("execution(public * *(..))")
private void allPublicMethods() {
}
#Pointcut("within(img.imaginary.service.*)")
private void inServices() {
}
#Pointcut("within(img.imaginary.dao.*)")
private void inDao() {
}
#Before("allPublicMethods() && inServices() || inDao()")
public void logBeforeCall(JoinPoint joinPoint) {
if (logger.isDebugEnabled()) {
logger.debug("begin method {} in {} class with arguments: {}", joinPoint.getSignature().getName(),
joinPoint.getTarget().getClass().getSimpleName(), joinPoint.getArgs());
}
}
}
this aspect simply catches all the public methods of the service and dao layers and outputs to the log at the beginning of execution the name of the method, the name of the class, and the masi of the values of the arguments of the method
in this aspect, I created a NonLoggingField annotation that I want to apply to some fields of classes of those objects that can be passed to the parameters of these logged methods, for example this:
public class User {
#NonLoggingField
public String userEmail;
public name;
public User(String userEmail, String name) {
this.userEmail = userEmail;
this.name= name;
}
public String tiString() {
return String.format("user name: %s and his email: %s", name, userEmail);
}
}
the fact is that such objects will be written to the log through its toString method, but it is necessary that the email somehow does not get into the log using the notLoggingField annotation, while there are thoughts in my head to do through reflection, but there is no clarity how to do this without over difficult code using reflection, especially considering that objects may have objects of other types inside, which may have the same fields with annotations or collections with objects with such fields. perhaps the AspectJ library can help, but I can't find such mechanisms in it. Please help me come up with something
During runtime, a method parameter is just a value. The JVM does not know at this point if the caller called the method using constants, literals, fields or results of other method calls. That kind of information, you only see in the source code. In byte code, whatever dereferencing operation or computation necessary to determine the parameter's value (or a reference to the corresponding object) is done before calling the method. So there is no connection to the field annotation.
Would annotating method parameters be an alternative for you?
If your requirement is very specific, e.g. intercept field accesses from toString methods and return dummy values instead, if the field is annotated, that would be possible. But this would not be fool-proof. Imagine for example that toString calls a getter method instead of directly accessing the field or that a method other than toString logs the field. You do not always want to falisfy the field value on read access, because other parts of the application might rely on it working correctly. Not every toString call is made in order to log something.
I think you should solve the problem in another way, e.g. by applying filter rules for the logging tool you use. Or if you really want solve it at the application level, you could create an interface like
public interface PrivacyLogger {
String toStringSensitive();
}
and make each class containing sensitive information implement that interface. The logging aspect could then for each printed object determine if it is instanceof toStringSensitive(). If so, it would log the result of toStringSensitive() instead of toString(), i.e. in the simplest case something like
Object toBeLogged = whatever();
logger.log(
toBeLogged instanceof PrivacyLogger
? ((PrivacyLogger) toBeLogged).toStringSensitive()
: toBeLogged
);
Of course, you need to iterate over getArgs() and determine the correct log string for each object. Probably, you want to write a utility method doing that for the whole parameters array.
Moreover, in a complex class, the toStringSensitive() implementation should of course also check if its own fields are PrivacyLogger instances and in that case fold the values of their resapctive toStringSensitive() methods into itw own, so that it works recursively.
I am sorry I have no better news for you, but privacy is something which needs too be built into an application from the ground. There is no simple, fool-proof way to do that with one little aspect. The aspect can utilise the existing application infrastructure and avoid scattering and tangling, but it cannot decide on its own what needs to be prohibited from getting logged and what not.
Can you change a method's signature in Spring using aspects?
Like effectively transform the following:
#GetMapping("/thing")
#User // custom annotation that should authenticate the user
public ResponseEntity getThing() {
... // user is successfully authenticated, get the "thing" from the database
}
into:
#GetMapping("/thing")
public ResponseEntity getThing(#CookieValue("Session-Token") String sessionToken) {
User user = authenticator.authenticateSessionTokenOrThrow(sessionToken);
... // user is successfully authenticated, get the "thing" from the database
}
With the user variable also becoming available for use in the method body.
If not, how can I achieve the same result without repeating the code (parameter and authenticator call) everywhere?
Aspects aren't meant for that.
Yes, they can effectively modify .class files bytecode, with compile time or run time weaving, but they do not override methods' signatures.
Also, the default Spring AOP Aspects are implemented in pure Java, and thus cannot touch the bytecode layer. For that you'd need AspectJ.
Tools for customizing bytecode at run/compile time are ASM, ByteBuddy, CGLIB or Javassist.
However, you can probably accomplish this via an Annotation Processor, which lets you modify the actual sources, instead of the already compiled bytecode.
If not, how can I achieve the same result without repeating the code
(parameter and authenticator call) everywhere?
Possible solutions are
HandlerInterceptor, which simply throws an Exception if the user isn't authenticated
Standard Spring AOP advice, which simply throws an Exception if the user isn't authenticated
Spring Security
1 is pretty easy.
2 is more time-consuming
3 imho, seems the best match for authentication, but it's the most complex, probably
The HandlerInterceptor can choose which methods it applies to?
No, unfortunately. I had a requirement a couple of months ago to "cover" only certain methods with an Interceptor, and I implemented a custom solution, which simply look for an annotation specified on the method itself.
This is an extract of my custom HandlerInterceptor, which looks for the CheckInit annotation, first on the type, and then on the method, for a more specific customization.
#Override
public boolean preHandle(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handler
) throws Exception {
if (handler instanceof HandlerMethod) {
if (shouldCheckInit((HandlerMethod) handler)) {
checkInit();
}
}
return true;
}
private static boolean shouldCheckInit(final HandlerMethod handlerMethod) {
final var typeAnnotation = handlerMethod.getBeanType().getAnnotation(CheckInit.class);
final var shouldCheckInit = typeAnnotation != null && typeAnnotation.value();
final var methodAnnotation = handlerMethod.getMethodAnnotation(CheckInit.class);
return (methodAnnotation == null || methodAnnotation.value()) && shouldCheckInit;
}
private void checkInit() throws Exception {
if (!manager.isActive()) {
throw new NotInitializedException();
}
}
The "Standard Spring AOP advice" seems interesting, do you have a link
for that?
Spring AOP documentation - look for the Java-based configuration (I hate XML)
AspectJ really touches the bytecode and can modify signatures as well?
You could make AspectJ modify signatures. Just fork the project and modify its Java Agent or compiler.
AFAIK Annotation Processors cannot modify classes, they can only
create new ones.
The thing is, they don't modify .class files, instead they modify source files, which means they simply edit them. E.g. Lombok uses annotation processing to modify source files.
But yes, the modified sources are written to a new file.
In the Spring Statemachine reference doc is this sample code:
#WithStateMachine
static class Bean1 {
#OnTransition(source = "S1", target = "S2")
public void fromS1ToS2() {
}
}
Is it possible to access the StateContext object from a method annotated with #OnTransition? Perhaps I don't understand the correct use of the annotation...I thought it could be used in a similar manner as an Action, where I could access data stored in the ExtendedState.
It seems that I've totally forget to add this specific information on our docs. We can't access StateContext but event headers and ExtendedState are available.
There's a unit test for this in MethodAnnotationTests.
Short story is that processor handling method calling detects argument types ExtendedState and Map if it's annotated with #EventHeaders. I've also been thinking to support StateContext in a same way via method arguments but haven't yet got that far.
#WithStateMachine
public class Bean2 {
#OnTransition(source = "S1", target = "S2")
public void method1(#EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
}
}
I'll also get docs sorted out for this, thx for pointing this out!
Their documentation says:
A method annotated with #OnTransition may accept a parameter of type
ExtendedState, Map if map argument itself is annotated with
EventHeaders, StateMachine, Message or Exception.
https://docs.spring.io/spring-statemachine/docs/current/api/org/springframework/statemachine/annotation/OnTransition.html
I'm working on converting a legacy project to Spring (trying to adjust little as possible for now) and I'm running into a small issue with mapping/translating legacy parameters to a model attribute object. I may be completely wrong in thinking about this problem but it appears to me that to translate a parameter to a specific model attribute setter is to pass in the request parameter through a method for creating a model attribute and manually call the correct setter:
#ModelAttribute("form")
public MyForm createMyForm(#RequestParameter("legacy-param") legacy) {
MyForm myForm = new MyForm();
myForm.setNewParam(legacy);
return myForm;
}
I don't necessarily want to change the request parameter name yet since some javascript and JSPs are depending on it being named that way but is there any way to do something like this? Or is there a different way to map/translate request parameters to model attributes?
public class MyForm {
#ParameterName("legacy-param")
private String newParam;
public void setNewParam(String value) { ... }
public String getNewParam() { ... }
}
#Controller
public class MyController {
#RequestMapping("/a/url")
public String myMethod(#ModelAttribute("form") MyForm myForm, BindingResult result) { ... }
}
The way you've written that model attribute method is indeed odd. I'm not entirely clear what you're actually trying to do.Assuming there are many parameters, you're going to end up with an awful lot of instances of MyForm in your ModelMap. A more 'normal' way to create model attribute would be like this:
#ModelAttribute("legacyParamNotCamel")
public MyForm createMyForm(#RequestParameter("legacy-param-not-camel") String legacy) {
return legacy;
}
Then in the JSP you can refer to it directly in expression language. e.g.,
<c:out value="${legacyParamNotCamel}"/>
If you want to put them onto a form backing object, you need to do it all in a single method that creates the object, not make new copies of it in each method. (assuming your form has more than a single parameter associated with it.)
--
It seems like what you're really trying to do though is translate the parameter names in the request before the web data binder gets ahold of it, so that you can bind oddly named parameters onto a java bean? For that you'll need to use an interceptor that translates the names before the binding process begins, or make your own subclass of the databinder than can take a property name translation map.
You placed the #ModelAttribute at the Method Level but the intention seems to be more of a formBackingObject hence we should be dealing at the Method Parameter Level
There's a difference.
I put up an explanation here on my blog along examples at Spring 3 MVC: Using #ModelAttribute in Your JSPs at http://krams915.blogspot.com/2010/12/spring-3-mvc-using-modelattribute-in.html
I'd like to implement declarative security with Spring/AOP and annotations.
As you see in the next code sample I have the Restricted Annotations with the paramter "allowedRoles" for defining who is allowed to execute an adviced method.
#Restricted(allowedRoles="jira-administrators")
public void setPassword(...) throws UserMgmtException {
// set password code
...
}
Now, the problem is that in my Advice I have no access to the defined Annotations:
public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
System.out.println("Allowed:" + rolesAllowedForJoinPoint(pjp));
...
}
private Restricted rolesAllowedForJoinPoint(ProceedingJoinPoint thisJoinPoint)
{
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
return targetMethod.getAnnotation(Restricted.class);
}
The method above always returns null (there are no annotations found at all).
Is there a simple solution to this?
I read something about using the AspectJ agent but I would prefer not to use this agent.
To whoever is still having problem after changing annotation retention to Runtime, you might be having the same problem I had: getMethod() returns interface method instead of the implementing class. So, if you have your annotations in the class then naturally getAnnotations() on the interface method returns null.
The following solution solved this problem:
final String methodName = pjp.getSignature().getName();
final MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
Method method = methodSignature.getMethod();
if (method.getDeclaringClass().isInterface()) {
method = pjp.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes());
}
and if you like, you have the option of handling interface annotations here too.
Some more comments available here:
getting template method instance from ProceedingJoinPoint
Oleg
I assume #Restricted is your annotation. If that is the case, make sure you have:
#Retention(RetentionPolicy.RUNTIME)
in your annotation definition. This means that the annotation is retained at runtime.
Even after changing the retention policy like Bozho mentioned this call to get annotation returns null:
targetMethod.getAnnotation(Restricted.class);
What I found is you have to bind the annotation. Given the interface is declared like this:
#Retention(RetentionPolicy.RUNTIME)
public #interface Restricted {
String[] allowedRoles();
}
The advice would need to be declared like this:
#Before("#annotation( restrictedAnnotation )")
public Object processRequest(final ProceedingJoinPoint pjp, Restricted restrictedAnnotation) throws Throwable {
String[] roles = restrictedAnnotation.allowedRoles();
System.out.println("Allowed:" + roles);
}
What this does is bind the annotation to the parameter in the method signature, restrictedAnnotation. The part I am not sure about is how it gets the annotation type, it seems to be based on the parameter. And once you have the annotation you can get the values.
Why don't you just use Spring Security ? It's a brief to implement and use, I don't really see the point in wasting time reinventing the wheel.
Whith Spring AOP if you have a situation like MyManagerImpl implements MyManager the pointcut is applied to the interface method so MethodSignature describes the method defined on MyManager that doesn't have any annotation. the only way I've found to fix this is to inspect the class of the jp.getTarget() object and retrieve the corresponding method.