问题
I have a peculiar requirement where I need ensure that only a particular method from one class is allowed to call a public (non-static) method from a second class. Inheritance cannot be used.
One option is to use StackTrace as follows:
ClassA.java
package org.rnd.stack;
public class ClassA {
public void methodA() throws IllegalAccessException {
Exception fake = new Exception("FAKE-IGNORE");
StackTraceElement[] stack = fake.getStackTrace();
StackTraceElement st = stack[1];
if ("org.rnd.stack.ClassB".equals(st.getClassName())
&& "methodB".equals(st.getMethodName())) {
System.out.println("You are allowed to call");
} else {
throw new IllegalAccessException("You are not allowed to call");
}
}
}
ClassB.java
package org.rnd.stack;
public class ClassB {
public void methodB() throws IllegalAccessException {
new ClassA().methodA();
}
public void illegalMethod() throws IllegalAccessException {
new ClassA().methodA();
}
public static void main(String[] args) {
try {
new ClassB().methodB();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Now the above solution works fine, but due to quality control in code audit I need to come up with another (or rather) better solution. Is there a better way to achieve this?
回答1:
A way to improve you method is that you don't need to create an exception to get the stacktrace, you can use the thread methods.
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
Also maybe you want to use the class instead of handwriting the package. For example:
if (ClassB.class.getName().equals(st.getClassName())
&& "methodB".equals(st.getMethodName())) {
System.out.println("You are allowed to call");
} else {
throw new IllegalAccessException("You are not allowed to call");
}
Apart from that I don't know how you can do it better without changing your logic or using inheritance.
回答2:
The right thing to do would be to revisit your requirement. A method that can only be called by certain other code paths is not compatible with public
. The general best practice is to use package-private to prevent external callers, and accept that any code in the package could call the method, but won't because you or your team is auditing it.
Method visibility is ultimately not a secure solution to preventing execution; someone has your .class files and the ability to execute them on a machine, they can do just about anything they want. You shouldn't spend too much time trying to lock down method calls. Instead, document the intent of the method clearly (e.g. "Helper function for methodB()
, please do not use elsewhere.") and trust the people developing with you know what they're doing. You can even give the method a clear name, like dangerousMethodBForInternalUseOnly()
if you really want to beat people over the head about it.
You may also be interested in dependency-injection, which is a design pattern that uses the type system to protect (not prevent) people from executing dangerous code. Here's a couple of talks on Guice, a popular DI framework, that goes into more detail about the concept:
- Google I/O 2009 - Big Modular Java with Guice
- Java on Guice: Dependency Injection, the Java Way
All of that said, as an academic exercise here's one option for restricting method invocation to a fixed number of codepaths - rely on a shared secret. Add an Object secret
field to your locked-down method, and cause the method to fail if the passed secret
does not match a hard-coded value (private static final Object SECRET = new Object()
). You can then use other mechanisms to share the secret only to code paths you allow (e.g. have a static initializer in your locked-down class publish it to classes you explicitly trust).
Obviously this can still be worked-around by a malicious developer, and it's pretty gross, but it would provide some sort of locking behavior assuming you can trust your locked-down class won't be changed without your knowledge.
回答3:
- Pass caller as an argument and check if the caller is
instanceof
required class - multithreaded solution, cannot bypass by reflecion. - Get thread stack dump and check top entry - weird, heavy but possible
- Create proxy - but that will be overheaded variation of solution 1.
回答4:
You may be able to satisfy this requirement by using the class Class
method getEnclosingMethod()
. This is how it works (docs here):
If this Class object represents a local or anonymous class within a method, returns a Method object representing the immediately enclosing method of the underlying class.
The signature for
methodA()
should be changed to accept aClass
object as parameter.public void methodA(Class c) { }
The legal method from
ClassB
should create an anonymous class object, and pass its class as argument tomethodA()
.public void methodB() throws IllegalAccessException, NoSuchMethodException { new ClassA().methodA(new Object(){}.getClass()); }
Then
methodA()
should check if the class enclosing method is indeedmethodB()
fromClassB
.public void methodA(Class c) throws IllegalAccessException, NoSuchMethodException { if (c.getEnclosingMethod().equals(ClassB.class.getMethod("methodB"))) { System.out.println("You are allowed to call"); } else { throw new IllegalAccessException("You are not allowed to call"); } }
Disadvantages:
You must instantiate a new object every time you call
methodB()
. This may get expensive depending on how many times you do it. Instead, you could create a local class insidemethodB()
so there is no object creation overhead:public void methodB() throws IllegalAccessException, NoSuchMethodException { class Local {}; new ClassA().methodA(Local.class); }
You need to handle
NoSuchMethodException
and change the code ifmethodB()
name changes;- Someone with access to the code could still modify
methodB()
to return the anonymous object class to another method, and use it to callmethodA()
from there. So this is not a perfect solution, but may be enough for your use case.
来源:https://stackoverflow.com/questions/29103314/java-how-to-restrict-method-calling-from-a-specific-method