안녕하세요? 재영입니다
이번에는 사용자가 회원가입 폼으로 사용자 등록을 진행할 때, 비밀번호 형식을 지정해서 안전한 비밀번호를 입력하도록 유도하고,
정해진 형식의 비밀번호가 아니라면 검증을 통해 사용자에게 메시지를 보여주는 방법을 정리해보겠습니따!
저는 검증 로직 구현 및 회원가입, 로그인 기능은 모두 구현해 둔 상태였는데,
비밀번호를 8자 이상 16자 이하 입력방식에서 ➡️ 영문 대문자, 영문 소문자, 숫자 중 2개를 선택 후 조합해서 비밀번호를 입력하도록 검증 로직을 추가했습니다.
로직을 추가하면서 커스텀 어노테이션 안에 정규식을 추가해서 코드 가독성을 높였는데요,
커스텀 어노테이션을 설정하는 방법, 비밀번호 입력에 정규식을 도입하는 방법을 중심으로 정리해보려고 합니다.

그럼~ 시~~~작!
1. 기존의 비밀번호 검증 방식
기존에는 사용자가 비밀번호 설정 시에 비밀번호는 8자 이상, 16자 이하로 입력하라고 유도하기 위해
DTO 의 password 필드에 @Length 어노테이션 (org.hibernate.validator.constraints.Length) 을 사용해서 비밀번호를 검증했습니다.
@Getter
@Setter
public class MemberFormDto {
@NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
@Length(min = 8, max = 16, message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요.")
private String password;
}
이렇게 설정해두면, 따로 커스텀 어노테이션에 정규식을 추가하지 않아도 입력값이 비어있는지, 입력값이 8자 이상 16자 이하가 들어오는지 검증을 진행합니다.
@Controller
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@PostMapping("/new")
public String newMember(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model) {
// 에러가 있다면 회원가입 페이지로 이동
if (bindingResult.hasErrors()){
return "member/memberForm";
}
// 에러가 없으면 회원 저장
try {
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
}
// 회원가입 시 중복 회원 가입 에러 발생하면 에러 메시지를 뷰로 전달
catch (IllegalStateException e) {
model.addAttribute("errorMessage", e.getMessage());
return "member/memberForm";
}
return "redirect:/";
}
}
검증이 필요한 MemerFormDto 에 @Valid 어노테이션을 붙여주고,
BindingResult 메소드를 사용한 bindingResult 변수를 추가해서 사용자가 회원가입 시 MemerFormDto 의 필드에 설정한 형식을 맞추지 않으면 message 에 설정한 메시지를 뷰로 전달하고, 회원가입 페이지로 이동시킵니다.
이렇게 회원가입 시 비밀번호 검증 로직을 구현하면 간단하게 사용자에게 어떤 형식의 비밀번호를 입력해야되는지 메시지로 알려줄 수 있습니다. 그리고 설정한 비밀번호의 형식이 입력되어야 서버에 값을 전달시킬 수 있습니다.

