Java后台防止重复提交

一、前言
由于网络原因,用户操作有误(连续点击两次以上提交按钮),或者页面卡顿等原因,可能会出现请求重复提交,造成数据库保存多条重复数据。后端实现拦截器防重。

那么如何防止请求重复提交呢?一般有两种解决方案:
第一种:前端处理,在提交完成之后,将按钮禁用/触发加载图标。
第二种:后端处理,使用拦截器拦截。

二、实现思路
使用拦截器防止请求重复提交,本文模仿若依防重给大家分享,利用 AOP 切面在进入方法前拦截,通过 Session 或 Redis 的 key-value 键值对存储,指定 key+url+消息头 来拼成字符串组成 key,使用 请求参数+时间 封装 map 对象赋值 value,当 key 不存在时,则为新的请求;若存在,则对请求参数以及请求的间隔时间进行判断是否重复提交。

1、自定义注解防止表单重复提交

package com.dian.jiao.interceptor.annotation;

import java.lang.annotation.*;

/**
 * 自定义注解防止表单重复提交
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    /**
     * 间隔时间(ms),小于此时间视为重复提交
     */
    public int interval() default 5000;

    /**
     * 提示消息
     */
    public String message() default "不允许重复提交,请稍候再试";
}


2、构建包装器

package com.dian.jiao.interceptor.wrapper;

import com.dian.jiao.util.ServletUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 构建可重复读取inputStream的request
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {

    public final String UTF8 = "UTF-8";

    private final byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
        super(request);
        request.setCharacterEncoding(UTF8);
        response.setCharacterEncoding(UTF8);

        body = ServletUtils.getBodyString(request).getBytes(UTF8);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public int available() throws IOException {
                return body.length;
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener
### 防止重复提交的Token机制 为了防止表单或请求的重复提交,在Java Web开发中通常会采用同步化令牌(synchronizer token pattern)。这种模式的核心思想是在每次生成页面时创建一个唯一的标识符(即token),并将该标识符存储在服务器端(通常是用户的HTTP Session中)。同时,这个token也会被嵌入到HTML表单中作为隐藏字段。当客户端提交表单时,服务端验证接收到的token是否与Session中的token匹配,并在成功处理后立即移除或更新此token。 以下是实现这一功能的具体方法: #### 创建自定义Tag来管理Token 可以利用JSP标签库简化token的管理和注入过程。例如,可以通过如下方式定义并使用一个简单的tag[^1]: ```jsp <%@tag description="Synchronizer Token" import="mypackage.TokenFormController"%> <input type="hidden" name="<%=TokenFormController.getTokenKey()%>" value="<%=session.getAttribute(TokenFormController.getTokenKey())%>" /> ``` 上述代码片段展示了如何动态生成包含唯一token的隐藏输入框。`TokenFormController.getTokenKey()` 方法负责返回用于标记当前会话中保存的token键名;而 `session.getAttribute(...)` 则获取实际的token值。 #### 后台逻辑处理流程 在接收前端传来的数据之前,先校验是否存在有效的token以及其正确性。如果一切正常,则继续执行业务操作,并清除或者重新生成新的token以防再次误触发提交行为。 具体步骤可能涉及以下几个方面: - **提取参数**: 获取来自请求体内的token字符串。 - **对比检查**: 将取得的token同储存在HttpSession里的副本做比较确认一致性。 - **清理资源**: 成功完成事务之后销毁旧有的token实例或将之替换掉以备下次调用所需。 下面给出一段伪代码表示整个控制流: ```java // 假设我们有一个名为 'form_token' 的隐藏域携带了我们的随机数串 String submittedToken = request.getParameter("form_token"); if (submittedToken != null && submittedToken.equals(session.getAttribute("expected_token"))) { // 如果两者相符则允许进一步动作... // 清理已使用的token避免重放攻击等问题发生 session.removeAttribute("expected_token"); // 执行真正的应用逻辑比如数据库写入等敏感任务 } else { // 报告错误给用户提示他们尝试非法活动或是网络异常引起的数据丢失状况 } ``` 值得注意的是,尽管这里讨论的重点是如何防范CSRF(跨站点伪造请求),但同样的技术也可以用来解决其他类型的重复提交场景下的安全性考量[^2]。 --- ### 示例代码展示完整的解决方案 假设我们需要保护某个注册界面免受恶意刷新带来的影响,那么可以在Servlet/Controller层面上加入这样的防护措施: ```java public class RegistrationHandler extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String clientToken = req.getParameter("csrf_token"); HttpSession sess = req.getSession(false); if(sess!=null){ Object serverSideTokenObj=sess.getAttribute("CSRF_TOKEN"); boolean isValid=(serverSideTokenObj instanceof String)&&((String)serverSideTokenObj).equals(clientToken); if(isValid){ // Process registration details safely // Invalidate used up tokens after successful validation. sess.removeAttribute("CSRF_TOKEN"); forwardToSuccessPage(req,resp); }else{ redirectToErrorPage(resp,"Invalid or missing CSRF protection."); } }else{ handleUninitializedSessionsLogic(); } } private static final long serialVersionUID=789L; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException ,IOException{ generateAndStoreCsrfTokenInSession(req); super.doGet(req,resp); } private void generateAndStoreCsrfTokenInSession(HttpServletRequest req){ Random rand=new SecureRandom(); byte[] bytes=new byte[16]; rand.nextBytes(bytes); Base64.Encoder encoder=Base64.getUrlEncoder().withoutPadding(); String newToken=encoder.encodeToString(bytes); req.getSession(true).setAttribute("CSRF_TOKEN",newToken); } ``` 以上程序段落清楚地说明了一个基于token的安全框架构建思路及其运作原理。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值