spring Security 5. - 수정 기능

알파로그·2023년 3월 22일
0

Spring Boot

목록 보기
27/57

✏️ 수정 기능 구현

  • 권한이 있는 클라이언트에 한에 수정기능이 정상 작동됨

📍 Web 계층 - 수정 버튼 추가

  • 권한이 있는 클라이언트만 수정 버튼이 활성화 되게 만들어야 한다.
    • sec:authorize="isAuthenticated()”
      • log-in 여부 확인
    • th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}”
      - log-in 한 사용자가 글쓴이인지 검증하는 로직
      - #authentication.getPrincipal() 은 Principal 객체를 반환하는 타임리프의 유틸이다.
  • 수정 날짜 표시
    • 조건문으로 modifyDate 가 null 이 아닐 경우로 설정
    • #temporals.format 으로 날짜 정보를 원하는 형태로 변환
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
    <!-- 질문 -->
    <h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
    <div class="card my-3">
        <div class="card-body">
            <div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
            <div class="d-flex justify-content-end">

<!--                날자, 글슨이 정보 시작-->
                <div class="badge bg-light text-dark p-2 text-start">
    <!--                글쓴이 정보 추가 시작-->
                    <div class="mb-2">
                        <span th:if="${question.author != null}"
                              th:text="${question.author.username}"></span>
                    </div>
    <!--                글쓴이 정보 추가 종료-->
                    <div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
              
<!--                질문 수정된 날짜 시작-->
                <div th:if="${question.modifyDate != null}"
                     class="badge bg-light text-dark p-2 text-start mx-3">
                    <div class="mb-2">modified at</div>
                    <div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
<!--                질문 수정된 날짜 종료-->
              
<!--                날자, 글슨이 정보 종료-->
            </div>
<!--            질문 수정 버튼 시작-->
            <div class="my-3">
                <a th:href="@{|/question/modify/${question.id}|}"
                   class="btn btn-sm btn-outline-secondary"
                   sec:authorize="isAuthenticated()"
                   th:if="${question.author != null
                   and #authentication.getPrincipal().getUsername() == question.author.username}"
                   th:text="수정"></a>
            </div>
<!--            질문 수정 버튼 종료-->
        </div>
    </div>
    <!-- 답변의 갯수 표시 -->
    <h5 class="border-bottom my-3 py-2"
        th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
    <!-- 답변 반복 시작 -->
    <div class="card my-3" th:each="answer : ${question.answerList}">
        <div class="card-body">
            <div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
            <div class="d-flex justify-content-end">
                <div class="badge bg-light text-dark p-2 text-start">
<!--                    답변자 정보 추가 시작-->
                    <div class="mb-2">
                        <span th:if="${answer.author != null}"
                              th:text="${answer.author.username}"></span>
                    </div>
<!--                    답변자 정보 추가 종료-->
                    <div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
            </div>
        </div>
    </div>
    <!-- 답변 반복 끝  -->
    <!-- 답변 작성 -->
    <form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">

        <!--        오류 처리 탬플릿 사용 시작-->
        <div th:replace="~{form_errors :: formErrorsFragment}"></div>
        <!--        오류 처리 탬플릿 사용 종료-->
<!--        log-out 클라이언트에게 답변 공간 비공개 시작-->
        <textarea sec:authorize="isAnonymous()" disabled
                  th:field="*{content}"
                  class="form-control"
                  rows="10"></textarea>

        <textarea sec:authorize="isAuthenticated()"
                  th:field="*{content}"
                  class="form-control"
                  rows="10"></textarea>
<!--        log-out 클라이언트에게 답변 공간 비공개 시작-->

        <input type="submit" value="답변등록" class="btn btn-primary my-2">
    </form>
</div>
</html>

📍 Service 계층 - 수정 로직

    //-- update --//
    public void modify(Question question, String subject, String content) {
        question.setSubject(subject);
        question.setContent(content);
        question.setModifyDate(LocalDateTime.now());
        repository.save(question);
    }

📍 Controller 계층 - 수정 폼

  • Web 계층에서 버튼을 막아두어도 url 을 통해 강제로 수정폼으로 접근할 수 있기때문에 principal 로 한번 더 권한을 확인해준다.
  • 기존에 작성했었던 내용을 다시 Form 으로 입력해 템플릿으로 전달해준다.
    • 이 작업을 하지 않으면 작성자가 다시 처음부터 작성해야 한다.
@Controller
@RequestMapping("/question")
@RequiredArgsConstructor
public class QuestionController {

    private final QuestionService questionService;
    private final UserService userService;

    //-- question 수정 폼 --//
    @GetMapping("/modify/{id}")
    @PreAuthorize("isAuthenticated()")
    public String questionModify(
            @PathVariable Integer id,
            QuestionForm questionForm,
            Principal principal
    ) {
        Question question = questionService.getQuestion(id);

        // 요청한 클라이언트와 작성자가 동일한 클라이언트인지 확인
        if (!question.getAuthor().getUsername().equals(principal.getName()))
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");

        // 글쓴이가 입력했었던 정보를 Form 으로 옮겨 템플릿으로 전달
        questionForm.setSubject(question.getSubject());
        questionForm.setContent(question.getContent());
        return "question_form";
    }

    //-- question 수정 처리 --//
    @PostMapping("/modify/{id}")
    @PreAuthorize("isAuthenticated()")
    public String questionModify(
            @Valid QuestionForm questionForm,
            @PathVariable Integer id,
            BindingResult bindingResult,
            Principal principal
    ) {
        // 양식에 맞지 않게 작성후 요청할 경우 실행되는 로직
        if (bindingResult.hasErrors())
            return "question_form";

        Question question = questionService.getQuestion(id);

        // 클라이언트와 글쓴이가 다를경우 실행되는 로직
        if (!question.getAuthor().getUsername().equals(principal.getName()))
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");

        // 새로운 question 을 저장하는 로직
        questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
        return String.format("redirect:/question/detail/%s", id);
    }

}

📍 Web 계층 - 수정 폼

  • 등록할 때 사용했던 question_form 을 수정할 때 재활용 할 수 있다.
  • form 의 action 속성을 삭제한다.
    • CSRF 값이 자동으로 생성되지 않게된다.
    • input 태그를 새로 선언해 CSRF 값을 hidden 형태로 수동 생성한다.
    • form 태그에 action 속성이 없을경우 form 은 현재의 URL 을 기준으로 전송이 된다.
      • 등록시 폼이 요청되면 등록으로,
        수정시 폼이 요청되면 수정으로 url 이 요청됨
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container">
    <h5 class="my-3 border-bottom pb-2">질문등록</h5>

<!--    action 기능 삭제 -->
    <form method="post"
          th:object="${questionForm}" >
<!--        csrf 값 수동 수정 시작 -->
        <input type="hidden"
               th:name="${_csrf.parameterName}"
               th:value="${_csrf.token}" />
<!--        csrf 값 수동 수정 종료 -->

        <div th:replace="~{form_errors :: formErrorsFragment}"></div><!--        오류 처리 템플릿 사용 종료-->

        <div class="mb-3">
            <label for="subject" class="form-label">제목</label>
            <input type="text" name="subject" th:field="*{subject}" id="subject" class="form-control">
        </div>
        <div class="mb-3">
            <label for="content" class="form-label">내용</label>
            <textarea name="content" id="content" th:field="*{content}" class="form-control" rows="10"></textarea>
        </div>
        <input type="submit" value="저장하기" class="btn btn-primary my-2">
    </form>
</div>
</html>
profile
잘못된 내용 PR 환영

0개의 댓글