发布网友 发布时间:2022-04-23 17:25
共1个回答
热心网友 时间:2022-04-07 07:43
利用同步令牌来解决重读提交的基本原理
1 用户访问包含表单的页面 服务器在这次会话中 创建一个session对象 并产生一个令牌值 将这个令牌值作为隐藏输入域值 随表单一起发送到客户端 同时将令牌值保存到session中
2 用户提交页面 服务器端首先判断请求参数中的令牌值和Session中保存的令牌值是否相等 如果相等 则清楚session的令牌值 然后执行数据处理操作 如果不相等 则提示用户已经提交过表单 同时产生一个新的令牌值保存到session中 当用户重读提交数据页面的时候 将新产生的令牌值最为隐藏输入域的值
TokenProcessor类主要提供下列方法
public java.lang.String generateToken(HttpServletRequest request)
根据当前用户会话ID和当前的系统时间生成一个唯一的令牌值
public void savaToken(HttpServletRequest request)
调用generateToken()方法产生一个令牌值 并把它保存到Session中 如果Session不存在 则创建一个新的Session
public void resetToken(HttpServletRequest request)
清楚保存在用户Session中的令牌值
public boolean isTokenValid(HttpServletRequest request)
public boolean isTokenValid(HttpServletRequest request, boolean reset)
以上两种方法获取请求参数中的令牌值 并与保存在用户Session中的令牌值进行比较 判断是否相等
参数reset表示检测后是否要清楚保存在用户Session中的令牌值 前一个方法调用后一个方法 并给reset传递参数false 即在检测后不清楚Session的令牌值
例子:
1 令牌处理类 Tokenprocessor.java
package org.sunxin.ch19.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* TokenProcessor类是一个单例类。
*/
public class TokenProcessor
{
static final String TOKEN_KEY="org.sunxin.token";
private static TokenProcessor instance = new TokenProcessor();
/**
* getInstance()方法得到单例类的实例。
*/
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;
}
}
2 index.jsp
增加一个隐藏域 并以服务器端产生的令牌值作为他的值
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %> //封装了request.getContextPath
<%@ page import="org.sunxin.ch19.util.TokenProcessor" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'login.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<%
//获取令牌类实例
TokenProcessor processor = TokenProcessor.getInstance();
//获取令牌值
String token = processor.getToken(request);
%>
<form action="${ctx}/servlet/handle" name="theForm" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>密码:</td>
<td>
<input type="password" name="password"/>
<%--设置隐藏域,其值为令牌值--%>
<input type="hidden" name="org.sunxin.token" value="<%=token%>"/>
</td>
</tr>
<tr>
<td>
<input type="reset" value="重设">
</td>
<td>
<input type="submit" value="提交" name="btnSubmit" >
</td>
</tr>
</table>
</form>
</body>
</html>
3 HandlerServlet.java
package org.sunxin.ch19.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.sunxin.ch19.util.TokenProcessor;
public class HandlerServlet extends HttpServlet
{
int count=0;
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException,IOException
{
resp.setContentType("text/html;charset=GBK");
PrintWriter out=resp.getWriter();
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++;
out.println("success");
}
else
{
processor.saveToken(req);
out.println("你已经提交了表单,同一表单不能提交两次。");
}
out.close();
}
}