Back-end/TIL

[SpringBoot] ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’๊ณผ Request Body๋ฅผ Validationํ•˜๋Š” 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•

sw_develop 2022. 4. 4. 15:45

๐Ÿ“Œ Validation์ด ํ•„์š”ํ•œ ์ด์œ 

์˜๋„์ ์œผ๋กœ ์ด์ƒํ•œ ๊ฐ’์œผ๋กœ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก ํŠธ์™€ ์„œ๋ฒ„์—์„œ ๋‘˜๋‹ค ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

ํšŒ์‚ฌ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ ์ด๋ฉ”์ผ์ด๋‚˜ ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ํ˜•์‹์„ ๋ชจ๋‘ ์„œ๋ฒ„์—์„œ๋„ ์ฒดํฌํ•ด์ฃผ์—ˆ๋‹ค.

(๊ธฐ์กด์— ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ ํ•  ๋•Œ๋Š” '์ž…๋ ฅ๊ฐ’์€ ํ”„๋ก ํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ•ด์ฃผ๋‹ˆ๊นŒ ๊ดœ์ฐฎ๊ฒ ์ง€' ํ•˜๊ณ  ๋„˜์–ด๊ฐ”์—ˆ๋Š”๋ฐ ๋ฐ˜์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๋‹ค์Œ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์„œ๋ฒ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋„ ์ถ”๊ฐ€ํ•ด์•ผ๊ฒ ๋‹ค)

 

๊ทธ๋ ‡๋‹ค๋ฉด, ์–ด์ฐจํ”ผ ์„œ๋ฒ„์—์„œ๋„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๋Š”๋ฐ ํ”„๋ก ํŠธ์—์„œ๋„ ํ•˜๋Š” ์ด์œ ๋Š”?

- ์„œ๋ฒ„ ๋ถ€ํ•˜๋ฅผ ์ค„์—ฌ์ฃผ๊ณ , ์‚ฌ์šฉ์ž์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด์„œ์ด๋‹ค.

- ์„œ๋ฒ„์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ํ”„๋ก ํŠธ์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ณด๋‹ค๋Š” ๋А๋ฆด ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

 

๐Ÿ“Œ ์ค€๋น„

  • ์˜์กด์„ฑ ์ถ”๊ฐ€
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

์œ„์˜ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด, Bean Validation ๊ตฌํ˜„์ฒด๋กœ Hibernate Validator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Spring Boot์—์„œ Validation์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“Œ ๋ฐฉ๋ฒ•1 - Hibernate์—์„œ ์ œ๊ณตํ•˜๋Š” javax.validation.constraints(@NotEmpty, @NotNull ๋“ฑ)์˜ ๊ธฐ์กด ๊ตฌํ˜„๋œ ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ

