[Spring Boot/JPA] #4 View와 Controller 제작 : 마이페이지 만들기 (2)

뀨뀨찬찬·2021년 3월 5일
0

spring

목록 보기
4/4
post-custom-banner

개인적으로 시작한 개발이며, 틀린 점이나 부족한 부분이 많을 수 있으니 보완할 사항이나 질문은 댓글로 남겨주세요!


개발 환경
Database : mysql community Server 8.0.23
language : Java 11
Framework : Spring
IDE : IntelliJ ultimate ver.
OS : MS Win10 64bit

컨트롤러와 뷰 개발

mypage/comments

  • mypage/comment.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div th:replace="fragments/nav :: fragment-nav"></div>

<th:block th:insert="fragments/mypage-body :: mypage-body"/>
<div class="container d-flex mt-5">

    <div th:replace="fragments/sidebar :: fragment-sidebar"></div>

    <table class="box shadow table">
        <thead>
        <tr>
            <td class="h4" colspan="6">내가 쓴 댓글</td>
        </tr>
        <tr>
            <th>#</th>
            <th>제목</th>
            <th>날짜</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="comment : ${comments}">
            <td th:text="${comment.id}"></td>
            <td><a th:href="@{/content/{postId}(postId=${comment.post.id},prev=1,prev_content='/board/'+${comment.post.category.id})}" th:text="${comment.content}"></a></td>
            <td th:text="${#temporals.format(comment.createTime, 'HH:mm')}"></td>
        </tr>
        </tbody>
    </table>
</div>
<div class = "container">
    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center"
            th:with="start=${T(Math).floor(comments.number/10)*10 + 1},
                            last=(${start + 9 < comments.totalPages ? start + 9 : comments.totalPages})">
            <li class="page-item">
                <a th:href="@{/board/(page=1)}" aria-label="First">
                    <span aria-hidden="true">First</span>
                </a>
            </li>

            <li class="page-item" th:class="${comments.first} ? 'disabled'">
                <a th:href="${comments.first} ? '#' :@{/comments/(page=${comments.number})}" aria-label="Previous">
                    <span aria-hidden="true">&lt;</span>
                </a>
            </li>

            <li class="page-item" th:each="page: ${#numbers.sequence(start, last)}" th:class="${page == comments.number + 1} ? 'active'">
                <a th:text="${page}" th:href="@{/comments/(page=${page})}"></a>
            </li>

            <li class="page-item" th:class="${comments.last} ? 'disabled'">
                <a th:href="${comments.last} ? '#' : @{/comments/(page=${comments.number + 2})}" aria-label="Next">
                    <span aria-hidden="true">&gt;</span>
                </a>
            </li>

            <li class="page-item">
                <a th:href="@{/comments/(page=${comments.totalPages})}" aria-label="Last">
                    <span aria-hidden="true">Last</span>
                </a>
            </li>
        </ul>
    </nav>
</div>
</body>
</html>

이전 포스트의 mypage/contents.html과 별로 다를 것이 없다. 컨트롤러로부터 넘겨 받는 변수가 posts에서 comments로 바뀐 것 뿐이다.

  • controller/MyPageController
    @GetMapping("/mypage/comments")
    public String myComments(Model model, @AuthenticationPrincipal Member currentMember,
                             @PageableDefault Pageable pageable) {
        Member member = memberService.findByUsername(currentMember.getUsername())
                .orElseThrow(()-> new UsernameNotFoundException(currentMember.getUsername()));
        Page<Comment> comments = commentService.findListByMember(member, pageable);

        List<Category> categoryList = categoryService.findAll();
  
        model.addAttribute("categoryList", categoryList);
        model.addAttribute("comments", comments);
        return "mypage/comments";
    }

Comment 도메인도 Member 도메인과 ManyToOne으로 연관관계 매핑되어있기 때문에 페이징하여 뷰에 넘겨준다.

mypage/password

