JSP2011. 4. 20. 11:50

JSP 프로그래밍에서 페이지 이동 방법이 여러가지가 있습니다.
비지니스 로직 수행에서 redirect나 forward 아니면 해당 페이지에서 직접 redirect, forward, html, javascript 를 이용한 페이지 이동 등등..

그중에서 request.getRequestDispatcher("/view.jsp").forward(request, response) 를 사용하였을때 중복 저장 또는 중복 로직 수행에 대해서 알아 보려고 합니다.

forward를 사용하는 이유는 아래처럼 정리 할 수 있지만.. forward의 특징 중 하나가 이전페이지의 객체를 보존하고 있습니다.
'특정 페이지에서 전달된 데이터를 로직을 수행하고 수행된 결과(Object Data)를 원하는 페이지에 전달 후 보여주려고 한다.'

예로
              '사용자가 입력 페이지의 폼에서 저장버튼 클릭 -> 저장 로직 수행 후 DB 입력 -> 리스트 페이지 이동'
을 하였을 경우 이동한 리스트 페이지에서 F5키 나 새로고침을 하였을 경우 위의 로직을 다시 타는 경우가 발생합니다.
만약 DB의 PK 설정이 oracle의 sequence나 mssql의 시드를 이용한 자동 증가가 아닐 경우 PK 값 위반에 대한 Exception 이 일어 날 것이며 PK 가 없다면 같은 데이터가 중복 저장되는 경우가 생깁니다.

그래서 double submit을 막아 보려고 구글링 등등 이것저것 찾아 보았는데
Spring의 경우 SessionStatus, Struts의 Token, Post - Redirect- Get 패턴 등등 ;;

이미 프로젝트가 막바지라 급하게 손쓸 방법을 찾다가 쉽게 쓸수 있을 것 같아 적용해 보았는데 성공하여 포스트 합니다;;
http://blog.naver.com/PostView.nhn?blogId=canya83&logNo=40094442229&parentCategoryNo=15&viewDate=&currentPage=1&listtype=0

## 원리 ::
1. 최초 페이지(Token 없음) : jsp에서 Token을 생성 ( T1 생성)
2. 서버의 컨트롤러에서 JSP에서 생성된 Token이 컨트롤러의 Token과 같을 경우 신규 요청으로 설정
     (T1 == T2 으면 Token 생성)
3. 비지니스 로직 처리 후 특정 페이지로 이동하여 새로고침 ( T1 != T2)
4. 서버의 컨트롤러에서 이전 JSP에서 생성된 Token이 컨트롤러의 Token과 다르므로 Token 생성 안함
     (T1 != T2 )  해당 요청에 대해서는 비지니스 로직을 수행 안함.

## 코드
1. 공통 Controller에서 사용할 Token.java 를 하나 만들자!

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class Token {
 
 private static final String TOKEN_KEY = "TOKEN_KEY";
 
 public static void set(HttpServletRequest request) {
  HttpSession session = request.getSession(true);
  long systemTime = System.currentTimeMillis();
  byte[] time = new Long(systemTime).toString().getBytes();
  byte[] id = session.getId().getBytes();
  
  try {
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(id);
   md5.update(time);
   
   String token = toHex(md5.digest());
   request.setAttribute(TOKEN_KEY, token);
   session.setAttribute(TOKEN_KEY, token);
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
  }
 }
 
 public static boolean isValid(HttpServletRequest request) {
  HttpSession session = request.getSession(true);
  String requestToken = request.getParameter(TOKEN_KEY);
  String sessionToken = (String)session.getAttribute(TOKEN_KEY);
  if(requestToken == null || sessionToken == null) {
   return false;
  } else {
   return requestToken.equals(sessionToken);
  }
 }
 
 private static String toHex(byte[] digest) {
  StringBuffer buf = new StringBuffer();
  for(int i=0;i<digest.length;i++) {
   buf.append(Integer.toHexString((int)digest[i] & 0x00ff));
  }
  return buf.toString();
 }
}


2. HttpServlet 을 재구현한 공통 컨트롤러의 forward 부분에 아래와 같이 추가 한다.

if(Token.isValid(request)) {
    Token.set(request);
    request.setAttribute("TOKEN_SAVE_CHECK", "TRUE");
} else {
     request.setAttribute("TOKEN_SAVE_CHECK", "FALSE");
}


3. 중복 저장을 막고자하는 JSP 페이지 아래와 같이 추가 한다.

       <%
        if(request.getAttribute("TOKEN_KEY")==null) Token.set(request);
       %>
       <input type="hidden" name="TOKEN_KEY" value="<%=request.getAttribute("TOKEN_KEY")%>"/>


4. business logic을 수행하는 부분에서 CRUD 등등 처리 부분의 처음 부분에 아래와 같이 추가한다.

if("TRUE".equals(request.getAttribute("TOKEN_SAVE_CHECK"))) {
   // 신규 요청이므로 원하는 로직 작성
} else {
  // 중복 요청이므로 그에 따른 로직 작성
}


5. 테스트!

'JSP' 카테고리의 다른 글

사용자 정의 태그  (0) 2010.04.09
필터와 리스너  (0) 2010.04.09
Posted by Tiwaz