struts--token 양식 중복 제출 방지 (원본 분석)

양식 중복 제출
1. 중복 제출의 주요 두 가지 원인:
(1) 서버 처리 시간이 길다.사용자가 폼에 정보를 채우고'제출'단추를 누르면 서버 반응 시간이 너무 길어서 응답 정보를 제때 보지 못하거나 다른 목적으로'제출'단추를 다시 누르면 서버에서 두 개 이상의 같은 정보를 받을 수 있다. 
(2) forward 점프로 인한 중복 제출.사용자가 정보를 서버에 제출하면 서버 응답은 forward 방식으로 다음 페이지로 이동합니다. 주소 표시줄에 이전 페이지의 URL이 표시됩니다. 현재 페이지를 새로 고치면 브라우저는 사용자가 이전에 입력한 데이터를 다시 제출하고 폼 중복 제출 문제가 발생합니다.물론 Redirect 방식으로 페이지를 돌릴 수 있습니다. 그러면 중복 제출 문제가 발생하지 않습니다.그러나 때로는 어떤 효과나 사이트 안전의 목적을 위해 웹 페이지를 숨겨야 하기 때문에forward 점프 방식을 사용해야 한다.
‍‍‍‍2, token에 대한 간단한 이해: (1) 사용자가 폼이 포함된 페이지에 처음 방문할 때 서버는 이번 세션에서 세션 대상을 만들고 영패 값을 생성한 다음에 이 영패 값을 숨겨진 입력 영역의 값으로 하고 폼과 함께 서버에 보내며 영패 값을 세션에 저장합니다.(2) 사용자가 페이지를 제출할 때 서버는 요청 매개 변수의 영패 값과 Session에 저장된 영패 값이 같은지 여부를 먼저 판단하고, 만약 같다면 Session의 영패 값을 정확히 판단하고 데이터 처리 작업을 실행한다.만약 같지 않다면, 사용자에게 폼을 제출했고, 새 영패 값을 만들어서 Session에 저장하라고 알립니다.사용자가 제출 데이터 페이지에 다시 접근할 때, 새로 생성된 영패 값을 숨겨진 입력 영역의 값으로 합니다.‍‍‍‍‍‍‍‍
3. 단계:
(1)struts.xml 프로필에 token 차단기 추가
<action name="doAddParameter" class="com.do.action.CaAction" method="doAddParameter">
      <interceptor-ref name="defaultStack" />
      <interceptor-ref name="token" />
 <result name="success" type="redirect">/car/listParameter.action?calculator_product_id=${#request.calculator_product_id}</result>
 </action>

여기서 중요한 코드는
(2) jsp 페이지에form 폼에 을 추가하고 jsp 머리에 <%@tagliburi='/struts-tags'prefix='s'%>
4. 소스 코드 분석:
(1) 라벨은struts-tags에 있습니다.tld 정의:
<tag>
    <name>token</name>
    <tag-class>org.apache.struts2.views.jsp.ui.TokenTag</tag-class>
    <body-content>JSP</body-content>
    <description><![CDATA[Stop double-submission of forms]]></description>
    <attribute>
      <name>accesskey</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
      <description><![CDATA[Set the html accesskey attribute on rendered html element]]></description>
    </attribute>
............
</tag>

주석: 위에서 가장 중요한 코드는 org입니다.apache.struts2.views.jsp.ui.TokenTag, 태그에 해당하는 클래스 지정
(2)TokenTag.java 소스:
/**
 * @see Token
 */
public class TokenTag extends AbstractUITag {
 
    private static final long serialVersionUID = 722480798151703457L;
 
    public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
        return new Token(stack, req, res);
    }
}

주석: 이 안에 new Token 대상.
(3)Token.java 소스:
@StrutsTag(name="token", tldTagClass="org.apache.struts2.views.jsp.ui.TokenTag", description="Stop double-submission of forms")
public class Token extends UIBean {
 
    public static final String TEMPLATE = "token";
 
    public Token(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
        super(stack, request, response);
    }
 
    protected String getDefaultTemplate() {
        return TEMPLATE;
    }
 
    /**
     * First looks for the token in the PageContext using the supplied name (or {@link org.apache.struts2.util.TokenHelper#DEFAULT_TOKEN_NAME}
     * if no name is provided) so that the same token can be re-used for the scope of a request for the same name. If
     * the token is not in the PageContext, a new Token is created and set into the Session and the PageContext with
     * the name.
     */
    protected void evaluateExtraParams() {
        super.evaluateExtraParams();
 
        String tokenName;
        Map parameters = getParameters();
        (1) map name , 
        if (parameters.containsKey("name")) {
            tokenName = (String) parameters.get("name");
        } else {
            if (name == null) {
                tokenName = TokenHelper.DEFAULT_TOKEN_NAME; //(2)<span></span> } else {
                tokenName = findString(name);
 
                if (tokenName == null) {
                    tokenName = name;
                }
            }
 
            addParameter("name", tokenName);
        }
 
        String token = buildToken(tokenName);
        addParameter("token", token);//(3) Token
        addParameter("tokenNameField", TokenHelper.TOKEN_NAME_FIELD);
    }
 
