在Spring MVC中要向表單傳遞或接受Java model物件常宣告@ModelAttriube於變數之前,但有時候並不是所有的model物件屬性都有對應的表單欄位,換句話說,表單欄位可能僅輸入部分資訊,其他屬性則可能是內部使用或是其他用途,不希望被寫入,雖然表單是以POST傳遞,但也有可能中途遭有心人加"料",為預防此類的攻擊,Springframework提供o.s.web.bind.WebDataBinder物件來customize data binding,通常是用在有處理表單的Controller中,宣告一個方法並以@InitBinder來初始化WebDataBinder及設定,今日將以DCNController為基礎介紹WebDataBinder,
WebDataBinder物件常用的三個方法: setAllowedFields(), setDisallowedFields(), setValidator(),第一個方法是設定model物件中允許binding的屬性,這樣的方法稱作whitelisting,不在清單中屬性的將視作不允許binding到表單欄位,第二種恰好顛倒,設定model物件中不允許binding的屬性,又稱blackinglisting,不在清單中的都允許binding到表單欄位,以安全性來看,whitelisting比較高,因為邏輯上只有允許的欄位可以binding,其餘的皆不允許,第三種是設定Spring Vaildator,稍後說明
先於DCN Controller下,表單方法加入BindingResult物件變數,利用getSuppressedFields()方法來判斷是否有不合法的binding,其code如下
@Controller
@RequestMapping("/dcn")
public class DCNController {
.....
@RequestMapping(value="/add", method=RequestMethod.POST)
public String processDCNForm(@ModelAttribute("newDCN") DCN newDCN,
BindingResult result){
if(result.getSuppressedFields().length>0){
throw new RuntimeException("Attempting to bind "
+ "disallowed fields: " + StringUtils.
arrayToCommaDelimitedString(result.getSuppressedFields()));
//getSupressedField回傳字串陣列,利用Spring提供的StringUtils可以將陣列裡
//的element以逗號分開。
}
dcnService.add(newDCN);
return "redirect:/dcn";
}
......
@InitBinder
public void initialzeBinder(WebDataBinder binder){
binder.setAllowedFields("no", "rev", "cateogy", "trackNumber"
, "issuedDate"); //whitelisting
// binder.setDisallowedFields("status"); //blacklisting
}
接著DCN物件中加入新status屬性,並於addDCN.jsp加入輸入欄位
@Column(length=50)
private String status;
...getter and setter
啟動Server,填入相關資訊如下
點選Add,後出現以下錯誤畫面
錯誤訊息告訴我們category跟status不允許被binding,category是我忘記加,那status的表單欄位要從addDCN.jsp中移除,再來新增紀錄
補上category
@InitBinder
public void initialzeBinder(WebDataBinder binder){
binder.setAllowedFields("no", "rev", "cateogy", "trackNumber"
, "issuedDate", "category"); //whitelisting
// binder.setDisallowedFields("status"); //blacklisting
}
接著跟大家介紹Spring Validator,WebDataBinder可以指定表單的validator,Spring Validator通常需要新增一個class實作o.s.validation.Validator介面,override supports(Class<?> clazz)及validate(Object target, Errors errors)兩個方法,第一個方法需要指定要validate的model物件,第二個方法即為Valiate的實際內容,其code如下
@Component
public class DcnValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return DCN.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
// TODO Auto-generated method stub
DCN dcn=(DCN) target;
Pattern noPattern=Pattern.compile("NED-LM[0-2[9]]-H-N\\d{5}"); //
Pattern datePattern=Pattern.compile("20\\d{2}/[0-2][0-9]/[]0-3][0-9]"); //
if(!noPattern.matcher(dcn.getNo()).matches()){
errors.rejectValue("no", "error.dcn.no.format", "The DCN Format Must Follow"
+ " NED-LM?-H-N?????");
}
if(dcn.getNo().length() !=16){
errors.rejectValue("no", "error.dcn.no.length", "The DCN Length Is Incorrect.");
}
if(dcn.getRev()==null){
errors.rejectValue("rev", "error.dcn.rev.notNull", "This Field Must Be Not Null.");
}
if(dcn.getTrackNumber() ==null)
errors.rejectValue("trackNumber", "error.dcn.trackNumber.notNull", "This Field "
+ "Must Be Not Null.");
if(!datePattern.matcher(dcn.getIssuedDate()).matches()){
errors.rejectValue("issuedDate", "error.dcn.date.format", "The Date Format Must Follow"
+ " yyyy/mm/dd.");
}
}
}
對於DCN的No與日期用Regular expression來指定格式,其餘簡單判斷是否為空白,errors.rejectValue方法,第一個參數是屬性,第二個是error code命名建議與欄位有關,第三個參數是預設的錯誤訊息。
接著到Controller更新Controller,以BindingResult物件變數中的hasErrors方法,來判斷是否有欄位違反規則,並於@Autowired DcnValidator以及WebDataBinder設定該validator,另外@ModelAttribute前面要記得加入@Valid,其code如下。
@Controller
@RequestMapping("/dcn")
public class DCNController {
......
@Autowired
private DcnValidator dcnValidator;
@RequestMapping(value="/add", method=RequestMethod.POST)
public String processDCNForm(@Valid @ModelAttribute("newDCN") DCN newDCN,
BindingResult result){
if(result.hasErrors()){
return "addDCN";
}
.....
dcnService.add(newDCN);
return "redirect:/dcn";
}
@InitBinder
public void initialzeBinder(WebDataBinder binder){
binder.setAllowedFields("no", "rev", "cateogy", "trackNumber"
, "issuedDate", "category"); //whitelisting
// binder.setDisallowedFields("status"); //blacklisting
binder.setValidator(dcnValidator); //設定Spring Validator
}
}
再來編輯addDCN.jsp,於每個欄位後面加入<form:errors path="屬性名稱" />,其code如下:
...
<form:errors path="no" element="div" cssClass="notice marker-on-top fg-white bg-darkRed"/>
....
<form:errors path="rev" cssClass="notice marker-on-top fg-white bg-darkRed"/>
....
<form:errors path="trackNumber" cssClass="notice marker-on-top fg-white bg-darkRed"/>
.....
<form:errors path="issuedDate" cssClass="notice marker-on-top fg-white bg-darkRed"/>
啟動Server,新增DCN表單中,如果都不填,則會出現以下畫面
但是其實Spring Validator不像Hibernate Validator更為簡單易用,因為Hibernate Validator僅須於model物件宣告相關Annotation語法可達成欄位驗證目的,明天繼續。