iT邦幫忙

DAY 28
0

無痛學習SpringMVC與Spring Security系列 第 28

[Controller/Security]WebDataBinder-@InitBinder應用於表單欄位binding及表單驗證(Spring Vaildator)

在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語法可達成欄位驗證目的,明天繼續。


上一篇
[Security]Method-Level Security(II)-@PostFilter篩選輸出條件
下一篇
[Security]JDBC Authentication Service-簡化版的UserDetailsService
系列文
無痛學習SpringMVC與Spring Security31

尚未有邦友留言

立即登入留言