[Spring Security] 로그인 기능 구현 (3) 회원 가입 기능
본문 바로가기

Web 개발/게시판 만들기

[Spring Security] 로그인 기능 구현 (3) 회원 가입 기능

728x90
반응형

💡 이전 내용

UserDetails 인터페이스를 활용하여 로그인 기능을 구현하였습니다.

이번 게시글에서는 회원가입 화면을 생성하고, 회원 가입 기능을 구현하겠습니다.

 

💻 화면 구현

회원 가입 화면은 다음과 같이 부트스트랩기반의 MDB에서 제공하는 화면을 사용하였습니다.

https://mdbootstrap.com

 

1. 회원 가입 화면

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>회원가입</title>

    <!-- Bootstrap -->
    <th:block th:replace="board2/fragments/config :: configFragment"> </th:block>

    <!-- Style CSS -->
    <link rel="stylesheet" th:href="@{/css/signup.css}"/>

</head>
<body>
    <section class="vh-100 bg-image"
             style="background-image: url('https://mdbcdn.b-cdn.net/img/Photos/new-templates/search-box/img4.webp');">
        <div class="mask d-flex align-items-center h-100 gradient-custom-3">
            <div class="container h-100">
                <div class="row d-flex justify-content-center align-items-center h-100">
                    <div class="col-12 col-md-9 col-lg-7 col-xl-6">
                        <div class="card" style="border-radius: 15px;">
                            <div class="card-body p-5">
                                <h2 class="text-uppercase text-center mb-5">Create an account</h2>

                                <form method="post" th:object="${accountDto}">

                                    <div class="form-outline mb-4">
                                        <label class="form-label" for="form3Example1cg">Your Name</label>
                                        <input type="text" id="form3Example1cg" class="form-control form-control-lg" th:field="*{korName}" th:errorclass="custom-invalid-input"/>
                                        <div class="custom-invalid-feedback">
                                            <span th:if="${#fields.hasErrors('korName')}" th:errors="*{korName}"></span>
                                        </div>
                                    </div>

                                    <div class="form-outline mb-4">
                                        <label class="form-label" for="form3Example3cg">Your ID (Email)</label>
                                        <input type="email" id="form3Example3cg" class="form-control form-control-lg" th:field="*{userId}" th:errorclass="custom-invalid-input"/>
                                        <div class="custom-invalid-feedback">
                                            <span th:if="${#fields.hasErrors('userId')}" th:errors="*{userId}"></span>
                                        </div>
                                    </div>

                                    <div class="form-outline mb-4">
                                        <label class="form-label" for="form3Example4cg">Password</label>
                                        <input type="password" id="form3Example4cg" class="form-control form-control-lg" th:field="*{password}" th:errorclass="custom-invalid-input"/>
                                        <div class="custom-invalid-feedback">
                                            <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>
                                        </div>
                                    </div>

                                    <div class="form-outline mb-4">
                                        <label class="form-label" for="form3Example4cdg">Repeat your password</label>
                                        <input type="password" id="form3Example4cdg" class="form-control form-control-lg" th:field="*{confirmPassword}" th:errorclass="custom-invalid-input"/>
                                        <div class="custom-invalid-feedback">
                                            <span th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}"></span>
                                        </div>
                                    </div>

                                    <div class="form-check d-flex justify-content-center mb-5">
                                        <input id="form2Example3cg" type="checkbox" class="form-check-input me-2" value=""/>
                                        <label class="form-check-label" for="form2Example3cg">
                                            I agree all statements in <a href="#" class="text-body"><u>Terms of service</u></a>
                                        </label>
                                    </div>

                                    <div class="d-flex justify-content-center">
                                        <button type="submit" class="btn btn-success btn-block btn-lg gradient-custom-4 text-body">Register</button>
                                    </div>

                                    <p class="text-center text-muted mt-5 mb-0">Have already an account?
                                        <a th:href="@{/login}" class="fw-bold text-body"><u>Login here</u></a>
                                    </p>

                                </form>

                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

</body>
</html>

 

.custom-invalid-input {
    border-color: #dc3545;
    padding-right: calc(1.5em + 0.75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right calc(.375em + .1875rem) center;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}

.custom-invalid-feedback {
    margin-top: 0.25rem;
    font-size: .875em;
    color: #dc3545;
}

 

양식 유효성 체크는 부트스트랩 클래스를 활용하려고 하였으나,  

클래스 적용 시 글자가 보이지 않아서 style을 가져와서 적용하였습니다.

 

2. 화면 호출, 회원 가입 기능

Controller에 다음 코드를 추가하여 "/signup" URL로 접근시 회원가입 화면을 보여주고,

"/signup" 화면에서 양식 전송 시, 회원을 등록하는 기능을 구현하겠습니다.

 

@Slf4j
@Controller
@AllArgsConstructor
public class AccountController {

    private final AccountService accountService;
    private final SignUpValidator signUpValidator;

    @GetMapping("/signup")
    public String signup(Model model){
        model.addAttribute("accountDto", new AccountDto());
        return "account/signup";
    }

    @PostMapping("/signup")
    public String signup(@ModelAttribute("accountDto") AccountDto accountDto, BindingResult result){
        signUpValidator.validate(accountDto, result);
        if(result.hasErrors()) {
            log.info("ERROR : {}", result.getAllErrors());
            return "account/signup";
        }

        accountService.join(accountDto);
        return "redirect:/login";
    }
    
}

 

3. 사용자 입력 유효성 확인

유효성 확인은 Spring Boot Validation 을 사용하였습니다.

먼저 build.gradle에 의존성을 추가합니다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

 

@Component
@RequiredArgsConstructor
public class SignUpValidator implements Validator {

    private final PasswordEncoder passwordEncoder;

    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }

    @Override
    public void validate(Object target, Errors errors) {
        AccountDto dto = (AccountDto) target;
        String strPasswordPattern = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$";

        if(ObjectUtils.isEmpty(dto.getKorName())) {
            errors.rejectValue("korName", "message.korName.required", "별명을 입력해 주세요.");
        }

        if(ObjectUtils.isEmpty(dto.getUserId())) {
            errors.rejectValue("userId", "message.userId.required", "아이디를 입력해 주세요.");
        }

        if(ObjectUtils.isEmpty(dto.getPassword())) {
            errors.rejectValue("password", "message.password.required", "비밀번호를 입력해 주세요.");
        }
        else if(!dto.getPassword().matches(strPasswordPattern)) {
            errors.rejectValue("password", "message.password.required", "영문자,숫자,특수기호 포함 8자 이상입력해주세요.");
        }
        else if(!dto.getPassword().equals(dto.getConfirmPassword())){
            errors.rejectValue("password", "message.password.mismatched", "비밀번호가 일치하지 않습니다.");
        }

        if(ObjectUtils.isEmpty(dto.getConfirmPassword())) {
            errors.rejectValue("confirmPassword", "message.confirmPassword.required", "비밀번호 확인란을 입력해 주세요.");
        }

    }
}

Validator에서 error발생시, BindingResults에 전달되고, 

th:errors="*{korName}" 를 통해 오류 메세지가 화면에 보여집니다.

728x90
반응형