在大型程序中管理隐私

Managing privacy within a large program

我正在开发一个具有这种结构的机器人程序(我将包括层的作用只是为了笑):

我想创建一个 java 应用程序,其中任何层都无法调用高于或低于一层的函数。

是否可以使用包隐私或类似工厂的模式来创建项目结构,这使得代码无法进入,比如说 A 层,从 D 层或 C 层导入任何东西?

这不是仅使用访问修饰符就能实现的。

您也不能通过(以某种方式)控制 import ... 因为 Java 语言没有对导入设置任何(额外)限制。 (import 指令实际上只是语法糖,因此您不需要在任何地方都使用完全限定名称。)

那你还能做什么?

  • 您可以尝试实施运行时限制以防止错误的层访问工厂对象。但是这样的限制很容易被有意或无意地破坏。

  • 您可以使用某种内部 "capability" 或 "credential" 机制,但很难看出如何防止凭据泄漏。 (如果凭据由安全管理器(见下文)管理,这可能会起作用,但这会使问题变得更加复杂。)

我认为您可以做到的唯一方法是实施自定义 SecurityManager,并在每次存在潜在的跨层调用时实施安全检查。例如,安全管理器可以(尽管很昂贵)检查调用堆栈以找到调用它的方法/class/包。您还需要关闭某些可用于(平凡地)破坏安全管理器的反射操作。本质上,除内圈外的所有内容都需要被视为 "untrusted" 代码。

坦率地说,用具有 "hacker-proof" 安全性的 JVM 实现这种事情可能超出了凡人的能力。 (Sun / Oracle 还没有成功....)


其他备选方案是:

  • 靠程序员纪律。

  • 依靠代码库的静态分析;例如借助记录访问规则的注释。您需要编写自己的代码分析器来执行此操作。

  • 在层之间使用地址-space 分离、占用空间小、具有安全意识的 API。 (我们这里不再谈论单一的常规JVM ...)

TL;DR 对此没有唯一的灵丹妙药解决方案,但可以利用许多不同的工具

有多种不同的技术可以隔离软件应用程序的不同部分,但我认为没有任何一种解决方案可以解决所有问题。一些构建系统可以限制目标之间的依赖关系(例如 Bazel 在构建目标上有一个 visibility 属性 可以防止一个目标依赖另一个目标,即使它们通过Java 的 class 可见性)可以与 Java 的内置可见性结合使用。例如:

 // Foo.java
 package com.yourcompany.foo;
 public class Foo {}

 // Build rule for Foo.java
 java_library(
    name = "Foo",
    srcs = ["Foo.java"],
    # Restricts visibility to this directory, even though
    # the class visibility was "public" 
    visibility = ["//visibility:private"],
 )

 // Bar.java
 package com.yourcompany.bar;

 import com.yourcompany.foo.Bar; // prevented by build visibility system

 public class Bar {
    Foo foo = new Foo();
 }

也可以使用接口来调解逻辑组件之间的所有交互并隐藏这些接口的实现(例如,仅通过服务注册接口或通过接口依赖注入公开实现)。例如,每层使用 Dagger, you could create a separate component,这样您就可以编写如下代码:

final class ControllerImpl implements Controller {
   // Since "ControllerImpl" is instantiated / wired into the
   // controller layer, the database dependency is available /
   // exposed for injection within this layer. The access control is
   // strictly performed by the way the dependencies are wired.
   @Inject
   public ControllerImpl(Database database) {
     // ...
   }
}

除上述之外,您还可以使用依赖分析/依赖分析测试或提交挂钩来自动检测依赖规则违规(并根据它们触发错误/拒绝提交)。例如,穷人的解决方案是简单地扫描每个文件的包声明和导入语句,然后使用一些试探法来检测错误的依赖项。

另一种方法是将不同的组件捆绑在单独的 JAR 中并使用自定义加载它们 ClassLoader,这将允许您使用反射防止非法访问(否则可能会绕过任何程序结构)。

除了自动化方法,手动方法也有其价值。手动方法包括定期代码审查和在这些代码审查和审计期间执行的策略。

简而言之,没有一个正确答案。有必要结合使用几种不同的方法,具体取决于这种分离的重要性。