如何实现REST资源的输入验证
如何實現(xiàn)REST資源的輸入驗證
我正在使用的SaaS平臺具有一個RESTful接口,該接口可以接受XML有效負(fù)載。
實施REST資源
對于像我們這樣的Java商店,使用JAX-B從XML Schema生成JavaBean類是有意義的。 在像Jersey的JAX-RS環(huán)境中,使用JAX-B處理XML(和JSON)有效負(fù)載非常容易。
@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();// ...} }(請注意,您不應(yīng)該使用這些通用媒體類型,但這是另一天的討論。)
本文的其余部分假定使用JAX-B,但其要點也適用于其他技術(shù)。 無論您做什么,都不要使用XMLDecoder ,因為這對許多漏洞都是開放的 。
保護(hù)REST資源
假設(shè)訂單的quantity用于結(jié)算,并且我們想防止人們輸入負(fù)數(shù)來竊取我們的錢 。
我們可以通過輸入驗證 ( AppSec工具箱中最重要的工具之一)來做到這一點。 讓我們看一下實現(xiàn)它的一些方法。
使用XML模式進(jìn)行輸入驗證
我們可以依靠XML Schema進(jìn)行驗證 ,但是XML Schema只能驗證那么多。
驗證單個屬性可能會很好,但是當(dāng)我們要驗證屬性之間的關(guān)系時,事情變得很麻煩。 為了獲得最大的靈活性,我們希望使用Java來表達(dá)約束。
更重要的是, 在REST服務(wù)中 , 模式驗證通常不是一個好主意 。
REST的主要目標(biāo)是使客戶端和服務(wù)器脫鉤,以便它們可以分別發(fā)展。
如果我們根據(jù)模式進(jìn)行驗證,則發(fā)送新屬性的新客戶端將與無法理解該新屬性的舊服務(wù)器發(fā)生沖突。 通常最好靜默忽略您不了解的屬性。
JAX-B可以做到這一點,反之亦然:舊客戶端未發(fā)送的屬性最終為null 。 因此,新服務(wù)器必須小心以正確處理null值。
使用Bean驗證的輸入驗證
如果我們不能使用模式驗證,那么使用JSR 303 Bean驗證又如何呢?
Jersey通過將jersey-bean-validation jar添加到您的類路徑來支持Bean驗證。
有一個非官方的Maven插件可以將Bean驗證注釋添加到JAX-B生成的類中,但是我寧愿使用更好的支持,并且可以與Gradle一起使用 。
因此,讓我們扭轉(zhuǎn)局勢。 我們將手工制作JavaBean并從Bean生成XML Schema進(jìn)行文檔編制:
@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與非陽性數(shù)量的訂單,現(xiàn)在將給予400 Bad Request狀態(tài)。
現(xiàn)在假設(shè)我們要允許客戶更改其掛單。 我們將使用PATCH或PUT更新單個訂單屬性,例如數(shù)量:
@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注釋,這是重復(fù)的。 為了使這個DRY ,我們可以將quantity變成負(fù)責(zé)驗證的類:
@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 {@XmlElementpublic 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;}@XmlValuepublic 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構(gòu)造函數(shù),以便能夠?qū)⒂行лd荷解組到JavaBean中,而另一個構(gòu)造函數(shù)則使用String來使@FormParam起作用。
setValue()拋出javax.xml.bind.ValidationException以便JAX-B將停止解組。 但是,Jersey看到異常時會返回500 Internal Server Error 。
我們可以通過使用異常映射器將驗證異常映射到400狀態(tài)代碼來解決此問題。 在此過程中,讓我們對IllegalArgumentException做同樣的事情:
@Provider public class DefaultExceptionMapper implements ExceptionMapper<Throwable> {@Overridepublic 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;}}域?qū)ο蟮妮斎腧炞C
即使上面概述的方法對于許多應(yīng)用程序都可以很好地工作,但從根本上來說還是有缺陷的。
乍一看, 領(lǐng)域驅(qū)動設(shè)計 (DDD)的支持者可能喜歡創(chuàng)建“ Quantity類的想法。
但是,“ Order和“ Quantity類不能為領(lǐng)域概念建模。 他們?yōu)镽EST表示建模。 這種區(qū)別可能很微妙,但很重要。
DDD處理領(lǐng)域概念,而REST處理這些概念的表示 。 發(fā)現(xiàn)了領(lǐng)域概念,但是設(shè)計了表示形式,并且需要進(jìn)行各種折衷。
例如,集合REST資源可以使用分頁來防止通過網(wǎng)絡(luò)發(fā)送太多數(shù)據(jù)。 另一個REST資源可能結(jié)合了多個域概念,以使客戶端-服務(wù)器協(xié)議的聊天性降低。
REST資源甚至可能根本沒有對應(yīng)的域概念。 例如,一個POST可能返回202 Accepted并指向代表異步事務(wù)進(jìn)度的REST資源。
域?qū)ο笮枰M可能接近地捕獲普遍存在的語言 ,并且必須權(quán)衡利弊才能使功能起作用。
另一方面,在設(shè)計REST資源時,需要權(quán)衡滿足非功能性需求,例如性能,可伸縮性和可擴(kuò)展性。
這就是為什么我認(rèn)為像RESTful Objects這樣的方法不起作用的原因。 (出于類似原因,我不相信UI的Naked Objects 。)
在我們的資源表示形式的JavaBeans中添加驗證意味著這些bean現(xiàn)在有兩個更改的原因,這明顯違反了“ 單一職責(zé)原則” 。
當(dāng)僅將JAX-B JavaBeans用于REST表示并創(chuàng)建處理驗證的單獨域?qū)ο髸r,我們得到的架構(gòu)會更簡潔。
將驗證放在域?qū)ο笾惺荄an Bergh Johnsson所謂的“ 域驅(qū)動的安全性” 。
在這種方法中,原始類型被值對象替代。 (甚至有人反對使用任何String 。)
起初,創(chuàng)建一個用于容納單個整數(shù)的全新類似乎有些矯kill過正,但是我敦促您嘗試一下。 您可能會發(fā)現(xiàn),擺脫原始的迷戀甚至可以提供超出驗證的價值。
你怎么看?
您如何在RESTful服務(wù)中處理輸入驗證? 您如何看待域驅(qū)動的安全性? 請發(fā)表評論。
翻譯自: https://www.javacodegeeks.com/2013/08/how-to-implement-input-validation-for-rest-resources.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的如何实现REST资源的输入验证的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 身主文昌什么意思 身主文昌是什么意思
- 下一篇: 哪个内存更快?Heap或ByteBuff