본문 바로가기

Server Programming/Spring Boot Full-Stack Programming

[스프링 풀스택 클론 코딩 - 계정 설정] (2-5) 패스워드 수정

반응형

SettingsController

package com.demo.settings;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.demo.account.AccountService;
import com.demo.account.CurrentUser;
import com.demo.domain.Account;

import lombok.RequiredArgsConstructor;

//프로필 수정 요청 처리
//패스워드 수정 요청 처리
@Controller
@RequiredArgsConstructor
public class SettingsController {

//	@InitBinder를 이용해 대체 
//	passwordFormValidator.validate(passwordForm, errors);
//	if (errors.hasErrors()) {
//		return "account/sign-up";
//	}
	// -> 자동으로 PasswordForm 검증을 한다.
	@InitBinder("passwordForm")
	public void initBinder(WebDataBinder webDataBinder) {
		webDataBinder.addValidators(new PasswordFormValidator());
	}

	// 자주 틀리는 오타는 변수를 만들어서 사용
	// 프로필 변경 위한 뷰 네임, url
	private static final String SETTINGS_PROFILE_VIEW_NAME = "settings/profile";
	private static final String SETTINGS_PROFILE_URL = "/settings/profile";

	// 패스워드 변경 위한 뷰 네임, url
	private static final String SETTINGS_PASSWORD_VIEW_NAME = "settings/password";
	private static final String SETTINGS_PASSWORD_URL = "/settings/password";

	private final AccountService accountService;

	// 패스워드 변경 요청 검증
	@GetMapping(SETTINGS_PASSWORD_URL)
	public String updatePasswordForm(@CurrentUser Account account, Model model) {
		model.addAttribute(account);
		model.addAttribute(new PasswordForm());

		return SETTINGS_PASSWORD_VIEW_NAME;
	}

	// 패스워드 변경 검증 처리
	@PostMapping(SETTINGS_PASSWORD_URL)
	// 현재 업데이트하려는 사용자는 현재 사용자, valid를 통해 바인딩하고 검증, 바인딩을 담을 모델, 만약 에러발생시 에러 바인딩받는 객체,
	// 잘 되었을 경우 메시지 전송
    public String updatePassword(@CurrentUser Account account, @Valid PasswordForm passwordForm, Errors errors,
            Model model, RedirectAttributes attributes){
		if (errors.hasErrors()) {
			model.addAttribute(account);
			return SETTINGS_PASSWORD_VIEW_NAME;
		}
		accountService.updatePassword(account, passwordForm.getNewPassword());
		attributes.addFlashAttribute("message", "패스워드를 변경했습니다.");
		return "redirect:" + SETTINGS_PASSWORD_URL;
	}

}

 

 

PasswordForm

package com.demo.settings;

import org.hibernate.validator.constraints.Length;

import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PasswordForm {
	
	@Length(min=8, max=50)
	private String newPassword;
	
	@Length(min=8, max=50)
	private String newPasswordConfirm;

}

 

PasswordFormValidator

package com.demo.settings;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

//빈으로 만들 필요없이 new해서 만들면 된다.
//여러번 사용할 일이 없기 때문에
public class PasswordFormValidator implements Validator{

	@Override
	//어떤 타입의 폼 객체를 검증할것인가
	public boolean supports(Class<?> clazz) {
		// TODO Auto-generated method stub
		return PasswordForm.class.isAssignableFrom(clazz);
		//패스워드 폼의 객체
	}

	//할당 가능한 타입이면 검증
	@Override
	public void validate(Object target, Errors errors) {
		// TODO Auto-generated method stub
		PasswordForm passwordForm= (PasswordForm) target;
		//패스워드폼으로 타겟을 변경

		//같은지 검증
		if(!passwordForm.getNewPassword().equals(passwordForm.getNewPasswordConfirm())) {
	        errors.rejectValue("newPassword", "wrong.value", "입력한 새 패스워드가 일치하지 않습니다.");
			//새로운 패스워드가 잘못된 값으로 변경 
		}
	}

}

 

fragments

<div th:fragment="settings-menu (currentMenu)" class="list-group">
    <a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'profile'}? active" href="#" th:href="@{/settings/profile}">프로필</a>
    <a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'password'}? active" href="#" th:href="@{/settings/password}">패스워드</a>
    <a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'notifications'}? active" href="#" th:href="@{/settings/notifications}">알림</a>
    <a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'tags'}? active" href="#" th:href="@{/settings/tags}">관심 주제</a>
    <a class="list-group-item list-group-item-action" th:classappend="${currentMenu == 'zones'}? active" href="#" th:href="@{/settings/zones}">활동 지역</a>
    <a class="list-group-item list-group-item-action list-group-item-danger" th:classappend="${currentMenu == 'account'}? active" href="#" th:href="@{/settings/account}">계정</a>
