Spring - RedirectAttributes

MeteorLee·2023년 3월 15일
0

문제 해결

목록 보기
1/6
post-thumbnail

문제

전반적인 redirect를 생각하는 과정에서 생긴 문제다. 이전 프로젝트 때도 redirect를 해야하는 상황에서 내가 넣고 싶은 부분을 넣는 과정에서 노출하기 싫은 정보들을 숨기거나 코드 자체를 수정하는 과정에서 겪는 문제점들을 해결하지 못했는데 이번에 문제를 해결하게 되었다.

Controller

@Controller
@RequestMapping("/board")
public class BoardController {
    @Autowired
    BoardService boardService;

    @PostMapping("/remove")
    public String remove(Integer bno,
                         Integer page,
                         Integer pageSize,
                         Model model,
                         HttpSession session,
                         RedirectAttributes rattr) {

        String writer = (String) session.getAttribute("id");
        try {
            model.addAttribute("page", page);
            model.addAttribute("pageSize", pageSize);

            int rowCnt = boardService.remove(bno, writer);

            if (rowCnt != 1)
                throw new Exception("board remove error");

            rattr.addFlashAttribute("msg", "DEL_OK");

        } catch (Exception e) {
            e.printStackTrace();
            model.addAttribute("page", page);
            model.addAttribute("pageSize", pageSize);
            rattr.addFlashAttribute("msg", "DEL_ERR");
        }

        return "redirect:/board/list";
    }

boardList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>fastcampus</title>
  <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
</head>
<body>
<div id="menu">
  <ul>
    <li id="logo">fastcampus</li>
    <li><a href="<c:url value='/'/>">Home</a></li>
    <li><a href="<c:url value='/board/list'/>">Board</a></li>
    <li><a href="<c:url value='/login/login'/>">login</a></li>
    <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
    <li><a href=""><i class="fas fa-search small"></i></a></li>
  </ul>
</div>
<script>
  let msg = "${msg}"
  if (msg=="WRT_OK") alert("성공적으로 등록되었습니다.")
  if (msg=="DEL_OK") alert("성공적으로 삭제되었습니다.");
  if (msg=="DEL_ERR") alert("삭제에 실패했습니다.");
</script>
<div style="text-align:center">
  <button type="button" id="writeBtn"token tag"><c:url value="/board/write"/>'">글쓰기</button>
  <table border="1">
    <tr>
      <th>번호</th>
      <th>제목</th>
      <th>이름</th>
      <th>등록일</th>
      <th>조회수</th>
    </tr>
    <c:forEach var="boardDto" items="${list}">
    <tr>
      <th>${boardDto.bno}</th>
      <th><a href="<c:url value='/board/read?bno=${boardDto.bno}&page=${page}&pageSize=${pageSize}'/>">${boardDto.title}</a></th>
      <th>${boardDto.writer}</th>
      <th>${boardDto.reg_date}</th>
      <th>${boardDto.view_cnt}</th>
    </tr>
    </c:forEach>
  </table>
  <br>
  <div>
    <c:if test="${ph.showPrev}">
      <a href="<c:url value='/board/list?page=${ph.beginPage-1}&pageSize=${ph.pageSize}'/>">&lt;</a>
    </c:if>
    <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
      <a href="<c:url value='/board/list?page=${i}&pageSize=${ph.pageSize}'/>">${i}</a>
    </c:forEach>
    <c:if test="${ph.showNext}">
      <a href="<c:url value='/board/list?page=${ph.endPage+1}&pageSize=${ph.pageSize}'/>">&gt;</a>
    </c:if>
  </div>
</div>
</body>
</html>

위의 컨트롤러와 jsp의 뷰 화면에서 생긴 문제들인데 더욱 간소화 해서 정리하고자 한다.

rattr.addFlashAttribute("msg", "WRT_OK");
	return "redirect:/board/list";
    
model.addAttribute("msg", "WRT_ERR");
	return "board";
<script>
  let msg = "${msg}"
  if (msg=="WRT_OK") alert("성공적으로 등록되었습니다.")
</script>

문제 인식

redirect시 내가 전달하고자 하는 정보

보통 내가 redirect를 많이 사용한 이유는 사용자가 어떤 동작을 시행하고 그 동작을 받아서 처리하는 과정에서 error가 발생하여 이를 처리하고 사용자에게 에러가 발생했고 그에 따라서 지금 있는 화면이 아닌 다른 화면으로 redirect로 다시 request를 보내도록 만들기 위해 사용한다. 이 과정에서 보통 사용자 화면의 URL 정보 + 에러 메세지의 2개의 정보를 보내주려고 한다. 그런데 이 상황에서 에러 메세지는 사용자에게 보여주고 싶지 않은 정보인데 이를 해결할 방법을 모르기에 그냥 노출하는 방식을 사용했었다.

model.addAttribute("page", page);
model.addAttribute("pageSize", pageSize);
model.addAttribute("msg", msg);
return "redirect:/board/list";
  • URL과 관련된 정보 : .../board/list?page=2&pageSize=10&msg=DEL_ERR
  • Error 메세지 : .../board/list?page=2&pageSize=10&msg=DEL_ERR

하지만 이런 방식이 문제가 있는 것을 알고 있음에도 해결할 방법을 몰랐다.

redirect는 GET 방식

이전에 문제를 해결하고자 하다가 포기한 이유가 redirect가 GET방식이라는 것이다. 내가 원하는 정보를 어떻게 해서 넣더라도 결국 GET 방식으로 다시 요청이 오기에 URL에 리소스들이 노출된다는 것이다. 하지만 redirect를 POST 방식으로 바꾸는 방법을 찾아봤지만 없었기에 다른 방법을 활용했었다.

문제 해결 과정

이전 프로젝트에서 활용한 방식

이전 프로젝트에서는 이 msg가 노출되는 방식을 해결하기 위해서 중간에 msg랑 URL을 처리하는 페이지를 따로 만들어서 사용했다. 그리고 이 때에는 URL을 처리하는 방식도 정말 최악이었다.

controller

if (content.length() == 0 || content.equals("") || content == null) {
            model.addAttribute("message", "댓글을 입력해 주세요.");
            model.addAttribute("url", "/noticeboard/" + tableNo);
            return "/noticeBoard/redirect";
}

redirect.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
</head>
<body>
<script th:inline="javascript">
  var message = [[${message}]];
  var url = [[${url}]];
  alert(message);
  document.location.href = url;
</script>
</body>
</html>

redirect를 바로 활용하는 것도 아니고 redirect를 사용하려는 html 페이지를 만들어서 Model에 msg와 url을 실어서 보내어 redirect.html에서 처리하고 javascpript의 redirect를 사용하여서 msg를 노출하는 방식을 사용했다.

문제점

여전히 msg를 짧은 순간이지만 URL에 노출하고 있고 redirect.html이라는 새로운 페이지를 운영해야하는 문제점이 있다는 것이다.
그리고 model.addAttribute("url", "/noticeboard/" + tableNo); 와 같은 방식으로 url을 설정하기에 유지 보수에 별로 좋지 않는 것이다.

💚RedirectAttributes

개념

일단 공식 문서에 따르면

A specialization of the Model interface that controllers can use to select attributes for a redirect scenario. Since the intent of adding redirect attributes is very explicit -- i.e. to be used for a redirect URL, attribute values may be formatted as Strings and stored that way to make them eligible to be appended to the query string or expanded as URI variables in org.springframework.web.servlet.view.RedirectView.
This interface also provides a way to add flash attributes. For a general overview of flash attributes see FlashMap. You can use RedirectAttributes to store flash attributes and they will be automatically propagated to the "output" FlashMap of the current request.

redirect를 사용하는데 특성을 선택하는 인터페이스이며 flash 특성을 가지고 있는데 이는 FlashMap에서 확인가능하다. 일단 내가 redirect에 attribute를 add하고 싶기에 사용하는 것은 이해가 되는데 flash 특성이 무엇인지 궁금하기에 FlashMap을 뒤져보자

A FlashMap provides a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting from one URL to another -- e.g. the Post/Redirect/Get pattern. A FlashMap is saved before the redirect (typically in the session) and is made available after the redirect and removed immediately.
A FlashMap can be set up with a request path and request parameters to help identify the target request. Without this information, a FlashMap is made available to the next request, which may or may not be the intended recipient. On a redirect, the target URL is known and a FlashMap can be updated with that information. This is done automatically when the org.springframework.web.servlet.view.RedirectView is used.

redirection 과정에서 가장 보편적으로 사용하며 가장 큰 특징은 Session을 사용하여 redirect의 정보를 저장했다가 redirect 이후에 정보를 바로 삭제하는 일시적으로 데이터를 저장하는 방식을 사용합니다.

종합해서 보자면 redirect과정에서 특성(attribute)을 전달하는데 많이 사용되는 인터페이스이며 원하는 특성을 Session에 잠깐 저장하여 내가 원하는 redirect에 전달하고 바로 삭제되어 서버에 부담이 되지 않고 URL에 노출하지 않는 안전한 방식의 특성 전달 방법(Method)를 가지고 있다는 것이다.

POST가 아닌 GET

처음 내가 RedirectAttributes를 알게 되면서 생각한 것은 redirect를 POST로 바꿔주는 방법인 줄 알았다. 이전에 공부하면서 POST로 바꾸는 방법을 찾아봤지만 몰랐기에 조금 더 찾아볼걸 이라는 생각을 했었는데 이는 잘못된 생각이었다. 내가 이런 잘못된 생각을 가지게 된 이유는 다음과 같다.

