控制添加器(使用拦截器防止表单重复提交)
Posted
篇首语:男儿欲遂平生志,五经勤向窗前读。本文由小常识网(cha138.com)小编为大家整理,主要介绍了控制添加器(使用拦截器防止表单重复提交)相关的知识,希望对你有一定的参考价值。
控制添加器(使用拦截器防止表单重复提交)
业务场景介绍
web系统经常会出现用户在页面上快速点击多次提交按钮(或者重复刷新页面),在后台会连续接收多次请求,除了第一次外,其他的相同请求就是重复提交。
如何避免页面重复提交呢,正常有以下几种方法:
- 前台控制:点击后,使用js将按钮事件移除
- 后台控制:生成token存储session中,使用页面拦截器校验
- 后台控制:生成token放入redis中并设置有效期,让其自动失效
方法1过于简单暴力,有一定效果;方法2有点复杂也不是很灵活,其实和方法2有点类似
这里要详细介绍的是方法2,通过拦截器+注解,使用简单灵活。
使用页面拦截器校验token,防止重复提交
基本原理
- 通过拦截器拦截页面请求
- 在打开页面时生成token,并存储在session中
- 页面上提交时将token传到后台,在拦截器中校验token
- 如果token不匹配则拒绝请求,并返回错误信息
- 如果token匹配,则删除或重新生成token
详细代码
1、添加springMvc请求拦截器AvoidDuplicateSubmitInterceptor.java
/** * 页面重复提交拦截器 * * @author huangjian * */public class AvoidDuplicateSubmitInterceptor extends HandlerInterceptorAdapter private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception if (handler instanceof HandlerMethod) HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); SubmitToken annotation = method.getAnnotation(SubmitToken.class); if (annotation == null) return true; HttpSession session = request.getSession(false); if (annotation.value().equals(TokenType.create)) SessionTokenUtil.createToken(session); return true; if (isRepeatSubmit(request)) logger.warn("页面重复提交,url:", request.getRequestURI()); RenderUtil.renderFailure("页面重复提交", response); return false; if (annotation.value().equals(TokenType.refresh)) // 验证成功后,重新生成token SessionTokenUtil.createToken(session); else // 验证成功后,删除token SessionTokenUtil.removeToken(session); return true; /** 判断是否重复提交 */ private boolean isRepeatSubmit(HttpServletRequest request) String serverToken = SessionTokenUtil.getToken(request.getSession(false)); if (serverToken == null) return true; String clientToken = request.getParameter(SessionTokenUtil.TOKEN_KEY); if (clientToken == null) return true; if (!serverToken.equals(clientToken)) return true; return false;
2、添加工具类SessionTokenUtil.java
public class SessionTokenUtil public static final String TOKEN_KEY = "submitToken"; /** 生成10位随机数作为token */ private static String createToken() return RandomUtils.generateString(10); public static void createToken(HttpSession session) session.setAttribute(SessionTokenUtil.TOKEN_KEY, createToken()); public static void removeToken(HttpSession session) session.removeAttribute(SessionTokenUtil.TOKEN_KEY); public static String getToken(HttpSession session) String serverToken = (String) session.getAttribute(TOKEN_KEY); return serverToken;
3、添加注解SubmitToken.java,以及枚举TokenType.java
@Retention(RetentionPolicy.RUNTIME)@Target( METHOD )public @interface SubmitToken TokenType value() default TokenType.create;
public enum TokenType create, remove, refresh
4、在controller中使用
1)在打开页面方法标记生成token(方法仅是示例)
/** 进入模块 详细/新增 页 */@SubmitToken(TokenType.create)@RequestMapping(value = "detail/id", method = RequestMethod.GET)public String detail(@PathVariable("id") Long id, Model model, HttpServletRequest request)
2)在提交保存方法标记刷新或删除token(方法仅是示例)
@SubmitToken(TokenType.refresh)@RequestMapping(value = "save", method = RequestMethod.POST)public void save(@Valid @ModelAttribute("preload") BsCx entity, HttpServletRequest request, HttpServletResponse response) try //TODO 执行保存操作... // 将新的token返回给页面,并在页面刷新token String serverToken = SessionTokenUtil.getToken(request.getSession(false)); RenderUtil.renderSuccess(serverToken, response); catch (Exception e) //
3)在页面上面添加token值,如果需要支持连续操作,要在保存成功后,更新submitToken值
<input type="hidden" id="submitToken" name="submitToken" th:value="$session.submitToken" />
上面是thymeleaf模板示例代码
5、配置拦截器
下面是springboot配置示例
@Configurationpublic class WebAppConfig implements WebMvcConfigurer @Override public void addInterceptors(InterceptorRegistry registry) registry.addInterceptor(new AvoidDuplicateSubmitInterceptor());
相关参考