출처 : 스프링부트 SNS 프로젝트 - 포토그램 만들기 (최주호)
스프링부트 회원가입 구현하기 1 : https://ysu96.tistory.com/8
스프링부트 회원가입 구현하기
출처 : 스프링부트 SNS 프로젝트 - 포토그램 만들기 (최주호) 1. SecurityConfig 생성 public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity htt..
ysu96.tistory.com
4. 비밀번호 암호화(해시)
이전까지 구현한 회원가입 기능은 비밀번호를 암호화하지 않아 그대로 노출되는 문제가 있었습니다. 이를 암호화하기 위해 BCryptPasswordEncoder라는 클래스를 사용합니다. SecurityConfig 클래스에 @Bean 어노테이션을 사용해서 스프링 컨테이너가 해당 객체를 받을 수 있도록 합니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//비밀번호 암호화
@Bean // 이 클래스가 IoC에 등록될 때 @Bean 어노테이션을 읽어서 이 함수를 리턴해 IoC가 들고있음 / 우리는 쓰기만 하면 됨
public BCryptPasswordEncoder encode() {
return new BCryptPasswordEncoder();
}
...
}
그 후 서비스 클래스에서 BCryptPasswordEncoder 객체를 받아 회원가입 할 때 입력했던 비밀번호를 .encode() 함수로 암호화해 다시 비밀번호를 세팅하면 됩니다.
public class AuthService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder; //비밀번호 암호화
@Transactional //Write(Insert, Update, Delete) 할 때는 트랜잭션 처리
public User 회원가입(User user) {
//회원가입 진행
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword); //패스워드 암호화 됨
user.setPassword(encPassword);
user.setRole("ROLE_USER"); // 권한 부여, 관리자 : ROLE_ADMIN
User userEntity = userRepository.save(user); //데이터베이스에 저장하고 저장한 객체 반환
return userEntity;
}
}
결과는 성공적으로 암호화가 된 걸 볼 수 있습니다.
5. 유저 아이디 중복검사 / 길이 제한
회원가입 시 유저의 아이디가 이미 존재하는 아이디거나 길이가 초과하는 경우를 막아줘야 합니다.
아이디 길이 제한은 DB 앞단에서 전처리로 구현할 수 있으므로
@Valid 어노테이션을 사용해 먼저 SignupDto 부터 수정해 줍니다.
https://bamdule.tistory.com/35
[Spring Boot] @Valid 어노테이션으로 Parameter 검증하기
java.validation의 @Valid 어노테이션 사용법 정리 글입니다. Spring Boot 라이브러리에서 기본적으로 탑재된 기능이며 따로 dependency해 줄 필요가 없습니다. Spring Boot Version은 2.2.2.RELEASE 입니다. 1. j..
bamdule.tistory.com
우선 아이디 길이가 20을 초과하지 않도록 최대값을 20으로 설정하고 나머지 정보들에 꼭 값이 채워지도록 @NotBlank 어노테이션을 사용했습니다.
public class SignupDto {
@Size(min=2, max=20)
private String username;
@NotBlank
private String password;
@NotBlank
private String email;
@NotBlank
private String name;
...
}
그리고 컨트롤러에서 해당 SignupDto를 받을 때 유효성 검사를 위해 @Valid 어노테이션을 붙이고 BindingResult라는 클래스도 추가로 사용합니다. 유효성 검사 시 오류를 발견하면 그 오류들을 모아 BindingResult 객체에 담아줍니다.
@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {
//@Valid에서 오류가 발생하면 BindingResult에 오류를 다 모아줌 -> getFieldErrors 콜렉션에 다 모아줌
if(bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
throw new CustumValidationException("유효성 검사 실패함", errorMap);
//유효성 검사 실패 -> BindingResult -> errorMap -> throw CustumValidationException -> ControllerExceptionHandler -> validationException함수 -> Script 리턴
}
else {
//signupDto -> User로 만들기
User user = signupDto.toEntity();
//log.info(user.toString());
User userEntity = authService.회원가입(user);
return "auth/signin"; //회원가입 성공하면 로그인 페이지로
}
}
6. 커스텀 예외처리 만들기
이제 예외가 발생하면 처리하기 위해 RuntimeException을 상속받아서 저만의 커스텀 예외를 만들겁니다.
RuntimeException을 상속받고 에러들을 담기 위한 Map을 만들어 줍니다.
public class CustumValidationException extends RuntimeException{
//객체를 구분할 때 사용 , 사용자한텐 중요x
private static final long serialVersionUID = 1L;
private Map<String, String> errorMap;
public CustumValidationException(String message, Map<String, String> errorMap) {
super(message);
this.errorMap = errorMap;
}
public Map<String, String> getErrorMap() {
return errorMap;
}
}
그리고 예외를 처리할 핸들러를 만들어줍니다.
@ControllerAdvice :exception이 발생 시 모든 exception을 낚아채 처리할 수 있게 해줍니다.
@ExceptionHandler : 해당 exception이 발생하면 처리합니다.
@RestController // 데이터 응답을 위해
@ControllerAdvice //모든 exception을 낚아채 처리함
public class ControllerExceptionHandler {
@ExceptionHandler(CustumValidationException.class) //해당 exception이 발생하면 이 함수가 가로챔
public String validationException(CustumValidationException e) {
return Script.back(e.getErrorMap().toString()); //스크립트 리턴
}
}
이제 예외가 발생했을 때 사용자에게 보여줄 스크립트를 구현합니다.
해당 클래스의 back함수를 호출하면 메세지를 담은 경고창을 띄우고 다시 돌아가게 됩니다.
public class Script {
public static String back(String msg) {
StringBuffer sb = new StringBuffer();
sb.append("<script>");
sb.append("alert('"+msg+"');"); //경고창 띄우고
sb.append("history.back();"); //뒤로 돌아가기
sb.append("</script>");
return sb.toString();
}
}
아이디를 20자 넘게 입력하면 결과는 다음과 같습니다.
이렇게 회원가입 구현을 마치겠습니다.
7. 공통응답 DTO 만들기
지금까지 한 스크립트 방식의 응답은 클라이언트가 응답을 받을 때 효과적입니다.
개발자에게 응답을 해야하는 경우 공통응답 DTO를 사용하는게 효율적일 때가 있기 때문에 만들어줍니다.
자바의 제네릭을 사용해 여러 데이터 타입의 경우를 대비합니다.
//공통 응답 DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CMRespDto<T> { //generic 사용, 다른 데이터를 리턴해야 할 수도 있기 때문(다른 객체나 String..)
private int code; // 1(성공) , -1(실패)
private String message;
private T data;
}
스크립트 방식이 아닌 Dto를 리턴할 경우 커스텀 예외처리 핸들러를 다음과 같이 구현하면 됩니다.
리턴 타입인 CMRespDto<?>의 ? 는 어떤 타입이든 추론해서 변경해줍니다.
public class ControllerExceptionHandler {
@ExceptionHandler(CustumValidationException.class) //해당 exception이 발생하면 이 함수가 가로챔
public CMRespDto<?> validationException(CustumValidationException e) {
return new CMRespDto(-1, e.getMessage(), e.getErrorMap());
}
// CMRespDto, Script 비교
// 1. 클라이언트에게 응답할 때는 Script가 좋음.
// 2. Ajax통신 - CMRespDto (개발자가 js코드로 서버쪽으로 던져서 응답받는 것)
// 3. Android 통신 - CMRespDto (응답을 안드로이드 앱에서 개발자가 해주는 것)
// -> 응답 받는 쪽이 클라이언트면 Script, 개발자면 CMRespDto 가 좋음
}
아이디가 20자를 넘게 입력 후 회원가입을 하면 결과는 다음과 같습니다.
해당 프로젝트에서는 클라이언트 응답용 스크립트 방식을 사용합니다.
'Spring' 카테고리의 다른 글
spring-security-taglibs 을 사용해 로그인 인증 확인, 세션 정보 활용하기 [스프링 시큐리티] (0) | 2021.08.18 |
---|---|
스프링부트 로그인 구현하기 (0) | 2021.08.14 |
스프링부트 회원가입 구현하기 (0) | 2021.08.09 |
Spring Boot Controller 동작 방식 정리 (0) | 2021.08.09 |
.yml 파일 이해하기 (0) | 2021.08.06 |