    /**
     * This will be removed in a future version of Struts.
     * @deprecated Templates should use $parameters from now on, not $tag.
     */
    public String getTokenNameField() {
        return TokenHelper.TOKEN_NAME_FIELD;
    }
     
<p>
    (4) Token
</p>
private String buildToken(String name) {
        Map context = stack.getContext();
        Object myToken = context.get(name);
 
        if (myToken == null) {
            myToken = TokenHelper.setToken(name);
            context.put(name, myToken);
        }
 
        return myToken.toString();
    }
}

(4)TokenHelper.setToken(name)
public static String setToken(String tokenName) {
        Map session = ActionContext.getContext().getSession();
        String token = generateGUID();
        try {
            session.put(tokenName, token);
        }
        catch(IllegalStateException e) {
            // WW-1182 explain to user what the problem is
            String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage();
            LOG.error(msg, e);
            throw new IllegalArgumentException(msg);
        }
 
        return token;
    }

참고: UUID가 생성되고 session에 저장됩니다.
위의 단계에서 Token이 생성되었고session에 저장되었습니다. 이제 차단기가 어떻게 처리되는지 봅시다.
(5) Struts2의 내장 차단기 에서 TokenInterceptor.java 소스:
* @see TokenSessionStoreInterceptor
 * @see TokenHelper
 */
public class TokenInterceptor extends MethodFilterInterceptor {
 
    private static final long serialVersionUID = -6680894220590585506L;
 
    public static final String INVALID_TOKEN_CODE = "invalid.token";
 
    /**
     * @see com.opensymphony.xwork2.interceptor.MethodFilterInterceptor#doIntercept(com.opensymphony.xwork2.ActionInvocation)
     */
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Intercepting invocation to check for valid transaction token.");
        }
 
        //see WW-2902: we need to use the real HttpSession here, as opposed to the map
        //that wraps the session, because a new wrap is created on every request
        HttpSession session = ServletActionContext.getRequest().getSession(true);
 
        synchronized (session) {
            //(1) Token 
            if (!TokenHelper.validToken()) {
                (2)Token , invalid.token
                return handleInvalidToken(invocation);
            }
        }
        //(3)Token , 
        return handleValidToken(invocation);
    }
 
    /**
     * Determines what to do if an invalid token is provided. If the action implements {@link ValidationAware}
     *
     * @param invocation the action invocation where the invalid token failed
     * @return the return code to indicate should be processed
     * @throws Exception when any unexpected error occurs.
     */
    protected String handleInvalidToken(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        String errorMessage = LocalizedTextUtil.findText(this.getClass(), "struts.messages.invalid.token",
                invocation.getInvocationContext().getLocale(),
                "The form has already been processed or no token was supplied, please try again.", new Object[0]);
 
        if (action instanceof ValidationAware) {
            ((ValidationAware) action).addActionError(errorMessage);
        } else {
            log.warn(errorMessage);
        }
 
        return INVALID_TOKEN_CODE;
    }
 
    /**
     * Called when a valid token is found. This method invokes the action by can be changed to do something more
     * interesting.
     *
     * @param invocation the action invocation
     * @throws Exception when any unexpected error occurs.
     */
    protected String handleValidToken(ActionInvocation invocation) throws Exception {
        return invocation.invoke();
    }
}

(6) Token이 TokenHelper에 적합한지 확인합니다.validToken () 소스:
public static boolean validToken() {
        String tokenName = getTokenName();//(1) tokenName
 
        if (tokenName == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("no token name found -> Invalid token ");
            }
            return false;
        }
 
        String token = getToken(tokenName); //(2) token , 
 
        if (token == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
            }
            return false;
        }
    //(3) session token 
        Map session = ActionContext.getContext().getSession();
        String sessionToken = (String) session.get(tokenName);
//(4) 2 token 
        if (!token.equals(sessionToken)) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{
                        token, sessionToken
                }));
            }
 
            return false;
        }
 
        // remove the token so it won't be used again
        //(5)token , session token 
        session.remove(tokenName);
 
        return true;
    }
 
public static String getTokenName() {
        Map params = ActionContext.getContext().getParameters();
 
        if (!params.containsKey(TOKEN_NAME_FIELD)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Could not find token name in params.");
            }
 
            return null;
        }
 
        String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
        String tokenName;
 
        if ((tokenNames == null) || (tokenNames.length < 1)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Got a null or empty token name.");
            }
 
            return null;
        }
 
        tokenName = tokenNames[0];
 
        return tokenName;
    }
 
   public static String getToken(String tokenName) {
        if (tokenName == null ) {
            return null;
        }
        Map params = ActionContext.getContext().getParameters();
        String[] tokens = (String[]) params.get(tokenName);
        String token;
 
        if ((tokens == null) || (tokens.length < 1)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Could not find token mapped to token name " + tokenName);
            }
 
            return null;
        }
 
        token = tokens[0];
 
        return token;
    }

여기서 마치겠습니다.

좋은 웹페이지 즐겨찾기