Struts2 token標籤:同步令牌標籤

2020-07-16 10:04:58
Struts 的 Token(令牌)機制能夠很好地解決表單重複提交的問題,基本原理是:伺服器端在處理用戶端的請求之前,會將請求中包含的令牌值與儲存在當前使用者對談中的令牌值進行比較,看是否匹配。在處理完該請求後,且在答復傳送給用戶端之前,將產生一個新的令牌,該令牌除傳給用戶端以外,也會替換使用者對談中儲存的舊令牌。這樣操作後,如果使用者回退到剛才的提交頁面並再次提交,用戶端傳過來的令牌就和伺服器端的令牌不一致,從而有效地防止重複提交。

語法

Token 令牌機制的作用

用來儲存請求中的令牌值,例如:

input type="hidden" name="userInfoName" value="userName"

value 是由 TokenProcessor 類中的 generateToken() 獲得的,是根據當前使用者的 session 物件和當前時間的 long 值來計算的。

在用戶端提交後,判斷請求中包含的值是否和伺服器的令牌一致,因為伺服器每次提交都會生成新的Token,因此如果是重複提交,用戶端的Token值和伺服器端的Token值就會不一致。

Token 相關的方法

Struts 框架的 Token(同步令牌)機制在 org.apache.struts.action.Action 類中提供了一些 Token 相關的方法,下面就以在資料庫中插入一條資料來說明防止頁面重複提交的主要方法。

1) isTokenValid() 方法:

protected boolean isTokenValid(javax.servlet.http.HttpServletRequest request)

isTokenValid() 方法判斷儲存在當前使用者對談中的令牌值和請求引數中的令牌值是否相同。如果相同,就返回 true,如果符合以下情況之一,便會返回 false。
  • 令牌值不能存在HttpSession物件。
  • 在請求引數和session對談中沒有儲存令牌值。
  • 儲存在當前使用者session對談中的令牌值和請求引數中的令牌值不相同。

2) resetToken() 方法:

protected void resetToken(javax.servlet.http.HttpServletRequest request)

resetToken() 方法從當前 session 對談中刪除令牌屬性。

3) saveToken() 方法:

protected void saveToken(javax.servlet.http.HttpServletRequest request)

saveToken() 方法用來建立一個新的令牌,並把它儲存在當前 session 範圍內。如果 HttpSession 物件不存在,就首先建立一個 HttpSession 物件。

4) add() 方法:
在 Action 類的 add() 方法中,將令牌值儲存在頁面中只需增加一條語句:saveToken(request),關鍵程式碼如下:
public ActionForward add(ActionMapping mapping,ActionForm form,HttpServletRequest
                         request,HttpServletResponse response)
  saveToken(request);  //前面的處理省略
  return mapping.findForward("add");
}

5) insert() 方法:
在 Action 類的 insert() 方法中,將表單中的令牌值與伺服器端的令牌值進行比較,關鍵程式碼如下:
public ActionForward insert(ActionMapping mapping,ActionForm form,
                            HttpServletRequest request,HttpServletResponse response)
  if(isTokenValid(request,true)){  //表單不是重複提交//這裡是儲存資料的程式碼
  }else{  //表單重複提交
  s  aveToken(request);  //其他的處理程式碼
  }
}

範例

本範例應用 token 標籤實現防止使用者重複提交。

在新增使用者之前,首先把請求轉發給 PrepareInsertAction 類,它將呼叫 saveToken(request)方法建立一個新令牌,並把令牌儲存在當前 session 對談中,然後 PrepareInsertAction 類把請求轉發給新增使用者的 insert.jsp 頁面。PrepareInsertAction 類的關鍵程式碼如下:
public class PrepareInsertAction extends Action{
  public ActionForward execute(ActionMapping mapping,ActionForm form,
                               HttpServletRequest request,
                               HttpServletResponse response){
    saveToken(request);  //建立一個新令牌
    return mapping.findForward("prepareInsertAction");
  }
}

