java - 如何在 Play Framework for Java 的分布式集群中实现安全的节点间通信?

标签 java playframework playframework-2.0 distributed-computing distributed

我的应用程序基于 Java 的 Play 框架。我通过简单地将相同的应用程序部署到多个服务器上并在它们之间相应地分发 Web 请求来使其分布式。每个节点都将连接到同一个数据库。

Play 框架已经使用 Netty,因此我可以选择使用 HTTP 请求在节点之间进行通信,但我不确定如何保护其中一些请求,以便可以从浏览器调用其中一些请求,即我使用框架的网站仍然可以调用某些路由/api端点,而某些路由/api端点受到限制,只能由集群中的其他节点调用,以便节点之间可以通信。

这是一个我希望公开访问的示例 Controller

public class TestController extends Controller {
  public static Result create() {
    JsonNode json = request().body().asJson();
    if (json == null) return badRequest("Invalid JSON!");

    Testmodel model = JsonHelper.fromJson(json.toString(), Testmodel.class);
    if (model == null) return badRequest("JSON does not conform to model!");

    MongoHelper.getInstance().getDatastore().save(model);

    return ok(JsonHelper.toJson(model));
  }

  public static Result addOneTest() {
    Testmodel somemodel = new Testmodel();
    somemodel.setName("name" + Math.random());
    somemodel.setValue("val" + Math.random());

    MongoHelper.getInstance().getDatastore().save(somemodel);

    return ok(JsonHelper.toJson(somemodel));
  }

  public static Result getAllTest() {
    List<Testmodel> all = MongoHelper.getInstance().getDatastore().find(Testmodel.class).asList();
    return ok(JsonHelper.toJson(all));
  }

  public static Result getCountTest() {
    long count = MongoHelper.getInstance().getDatastore().getCount(Testmodel.class);
    return ok(Long.toString(count));
  }

  public static Result deleteAllTest() {
    Datastore ds = MongoHelper.getInstance().getDatastore();
    WriteResult result = ds.delete(ds.createQuery(Testmodel.class));

    return ok(JsonHelper.toJson(result));
  }
}

这里是该 Controller 的示例路由/API 端点

POST    /api/test/create                    controllers.TestController.create()
POST    /api/test/add                       controllers.TestController.addOneTest()
DELETE  /api/test/delete                    controllers.TestController.deleteAllTest()
GET     /api/test/get                       controllers.TestController.getAllTest
GET     /api/test/count                     controllers.TestController.getCountTest

这是一个我想要公开访问的 Controller 的示例,即我只希望集群中的节点能够使用它,以便它们可以通过 HTTP 请求相互通信并从另一个节点运行某些方法或将数据传递到另一个节点。出于安全目的,对公共(public)可访问性的限制是显而易见的,我不希望网站上的人访问此 Controller 的任何路由。

public class NodeController extends Controller {
  public static Result runJob(ObjectId jobId) {
    Submission submission = MongoHelper.getInstance().getDatastore().get(Submission.class, jobId);
    if (submission == null) {
      return badRequest("No job with that id");
    }
    // TODO Do some more error checking and validation on the submission

    JobRunner.getInstance().run(jobId);
    return ok();
  }

  public static Result cancelJob(ObjectId jobId) {
    boolean cancelled = JobRunner.getInstance().cancel(jobId);
    if (cancelled) return ok();
    else return badRequest("Example error");
  }
}

我能找到的保护这些的唯一选择是使用 Play Framework's Allowed Hosts Filters ,这使得框架仅接受来自指定主机的 HTTP 请求,但问题是这应用了全面过滤器,即所有请求都将受到限制,即使是那些我希望公开访问的请求。

我读到的另一个选项是 Java 远程方法调用 (RMI),但我是初学者,所以这对我来说可能有点困难。该项目也相当小且简单,所以我担心这可能有点矫枉过正。正如您在上面的示例(特别是 NodeController)中看到的,我所需要的只是让节点从另一个节点调用一两行代码。

编辑

我设法使用操作组合将给定 Controller 仅限制为预先配置的源地址,如下所示:

public class SourceAddressFilter extends Action<SourceAddressFilter> {
  private static final org.slf4j.Logger LOGGER = play.Logger.underlying();
  private static final List<String> allowedAddresses =
          Configuration.root().getStringList("nodes.allowedSourceAddresses");

  @Override
  public CompletionStage<Result> call(Http.Context ctx) {
    String srcAddress = ctx.request().remoteAddress();

    if (!allowedAddresses.contains(srcAddress)) {
      LOGGER.error("Address {} attempted to perform action, but is not in the list of allowed hosts!", srcAddress);
      return CompletableFuture.completedFuture(badRequest("Address " + srcAddress + " is not authorized!"));
    }

    return delegate.call(ctx);
  }
}

然后在每个 Controller 的开头添加以下注释:

@With(SourceAddressFilter.class)

它确实有效,尽管我有点担心这是一种多么好的方法,以及是否有可能以某种方式在请求中伪造这个方法,或者只是通过在操作中调用 request().remoteAddress() 来接收错误的方法.

我仍然有兴趣知道是否可以按照答案中的建议使用 JWT 来完成,但我不知道如何实现。

最佳答案

您应该使用JWT并使用 token + action composition 管理您的身份验证.

JWT 允许您创建可在应用程序之间共享的签名 token 。

关于java - 如何在 Play Framework for Java 的分布式集群中实现安全的节点间通信?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43229323/

相关文章:

java - Chris Banes实现ActionBar-PulltoRefresh,库错误等

java - 数据类型 datetime 和 time 在大于或等于运算符中不兼容

java - Play 2.0 与 Play 1.2.5-RC1 + CRUD + 其他模块

java - 有没有办法使用 playframework 接受路由中的自定义对象?

java - 如何从 json 对象中获取字符串列表

scala - 如何不观看文件以了解 Play Framework 中的更改

java - Play Framework : How can I read a png image using the WS client?

java - 使用 Play Framework 获取请求参数?

javascript - 在 AMD 文件中添加隐式依赖项

java - 使用 com.google.code.gson 在 Java8 中创建 JSON 对象