1)修改Struts的源码
⑴首先从官网下载Struts1的源码
我下载的是struts-1.3.10-src。
⑵找到 TokenProcessor 这个类
路径 :
struts-1.3.10-src\src\core\src\main\java\org\apache\struts\util
⑶修改源码
一共有6处错误。
这6处错误都很好修改:
①修改包名
②去掉一个多余引用
③添加2个常量 :
private static final String TOKEN_KEY = "TOKEN_KEY";
private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";
④修改 saveToken 方法
找到 saveToken 方法。
修改返回值为 String,然后 return token; 即可。
public synchronized String saveToken(HttpServletRequest request) {
HttpSession session = request.getSession();
String token = generateToken(request);
if (token != null) {
session.setAttribute(TRANSACTION_TOKEN_KEY, token);
}
return token;
}
这是修改后的完整类 (自己修改包名):
package com.test.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* TokenProcessor is responsible for handling all token related functionality.
* The methods in this class are synchronized to protect token processing from
* multiple threads. Servlet containers are allowed to return a different
* HttpSession object for two threads accessing the same session so it is not
* possible to synchronize on the session.
*
* @since Struts 1.1
*/
public class TokenProcessor {
private static final String TOKEN_KEY = "TOKEN_KEY";
private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";
/**
* The singleton instance of this class.
*/
private static TokenProcessor instance = new TokenProcessor();
/**
* The timestamp used most recently to generate a token value.
*/
private long previous;
/**
* Protected constructor for TokenProcessor. Use TokenProcessor.getInstance()
* to obtain a reference to the processor.
*/
protected TokenProcessor() {
super();
}
/**
* Retrieves the singleton instance of this class.
*/
public static TokenProcessor getInstance() {
return instance;
}
/**
* <p>Return <code>true</code> if there is a transaction token stored in
* the user's current session, and the value submitted as a request
* parameter with this action matches it. Returns <code>false</code>
* under any of the following circumstances:</p>
*
* <ul>
*
* <li>No session associated with this request</li>
*
* <li>No transaction token saved in the session</li>
*
* <li>No transaction token included as a request parameter</li>
*
* <li>The included transaction token value does not match the transaction
* token in the user's session</li>
*
* </ul>
*
* @param request The servlet request we are processing
*/
public synchronized boolean isTokenValid(HttpServletRequest request) {
return this.isTokenValid(request, false);
}
/**
* Return <code>true</code> if there is a transaction token stored in the
* user's current session, and the value submitted as a request parameter
* with this action matches it. Returns <code>false</code>
*
* <ul>
*
* <li>No session associated with this request</li> <li>No transaction
* token saved in the session</li>
*
* <li>No transaction token included as a request parameter</li>
*
* <li>The included transaction token value does not match the transaction
* token in the user's session</li>
*
* </ul>
*
* @param request The servlet request we are processing
* @param reset Should we reset the token after checking it?
*/
public synchronized boolean isTokenValid(HttpServletRequest request,
boolean reset) {
// Retrieve the current session for this request
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
// Retrieve the transaction token from this session, and
// reset it if requested
String saved =
(String) session.getAttribute(TRANSACTION_TOKEN_KEY);
if (saved == null) {
return false;
}
if (reset) {
this.resetToken(request);
}
// Retrieve the transaction token included in this request
String token = request.getParameter(TOKEN_KEY);
if (token == null) {
return false;
}
return saved.equals(token);
}
/**
* Reset the saved transaction token in the user's session. This
* indicates that transactional token checking will not be needed on the
* next request that is submitted.
*
* @param request The servlet request we are processing
*/
public synchronized void resetToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(TRANSACTION_TOKEN_KEY);
}
/**
* Save a new transaction token in the user's current session, creating a
* new session if necessary.
*
* @param request The servlet request we are processing
*/
public synchronized String saveToken(HttpServletRequest request) {
HttpSession session = request.getSession();
String token = generateToken(request);
if (token != null) {
session.setAttribute(TRANSACTION_TOKEN_KEY, token);
}
return token;
}
/**
* Generate a new transaction token, to be used for enforcing a single
* request for a particular transaction.
*
* @param request The request we are processing
*/
public synchronized String generateToken(HttpServletRequest request) {
HttpSession session = request.getSession();
return generateToken(session.getId());
}
/**
* Generate a new transaction token, to be used for enforcing a single
* request for a particular transaction.
*
* @param id a unique Identifier for the session or other context in which
* this token is to be used.
*/
public synchronized String generateToken(String id) {
try {
long current = System.currentTimeMillis();
if (current == previous) {
current++;
}
previous = current;
byte[] now = new Long(current).toString().getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(id.getBytes());
md.update(now);
return toHex(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
}
}
/**
* Convert a byte array to a String of hexadecimal digits and return it.
*
* @param buffer The byte array to be converted
*/
private String toHex(byte[] buffer) {
StringBuffer sb = new StringBuffer(buffer.length * 2);
for (int i = 0; i < buffer.length; i++) {
sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
}
return sb.toString();
}
}
2)编写jsp页面
页面很简单:一个表单。里面一个隐藏域,一个输入框,一个提交按钮。
值得注意的是:
在导包的时候可能会出现下面的情况:
这样可能下次再打开工程会报错,我们只要加一个空格即可。
3)编写Servlet
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean valid = TokenProcessor.getInstance().isTokenValid(request);
if (valid) {
// 移除随机生成的 TOKEN_KEY
TokenProcessor.getInstance().resetToken(request);
} else {
// 要重定向的 “重复提交” 的提示页面
response.sendRedirect(request.getContextPath() + "/...");
return;
}
String nameStr = request.getParameter("name");
System.out.println(nameStr);
// 要转发的 “提交成功” 的提示页面
request.getRequestDispatcher("/...").forward(request, response);
}
编写doPost方法,即可。
随机生成的 TOKEN_KEY
4)Tips
在修改 TokenProcessor 类时,添加的 2 个常量的其中一个:
private static final String TOKEN_KEY = “TOKEN_KEY”;
这个 TOKEN_KEY 所赋的值 与我们编写的 jsp 页面中的 隐藏域的 name 的值 要一致。
input type=”hidden”name=”TOKEN_KEY”
value=”%=TokenProcessor.getInstance().saveToken(request)%>”