在 insert.jsp 中的 <html:form> 標籤的處理類中判斷 session 對談中是否存在令牌,如果存在,就在表單中生成一個包含令牌資訊的隱藏欄位。insert.jsp 頁面的關鍵程式碼如下:
<%@taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean"%>
  <%@taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html"%>
    <html:form action="userInfoAction.do">
      <table width="281" height="102" border="1">
        <tr>
          <td width="73" height="26" bgcolor="#000000"><div align="center" class=
                                                          "word_white">姓名</div></td>
          <td width="192"><div align="center">
            <html:text property="name"/><html:errors property="name"/>//定義文字方塊表單項
            </div></td>
        </tr>
        <tr>
          <td height="32"bgcolor="#000000"><div align="center" class="word_white">年齡
            </div></td>
          <td><div align="center">
            <html:text property="age"/><html:errors property="age"/>
            </div></td>
        </tr>
        <tr>
          <td height="34"bgcolor="#000000"><div align="center" class="word_white">職業
            </div></td>
          <td><div align="center">
            <html:text property="profession"/><html:errors property="profession"/>
            </div></td>
        </tr>
      </table>
      <input type="submit" name="Submit2" value="提交">&nbsp;&nbsp;&nbsp;//提交按鈕
      <input type="reset" name="Submit" value="重置">&nbsp;&nbsp;&nbsp;//重置按鈕
      <a href="index.jsp">返回</a>//返回超級連結
    </html:form>

當使用者收到 insert.jsp 頁面後,在原始檔中會看到表單中定義了一個包含令牌資訊的隱藏欄位,顯示的程式碼如下:
<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value=
"a9bf32c5fade032e405947bfe15ea18f">

在使用者提交表單後,由 UserInfoAction 類處理請求,在 UserInfoAction 類中,首先呼叫 isTokenValid(request)方法,判斷當前 session 對談中的令牌值和請求引數中的令牌值是否相同。如果相同,就呼叫 resetToken(request)方法,從當前對談中刪除 Token,然後執行新增資料的操作。UserInfoAction 類的關鍵程式碼如下:
public ActionForward execute(ActionMapping mapping,ActionForm form,
                             HttpServletRequest request,
                             HttpServletResponse response){
  UserInfoForm userInfoForm = (UserInfoForm)form;  //獲取與表單對應的ActionForm物件
  userInfoForm.setAge(Integer.valueOf(request.getParameter("age")));  //設定ActionForm物件的age屬性
  userInfoForm.setName(Chinese.chinese(request.getParameter("name")));  //設定ActionForm物件的name屬性
  userInfoForm.setProfession(Chinese.chinese(request.getParameter("profession")));
  ActionMessages errors = new ActionMessages() ;  //建立ActionMessages物件
  if(!isTokenValid(request)){  //判斷session對談中的令牌值和請求引數中的值是否相等
    errors.add(ActionMessages.GLOBAL_MESSAGE,
               new ActionMessage("error.invalid.token"));  //向ActionMessages物件中新增物件
    saveErrors(request,errors);  //儲存ActionMessages物件
    saveToken(request);  //建立新的令牌
    request.setAttribute("success","錯誤!");  //將提示資訊儲存在request物件中
  }else{
    dao.addUserInfo(userInfoForm);  //新增使用者資訊的方法
    resetToken(request);
    request.setAttribute("success","新增使用者資訊成功!");
  }
  return mapping.findForward("success");
}

在 Struts-config.xml 檔案中設定 PrepareInsertAction 類和 UserInfoAction 類的關鍵程式碼如下:
<action-mappings>
  <action name="userInfoForm" path="/userInfoAction" scope="request" type=
          "com.action.UserInfoAction" validate="true">//設定Action
    <forward name="success" path="/success.jsp"/>//請求轉發地址
  </action>
  <action path="/prepareInsertAction" type="com.action.PrepareInsertAction">
    <forward name="prepareInsertAction" path="/insert.jsp"/>
  </action>
</action-mappings>

使用者提交表單後,如果使用瀏覽器的後退功能退回到剛才的 insert.jsp 頁面,並再次提交表單,其請求將由 UserInfoAction 類來處理,並彈出錯誤資訊。