Let us talk straight here: Henceforth I am assuming that your "special use case" is that you want to tweak/hack user authentication, hopefully in a legal way for testing or whatever.
The main problems to be solved are:
- It would be easy to just manipulate the result of your
foo
method (I have renamed it toisCurrentUser(String userID)
to clarify its intent). But if I understand correctly, the method has side effects, i.e. it calls other methods, and you want to keep those side effects. So we have to be more careful and use a scalpel, not an axe. - There is no pointcut for local variable changes so you have to intercept the method execution or call which changes the local variable's value.
- AspectJ cannot normally intercept JDK method
execution()
(unless you want to weave the JDK first, which is possible but out of scope here). Thus, you have to intercept thecall()
from your own code. I am assuming it is possible to weave into that code even if it is contained in a JAR and you do not have the sources. You can either use LTW for the target class or binary weaving for the JAR, creating a new, woven version of it. - Your method call to
NTSystem.getName()
is not done in a normal way but via Reflection API. Thus, you cannot just use a pointcut likecall(NTSystem.getName())
because it will never be triggered. You have to interceptcall(public Object Method.invoke(Object, Object...))
. - Theoretically it could be that other reflective method calls are made from within
isCurrentUser(..)
, so we have to refine our pointcut in order to only match if reallyNTSystem.getName()
is called, not any other method. - As a convenience function, we want to be able to dynamically switch the
hackingMode
on and off.
Now here is a complete, compileable code sample (obviously only working on Windows just like your own code snippet):
Java class with method to be manipulated and main method for demonstration purposes:
package de.scrum_master.app;
import java.lang.reflect.Method;
import de.scrum_master.aspect.TweakAuthenticationAspect;
public class UserAuthentication {
private static final String USER_NAME_GOOD = "alexander"; // Add your own user name here
private static final String USER_NAME_BAD = "hacker";
public static boolean isCurrentUser(String userID) throws Exception {
Class<?> ntSystemClass = Thread.currentThread().getContextClassLoader()
.loadClass("com.sun.security.auth.module.NTSystem");
Method getNameMethod = ntSystemClass.getMethod("getName");
Object ntSystem = ntSystemClass.newInstance();
String currentUserID = (String) getNameMethod.invoke(ntSystem);
boolean same = userID.equalsIgnoreCase(currentUserID);
if (same) {
System.out.println("Do something (same == true)");
} else {
System.out.println("Do something (same == false)");
}
return same;
}
public static void main(String[] args) throws Exception {
testAuthentication(false);
testAuthentication(true);
}
private static void testAuthentication(boolean hackingMode) throws Exception {
TweakAuthenticationAspect.hackingMode = hackingMode;
System.out.println("Testing authentication for hackingMode == " + hackingMode);
System.out.println("Authentication result for " + USER_NAME_GOOD + ": "
+ isCurrentUser(USER_NAME_GOOD));
System.out.println("Authentication result for " + USER_NAME_BAD + ": "
+ isCurrentUser(USER_NAME_BAD));
System.out.println();
}
}
As you can see, testAuthentication(boolean hackingMode)
is called twice, once with the hacking code disabled and then enabled. In both cases it tests a good/correct user name (please edit!) first and then a bad one ("hacker").
Aspect manipulating the authentication method:
package de.scrum_master.aspect;
import com.sun.security.auth.module.NTSystem;
import de.scrum_master.app.UserAuthentication;
import java.lang.reflect.Method;
public aspect TweakAuthenticationAspect {
public static boolean hackingMode = false;
pointcut reflectiveCall_NTSystem_getName(NTSystem ntSystem, Method method) :
call(public Object Method.invoke(Object, Object...)) &&
args(ntSystem, *) &&
target(method) &&
if(method.getName().equals("getName"));
pointcut cflow_isCurrentUser(String userID) :
cflow(
execution(* UserAuthentication.isCurrentUser(String)) &&
args(userID)
);
Object around(NTSystem ntSystem, Method method, String userID) :
reflectiveCall_NTSystem_getName(ntSystem, method) &&
cflow_isCurrentUser(userID) &&
if(hackingMode)
{
System.out.println("Join point: " + thisJoinPoint);
System.out.println("Given user ID: " + userID);
System.out.println("Reflectively called method: " + method);
return userID;
}
}
A few words of explanation here:
- Pointcut
reflectiveCall_NTSystem_getName
intercepts calls toMethod.invoke(..)
, restricting the first parameter toNTSystem
type, which eliminates reflective calls to other classes. It also checks if the target method is actuallygetName
. I.e. the pointcut checks if really NTSystem.getName()` is to be invoked. - Pointcut
cflow_isCurrentUser
catches joinpoints in the control flow of methodUserAuthentication.isCurrentUser(..)
, exposing its parameteruserID
for later use. - The
around(NTSystem ntSystem, Method method, String userID)
advice combines both pointcuts with&&
and has access to the three named objects in its signature. In its method body we can do whatever we please with those objects, e.g. print them to the console. We could also change their state, which is not necessary in this case. The advice is activated dynamically viaif(hackingMode)
. If you do not need this, you can remove it, it is just for convenience. Because we use anaround()
advice here, we can return anything instead of the original method result. In this case, we always returnuserID
, as if the given user was the one currently logged into Windows. This effectively results in the localsame
variable to become alwaystrue
because the call toequalsIgnoreCase(..)
always returns true as well. - I could also have manipulated the result of
equalsIgnoreCase(..)
directly, but then local variablecurrentUserID
would be non-equal touserID
. Depending on which kinds of side effects you want, you can change this behaviour according to your preferences.
Sample output:
Testing authentication for hackingMode == false
Do something (same == true)
Authentication result for alexander: true
Do something (same == false)
Authentication result for hacker: false
Testing authentication for hackingMode == true
Join point: call(Object java.lang.reflect.Method.invoke(Object, Object[]))
Given user ID: alexander
Reflectively called method: public java.lang.String com.sun.security.auth.module.NTSystem.getName()
Do something (same == true)
Authentication result for alexander: true
Join point: call(Object java.lang.reflect.Method.invoke(Object, Object[]))
Given user ID: hacker
Reflectively called method: public java.lang.String com.sun.security.auth.module.NTSystem.getName()
Do something (same == true)
Authentication result for hacker: true
You can see in the upper part that authentication works as usual if hackingMode == false
, but will always positively authenticate any given user name if hackingMode == true
.