@Getter
@Setter
@NoArgsConstructor
    publicclass UserSignUpReqDto {
				...

        @NotEmpty(message = "๋‹‰๋„ค์ž„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
        private String nickName;    //๋‹‰๋„ค์ž„

        @NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
        @Email
        private String email;   //์ด๋ฉ”์ผ

	      ...
    }

 

์œ„์™€ ๊ฐ™์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

@NotEmpty, @NotNull, @NotBlank์˜ ์ฐจ์ด๋Š”?

  • @NotEmpty - null, ""์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. " "๋Š” ํ—ˆ์šฉํ•œ๋‹ค.
  • @NotNull - null๋งŒ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. "", " "๋Š” ํ—ˆ์šฉํ•œ๋‹ค.
  • @NotBlank - null, "", " "์„ ๋ชจ๋‘ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

๐Ÿ“Œ ๋ฐฉ๋ฒ•2 - ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด Custom Validation ๊ตฌํ˜„

Custom Validation ์‚ฌ์šฉ ์ด์œ 

  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์˜ ์œ ํšจ์„ฑ์„ ์„ธ๋ถ€์ ์œผ๋กœ ๊ฒ€์‚ฌํ•ด์•ผํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์˜ˆ์™ธ ๊ฒ€์ฆ ๋กœ์ง์„ ์ƒ์„ฑํ•ด๋‘์–ด ๋ฐ˜๋ณต์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์ฃผ์˜ํ•  ์ 

  • ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋‚ด๋ถ€ ๋กœ์ง์ด ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ๋ชจ๋ฅธ๋‹ค๋ฉด, ํ”Œ๋กœ์šฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
  • ๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ํŠน์ • ์š”์ฒญ์—๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์˜ ๊ฒฝ์šฐ์—๋Š” ์ด๊ฒƒ ๋Œ€์‹  ๋‹จ์ˆœ ๋ฉ”์„œ๋“œ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.

 

๊ตฌํ˜„ ๋ฐฉ๋ฒ•

  • ๊ธฐ์กด์— ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” @NotEmpty๋‚˜ @NotNull๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.
  • ์ถ”๊ฐ€ํ•ด์•ผํ•  ํŒŒ์ผ์€ ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ interface์™€ ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค ์ด 2๊ฐ€์ง€ ์ด๋‹ค.

 

์‹ค์Šต

ํšŒ์›๊ฐ€์ž… ์ •๋ณด ์ž…๋ ฅ ์‹œ ์ „ํ™”๋ฒˆํ˜ธ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜•์‹์„ ์ฒดํฌํ•ด์ฃผ๋Š” custom validator๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

 

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป ์‹ค์Šต ์ฝ”๋“œ ํ™•์ธ : spring-validation-practice

 

A. ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ interface

// SignUpDtoCheck interface
@Constraint(validatedBy = SignUpDtoCheckValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignUpDtoCheck {

    String message() default "SignUpDtoCheck is invalid";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}
  • @Constraint
    • validatedBy์˜ ๊ฐ’์— ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•ด์ค€๋‹ค. ์‹ค์งˆ์ ์ธ ๊ฒ€์ฆ์ด ์ด๋ค„์ง€๋Š” ํด๋ž˜์Šค์ด๋‹ค.
  • @Target
    • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
    • ElementType.TYPE์˜ ๊ฒฝ์šฐ ํด๋ž˜์Šค, ์ธํ„ฐํŽ˜์ด์Šค, Enum์— ์ ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • @Retention
    • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • @interface
    • ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

 

B. ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค

public class SignUpDtoCheckValidator implements ConstraintValidator<SignUpDtoCheck, UserDto.UserSignUpReqDto> {

    @Override
    public void initialize(SignUpDtoCheck constraintAnnotation) {

    }

    @Override
    public boolean isValid(UserDto.UserSignUpReqDto value, ConstraintValidatorContext context) {

        int invalidCount = 0;

        PatternUtils patternUtils = new PatternUtils();

        if(!patternUtils.phoneNumberMatch(value.getPhoneNumber())) {
            addConstraintViolation(context, "์ „ํ™”๋ฒˆํ˜ธ๋Š” ์ˆซ์ž 8~11์ž๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", "phoneNumber");
            invalidCount++;
        }

        if (!patternUtils.passwordMatch(value.getPassword())) {
            addConstraintViolation(context, "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์˜๋ฌธ์ž 4~6์ž๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", "password");
            invalidCount++;
        }

        return invalidCount == 0;
    }

    private void addConstraintViolation(ConstraintValidatorContext context, String errorMessage, String firstNode) {
        context.disableDefaultConstraintViolation()
        context.buildConstraintViolationWithTemplate(errorMessage)
                .addPropertyNode(firstNode)
                .addConstraintViolation();
    }

}
  • ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” initialize()์™€ isValid() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
  • isValid() ๋‚ด๋ถ€์— ๊ฒ€์ฆ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์—ฌ boolean์œผ๋กœ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.
  • addConstraintViolation()
    • disableDefaultConstraintViolation() - SignUpDtoCheck ์ธํ„ฐํŽ˜์ด์Šค์— ์„ค์ •ํ•ด๋‘” default message ๋Œ€์‹  ๋‹ค๋ฅธ ๋‚ด์šฉ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.
    • ๊ตฌ์ฒด์ ์ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ๊ฒ€์ฆํ•œ Node Key๊ฐ’์„ ๋„˜๊ฒจ์ค€๋‹ค. ํ•ด๋‹น Node๋Š” Errors ๊ฐ์ฒด์˜ errors ๋ฆฌ์ŠคํŠธ์— ์ €์žฅ๋  ์›์†Œ์˜ field ์†์„ฑ์— ๋ฐ”์ธ๋”ฉ๋œ๋‹ค.

 

C. Validation ์ ์šฉ

@Getter @Setter
@NoArgsConstructor
@SignUpDtoCheck //์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ ์šฉ
public static class UserSignUpReqDto {

        @NotEmpty(message = "๋‹‰๋„ค์ž„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
        private String nickName;    //๋‹‰๋„ค์ž„

        private String phoneNumber; //์ „ํ™”๋ฒˆํ˜ธ

        @NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
        @Email
        private String email;   //์ด๋ฉ”์ผ

        private String password;    //๋น„๋ฐ€๋ฒˆํ˜ธ

        @Builder
        public UserSignUpReqDto(String nickName, String phoneNumber, String email, String password) {
            this.nickName = nickName;
            this.phoneNumber = phoneNumber;
            this.email = email;
            this.password = password;
        }
 }

//Controller
@PostMapping("/user/sign-up")
public String userSignUp(@Valid UserDto.UserSignUpReqDto userSignUpReqDto, Errors errors, Model model) {
        //ํšŒ์›๊ฐ€์ž… ์‹คํŒจ
        if (errors.hasErrors()) {

            return "user/sign-up";
        }

        //ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต (๋กœ๊ทธ์ธ ์™„๋ฃŒ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜)
        return "user/login";
}

 

๐ŸŽˆ ์ฐธ๊ณ 

 

๐Ÿ“Œ ์œ„์˜ ๋ฐฉ๋ฒ•1๊ณผ ๋ฐฉ๋ฒ•2์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฃผ์˜์‚ฌํ•ญ

๋งŒ์•ฝ ๋™์ผํ•œ ํ•„๋“œ์— @NotNull๊ณผ Validator ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•œ ๋ฐฉ์‹์„ ๋ชจ๋‘ ์ ์šฉํ•˜์˜€๋‹ค๋ฉด, @NotNull๊ณผ ์ง์ ‘ ๊ตฌํ˜„ํ•œ Validator๋ฅผ ๋ชจ๋‘ ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

@NotNull์—์„œ ์ฒดํฌ๊ฐ€ ๋˜์—ˆ๋‹ค๊ณ  ๊ตฌํ˜„ํ•œ Validator ํด๋ž˜์Šค์˜ isValid()๋ฅผ ์•ˆ๊ฑฐ์น˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฌด์กฐ๊ฑด ๋‘˜๋‹ค ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

์ด๋•Œ ์ ์ ˆํ•œ if๋ฌธ ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, null ๊ฐ’์ด ๋“ค์–ด์™”์„ ๋•Œ ๋™์ผํ•œ ํ•„๋“œ์— ๋Œ€ํ•ด 2๊ฐœ์˜ error๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. 

 

๋”ฐ๋ผ์„œ ์•„์˜ˆ Validator ํด๋ž˜์Šค์—์„œ๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒŒ ํ•˜๋“ ์ง€, ํ˜น์€ Validator ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ if๋ฌธ์— ์กฐ๊ฑด์„ ์ž˜ ์ •์˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค. (์ด๊ฒƒ์„ ๊ฐ„๊ณผํ•˜๊ณ  ์˜ค๋ฅ˜๊ฐ€ 2๊ฐœ ๋‚˜์˜จ๋‹ค๋Š” ๊ฒƒ์„ ์ข€ ์ง€๋‚˜์„œ์•ผ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค..^^)

 

โ–ถ๏ธ ์˜ˆ์‹œ ์ฝ”๋“œ

//@NotBlank๋ฅผ ์„ค์ •ํ•ด์คฌ์„ ๋•Œ
public class sampleDto {
	...
	@NotBlank
	private String email;
	...
}

//์ „๋‹ฌ๋œ ๊ฐ’์ด blank๋‚˜ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ˆ˜ํ–‰
public class sampleValidator implements ConstraintValidator<...> {

	@Override
	public boolean isValid(...) {
		...
		if (!StringUtils.isBlank(value.getEmail()) && !validationUtils.emailMatch(value.getEmail())) {
            addConstraintViolation(context, "์ด๋ฉ”์ผ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "email");
            invalidCount++;
    }
	}
}

์œ„์™€ ๊ฐ™์€ ์˜ˆ์‹œ ์ƒํ™ฉ์—์„œ ๋งŒ์•ฝ @NotBlank๋กœ ์„ค์ •ํ•ด์คฌ๋‹ค๋ฉด, ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค์—์„œ๋Š” blank๋‚˜ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€๋กœ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.