如何实现REST资源的输入验证

如何实现REST资源的输入验证

我正在使用的SaaS平台具有一个RESTful接口,该接口可以接受XML有效负载。 剩余验证

实施REST资源

对于像我们这样的Java商店,使用JAX-B从XML Schema生成JavaBean类是有意义的。 在像JerseyJAX-RS环境中,使用JAX-B处理XML(和JSON)有效负载非常容易。

@Path("orders")
public class OrdersResource {
  @POST
  @Consumes({ "application/xml", "application/json" })
  public void place(Order order) {
    // Jersey marshalls the XML payload into the Order 
    // JavaBean, allowing us to write type-safe code 
    // using Order's getters and setters.
    int quantity = order.getQuantity();
    // ...
  }
}

(请注意,您不应该使用这些通用媒体类型,但这是另一天的讨论。)

本文的其余部分假定使用JAX-B,但其要点也适用于其他技术。 无论您做什么,都不要使用XMLDecoder ,因为这对许多漏洞都是开放

保护REST资源

假设订单的quantity用于结算,并且我们想防止人们输入负数窃取我们的钱

我们可以通过输入验证AppSec工具箱中最重要的工具之一)来做到这一点。 让我们看一下实现它的一些方法。

使用XML模式进行输入验证

XML模式 我们可以依靠XML Schema进行验证 ,但是XML Schema只能验证那么多。

验证单个属性可能会很好,但是当我们要验证属性之间的关系时,事情变得很麻烦。 为了获得最大的灵活性,我们希望使用Java来表达约束。

更重要的是, 在REST服务中模式验证通常不是一个好主意

REST的主要目标是使客户端和服务器脱钩,以便它们可以分别发展。

如果我们根据模式进行验证,则发送新属性的新客户端将与无法理解该新属性的旧服务器发生冲突。 通常最好静默忽略您不了解的属性。

JAX-B可以做到这一点,反之亦然:旧客户端未发送的属性最终为null 。 因此,新服务器必须小心以正确处理null值。

使用Bean验证的输入验证

豆验证 如果我们不能使用模式验证,那么使用JSR 303 Bean验证又如何呢?

Jersey通过将jersey-bean-validation jar添加到您的类路径来支持Bean验证。

有一个非官方的Maven插件可以将Bean验证注释添加到JAX-B生成的类中,但是我宁愿使用更好的支持,并且可以与Gradle一起使用

因此,让我们扭转局势。 我们将手工制作JavaBean并从Bean生成XML Schema进行文档编制:

@XmlRootElement(name = "order")
public class Order {
  @XmlElement
  @Min(1)
  public int quantity;
}
@Path("orders")
public class OrdersResource {
  @POST
  @Consumes({ "application/xml", "application/json" })
  public void place(@Valid Order order) {
    // Jersey recognizes the @Valid annotation and
    // returns 400 when the JavaBean is not valid
  }
}

任何企图POST与非阳性数量的订单,现在将给予400 Bad Request状态。

现在假设我们要允许客户更改其挂单。 我们将使用PATCHPUT更新单个订单属性,例如数量:

@Path("orders")
public class OrdersResource {
  @Path("{id}")
  @PUT
  @Consumes("application/x-www-form-urlencoded")
  public Order update(@PathParam("id") String id, 
      @Min(1) @FormParam("quantity") int quantity) {
    // ...
  }
}

我们也需要在此处添加@Min注释,这是重复的。 为了使这个DRY ,我们可以将quantity变成负责验证的类:

@Path("orders")
public class OrdersResource {
  @Path("{id}")
  @PUT
  @Consumes("application/x-www-form-urlencoded")
  public Order update(@PathParam("id") String id, 
      @FormParam("quantity")
      Quantity quantity) {
    // ...
  }
}
@XmlRootElement(name = "order")
public class Order {
  @XmlElement
  public Quantity quantity;
}
public class Quantity {
  private int value;

  public Quantity() { }

  public Quantity(String value) {
    try {
      setValue(Integer.parseInt(value));
    } catch (ValidationException e) {
      throw new IllegalArgumentException(e);
    }
  }

