|
|
(27 intermediate revisions by 5 users not shown) |
Line 1: |
Line 1: |
− | https://www.owasp.org/images/6/69/Bean_validation_jsr.jpg
| + | __NOTOC__ |
| + | <div style="width:100%;height:160px;border:0,margin:0;overflow: hidden;">[[File:Cheatsheets-header.jpg|link=]]</div> |
| | | |
− | = Introduction =
| + | The Cheat Sheet Series project has been moved to [https://github.com/OWASP/CheatSheetSeries GitHub]! |
− | This article is focused on providing clear, simple, actionable guidance for providing Java Bean Validation security functionality in your applications.
| |
| | | |
− | Bean validation (JSR303 aka [http://beanvalidation.org/1.0/spec/ Bean Validation 1.0] /JSR349 aka [http://beanvalidation.org/1.1/spec/ Bean Validaiton 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.
| + | 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. |
− | | |
− | 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:
| |
− | | |
− | Image placeholder
| |
− | | |
− | Bean Validation: | |
− | | |
− | Image placeholder
| |
− | | |
− | | |
− | = 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 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 =
| |
− | <br />
| |
− | == @Pattern ==
| |
− | | |
− | <br />
| |
− | '''Model'''
| |
− | | |
− | import org.hibernate.validator.constraints.Pattern
| |
− |
| |
− | public class Article {
| |
− |
| |
− | //Constraint: Alpha Numeric article titles only
| |
− | //https://www.owasp.org/index.php/OWASP_Validation_Regex_Repository for useful regex's
| |
− |
| |
− | @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";
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | | |
− | == @Digits ==
| |
− | | |
− | <br />
| |
− | '''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";
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | == @Min / @Max ==
| |
− | | |
− | <br />
| |
− | '''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";
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | == @Size ==
| |
− | | |
− | <br />
| |
− | '''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";
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | == @Past / @Future ==
| |
− | | |
− | <br />
| |
− | '''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";
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | == Additional Constraints ==
| |
− | | |
− | In addition to providing the complete set of JSR303 constraints, Hibernate Validator also defines some additional constraints for convenience:
| |
− | | |
− | * @CreditCardNumber
| |
− | * @EAN
| |
− | * @Email
| |
− | * @Length
| |
− | * @Range
| |
− | * @SafeHtml
| |
− | * @ScriptAssert
| |
− | * @URL
| |
− | | |
− | http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch02.html#table-custom-constraints
| |
− | | |
− | = 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
| |