Wanna be Brilliant Full-Stack Developer

SpringBoot 회원정보수정 - 유효성 검사하기! 본문

카테고리 없음

SpringBoot 회원정보수정 - 유효성 검사하기!

Flashpacker 2022. 2. 14. 09:56


목적

회원정보수정은 완료된 상태이지만 완료는 됬지만 서비스 하기에는 불안하다

 

왜 서비스하기에 불안하냐면 서버입장에서 보면 일단은 name값과 패스워드값은 무조건 받아야한다.

만약에 패스워드를 받지 않으면 공백이 데이터베이스에 들어가게되면 문제가 된다. 

그래서 name과 패스워드가 안들어오는것을 막으려면 앞단에서 막아야한다!

 

이 앞단에서 막는것은 유효성 검사라는것인데!

그리고 또한 프론트단에서도 막아주는것이 좋다! ( 패스워드를 안적으면 전송이 안되도록) 

이 둘을 통해 정보가 잘들어왔으면 두번쨰로도 문제가 생길수도 있는데

두번째 문제는 내가 1번 유저를 수정하려고하는데 영속화를 시켜놨더니 1번유저가 없다. 

그러면 수정을 못하는데 이것을 처리 해야한다!

이것들은 프론트단과 유효성검사는 데이터베이스에 접근 하지 않더라도 확인할 수 있는것들이다.

두번쨰 문제들은 데이터베이스에 접근해야 확인할 수있는 영역이기떄문에 

앞단에서 처리할 문제와 뒷단에서 처리할 문제로 나눠진다.

값만 검증을 하거나 DB에서 확인하는것 두가지 로직이 필요하다 .

 

그러면 프론트단에서 막는것부터 해보려고한다!

프론트단에 required="required"로만 설정 하면 넘어가지 말아야하는데 넘어간다

왜넘어가냐면 제출할때 쓰이는 버튼이 일반적인 버튼이고 submit버튼이 아니기때문이다 

그래서 조금 수정을 해야하는데 어떻게 하는지 보자 

<button type="button" onclick="update(${principal.user.id})">제출</button>

에서 <button>제출</button> 이렇게 바꾼다! 

버튼이 눌리면 form 태그가 실행이 된다. 

<form id="profileUpdate"" onsubmit="update(${principal.user.id})">

이렇게 하면 이름과 비밀번호를 입력하지 않고 제출을 하지 못하도록 막는다 ! 

하지만 제대로 로직을 타지 않는다! 

 

왜냐하면 form 태그는 버튼을 넣으면 일반적으로  submit버튼이 되는데

이 버튼을 누르면 form 태그에 액션이 일어나는데 그 액션이 머냐면 form 태그는 어딘가로 이동을 해야하는데

action에 어떠한 주소가 적혀 있지 않다. 

안적혀 있으면 자기주소 default로 돌아온다. 

우리는 그러면 안되고 update함수가 실행이 되어야한다! 

잘되면 update.js로 가고 안되면 update실패가 떠야한다 

// (1) 회원정보 수정
function update(userId,event) {
	event.preventDefault(); // form 태그 액션을 막기! 
	
	let data  =$("#profileUpdate").serialize();
	
	console.log(data);
	
	$.ajax({
		type: "put",
		url: `/api/user/${userId}`,
		data:data,
		conentType:"application/x-www-form-urlencoded; charset=utf-8",
		dataType:"json"
	}).done(res=>{
		console.log("update 성공");
		location.href=`/user/${userId}`;
	}).fail(error=>{
		console.log("update 실패");
	});
}

이것을 통해 프론트단은 다막았고

두번쨰로는 postman을 통해 요청하면 프론트단에서 막아봤자 소용이 없다. 

그래서 유효성 검사를 할것이다. 

 

UserUpdateDto에서 필수적인 것들에 @NotBlank 어노테이션을 추가한다 

이렇게 건다고 작동하는것은 아니다. 

UserApiController에서 

package com.cos.photogramstart.web.api;

import java.util.HashMap;
import java.util.Map;

import javax.validation.Valid;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.UserService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.user.UserUpdateDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RestController
public class UserApiController {

	private final UserService userService;
	
	@PutMapping("/api/user/{id}")
	public CMRespDto<?> update(
			@PathVariable int id, 
			@Valid UserUpdateDto userUpdateDto, 
			BindingResult bindingResult, // 꼭 @Valid가 적혀있는 다음 파라메터에 적어야됨
			@AuthenticationPrincipal PrincipalDetails principalDetails) {
		
		if(bindingResult.hasErrors()) {
			Map<String, String> errorMap = new HashMap<>();
			
			for(FieldError error: bindingResult.getFieldErrors()) {
				errorMap.put(error.getField(), error.getDefaultMessage());
				System.out.println("==========================");
				System.out.println(error.getDefaultMessage());
				System.out.println("==========================");
			}
			throw new CustomValidationException("유효성 검사 실패함", errorMap);
		} 
		
		User userEntity = userService.회원수정(id, userUpdateDto.toEntitiy());
		principalDetails.setUser(userEntity); // 세션 정보 변경
		return new CMRespDto<>(1, "회원수정완료", userEntity);
	}
	
}

