思路
- 在请求一个表单页面时,服务端生成一个随机的token,把token放入session中并回传到前端页面。
- 前端表单把token作为一个隐藏域提交给服务端。
- 服务端校验提交的token和session中的token是否一致来判断是否是重复提交,然后清除session中的token。
想法
- 服务端:在请求一个表单页面的controller的方法上加上注解@Token,即可生成token,放入session并回传到前端。
- 客户端:我使用的是freemarker,想使用宏定义来封装一下表单组件,如果这个表单需要做防重复提交,只需要传入token=true即可,它会自动加入一个隐藏域,其值为服务端回传的token。
- 服务端:如果表单提交的controller的方法上有注解@Token(type = Token.Type.CHECK),那么就校验session中的token和提交的token是否一致,从而判断是否是重复提交。
实现
自定义运行时注解@Token
1 | package com.kangyonggan.cms.annotation; |
自定义SpringMVC拦截器
在SpringMVC的配置文件applicationContext-mvc.xml
中配置一个拦截器:
1 | <!--MVC拦截器--> |
HandlerInterceptor
的实现如下:
1 | package com.kangyonggan.cms.interceptor; |
使用
假设我现在需要修改用户信息,首先是请求一个用户修改页面,然后返回一个修改用户的表单页面,最后提交到服务端。
拦截请求页面的请求并生成token
@Token(key = "editUser")
1 | /** |
在表单中加入隐藏域
token=true
1 | <@c.form id="modal-form" action="${ctx}/dashboard/system/user/${isEdit?string('update', 'save')}" token=true> |
token=true
背后做了什么呢?其实就是在表单中加了一个隐藏域
1 | <input type="hidden" name="_token" value="${_token!''}"/> |
思考
我之所以给@Token注解添加一个key,是防止一种特殊的情况:
- 请求表单A,但是未提交。
- 请求表单B,也未提交。
- 回到表单A,提交。
如果没有设计key
, 那么所有表单的token放在session中的key都是一样的,后面请求的token就会覆盖前面的token,会导致前面的表单无法提交。