  • 이전 URL : .../board/list?page=2&pageSize=10&msg=DEL_ERR
model.addAttribute("page", page);
model.addAttribute("pageSize", pageSize);
model.addAttribute("msg", msg);
return "redirect:/board/list";
  • 사용 URL : .../board/list
model.addAttribute("page", page);
model.addAttribute("pageSize", pageSize);
redirectAttributes.addFlashAttribute("msg", msg);
return "redirect:/board/list";

redirectAttributes를 한번 사용하였음에도 바로 모든 정보들이 URL에서 사라졌기에 나는 자연스럽게 POST 방식을 사용한다고 생각하였다. 하지만 이는 잘못된 방식인데 이는 내가 잘못된 생각을 가지고 있었기 때문이다.

ModelAttributes과 RedirectAttributes

일단 일반적인 redirect의 사용이다.

일단 위에서 내가 착각한 큰 이유는 ModelAttributes의 FlashMap의 특성을 가지는 것을 몰랐기 때문이다. 거기다가 나는 몰랐지만 공식 문서를 뜯어보면서 알게된 점인데

Interface RedirectAttributes
All Superinterfaces: Model

Model이 상위 인터페이스 라는 것이다. 사실 공식 문서를 보았기에 알게된 일이지 보지 않았다면 전혀 다른 관계라고 생각했을 것이다. 그래서 내가

public String remove(Integer bno,
                         Integer page,
                         Integer pageSize,
                         Model model,
                         HttpSession session,
                         RedirectAttributes redirectAttributes)

에서 RedirectAttributes를 매개변수로 받게 되면서 Model이 같이 FlashMap의 특성을 가지게 되는 것이다. 따라서 model.addAttribute()를 사용해서 URL에 나타내려고 해도 Model에 정보가 Session에 잠깐 저장되었다가 다시 redirect로 왔을 때 Model에 담겨져서 Controller에 다시 돌아오는 것이다.

기능적으로 본다면 model.addAttribute()와 redirectAttributes를.addAttribute()는 거의 같은 기능을 하지만 URL의 관점에서 본다면 redirectAttributes.addAttribute()사용해야 한다는 것이다.

FlashMap의 Session 방식

redirectAttributes는 사실 Session을 이용한 방식이었다. 나처럼 URL에 정보를 노출하기 싫은 개발자들을 위해 리소스 정보들을 잠깐 Session에 저장하여 잠깐 사용하는 방식이었다. 잠깐 사용하는 방식이기에 Session에 저장되는 시간도 짧기에 서버에 부담도 적고 URL에 노출되는 정보도 없기에 안전한 방식이었다.

사용 방식