저는 설정한 message 값을 Thymeleaf 템플릿 엔진을 사용해서 회원가입 페이지에 값을 추가해줬습니다.
2. 비밀번호 입력 시 추가 검증을 위한 커스텀 어노테이션 구현
그러나, 우리가 많이 사용하는 서비스를 회원가입 할 때, 저렇게 비밀번호를 단순하게 (예 : 12341234) 입력하라고 하지 않습니다.
보통 영문 대소문자와 숫자를 섞고, 요즘에는 특수문자까지 입력해야 회원가입이 완료됩니다.
이런 검증을 지금 개발중인 쇼핑몰 프로젝트에 도입할 때, 저는 커스텀 어노테이션에 정규식을 넣어서 DTO의 password 필드에 추가하는 방식으로 검증 방식을 추가했습니다.
커스텀 어노테이션을 추가하는 방식을 채택하면, 기존에 사용하던 비밀번호 검증 로직을 재사용할 수 있고, 정규식 메소드를 다른 클래스에 구현하여 코드 가독성을 향상할 수 있습니다.
아래에 차근차근 커스텀 어노테이션을 구현하는 순서를 설명하겠습니다!
2-1. 커스텀 어노테이션 인터페이스 생성
먼저, 비밀번호 검증에 사용할 커스텀 어노테이션을 @interface 로 구현합니다.
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "비밀번호는 영문 대문자, 영문 소문자, 숫자 중 2개 이상을 포함하여 8자 이상이어야 합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
저는 비밀번호 검증에 사용하니까, 인터페이스명을 ValidPassword 라고 지정해 줬으며,
안에 default message 필드를 추가하고, 필요한 groups() 메소드와 payload() 메소드를 추가했습니다.
2-2. 커스텀 어노테이션 인터페이스를 구현하는 클래스 생성
위에서 만든 ValidPassword 를 구현하는 클래스를 생성해 줍니다.
비밀번호 검증 로직은 나중에 변경할 수 있으니까, 변경이 용이하도록 PasswordConstraintValidator 클래스 아래에 구현하겠습니다.
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public void initialize(ValidPassword constraintAnnotation) {
// 초기화 메소드 (초기화 작업 수행 안 해서 비워둠)
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
// 비밀번호가 null 이거나 길이가 8자 미만일 경우 false 반환
if (password == null || password.length() < 8) {
return false;
}
int count = 0;
if (password.matches(".*[A-Z].*")) count++; // 비밀번호에 영문 대문자가 포함되어 있으면 count 중가
if (password.matches(".*[a-z].*")) count++; // 비밀번호에 영문 소문자가 포함되어 있으면 count 증가
if (password.matches(".*\\d.*")) count++; // 비밀번호에 숫자가 포함되어 있으면 count 증가
// count가 2 이상일 경우 true 반환, 아니면 false 반환
return count >= 2;
}
}
PasswordConstraintValidator는 ConstraintValidator 인터페이스를 상속받습니다.
그러면 initialize 메소드와 isValid 메소드를 Override 해서 구현해야되는데, 여기서 initialize 메소드 (초기화 메소드, 검증 로직이 초기화될 때 호출됨, 저는 별다른 초기화 작업을 수행하지 않아서 비워뒀습니다.) 는 사용하지 않으니 비워두고,
isValid 메소드 (실제 검증 로직) 만 구현했습니다.
isValid 메소드에 password 와 context 변수를 지정하고, 원하는 방식의 비밀번호 검증 구현을 위해 정규식을 추가해줍니다.
저는 영문 대문자, 영문 소문자, 숫자 중 2가지를 조합한 비밀번호를 검증하기 위해 위와 같은 방식으로 isValid 메소드를 구현했습니다.
특수문자나 반복되는 문자를 추가하면 안 되는 등의 추가 검증을 수행하는 정규식을 아래에 같이 적어두겠습니다!
2-3. 비밀번호 검증 로직에 사용할 수 있는 정규식 예제
1) 최소 8자 이상, 영문 대문자, 영문 소문자, 특수문자 포함
// 최소 8자 이상, 하나 이상의 영문 소문자, 영문 대문자, 숫자, 특수문자를 포함해야 합니다.
// 비밀번호 예시 : Password1!
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$
2) 최소 8자 이상, 영문 대문자, 영문 소문자, 숫자 포함
// 최소 8자 이상, 하나 이상의 영문 소문자, 영문 대문자, 숫자를 포함해야 합니다.
// 비밀번호 예시 : Password1
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,}$
3) 최소 8자 이상, 영문 대문자, 영문 소문자 포함
// 최소 8자 이상, 하나 이상의 영문 소문자, 영문 대문자를 포함해야 합니다.
// 비밀번호 예시 : Password
^(?=.*[a-z])(?=.*[A-Z])[A-Za-z]{8,}$
4) 최소 8자 이상, 영문 대문자, 영문 소문자, 숫자, 특수문자 포함 (공백 제외)
// 최소 8자 이상, 하나 이상의 영문 소문자, 영문 대문자, 숫자, 특수문자를 포함해야 하며, 공백은 허용하지 않습니다.
// 비밀번호 예시 : Password1!
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])\S{8,}$
5) 최소 8자 이상, 영문 대문자, 영문 소문자, 숫자 포함 (연속된 문자 3개 이상 사용 불가)
// 최소 8자 이상, 하나 이상의 영문 소문자, 영문 대문자, 숫자를 포함해야 하며, 연속된 동일 문자가 3개 이상 포함될 수 없습니다.
// 비밀번호 예시 : Passw0rd (가능), Paaassword1 (불가능)
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?!.*(.)\1{2,})[A-Za-z\d]{8,}$
정규식이 길어지면 보기 힘드니까, 저처럼 if문으로 분기처리하는 방식으로 구현해도 괜찮을 것 같습니다!
3. DTO 클래스에 커스텀 어노테이션 추가
추가 검증 로직을 적용하고 싶은 DTO 클래스의 필드에 위에 구현한 커스텀 어노테이션을 추가해 줍니다.
@Getter
@Setter
public class MemberFormDto {
@NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
@Length(min = 8, max = 16, message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요.")
@ValidPassword(message = "비밀번호는 영문 대문자, 영문 소문자, 숫자 중 2개 이상을 포함하여 8자 이상이어야 합니다.")
private String password;
}
저는 기존에 사용하던 @NotEmpty, @Length 어노테이션은 유지하고,
추가한 비밀번호 검증 로직을 사용하기 위해 커스텀 어노테이션 (@ValidPassword) 를 추가하고, message 값을 설정했습니다.
message 값은 default message 값을 출력하려면 사용하지 않아도 되고, 별도의 message 값을 추가하려면 저렇게 적어주면 됩니다.
이미 서비스계층과 컨트롤러계층에 검증 로직이 추가되어 있어, 그쪽은 수정하지 않아도 됩니다!

이러면 기존에 검증을 통과하던 비밀번호 (예 : 12341234)는 커스텀 어노테이션의 조건에 걸려서 저렇게 에러메시지를 사용자에게 보여줍니다.
이렇게 기존의 사용자 입력값 검증 로직에 커스텀 어노테이션을 추가해서 원하는 방식으로 입력값을 검증할 수 있습니다!
마무리를 어떻게 해야 하지...
그럼 20000!
🍀
좋아하는 것을 계속 좋아하세요!
반드시 행복해집니다
[Github] https://github.com/chujaeyeong
[E-mail] chujy1224@gmail.com
'Project' 카테고리의 다른 글
| Swagger 도구를 사용한 API 문서 작성 방법과 사용 시 이점 - 쇼핑몰 프로젝트 06 (1) | 2024.06.09 |
|---|---|
| JPA CascadeType 설정으로 인한 리뷰 삭제 문제 해결하기 - 쇼핑몰 프로젝트 05 (2) | 2024.06.08 |
| Principal 객체를 활용하여 인증된 사용자의 정보를 반환하기 - 쇼핑몰 프로젝트 04 (3) | 2024.05.27 |
| 여러 유형의 회원을 하나의 엔티티로 관리하기 - 쇼핑몰 개인 프로젝트 03 (0) | 2024.05.27 |
| Spring Boot 3.x + Spring Security 6 + OAuth2 소셜 로그인의 흐름 - 쇼핑몰 개인 프로젝트 02 (1) | 2024.05.16 |