  public int getValue() {
    return value;
  }

  @XmlValue
  public void setValue(int value) 
      throws ValidationException {
    if (value < 1) {
      throw new ValidationException(
          "Quantity value must be positive, but is: " 
          + value);
    }
    this.value = value;
  }
}

我们需要JAX-B的公共no-arg构造函数,以便能够将有效载荷解组到JavaBean中,而另一个构造函数则使用String来使@FormParam起作用。

setValue()抛出javax.xml.bind.ValidationException以便JAX-B将停止解组。 但是,Jersey看到异常时会返回500 Internal Server Error

我们可以通过使用异常映射器将验证异常映射到400状态代码来解决此问题。 在此过程中,让我们对IllegalArgumentException做同样的事情:

@Provider
public class DefaultExceptionMapper 
    implements ExceptionMapper<Throwable> {

  @Override
  public Response toResponse(Throwable exception) {
    Throwable badRequestException 
        = getBadRequestException(exception);
    if (badRequestException != null) {
      return Response.status(Status.BAD_REQUEST)
          .entity(badRequestException.getMessage())
          .build();
    }
    if (exception instanceof WebApplicationException) {
      return ((WebApplicationException)exception)
          .getResponse();
    }
    return Response.serverError()
        .entity(exception.getMessage())
        .build();
  }

  private Throwable getBadRequestException(
      Throwable exception) {
    if (exception instanceof ValidationException) {
      return exception;
    }
    Throwable cause = exception.getCause();
    if (cause != null && cause != exception) {
      Throwable result = getBadRequestException(cause);
      if (result != null) {
        return result;
      }
    }
    if (exception instanceof IllegalArgumentException) {
      return exception;
    }
    if (exception instanceof BadRequestException) {
      return exception;
    }
    return null;
  }

}
域对象的输入验证

http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-ebook/dp/B00794TAUG/ref=tmm_kin_title_0?ie=UTF8&qid=1376856556&sr=1-1 即使上面概述的方法对于许多应用程序都可以很好地工作,但从根本上来说还是有缺陷的。

乍一看, 领域驱动设计 (DDD)的支持者可能喜欢创建“ Quantity类的想法。

但是,“ Order和“ Quantity类不能为领域概念建模。 他们为REST表示建模。 这种区别可能很微妙,但很重要。

DDD处理领域概念,而REST处理这些概念的表示 发现了领域概念,但是设计了表示形式,并且需要进行各种折衷。

例如,集合REST资源可以使用分页来防止通过网络发送太多数据。 另一个REST资源可能结合了多个域概念,以使客户端-服务器协议的聊天性降低。

REST资源甚至可能根本没有对应的域概念。 例如,一个POST可能返回202 Accepted并指向代表异步事务进度的REST资源。

无处不在的语言 域对象需要尽可能接近地捕获普遍存在的语言 ,并且必须权衡利弊才能使功能起作用。

另一方面,在设计REST资源时,需要权衡满足非功能性需求,例如性能,可伸缩性和可扩展性。

这就是为什么我认为像RESTful Objects这样的方法不起作用的原因。 (出于类似原因,我不相信UI的Naked Objects 。)

在我们的资源表示形式的JavaBeans中添加验证意味着这些bean现在有两个更改的原因,这明显违反了“ 单一职责原则”

当仅将JAX-B JavaBeans用于REST表示并创建处理验证的单独域对象时,我们得到的架构简洁。

将验证放在域对象中是Dan Bergh Johnsson所谓的“ 域驱动的安全性”

洞穴艺术 在这种方法中,原始类型被值对象替代。 (甚至有人反对使用任何String 。)

起初,创建一个用于容纳单个整数的全新类似乎有些矫kill过正,但是我敦促您尝试一下。 您可能会发现,摆脱原始的迷恋甚至可以提供超出验证的价值。

你怎么看?

您如何在RESTful服务中处理输入验证? 您如何看待域驱动的安全性? 请发表评论。


翻译自: https://www.javacodegeeks.com/2013/08/how-to-implement-input-validation-for-rest-resources.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值