Java - 如何限制从特定方法调用方法
Java - How to restrict method calling from a specific method
我有一个特殊的要求,我需要确保只有一个 class 中的特定方法被允许调用第二个 class 中的 public(非静态)方法.不能使用继承。
一种选择是使用 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();
}
}
}
现在上述解决方案工作正常,但由于代码审计中的质量控制,我需要提出另一个(或更确切地说)更好的解决方案。有没有更好的方法来实现这个?
正确的做法是重新审视您的要求。只能由某些其他代码路径调用的方法与 public
不兼容。一般的最佳做法是使用 package-private 来防止外部调用者,并接受包中的任何代码 可以 调用该方法,但不会因为您或您的团队正在审核它.
方法可见性最终不是防止执行的安全解决方案;有人拥有您的 .class 文件并能够在机器上执行它们,他们几乎可以做任何他们想做的事。您不应该花太多时间尝试锁定方法调用。相反,清楚地记录该方法的意图(例如“methodB()
的辅助函数,请不要在其他地方使用。”)并相信与您一起开发的人知道他们在做什么正在做。你甚至可以给这个方法一个明确的名字,比如 dangerousMethodBForInternalUseOnly()
如果你真的想用它来打败别人。
您可能还对 dependency-injection 感兴趣,这是一种使用类型系统来保护(而不是阻止)人们执行危险代码的设计模式。下面是关于 Guice(一种流行的 DI 框架)的一些讨论,其中更详细地介绍了这个概念:
综上所述,作为一项学术练习,这里有一个选项可以将方法调用限制在固定数量的代码路径上——依赖于共享秘密。将 Object secret
字段添加到您的锁定方法,如果传递的 secret
与硬编码值 (private static final Object SECRET = new Object()
) 不匹配,则会导致该方法失败。然后,您可以使用其他机制仅将秘密共享给您允许的代码路径(例如,在您锁定的 class 中有一个静态初始化程序将其发布到您明确信任的 classes)。
显然,这仍然可以被恶意开发人员解决,这非常糟糕,但它会提供某种锁定行为,前提是您可以相信您的锁定 class 不会被更改在你不知情的情况下。
一种改进方法的方法是不需要创建异常来获取堆栈跟踪,您可以使用线程方法。
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
也可能您想使用 class 而不是手写包。例如:
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");
}
除此之外,我不知道如何在不更改逻辑或使用继承的情况下做得更好。
- 将调用者作为参数传递并检查调用者是否
instanceof
必需class - 多线程解决方案,无法通过反射绕过。
- 获取线程堆栈转储并检查顶部条目 - 奇怪、繁重但可能
- 创建代理 - 但这将是解决方案 1 的开销变体。
您可以使用 class Class
方法 getEnclosingMethod()
来满足此要求。这是它的工作原理 (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.
应更改 methodA()
的签名以接受 Class
对象作为参数。
public void methodA(Class c) { }
来自 ClassB
的合法方法应该创建一个匿名 class 对象,并将其 class 作为参数传递给 methodA()
.
public void methodB() throws IllegalAccessException, NoSuchMethodException {
new ClassA().methodA(new Object(){}.getClass());
}
然后 methodA()
应该检查 class 封闭方法是否确实是来自 ClassB
.
的 methodB()
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()
中创建一个本地 class,因此 没有对象创建开销 :
public void methodB() throws IllegalAccessException, NoSuchMethodException {
class Local {};
new ClassA().methodA(Local.class);
}
如果methodB()
名称发生变化,您需要处理NoSuchMethodException
并更改代码;
- 有权访问代码的人仍然可以将
methodB()
修改为 return 匿名对象 class 为另一个方法,并使用它从那里调用 methodA()
。 所以这不是一个完美的解决方案,但可能足以满足您的用例。
我有一个特殊的要求,我需要确保只有一个 class 中的特定方法被允许调用第二个 class 中的 public(非静态)方法.不能使用继承。
一种选择是使用 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();
}
}
}
现在上述解决方案工作正常,但由于代码审计中的质量控制,我需要提出另一个(或更确切地说)更好的解决方案。有没有更好的方法来实现这个?
正确的做法是重新审视您的要求。只能由某些其他代码路径调用的方法与 public
不兼容。一般的最佳做法是使用 package-private 来防止外部调用者,并接受包中的任何代码 可以 调用该方法,但不会因为您或您的团队正在审核它.
方法可见性最终不是防止执行的安全解决方案;有人拥有您的 .class 文件并能够在机器上执行它们,他们几乎可以做任何他们想做的事。您不应该花太多时间尝试锁定方法调用。相反,清楚地记录该方法的意图(例如“methodB()
的辅助函数,请不要在其他地方使用。”)并相信与您一起开发的人知道他们在做什么正在做。你甚至可以给这个方法一个明确的名字,比如 dangerousMethodBForInternalUseOnly()
如果你真的想用它来打败别人。
您可能还对 dependency-injection 感兴趣,这是一种使用类型系统来保护(而不是阻止)人们执行危险代码的设计模式。下面是关于 Guice(一种流行的 DI 框架)的一些讨论,其中更详细地介绍了这个概念:
综上所述,作为一项学术练习,这里有一个选项可以将方法调用限制在固定数量的代码路径上——依赖于共享秘密。将 Object secret
字段添加到您的锁定方法,如果传递的 secret
与硬编码值 (private static final Object SECRET = new Object()
) 不匹配,则会导致该方法失败。然后,您可以使用其他机制仅将秘密共享给您允许的代码路径(例如,在您锁定的 class 中有一个静态初始化程序将其发布到您明确信任的 classes)。
显然,这仍然可以被恶意开发人员解决,这非常糟糕,但它会提供某种锁定行为,前提是您可以相信您的锁定 class 不会被更改在你不知情的情况下。
一种改进方法的方法是不需要创建异常来获取堆栈跟踪,您可以使用线程方法。
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
也可能您想使用 class 而不是手写包。例如:
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");
}
除此之外,我不知道如何在不更改逻辑或使用继承的情况下做得更好。
- 将调用者作为参数传递并检查调用者是否
instanceof
必需class - 多线程解决方案,无法通过反射绕过。 - 获取线程堆栈转储并检查顶部条目 - 奇怪、繁重但可能
- 创建代理 - 但这将是解决方案 1 的开销变体。
您可以使用 class Class
方法 getEnclosingMethod()
来满足此要求。这是它的工作原理 (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.
应更改
methodA()
的签名以接受Class
对象作为参数。public void methodA(Class c) { }
来自
ClassB
的合法方法应该创建一个匿名 class 对象,并将其 class 作为参数传递给methodA()
.public void methodB() throws IllegalAccessException, NoSuchMethodException { new ClassA().methodA(new Object(){}.getClass()); }
然后
的methodA()
应该检查 class 封闭方法是否确实是来自ClassB
.methodB()
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()
中创建一个本地 class,因此 没有对象创建开销 :public void methodB() throws IllegalAccessException, NoSuchMethodException { class Local {}; new ClassA().methodA(Local.class); }
如果
methodB()
名称发生变化,您需要处理NoSuchMethodException
并更改代码;- 有权访问代码的人仍然可以将
methodB()
修改为 return 匿名对象 class 为另一个方法,并使用它从那里调用methodA()
。 所以这不是一个完美的解决方案,但可能足以满足您的用例。