集群 分布式WEB开发如何阻止重复提交

1 篇文章 0 订阅
1 篇文章 0 订阅

作者:许瑜钊


实现思路

   在springmvc配置文件中加入拦截器的配置,当转到页面的请求到来时,生成token的名字和token值,一份放到redis缓存中,一份放传给页面表单的隐藏域。

当表单请求提交时,拦截器得到参数中的tokenName和token,然后到缓存中去取token值,如果能匹配上,请求就通过,不能匹配上就不通过。这里的tokenName生成时也是随机的,每次请求都不一样。而从缓存中取token值时,会立即将其删除(删与读是原子的,无线程安全问题)。


实现方式

1.新建注解:

import  java.lang.annotation.ElementType;
import  java.lang.annotation.Retention;
import  java.lang.annotation.RetentionPolicy;
import  java.lang.annotation.Target;
/**
  * <p>
  * 防止重复提交注解,用于方法上<br/>
  * 在新建页面方法上,设置needSaveToken()为true,此时拦截器会在Session中保存一个token,
  * 同时需要在新建的页面中添加
  * <input type="hidden" id="ffzx.tokens" name="ffzx.tokens" value="${(token)  !}">
  * <br/>
  * 保存方法需要验证重复提交的,设置needRemoveToken为true
  * 此时会在拦截器中验证是否重复提交
  * </p>
  * @author: 张明
  * @date: 2016-7-18上午11:14:02
  *
  */
@Target (ElementType.METHOD)
@Retention (RetentionPolicy.RUNTIME)
public  @interface  PreventDuplicateSubmission {
     boolean  needSaveToken()  default  false ;
     boolean  needRemoveToken()  default  false ;
}

2.Token工具类

import  javax.servlet.http.HttpServletRequest;
import  org.apache.log4j.Logger;
import  java.util.Map;
/**
  * @author zhangming
  * @date 2016/7/18
  */
