【问题标题】:Java - How to restrict method calling from a specific methodJava - 如何限制从特定方法调用方法
【发布时间】:2015-03-17 15:35:58
【问题描述】:

我有一个特殊的要求,我需要确保只允许一个类中的特定方法调用第二个类的公共(非静态)方法。不能使用继承。

一种选择是使用 StackTrace,如下所示:

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();
        }
    }
}

现在上述解决方案工作正常,但由于代码审计中的质量控制,我需要提出另一个(或更确切地说)更好的解决方案。有没有更好的方法来实现这一点?

【问题讨论】:

  • 这是一个非常奇怪的要求。为什么需要它?
  • 正是...通常对于像这样的奇怪要求,您应该考虑更改逻辑以完全避免这种情况。
  • 更改公共方法以获取对象引用,并使用instanceof 检查对象引用类型可能不那么脆弱。否则编写一些特殊的单元测试。但@Neilos 的建议更明智。
  • 用包含非法方法的新 C 扩展 B 怎么样?
  • 嗯...这有点尴尬,但我的情况是架构师不会让我使用继承来解决这个问题,忘记改变逻辑(就在第八个致命的罪.. . ). “改变设计方式”的空间很小另一方面,质量控制有一个自动审核员,它会扫描 StackTrace 的使用情况,作为“潜在的 JVM 内存泄漏”。相信我,当我这么说的时候,自动审核员就像是某个神圣的神灵一样受到尊重。是的,这个要求不仅很奇怪,而且完全无法解释。 @Antoniossss 谢谢

标签: java methods restrict


【解决方案1】:

正确的做法是重新审视您的要求。只能由某些其他代码路径调用的方法与public 不兼容。一般的最佳实践是使用包私有来阻止外部调用者,并接受包中的任何代码可以调用该方法,但不会因为您或您的团队正在审核它。 p>

方法可见性最终不是阻止执行的安全解决方案;有人拥有您的 .class 文件并能够在机器上执行它们,他们可以做任何他们想做的事情。您不应该花太多时间尝试锁定方法调用。相反,清楚地记录该方法的意图(例如“methodB() 的帮助函数,请不要在其他地方使用。”)并相信与您一起开发的人知道他们在做什么。你甚至可以给这个方法起一个清晰的名字,比如dangerousMethodBForInternalUseOnly(),如果你真的想在这件事上打败别人的话。

您可能还对dependency-injection 感兴趣,这是一种使用类型系统来保护(而不是阻止)人们执行危险代码的设计模式。以下是关于 Guice(一个流行的 DI 框架)的几场演讲,其中更详细地介绍了这个概念:


综上所述,作为一项学术练习,这里有一个将方法调用限制为固定数量的代码路径的选项 - 依赖于共享密钥。将Object secret 字段添加到您的锁定方法,如果传递的secret 与硬编码值(private static final Object SECRET = new Object())不匹配,则导致方法失败。然后,您可以使用其他机制仅将密钥共享给您允许的代码路径(例如,在您的锁定类中使用静态初始化程序将其发布到您明确信任的类)。

显然,恶意开发人员仍然可以解决此问题,而且这很糟糕,但它会提供某种锁定行为,前提是您可以相信您的锁定类不会在您不知情的情况下被更改。

【讨论】:

  • 我喜欢这个想法,但这与 JDK 开发人员对sun.misc.Unsafe 所做的不同。类名解释它是不安全的,不应该使用它,但仍然有很多开发人员使用它。因此,通过解释类名或方法名来明确警告开发人员无济于事。那么检查堆栈跟踪作为将方法调用者限制为特定类的一种方式不是很好吗?在使用 sun.misc.Unsafe 类引用此用例时,您有什么想法?
  • Unsafe 并不是真正的同一种情况,它 public 的设计,有时(尽管很少)适合用户调用它。当您说“仍然有很多开发人员使用它”时,真正的问题是他们是否在滥用它,这在抽象中很难回答。它更类似于 Rust 的 unsafe block,而不是像 OP 所询问的 API 中的泄漏抽象。
【解决方案2】:

改进方法的一个方法是不需要创建异常来获取堆栈跟踪,可以使用线程方法。

StackTraceElement[] stack = Thread.currentThread().getStackTrace();

也许你想使用类而不是手写包。例如:

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");
}

除此之外,我不知道如何在不改变逻辑或使用继承的情况下做得更好。

【讨论】:

  • 为什么投反对票?如果你不告诉我,我该如何解决?
【解决方案3】:
  1. 将调用者作为参数传递,并检查调用者是否为instanceof 所需类 - 多线程解决方案,无法通过反射绕过。
  2. 获取线程堆栈转储并检查顶部条目 - 奇怪、繁重但可能
  3. 创建代理 - 但这将是解决方案 1 的开销变体。

【讨论】:

    【解决方案4】:

    您可以通过使用类Class 方法getEnclosingMethod() 来满足此要求。这就是它的工作原理(docs here):

    如果此 Class 对象表示方法中的本地或匿名类,则返回 Method 对象,表示底层类的直接封闭方法。

    1. methodA() 的签名应更改为接受 Class 对象作为参数。

      public void methodA(Class c) { }
      
    2. 来自ClassB 的合法方法应该创建一个匿名类对象,并将其类作为参数传递给methodA()

      public void methodB() throws IllegalAccessException, NoSuchMethodException {
          new ClassA().methodA(new Object(){}.getClass());
      }
      
    3. 然后methodA() 应该检查类封闭方法是否确实是来自ClassBmethodB()

      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");
          }
      }
      

    缺点:

    • 每次调用methodB() 时都必须实例化一个新对象。这可能会变得昂贵,具体取决于您执行的次数。相反,您可以在 methodB() 内创建一个本地类,这样就没有对象创建开销

      public void methodB() throws IllegalAccessException, NoSuchMethodException {
          class Local {};
          new ClassA().methodA(Local.class);
      }
      
    • NoSuchMethodException改名,methodB()改名需要处理;

    • 有权访问代码的人仍然可以修改methodB() 以将匿名对象类返回到另一个方法,并使用它从那里调用methodA()所以这不是一个完美的解决方案,但可能足以满足您的用例。

    【讨论】:

      猜你喜欢
      • 2015-06-19
      • 1970-01-01
      • 1970-01-01
      • 2020-03-16
      • 1970-01-01
      • 2013-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多