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

时间:2017-04-05 11:03:15

标签: java playframework playframework-2.0 distributed-computing distributed

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

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

以下是我希望公开访问的示例控制器

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

以下是此控制器的示例routes / 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

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

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),我只需要节点可能从另一个节点调用一行或两行代码。

修改

我设法使用Action Composition将给定控制器仅限制为预先配置的源地址,如下所示:

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

然后在每个控制器的开头添加以下注释:

@With(SourceAddressFilter.class)

虽然我有点担心这个方法有多好,如果它可能以某种方式在请求中伪造它或者只是通过调用request()来接收错误的方法,它是有效的.remoteAddress( )在行动中。

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

1 个答案:

答案 0 :(得分:1)

您应该使用JWT并使用令牌action composition管理您的身份验证。

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