token防表单重复提交

文章目录
  1. 1. 生成 token
  2. 2. 表单提交
  3. 3. 方法添加注解
  4. 4. 自定义注解
  5. 5. 自定义注解拦截器
  6. 6. 拦截器内容

生成 token

加载页面时候调用 initToken() 方法,生成 token 存入页面隐藏域中

<%--隐藏域-防止表单的重复提交--%>
<input type="hidden" name="examToken" id="examToken"></input>

表单提交

表单提交的时候带上 token

方法添加注解

springMVC 方法上添加注解 @TokenCheck(isCheckToken = true)

自定义注解

自定义注解 TokenCheck.java

// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
// 定义注解的作用目标**作用范围字段、枚举的常量/方法
@Target({ElementType.METHOD})
// 说明该注解将被包含在javadoc中
@Documented
public @interface TokenCheck {

/**
* 是否需要校验
*/
boolean isCheckToken() default false;
}

自定义注解拦截器

springMVC 配置文件 spring-servlet.xml 添加 <mvc:interceptors 拦截器

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.ycx.exam.web.comm.interceptor.TokenCheckDuplicateSubmitInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

拦截器内容

TokenCheckDuplicateSubmitInterceptor.java

在拦截器中判断TokenCheck注解isCheckToken是否为 true,如果为 true,则执行缓存校验;

先从 redis 缓存中获取到 token 集合,再从缓存中查询 token;

如果存在,则属于重复提交,返回;

如果不存在,则属于首次提交,将此token压入token集合中并将token集合放回redis中;

public class TokenCheckDuplicateSubmitInterceptor extends HandlerInterceptorAdapter {

private static final Logger log = Logger.getLogger(TokenCheckDuplicateSubmitInterceptor.class);

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
CacheService cacheService = (CacheService) context.getBean("cacheService");
if (cacheService == null) {
log.warn("token拦截器校验重复提交,缓存service为空!");
return true;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
TokenCheck annotation = method.getAnnotation(TokenCheck.class);
if (annotation != null) {
boolean isCheckToken = annotation.isCheckToken();
if (isCheckToken) {
boolean isExist = true;
boolean isgetLock = getLock(cacheService, "examTokenLock", 1);
if (isgetLock) {
isExist = checkTokenExist(request, cacheService);
if (!isExist) {
log.warn("token拦截重复提交校验" + method.getName() + "重复提交!");
}
}
cacheService.releaseLock("examTokenLock");
return isExist;
}
}
} else {
try {
return super.preHandle(request, response, handler);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}

/**
* @param cacheService
* @param cacheKey
* @param second 秒
* @return
*/
private boolean getLock(CacheService cacheService, String cacheKey, long second) {
boolean isgetLock = cacheService.getLock(cacheKey, "1", second);
//如果没有获得锁,将默认进行三次锁的获取
if (!isgetLock) {
log.info("获得分布式锁失败,进入等待...........");
for (int i = 0; i <= 2; i++) {
isgetLock = cacheService.getLock(cacheKey, "1", second);
try {
if (!isgetLock) {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (isgetLock) {
break;
}
}
}
return isgetLock;
}

/**
* 缓存校验
*
* @param request
* @param cacheService
* @return
*/
private boolean checkTokenExist(HttpServletRequest request, CacheService cacheService) {
String token = request.getParameter("examToken");
if (StringUtils.isEmpty(token)) {
log.warn("token拦截器校验重复提交,页面提交过来token为空!");
return true;
}
token = token.trim();
LRULinkedHashMap<String, String> cmap = (LRULinkedHashMap<String, String>) cacheService.getCacheData("examTokenMap");
if (cmap == null) {
int size = 131072;
// 最近最少使用算法,linkedHashMap实现,主要是针对缓存过期策略实现
cmap = new LRULinkedHashMap<String, String>(size);
} else {
log.info("token缓存map存在,size=" + cmap.size());
}
String ieExist = cmap.get(token);
if (StringUtils.isEmpty(ieExist)) {
log.info("缓存不存在token=" + token);
String valu = cmap.put(token, "1");
cacheService.setCacheDataForType("examTokenMap", cmap, 1, TimeUnit.HOURS);
log.info("将token=" + token + "加入缓存成功!");
return true;
} else {
log.info("将token=" + token + "已经存在,重复提交!");
return false;
}
}
}