여기서 CustomValidationException이 발동하는데 이건 우리가 새로 무언갈 만들어야한다 .

여기서 핸들러를 보면 커스텀벨리드익셉션을 우리가 복사 붙여넣기로 새로운 CustomValidationApiException을 만든다.

똑같은건데 하나를 더만든 이유는? 

package com.cos.photogramstart.handler;

import java.util.Map;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.util.Script;
import com.cos.photogramstart.web.dto.CMRespDto;

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
	
	@ExceptionHandler(CustomValidationException.class) // RuntimeExcpetion -> CustomValidaionException 으로 변경 
	public String validationException(CustomValidationException e) {
		// CMRespDto, Script 비교
		// 1. 클라이언트에게 응답할 때는 Script 좋음.
		// 2. Ajax통신 - CMRespDto
		// 3. Android 통신 -CMRespDto 
		return Script.back(e.getErrorMap().toString());
	}

	
	@ExceptionHandler(CustomValidationApiException.class) 
	public CMRespDto<?> validationApiException(CustomValidationApiException e) {
		return new CMRespDto<>(-1,e.getMessage(),e.getErrorMap());
	}

}

지금 두개로 나뉘어지는데 하나는 자바스크립트가 응답이 되고( 자바스크립트를 리턴) 

하나는 오브젝트 CMRespDto가 응답이 된다. ( 데이터를 리턴) : API 통신 할때는 Ajax로 통신할때는 데이터로 응답한다 

이 두가지를 구분해야한다! 

 

또한 유효성 검사 실패를 하면 UserApiController에서  

throw new CustomValidationException("유효성 검사 실패함", errorMap); -> 

package com.cos.photogramstart.web.api;

import java.util.HashMap;
import java.util.Map;

import javax.validation.Valid;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.UserService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.user.UserUpdateDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RestController
public class UserApiController {

	private final UserService userService;
	
	@PutMapping("/api/user/{id}")
	public CMRespDto<?> update(
			@PathVariable int id, 
			@Valid UserUpdateDto userUpdateDto, 
			BindingResult bindingResult, // 꼭 @Valid가 적혀있는 다음 파라메터에 적어야됨
			@AuthenticationPrincipal PrincipalDetails principalDetails) {
		
		if(bindingResult.hasErrors()) {
			Map<String, String> errorMap = new HashMap<>();
			
			for(FieldError error: bindingResult.getFieldErrors()) {
				errorMap.put(error.getField(), error.getDefaultMessage());
				System.out.println("==========================");
				System.out.println(error.getDefaultMessage());
				System.out.println("==========================");
			}
			throw new CustomValidationApiException("유효성 검사 실패함", errorMap);
		} else
		{
			User userEntity = userService.회원수정(id, userUpdateDto.toEntitiy());
			principalDetails.setUser(userEntity); // 세션 정보 변경
			return new CMRespDto<>(1, "회원수정완료", userEntity);
		}
	}
}

만약에 프론트 단에서(jsp)에서 required를 이름에서 속성을 제거하고 하면 회원정보 수정에서 이름을 빼고 업로드 하였을때 결과는 성공이라 나오지만 message는 유효성 검사 실패함으로 나온다. 

 

// (1) 회원정보 수정
function update(userId,event) {
	event.preventDefault(); // form 태그 액션을 막기! 
	
	let data  =$("#profileUpdate").serialize();
	
	console.log(data);
	
	$.ajax({
		type: "put",
		url: `/api/user/${userId}`,
		data:data,
		conentType:"application/x-www-form-urlencoded; charset=utf-8",
		dataType:"json"
	}).done(res=>{ // HttpStatus 상태코드 200번대 
		console.log("성공",res);
		//location.href=`/user/${userId}`;
	}).fail(error=>{ // HttpStatus 상태코드 200번대가 이날때 
		console.log("실패",eroor);
	});
}

Ajax통신 할떄는 브라우저끼리 통신할떄는 HTTP 상태코드를 같이 던져주는게 좋다! 

package com.cos.photogramstart.handler;

import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.util.Script;
import com.cos.photogramstart.web.dto.CMRespDto;

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
	
	@ExceptionHandler(CustomValidationException.class) // RuntimeExcpetion -> CustomValidaionException 으로 변경 
	public String validationException(CustomValidationException e) {
		// CMRespDto, Script 비교
		// 1. 클라이언트에게 응답할 때는 Script 좋음.
		// 2. Ajax통신 - CMRespDto
		// 3. Android 통신 -CMRespDto 
		return Script.back(e.getErrorMap().toString());
	}

	
	@ExceptionHandler(CustomValidationApiException.class) 
	public ResponseEntity<?> validationApiException(CustomValidationApiException e) {
		return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(-1,e.getMessage(),e.getErrorMap()),HttpStatus.BAD_REQUEST);
	}

}