Reputation: 43
I have some compiled library which contains a method like this:
public boolean foo(String userID) {
Class<?> ntSystemClass = Thread.currentThread().getContextClassLoader()
.loadClass("com.sun.security.auth.module.NTSystem");
Method getNameMethod = ntSystemClass.getMethod("getName", null);
Object ntSystem = ntSystemClass.newInstance();
String name = (String)getNameMethod.invoke(ntSystem, null);
boolean same=userID.equalsIgnoreCase(name);
if (same) {
// more work done here
} else {
// more work done here
}
}
For some rather special use case I need to ensure that the boolean same
is always true.
My first approach was extending the class and overriding the method foo()
but that wasn't realizable because within the method many references on other library private stuff is required.
So the next approach is using AOP. I tried a few things with AspectJ but didn't find a solution. Can anyone help we with this? As far as I understand I can't alter the boolean same
directly. Is there a possibility to address the String name = (String)getNameMethod.invoke(ntSystem, null);
in any way just within the library?
Upvotes: 1
Views: 110
Reputation: 67297
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:
foo
method (I have renamed it to isCurrentUser(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.execution()
(unless you want to weave the JDK first, which is possible but out of scope here). Thus, you have to intercept the call()
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.NTSystem.getName()
is not done in a normal way but via Reflection API. Thus, you cannot just use a pointcut like call(NTSystem.getName())
because it will never be triggered. You have to intercept call(public Object Method.invoke(Object, Object...))
.isCurrentUser(..)
, so we have to refine our pointcut in order to only match if really NTSystem.getName()
is called, not any other method.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:
reflectiveCall_NTSystem_getName
intercepts calls to Method.invoke(..)
, restricting the first parameter to NTSystem
type, which eliminates reflective calls to other classes. It also checks if the target method is actually getName
. I.e. the pointcut checks if really NTSystem.getName()` is to be invoked.cflow_isCurrentUser
catches joinpoints in the control flow of method UserAuthentication.isCurrentUser(..)
, exposing its parameter userID
for later use.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 via if(hackingMode)
. If you do not need this, you can remove it, it is just for convenience. Because we use an around()
advice here, we can return anything instead of the original method result. In this case, we always return userID
, as if the given user was the one currently logged into Windows. This effectively results in the local same
variable to become always true
because the call to equalsIgnoreCase(..)
always returns true as well.equalsIgnoreCase(..)
directly, but then local variable currentUserID
would be non-equal to userID
. 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
.
Upvotes: 4