session防止重複提交
阿新 • • 發佈:2019-02-15
http://blog.chinaunix.net/uid-26284395-id-3044216.html
針對這個博文進行了優化,將servlet改寫為攔截器,其它action需要防止重複提交時只需在TokenInterceptor中的if下新增一個actionString;在頁面中新增EL表示式就ok了
struts2 token不僅可以防止重複提交還可以防禦CSRF,不過是在不存在XSS漏洞的情況下;使用struts2 token防止CSRF攻擊(http://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/),要保證token不會洩露,一般的做法是在記憶體中儲存一個唯一的token,有提交操作的表單頁面是使用CGI,PHP,JSP等生成的,保證只有你的頁面才能通過記憶體取到這個token,這樣跨站的網頁是無法偽造的。
最安全的防止CSRF就是圖片驗證碼了
1)TokenInterceptor.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.cmcc.db.session.TokenProcessor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy ;
public class TokenInterceptor extends org.apache.struts2.interceptor.TokenInterceptor {
private static final long serialVersionUID = 1L;
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
int count = 0;
HttpServletRequest req = (HttpServletRequest) invocation
.getInvocationContext ().get(ServletActionContext.HTTP_REQUEST);
HttpServletResponse resp = (HttpServletResponse) invocation
.getInvocationContext().get(ServletActionContext.HTTP_RESPONSE);
resp.setContentType("text/html;charset=GBK");
ActionProxy proxy = invocation.getProxy();
String actionString = proxy.getNamespace() + "/"
+ proxy.getActionName() + "!" + proxy.getMethod() + ".action";
if (actionString.startsWith("/user/user!add.action")) {
TokenProcessor processor = TokenProcessor.getInstance();
if (processor.isTokenValid(req)) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println("submit : " + count);
if (count % 2 == 1)
count = 0;
else
count++;
System.out.println("success");
return invocation.invoke();
} else {
processor.saveToken(req);
System.out.println("你已經提交了表單,同一表單不能提交兩次。");
return null;
}
} else {
return invocation.invoke();
}
}
}
2)TokenProcessor.java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class TokenProcessor {
static final String TOKEN_KEY = "org.sunxin.token";
private static TokenProcessor instance = new TokenProcessor();
public static TokenProcessor getInstance()
{
return instance;
}
/**
*
* 最近一次生成令牌值的時間戳。
*/
private long previous;
/**
*
* 判斷請求引數中的令牌值是否有效。
*/
public synchronized boolean isTokenValid(HttpServletRequest request) {
// 得到請求的當前Session物件。
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
// 從Session中取出儲存的令牌值。
String saved = (String) session.getAttribute(TOKEN_KEY);
if (saved == null) {
return false;
}
// 清除Session中的令牌值。
resetToken(request);
// 得到請求引數中的令牌值。
String token = request.getParameter(TOKEN_KEY);
if (token == null) {
return false;
}
return saved.equals(token);
}
/**
* 清除Session中的令牌值。
*/
public synchronized void resetToken(HttpServletRequest request)
{
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(TOKEN_KEY);
}
/**
* 產生一個新的令牌值,儲存到Session中, 如果當前Session不存在,則建立一個新的Session。
*/
public synchronized void saveToken(HttpServletRequest request) {
HttpSession session = request.getSession();
String token = generateToken(request);
if (token != null) {
session.setAttribute(TOKEN_KEY, token);
}
}
/**
* 根據使用者會話ID和當前的系統時間生成一個唯一的令牌。
*/
public synchronized String generateToken(HttpServletRequest request) {
HttpSession session = request.getSession();
try {
byte id[] = session.getId().getBytes();
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);
md.update(now);
return toHex(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
}
}
/**
* 將一個位元組陣列轉換為一個十六進位制數字的字串。
*/
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();
}
/**
* 從Session中得到令牌值,如果Session中沒有儲存令牌值,則生成一個新的令牌值。
*/
public synchronized String getToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (null == session)
return null;
String token = (String) session.getAttribute(TOKEN_KEY);
if (null == token) {
token = generateToken(request);
if (token != null) {
session.setAttribute(TOKEN_KEY, token);
return token;
} else
return null;
} else
return token;
}
}
3)其它程式碼
①需要防止重複提交的頁面
<%@ page import="com.cmcc.db.session.TokenProcessor" language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
**<%
//獲取令牌類例項
TokenProcessor processor = TokenProcessor.getInstance(); //獲取令牌值
String token = processor.getToken(request);
%>**
<head>
<title>新增使用者資訊介面</title>
</head>
<body>
<form method="post" action="user!add.action" name="form">
</form>
<form action="user!add.action" method="post">
<%--使用隱藏域儲存生成的token--%>
<%--
<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
--%>
<%--使用EL表示式取出儲存在session中的token--%>
<table>
<tr>
<td>使用者名稱:<input type="text" name="userName" id="userName"></td>
</tr>
<tr>
<td>年齡:<input type="text" name="userAge" id="userAge"></td>
</tr>
<tr>
<td>地址:<input type="text" name="userAddress" id="userAddress"></td>
</tr>
<tr>
<td>**<input type="hidden" name="org.sunxin.token" value="<%=token%>"/>**
<!-- 作為hidden提交,request的token -->
<input type="submit" value="提交" style="background-color: pink">
<input type="reset" value="重置" style="background-color: red"></td>
</tr>
</table>
</form>
</body>
</html>
②struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="struts.convention.default.parent.package"
value="cmcc-default" />
<constant name="struts.convention.package.locators" value="web" />
<constant name="struts.convention.package.locators.basePackage"
value="com.cmcc.db.web" />
<constant name="struts.convention.result.path" value="/WEB-INF/jsp/"/>
<constant name="struts.i18n.encoding" value="utf-8" />
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<package name="cmcc-default" extends="convention-default">
<interceptors>
<interceptor name="loginInter" class="com.cmcc.db.base.LoginInterceptor" />
<interceptor name="token" class="com.cmcc.db.base.TokenInterceptor" />
<interceptor-stack name="webStack">
<interceptor-ref name="store">
<param name="operationMode">AUTOMATIC</param>
</interceptor-ref>
<interceptor-ref name="paramsPrepareParamsStack" />
<!-- <interceptor-ref name="loginInter" /> -->
<interceptor-ref name="token"/>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="webStack" />
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception"
result="error" />
</global-exception-mappings>
</package>
<!-- 使用Convention外掛,實現約定大於配置的零配置檔案風格. 特殊的Result路徑在Action類中使用@Result設定. -->
</struts>