Disable/Avoid an advice execution in AspectJ
aop, aspectj, java
Solution
In order simulate your situation, I had written the following Authenticator code:
public class Authenticator {
public boolean authenticate(String user, String pass) {
System.out.println("User: '" + user + "', pass: '" + pass + "'");
return true;
}
}
This is my Main class:
public class Main {
public static void main(String[] args) {
Authenticator authenticator = new Authenticator();
boolean status = authenticator.authenticate("Yaneeve", "12345");
System.out.println("Status: '" + status + "'");
}
}
output is:
User: 'Yaneeve', pass: '12345'
Status: 'true'
I added your Hack aspect:
public aspect Hack {
pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass);
boolean around(String user, String pass): authHack(user,pass) {
System.out.println("$$$ " + user + ":" + pass + " $$$");
return false;
}
}
Now the output is:
$$$ Yaneeve:12345 $$$
Status: 'false'
Now for the solution:
I had created the following HackTheHack aspect:
public aspect HackTheHack {
declare precedence: "HackTheHack", "Hack";
pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass);
boolean around(String user, String pass): authHack(user,pass) {
boolean status = false;
try {
Class<?> klass = Class.forName("Authenticator");
Object newInstance = klass.newInstance();
Method authMethod = klass.getDeclaredMethod("authenticate", String.class, String.class);
status = (Boolean) authMethod.invoke(newInstance, user, pass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return status;
}
}
Output is again:
User: 'Yaneeve', pass: '12345'
Status: 'true'
This only works if the original pointcut in Hack aspect was 'call' and not 'execution' as execution actually catches reflection.
Explanation:
I used Aspect precedence to invoke HackTheHack before Hack:
declare precedence: "HackTheHack", "Hack";
I then used reflection (note can and should be optimized to reduce the repeating lookup of the method) to simply invoke the original method without the Hack around advice. This had been made possible due to 2 things:
- the authHack pointcut: `pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass);` uses (in both aspects) `call()` instead of `execution()`
- I did not call `proceed()` in HackTheHack
I would like to refer you to Manning's AspectJ in Action, Second Edition which had put me on the right track with:
6.3.1 Ordering of advice
As you’ve just seen, with multiple aspects present in a system, pieces of advice in the different aspects can often apply to a single join point. When this happens, AspectJ uses the following precedence rules to determine the order in which the advice is applied. Later, you’ll see how to control precedence:
1 The aspect with higher precedence executes its before advice on a join point before the aspect with lower precedence.
2 The aspect with higher precedence executes its after advice on a join point after the aspect with lower precedence.
3 The around advice in the higher-precedence aspect encloses the around advice in the lower-precedence aspect. This kind of arrangement allows the higher- precedence aspect to control whether the lower-precedence advice will run by controlling the call to proceed(). If the higher-precedence aspect doesn’t call proceed() in its advice body, not only will the lower-precedence aspects not execute, but the advised join point also won’t execute.
Problem
Suppose I have an aspect ``` public aspect Hack { pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); boolean around(String user, String pass): authHack(user,pass) { out("$$$ " + user + ":" + pass + " $$$"); return false; } } ``` The `Authenticator.authenticate` method is important. The hack intercepts calls to this method. Is it possible to write a second aspect that cancels/disables the `authHack` advice of Hack aspect? I can catch the execution of the `around authHack` advice, but if I want to continue the authentication i need to call `Authenticator.authenticate` again and this creates an infinite loop..