  • 내가 노출해도 상관없는 page, pageSize와 같은 정보는 redirectAttributes.addAttribute()로 model.addAttribute()과 같은 방식이라고 생각해도 된다.
  • 노출하면 안되는 error의 msg는 redirectAttributes.addFlashAttribute()로 이름처럼 flash로 잠깐 사용했다가 사라지는 정보는 redirectAttributes.addFlashAttribute()에 담아서 사용한다.
  • 만약 그대로 모델에 넘겨주고 싶은 정보가 있다면 model.addAttribute()에 담아서 다시 Model로 넘겨준다.
redirectAttributes.addAttribute("page", page);
redirectAttributes.addAttribute("pageSize", pageSize);
model.addAttribute("user", user);
redirectAttributes.addFlashAttribute("msg", msg);
return "redirect:/board/list";

URL : .../board/list?page=2&pageSize=10

느낀 점

이전 프로젝트에서 redirect를 사용하는 데 겪었던 큰 문제점을 해결하였다. 새로운 html페이지를 만들어서 처리하고 다시 url을 넣어서 redirect를 사용하는 아주 획기적으로 돌아가는 방식을 사용하고 그래도 이 정도면 작동은 하니까 괜찮지 않을까? 라는 생각을 한 내가 참 부끄럽다. 이전에도 느꼈지만 원래는 혼자 무언가를 만들고 하나의 책을 여러 번 보는 방식을 하면서 공부를 했었는데 이런 문제점을 만날 때마다 그런 방식이 조금 좋지 않다는 생각이 다시 들었다.

좋은 방식을 많이 보고 그를 따라하며 이렇게 RedirectAttributes와 같은 것들을 알아가고 내가 이렇게 저렇게 사용해보면서 하나씩 내것으로 만드는 공부 방식이 좋은 것 같다. 이전에는 내가 혼자서 공부하면서 알아보았기에 질이 낮은 방식을 자주 접하고 이를 괜찮은 방식이라고 인식하였다.

아는 것이 힘이다 라는 말이 있는 만큼 많은 경험을 하는 공부를 계속해서 해나갈 생각이다. 그리고 공식 문서를 자주 보는 습관이 중요하다고 다시 한번 느꼈다.

도움 받은 곳
https://velog.io/@username-bb/Spring-Model%EA%B3%BC-RedirectAttributes%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
RedirectAttributes 공식 문서
Model 공식 문서
FlashMap 공식 문서

profile
코딩 시작

0개의 댓글