비밀번호 변경을 위해서 우선 비밀번호를 입력할 폼이 필요하고, DB에 저장된 비밀번호와 폼에 입력한 현재 비밀번호가 같은지 검증해야 한다.
그 다음에 새 비밀번호와 현재 비밀번호가 서로 다른지 검증해야 하고, 새 비밀번호와 새 비밀번호 다시 입력이 서로 같은지 검증한 후에 DB의 비밀번호를 새 비밀번호로 변경하면 된다.

  • mypage/password.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div th:replace="fragments/nav :: fragment-nav"></div>

<th:block th:insert="fragments/mypage-body :: mypage-body"/>
<div class="col-lg-4">
    <div class="passwordEdit">
        <div class="fa-user">비밀번호 변경</div>
        <form role="form" action="/password"th:object="${passwordForm}" method="post">
            <div class="form-group">
                <label th:for="name">현재 비밀번호</label>
                <input type="password" th:field="*{password}" class="form-control"
                       placeholder="비밀번호를 입력하세요"
                       th:class="${#fields.hasErrors('password')}? 'form-control fieldError' : 'form-control'">
                <p th:if="${#fields.hasErrors('password')}"
                   th:errors="*{password}">Incorrect input</p>
            </div>
            <div class="form-group">
                <label th:for="newPassword">새 비밀번호</label>
                <input type="password" th:field="*{newPassword}" class="form-control"
                    placeholder="새 비밀번호를 입력하세요">
            </div>
            <div class="form-group">
                <label th:for="retype">새 비밀번호 확인</label>
                <input type="password" th:field="*{retype}" class="form-control"
                    placeholder="새 비밀번호 다시 입력">
            </div>
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
            <button type="submit" class="btn btn-primary">수정</button>
        </form>
        <br/>
    </div>
</div>
</body>
</html>

hidden으로 넘긴 csrf는 Spring Security 설정에서 csrf 공격 방어를 위해 사용하기로 했으므로 넘겨준다. 사실 Thymeleaf object를 사용하면 hidden으로 input 태그를 넣어주지 않아도 자동으로 넘어간다고 하는데,, 다른 팀원 분들은 th가 아닌 그냥 html form을 사용하신 경우도 있어서 우선 다 넣어주었다.

  • controller/MyPageController
@PostMapping("/mypage/password")
    public String passwordEdit(Model model,
                               PasswordForm form,
                               BindingResult result,
                               @AuthenticationPrincipal Member currentMember) {
        if (result.hasErrors()) {
            return "redirect:/mypage/password";
        }

        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        if (!encoder.matches(form.getPassword(), currentMember.getPassword())) {
            model.addAttribute("error", "현재 패스워드 불일치");
            return "mypage/passwordError";
        }

        if(form.getNewPassword().equals(form.getPassword())){
            model.addAttribute("error", "동일한 패스워드");
            return "mypage/passwordError";
        }

        if (!form.getNewPassword().equals(form.getRetype())) {
            model.addAttribute("error", "새 패스워드 불일치");
            return "mypage/passwordError";
        }

        String encodedNewPwd = encoder.encode(form.getNewPassword());
        memberService.updatePassword(currentMember.getUsername(), encodedNewPwd);
        currentMember.setPassword(encodedNewPwd);
        return "redirect:/mypage/me";
    }
  1. BCryptPassword의 matches()를 사용하여 인코딩되지 않은, 폼으로부터 입력받은 raw 비밀번호와, 현재 사용자의(DB의) 인코딩된 비밀번호가 같은지 검사한다.

  2. 새로 입력한 비밀번호와 폼으로 입력받은 현재 비밀번호가 같은지 검사하고, 새로 입력한 비밀번호와 다시 입력한 새 비밀번호가 같은지 검사한다.

  3. 모든 검증을 통과하면 새 비밀번호를 인코딩한 후 DB와 Session을 업데이트한다.

비밀번호 변경 화면

  • 1.의 에러
  • 2.의 에러
  • 3.의 에러
profile
공부하고 있어요!
post-custom-banner

0개의 댓글