|
|
(3 intermediate revisions by 3 users not shown) |
Line 2: |
Line 2: |
| <div style="width:100%;height:160px;border:0,margin:0;overflow: hidden;">[[File:Cheatsheets-header.jpg|link=]]</div> | | <div style="width:100%;height:160px;border:0,margin:0;overflow: hidden;">[[File:Cheatsheets-header.jpg|link=]]</div> |
| | | |
− | Last revision (mm/dd/yy): '''{{REVISIONMONTH}}/{{REVISIONDAY}}/{{REVISIONYEAR}}'''
| + | The Cheat Sheet Series project has been moved to [https://github.com/OWASP/CheatSheetSeries GitHub]! |
− | = Bean Validation Introduction =
| |
− | __TOC__{{TOC hidden}}
| |
| | | |
− | This article is focused on providing clear, simple, actionable guidance for providing Java Bean Validation security functionality in your applications.
| + | Please visit [https://cheatsheetseries.owasp.org/cheatsheets/Bean_Validation_Cheat_Sheet.html Bean Validation Cheat Sheet] to see the latest version of the cheat sheet. |
− | | |
− | Bean validation (JSR303 aka [http://beanvalidation.org/1.0/spec/ Bean Validation 1.0] /JSR349 aka [http://beanvalidation.org/1.1/spec/ Bean Validation 1.1]) is one of the most common ways to perform [https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet input validation] in Java. It is an application layer agnostic validation spec which provides the developer with the means to define a set of validation constraints on a domain model and then perform validation of those constraints through out the various application tiers.
| |
− | | |
− | One advantage of this approach is that the validation constraints and the corresponding validators are only written once, thus reducing duplication of effort and ensuring uniformity:
| |
− | | |
− | == Typical Validation ==
| |
− | | |
− | https://www.owasp.org/images/d/db/Bean_validation_typical.jpg | |
− | <br />
| |
− | == Bean Validation ==
| |
− | | |
− | https://www.owasp.org/images/6/69/Bean_validation_jsr.jpg
| |
− | | |
− | <br />
| |
− | = Setup =
| |
− | | |
− | The examples in this guide use Hibernate Validator (the reference implementation for Bean Validation 1.1).
| |
− | | |
− | Add Hibernate Validator to your pom.xml :
| |
− | | |
− | <dependency>
| |
− | <groupId>org.hibernate</groupId>
| |
− | <artifactId>hibernate-validator</artifactId>
| |
− | <version>5.2.4.Final</version>
| |
− | </dependency>
| |
− | | |
− | | |
− | Enable bean validation support in Spring's context.xml
| |
− | | |
− | <beans:beans ...
| |
− | ...
| |
− | <mvc:annotation-driven />
| |
− | ...
| |
− | </beans:beans>
| |
− | | |
− | For more info, please see the [http://hibernate.org/validator/documentation/getting-started/ setup guide]
| |
− | | |
− | <br />
| |
− | | |
− | = Basics =
| |
− | | |
− | In order to get started using Bean Validation, you must add validation constraints (<code>@Pattern, @Digits, @Min, @Max, @Size, @Past, @Future, @CreditCardNumber, @Email, @URL</code>, etc.) to your model and then utilize the <code>@Valid</code> annotation when passing your model around in various application layers.
| |
− | | |
− | Constraints can be applied in several places:
| |
− | * Fields
| |
− | * Properties
| |
− | * Classes
| |
− | | |
− | For Bean Validation 1.1 also on:
| |
− | * Parameters
| |
− | * Return values
| |
− | * Constructors
| |
− | | |
− | For the sake of simplicity all the examples below feature field constraints and all validation is triggered by the controller. Refer to the Bean Validation documentation for a full list of examples.
| |
− | | |
− | When it comes to error handling, the Hibernate Validator returns a <code>BindingResult</code> object which contains a <code>List<ObjectError></code>. The examples below feature simplistic error handling, while a production ready application would have a more elaborate design that takes care of logging and error page redirection.
| |
− | | |
− | <br />
| |
− | | |
− | = Pre-defined Constraints =
| |
− | | |
− | == @Pattern ==
| |
− | | |
− | '''Annotation''': <code>@Pattern(regex=,flag=)</code>
| |
− | | |
− | '''Data Type''': CharSequence
| |
− | | |
− | '''Use''': Checks if the annotated string matches the regular expression regex considering the given flag match. Please visit [[OWASP Validation Regex Repository]] for other useful regex's.
| |
− | | |
− | [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#section-builtin-constraints Reference]
| |
− | | |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Pattern;
| |
− |
| |
− | public class Article {
| |
− |
| |
− | //Constraint: Alpha Numeric article titles only using a regular expression
| |
− | @Pattern(regexp = "[a-zA-Z0-9 ]")
| |
− | private String articleTitle;
| |
− |
| |
− | public String getArticleTitle() {
| |
− | return articleTitle;
| |
− | }
| |
− |
| |
− | public void setArticleTitle(String articleTitle) {
| |
− | this.articleTitle = articleTitle;
| |
− | }
| |
− |
| |
− | ...
| |
− |
| |
− | }
| |
− | | |
− |
| |
− | '''Controller'''
| |
− | | |
− | import javax.validation.Valid;
| |
− | import com.company.app.model.Article;
| |
− |
| |
− | @Controller
| |
− | public class ArticleController {
| |
− |
| |
− | ...
| |
− |
| |
− | @RequestMapping(value={"/postArticle", method={RequestMethod.POST})
| |
− | public @ResponseBody String postArticle(@Valid Article article, BindingResult result, HttpServletResponse response){
| |
− |
| |
− | if(result.hasErrors()){
| |
− | String errorMessage = "";
| |
− | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
| |
− | List<ObjectError> errors = result.getAllErrors();
| |
− |
| |
− | for( ObjectError e : errors){
| |
− | errorMessage+= "ERROR: " + e.getDefaultMessage();
| |
− | }
| |
− | return errorMessage;
| |
− | }
| |
− |
| |
− | else{
| |
− | return "Validation Successful";
| |
− | }
| |
− | }
| |
− | }
| |
− | <br />
| |
− | | |
− | == @Digits ==
| |
− | | |
− | '''Annotation''': <code>@Digits(integer=,fraction=)</code>
| |
− | | |
− | '''Data Type''': BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective wrappers of the primitive types; Additionally supported by HV: any sub-type of Number
| |
− | | |
− | '''Use''': Checks whether the annotated value is a number having up to integer digits and fraction fractional digits
| |
− | | |
− | [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#section-builtin-constraints Reference]
| |
− | | |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Digits;
| |
− |
| |
− | public class Customer {
| |
− |
| |
− | //Constraint: Age can only be 3 digits long or less
| |
− | @Digits(integer=3, fraction=0)
| |
− | private int age;
| |
− |
| |
− | public String getAge() {
| |
− | return age;
| |
− | }
| |
− |
| |
− | public void setAge(String age) {
| |
− | this.age = age;
| |
− | }
| |
− |
| |
− | ...
| |
− | }
| |
− | | |
− |
| |
− | '''Controller'''
| |
− | | |
− | import javax.validation.Valid;
| |
− | import com.company.app.model.Customer;
| |
− |
| |
− | @Controller
| |
− | public class CustomerController {
| |
− |
| |
− | ...
| |
− |
| |
− | @RequestMapping(value={"/registerCustomer", method={RequestMethod.POST})
| |
− | public @ResponseBody String registerCustomer(@Valid Customer customer, BindingResult result, HttpServletResponse response){
| |
− |
| |
− | if(result.hasErrors()){
| |
− | String errorMessage = "";
| |
− | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
| |
− | List<ObjectError> errors = result.getAllErrors();
| |
− |
| |
− | for( ObjectError e : errors){
| |
− | errorMessage+= "ERROR: " + e.getDefaultMessage();
| |
− | }
| |
− | return errorMessage;
| |
− | }
| |
− |
| |
− | else{
| |
− | return "Validation Successful";
| |
− | }
| |
− | }
| |
− | }
| |
− | <br />
| |
− | == @Size ==
| |
− | | |
− | '''Annotation''': <code>@Size(min=, max=)</code>
| |
− | | |
− | '''Data Type''': CharSequence, Collection, Map and arrays
| |
− | | |
− | '''Use''': Checks if the annotated element’s size is between min and max (inclusive)
| |
− | | |
− | [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#section-builtin-constraints Reference]
| |
− | | |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Size;
| |
− |
| |
− | public class Message {
| |
− |
| |
− | //Constraint: Message must be at least 10 characters long, but less than 500
| |
− | @Size(min=10, max=500)
| |
− | private String message;
| |
− |
| |
− | public String getMessage() {
| |
− | return message;
| |
− | }
| |
− |
| |
− | public void setMessage(String message) {
| |
− | this.message = message;
| |
− | }
| |
− |
| |
− | ...
| |
− | }
| |
− | | |
− | | |
− | '''Controller'''
| |
− | | |
− | import javax.validation.Valid;
| |
− | import com.company.app.model.Message;
| |
− |
| |
− | @Controller
| |
− | public class MessageController {
| |
− |
| |
− | ...
| |
− |
| |
− | @RequestMapping(value={"/sendMessage", method={RequestMethod.POST})
| |
− | public @ResponseBody String sendMessage(@Valid Message message, BindingResult result, HttpServletResponse response){
| |
− |
| |
− | if(result.hasErrors()){
| |
− | String errorMessage = "";
| |
− | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
| |
− | List<ObjectError> errors = result.getAllErrors();
| |
− |
| |
− | for( ObjectError e : errors){
| |
− | errorMessage+= "ERROR: " + e.getDefaultMessage();
| |
− | }
| |
− | return errorMessage;
| |
− | }
| |
− |
| |
− | else{
| |
− | return "Validation Successful";
| |
− | }
| |
− | }
| |
− | }
| |
− | <br />
| |
− | == @Past / @Future ==
| |
− | | |
− | '''Annotation''': <code>@Past, @Future</code>
| |
− | | |
− | '''Data Type''': java.util.Date, java.util.Calendar, java.time.chrono.ChronoZonedDateTime, java.time.Instant, java.time.OffsetDateTime
| |
− | | |
− | '''Use''': Checks whether the annotated date is in the past / future
| |
− | | |
− | [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#section-builtin-constraints Reference]
| |
− | | |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Past;
| |
− | import org.hibernate.validator.constraints.Future;
| |
− |
| |
− | public class DoctorVisit {
| |
− |
| |
− | //Constraint: Birthdate must be in the past
| |
− | @Past
| |
− | private Date birthDate;
| |
− |
| |
− | public Date getBirthDate() {
| |
− | return birthDate;
| |
− | }
| |
− |
| |
− | public void setBirthDate(Date birthDate) {
| |
− | this.birthDate = birthDate;
| |
− | }
| |
− |
| |
− | //Constraint: Schedule visit date must be in the future
| |
− | @Future
| |
− | private String scheduledVisitDate;
| |
− |
| |
− | public String getScheduledVisitDate() {
| |
− | return scheduledVisitDate;
| |
− | }
| |
− |
| |
− | public void setScheduledVisitDate(String scheduledVisitDate) {
| |
− | this.scheduledVisitDate = scheduledVisitDate;
| |
− | }
| |
− |
| |
− | ...
| |
− | }
| |
− |
| |
− | | |
− | '''Controller'''
| |
− | | |
− | import javax.validation.Valid;
| |
− | import com.company.app.model.DoctorVisit;
| |
− |
| |
− | @Controller
| |
− | public class DoctorVisitController {
| |
− |
| |
− | ...
| |
− |
| |
− | @RequestMapping(value={"/scheduleVisit", method={RequestMethod.POST})
| |
− | public @ResponseBody String scheduleVisit(@Valid DoctorVisit doctorvisit, BindingResult result, HttpServletResponse response){
| |
− |
| |
− | if(result.hasErrors()){
| |
− | String errorMessage = "";
| |
− | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
| |
− | List<ObjectError> errors = result.getAllErrors();
| |
− |
| |
− | for( ObjectError e : errors){
| |
− | errorMessage+= "ERROR: " + e.getDefaultMessage();
| |
− | }
| |
− | return errorMessage;
| |
− | }
| |
− |
| |
− | else{
| |
− | return "Validation Successful";
| |
− | }
| |
− | }
| |
− | }
| |
− | <br />
| |
− | == Combining Constraints ==
| |
− | Validation annotations can be combined in any suitable way. For instance, to specify a valid reviewRating value between 1 and 5, specify the validation like this :
| |
− | | |
− | '''Annotation''': <code>@Min(value=), @Max(value=)</code>
| |
− | | |
− | '''Data Type''': BigDecimal, BigInteger, byte, short, int, long and the respective wrappers of the primitive types; Additionally supported by HV: any sub-type of CharSequence (the numeric value represented by the character sequence is evaluated), any sub-type of Number
| |
− | | |
− | '''Use''': Checks whether the annotated value is higher/lower than or equal to the specified minimum
| |
− | | |
− | [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#section-builtin-constraints Reference]
| |
− | | |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Min;
| |
− | import org.hibernate.validator.constraints.Max;
| |
− |
| |
− | public class Review {
| |
− |
| |
− | //Constraint: Review rating must be between 1 and 5
| |
− | @Min(1)
| |
− | @Max(5)
| |
− | private int reviewRating;
| |
− |
| |
− | public int getReviewRating() {
| |
− | return reviewRating;
| |
− | }
| |
− |
| |
− | public void setReviewRating(int reviewRating) {
| |
− | this.reviewRating = reviewRating;
| |
− | }
| |
− |
| |
− | ...
| |
− | }
| |
− | | |
− |
| |
− | '''Controller'''
| |
− | | |
− | import javax.validation.Valid;
| |
− | import com.company.app.model.ReviewRating;
| |
− |
| |
− | @Controller
| |
− | public class ReviewController {
| |
− |
| |
− | ...
| |
− |
| |
− | @RequestMapping(value={"/postReview", method={RequestMethod.POST})
| |
− | public @ResponseBody String postReview(@Valid Review review, BindingResult result, HttpServletResponse response){
| |
− |
| |
− | if(result.hasErrors()){
| |
− | String errorMessage = "";
| |
− | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
| |
− | List<ObjectError> errors = result.getAllErrors();
| |
− |
| |
− | for( ObjectError e : errors){
| |
− | errorMessage+= "ERROR: " + e.getDefaultMessage();
| |
− | }
| |
− | return errorMessage;
| |
− | }
| |
− |
| |
− | else{
| |
− | return "Validation Successful";
| |
− | }
| |
− | }
| |
− | }
| |
− | <br />
| |
− | | |
− | == Cascading Constraints ==
| |
− | | |
− | Validating one bean is a good start, but often, beans are nested or in a complete graph of beans. To validate that graph in one go, apply casding valiation with @Valid [http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch03.html#_cascaded_validation]
| |
− | | |
− | == Additional Constraints ==
| |
− | | |
− | In addition to providing the complete set of JSR303 constraints, Hibernate Validator also defines some additional constraints for convenience:
| |
− | | |
− | * <code>@CreditCardNumber</code>
| |
− | * <code>@EAN</code>
| |
− | * <code>@Email</code>
| |
− | * <code>@Length</code>
| |
− | * <code>@Range</code>
| |
− | * <code>@SafeHtml</code>
| |
− | * <code>@ScriptAssert</code>
| |
− | * <code>@URL</code>
| |
− | | |
− | http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#table-custom-constraints
| |
− | <br />
| |
− | | |
− | = Custom Constraints =
| |
− | | |
− | One of the most powerful features of bean validation is the ability to define your own constraints that go beyond the simple validation offered by built in constraints.
| |
− | | |
− | Creating custom constraints is beyond the scope of this guide. Please see http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html
| |
− | <br />
| |
− | | |
− | = Error Messages =
| |
− | It is possible to specify a meesage ID with the validation annotation, so that error messages are customized :
| |
− | | |
− | @Pattern(regexp = "[a-zA-Z0-9 ]", message="article.title.error")
| |
− | private String articleTitle;
| |
− | | |
− | Spring MVC will then look up a message with id "article.title.error" in a defined MessageSource. More on this [http://www.silverbaytech.com/2013/04/16/custom-messages-in-spring-validation/]
| |
− | | |
− | | |
− | = Author =
| |
− | | |
− | Anton Abashkin - abashkin.anton [at] gmail.com
| |
− | <br />
| |
− | = Other Cheatsheets =
| |
− | {{Cheatsheet_Navigation_Body}}
| |
− | | |
− | [[Category:Cheatsheets]]
| |