public  class  TokenUtils {
     private  static  final  Logger LOG = Logger.getLogger(TokenUtils. class );
     private  static  RedisUtil redisUtil;
     public  static  RedisUtil getRedisCacheClient() {
         return  redisUtil;
     }
     public  static  void  setRedisCacheClient(RedisUtil redisUtil) {
         TokenUtils.redisUtil = redisUtil;
     }
     /**
      * 保存token值的默认命名空间
      */
     public  static  final  String TOKEN_NAMESPACE =  "ffzx.tokens" ;
     /**
      * 持有token名称的字段名
      */
     public  static  final  String TOKEN_NAME_FIELD =  "token" ;
     /**
      * 使用UUID字串作为token名字保存token
      *
      * @param request
      * @return token
      */
     public  static  String setToken(HttpServletRequest request) {
         return  setToken(request, generateGUID());
     }
     /**
      * 使用给定的字串作为token名字保存token
      *
      * @param request
      * @param tokenName
      * @return token
      */
     private  static  String setToken(HttpServletRequest request, String tokenName) {
         String token = tokenName;
         setCacheToken(request, tokenName, token);
         return  token;
     }
     /**
      * 保存一个给定名字和值的token
      *
      * @param request
      * @param tokenName
      * @param token
      */
     private  static  void  setCacheToken(HttpServletRequest request, String tokenName, String token) {
         try  {
             String tokenName0 = buildTokenCacheAttributeName(tokenName);
             redisUtil.set(tokenName0, token);
             request.setAttribute(TOKEN_NAME_FIELD, tokenName);
             request.setAttribute(tokenName, token);
         catch  (IllegalStateException e) {
             String msg =  "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: "
                     + e.getMessage();
             LOG.error(msg, e);
             throw  new  IllegalArgumentException(msg);
         }
     }
     /**
      * 构建一个基于token名字的带有命名空间为前缀的token名字
      *
      * @param tokenName
      * @return the name space prefixed session token name
      */
     public  static  String buildTokenCacheAttributeName(String tokenName) {
         return  TOKEN_NAMESPACE +  ":"  + tokenName;
     }
     /**
      * 从请求域中获取给定token名字的token值
      *
      * @param tokenName
      * @return the token String or null, if the token could not be found
      */
     public  static  String getToken(HttpServletRequest request, String tokenName) {
         if  (tokenName ==  null ) {
             return  null ;
         }
         Map<?, ?> params = request.getParameterMap();
         String[] tokens = (String[]) params.get(tokenName);
         String token;
         if  ((tokens ==  null ) || (tokens.length <  1 )) {
             LOG.warn( "Could not find token mapped to token name "  + tokenName);
             return  null ;
         }
         token = tokens[ 0 ];
         return  token;
     }
     /**
      * 从请求参数中获取token名字
      *
      * @return the token name found in the params, or null if it could not be
      *         found
      */
     public  static  String getTokenName(HttpServletRequest request) {
         Map<?, ?> params = request.getParameterMap();
         if  (!params.containsKey(TOKEN_NAMESPACE)) {
             LOG.warn( "Could not find token name in params." );
             return  null ;
         }
         String[] tokenNames = (String[]) params.get(TOKEN_NAMESPACE);
         String tokenName;
         if  ((tokenNames ==  null ) || (tokenNames.length <  1 )) {
             LOG.warn( "Got a null or empty token name." );
             return  null ;
         }
         tokenName = tokenNames[ 0 ];
         return  tokenName;
     }
     /**
      * 验证当前请求参数中的token是否合法,如果合法的token出现就会删除它,它不会再次成功合法的token
      *
      * @return 验证结果
      */
     public  static  boolean  validToken(HttpServletRequest request) {
         String tokenName = getTokenName(request);
         if  (tokenName ==  null ) {
             LOG.warn( "no token name found -> Invalid token " );
             return  false ;
         }
         String tokenCacheName = buildTokenCacheAttributeName(tokenName);
         String cacheToken = (String) redisUtil.get(tokenCacheName);
         if  (!tokenName.equals(cacheToken)) {
             LOG.warn( "invalid.token Form token "  + tokenName +  " does not match the session token "
                     + cacheToken +  "." );
             return  false ;
         }
         redisUtil.remove(tokenCacheName);
         return  true ;
     }
     public  static  String generateGUID() {
         return  UUIDGenerator.getUUID();
     }
}

3. 新建拦截器:

import  java.lang.reflect.Method;
import  javax.annotation.Resource;
import  javax.servlet.http.HttpServletRequest;
import  javax.servlet.http.HttpServletResponse;
import  org.apache.log4j.Logger;
import  org.springframework.web.method.HandlerMethod;
import  org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import  com.ffzx.commerce.framework.annotation.PreventDuplicateSubmission;
import  com.ffzx.commerce.framework.utils.RedisUtil;
import  com.ffzx.commerce.framework.utils.TokenUtils;
/**
  * @author zhangming
  * @date 2016/7/18
  */
public  class  PreventDuplicateSubmissionInterceptor  extends  HandlerInterceptorAdapter {
     private  static  final  Logger LOG = Logger.getLogger(PreventDuplicateSubmissionInterceptor. class );
     @Resource
     private  RedisUtil redisUtil;
     @Override
     public  boolean  preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws  Exception {
         if  (TokenUtils.getRedisCacheClient() ==  null ) {
             TokenUtils.setRedisCacheClient(redisUtil);
         }
         HandlerMethod handlerMethod = (HandlerMethod) handler;
         Method method = handlerMethod.getMethod();
         PreventDuplicateSubmission annotation = method.getAnnotation(PreventDuplicateSubmission. class );
         if  (annotation !=  null ) {
             boolean  needSaveSession = annotation.needSaveToken();
             if  (needSaveSession) {
                 String tokenString = TokenUtils.setToken(request);
                 LOG.debug( "本次请求的token已保存==>>"  + tokenString);
                 return  true ;
             }
             boolean  needRemoveSession = annotation.needRemoveToken();
             if  (needRemoveSession) {
                 if  (!TokenUtils.validToken(request)) {
                     return  false ;
                 }
             }
         }
         return  true ;
     }
}

4. 在Spring中配置:

< mvc:interceptors >       
     <!--  这里是拦截所有的请求, 用于解决重复提交的问题 -->
     < mvc:interceptor >
         < mvc:mapping  path = "/**"  />
         < bean  class = "com.ffzx.commerce.framework.interceptor.PreventDuplicateSubmissionInterceptor" ></ bean >
     </ mvc:interceptor >
</ mvc:interceptors >
5. 在相关方法中加入注解:
/**
  * 关于这个方法的用法是:在需要生成token的controller上增加@PreventDuplicateSubmission(needSaveToken=true),
  * 而在需要检查重复提交的controller上添加@PreventDuplicateSubmission(needRemoveToken =true)就可以了
  */ 
  @RequestMapping (value =  "/form" )
  @PreventDuplicateSubmission (needSaveToken =  true )
  public  String form(String id, ModelMap map){
      
 
  @RequestMapping ( "/save" )
  @ResponseBody
  @PreventDuplicateSubmission (needRemoveToken =  true )
  public  ResultVo save( @Valid  CustomerResource customerResource, BindingResult result) {
6.在新建页面中加入:
< input  type = "hidden"  id = "ffzx.tokens"  name = "ffzx.tokens"  value = "${(token) !}" >

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值