Java - How to restrict method calling from a specific method

爷,独闯天下 提交于 2019-12-23 16:09:07

问题


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:


  1. Pass caller as an argument and check if the caller is instanceof required class - multithreaded solution, cannot bypass by reflecion.
  2. Get thread stack dump and check top entry - weird, heavy but possible
  3. 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.

  1. The signature for methodA() should be changed to accept a Class object as parameter.

    public void methodA(Class c) { }
    
  2. The legal method from ClassB should create an anonymous class object, and pass its class as argument to methodA().

    public void methodB() throws IllegalAccessException, NoSuchMethodException {
        new ClassA().methodA(new Object(){}.getClass());
    }
    
  3. Then methodA() should check if the class enclosing method is indeed methodB() from ClassB.

    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 inside methodB() 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 if methodB() 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 call methodA() 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!