</div>

<!--needs-validation을 가지는 폼을 가져와 submit 이벤트 발생시 유효여부 검증, 유효하지 않으면, submit안되도록 처리 -->
	<!--유효하면 was-validated에 추가 -> input의 required을 기반으로 처리-->
	<script type="application/javascript" th:fragment="form-validation">
		(function () {
			'use strict';

			window.addEventListener('load', function () {
				// Fetch all the forms we want to apply custom Bootstrap validation styles to
				var forms = document.getElementsByClassName('needs-validation');

				// Loop over them and prevent submission
				Array.prototype.filter.call(forms, function (form) {
					form.addEventListener('submit', function (event) {
						if (form.checkValidity() === false) {
							event.preventDefault();
							event.stopPropagation();
						}
						form.classList.add('was-validated')
					}, false)
				})
			}, false)
		}())
	</script>

 

password

<!DOCTYPE html>
<!-- 프로필 수정 뷰 -> password form을 만들어야 한다. 닉네임 이메일 비밀번호-->
<!-- 타임리프 네임스페이스 설정-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

<!-- replace를 이용해 교체-->

<head th:replace="fragments.html :: head">
</head>

<body class="bg-light">
	<div th:replace="fragments.html :: main-nav"></div>

	<!-- password html -> 그리드 시스템 이용 row를 column 12개로 나눈 상태-->
	<div class="container">
		<!-- top 마진 5만큼-->
		<div class="row mt-5 justify-content-center">
			<!-- 아바타-->
			<div class="col-2">
				<!--프로필, 패스워드, 알림, 관심주제, 활동지역 계정 메뉴 -> fragment 이용-->
				<!-- 패스워드가 선택된 상태의 뷰 -->

				<div
					th:replace="fragments.html :: settings-menu(currentMenu='password')"></div>
			</div>

			<!-- 폼을 수정할 수 있는 프로필화면을 보면서 직접 작성 -->
			<div class="col-8">
				<div th:if="${message}"
					class="alert alert-info alert-dismissible fade show mt-3"
					role="alert">
					<span th:text="${message}">메시지</span>
					<button type="button" class="close" data-dismiss="alert"
						aria-label="Close">
						<span aria-hidden="true">&times;</span>
					</button>
				</div>
				<!-- row마다 설정 -->
				<div class="row">
					<h2 class="col-sm-12">패스워드 변경</h2>
				</div>
				<div class="row mt-3">
					<form class="needs-validation col-12" action="#"
						th:action="@{/settings/password}" th:object="${passwordForm}"
						method="post" novalidate>

						<div class="form-group">
							<!-- 프론트엔드에서 미리 길이 체크를 하고  서버에서도 길이 체크를 한다-->
							<label for="newPassword">새 패스워드</label> <input id="newPassword"
								type="password" th:field="*{newPassword}" class="form-control"
								aria-describedby="newPasswordHelp" required min="8" max="50">
							<small id="newPasswordHelp" class="form-text text-muted">
								새 패스워드를 입력하세요. </small> <small class="invalid-feedback">패스워드를
								입력하세요.</small> <small class="form-text text-danger"
								th:if="${#fields.hasErrors('newPassword')}"
								th:errors="*{newPassword}">New Password Error</small>
						</div>

						<div class="form-group">
							<label for="newPasswordConfirm">새 패스워드 확인</label> <input
								id="newPasswordConfirm" type="password"
								th:field="*{newPasswordConfirm}" class="form-control"
								aria-describedby="newPasswordConfirmHelp" required min="8"
								max="50"> <small id="newPasswordConfirmHelp"
								class="form-text text-muted"> 새 패스워드를 다시 한번 입력하세요. </small> <small
								class="invalid-feedback">새 패스워드를 다시 입력하세요.</small> <small
								class="form-text text-danger"
								th:if="${#fields.hasErrors('newPasswordConfirm')}"
								th:errors="*{newPasswordConfirm}">New Password Confirm
								Error</small>
						</div>

						<div class="form-group">
							<button class="btn btn-outline-primary" type="submit"
								aria-describedby="submitHelp">패스워드 변경하기</button>
						</div>
					</form>
				</div>
			</div>
		</div>
	</div>
	<script th:replace="fragments.html :: form-validation"></script>

</body>

</html>
반응형