springMVC中基于token防止表单重复提交方法

本文介绍了springMVC中基于token防止表单重复提交方法,分享给大家,具体如下:

实现思路:

在springmvc配置文件中加入拦截器的配置,拦截两类请求,一类是到页面的,一类是提交表单的。当转到页面的请求到来时,生成token的名字和token值,一份放到Redis缓存中,一份放传给页面表单的隐藏域。(注:这里之所以使用redis缓存,是因为tomcat服务器是集群部署的,要保证token的存储介质是全局线程安全的,而redis是单线程的)

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

实现方式:

TokenInterceptor.Java

package com.xxx.www.common.interceptor; 

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.xxx.cache.redis.IRedisCacheClient;
import com.xxx.common.utility.JsonUtil;
import com.xxx.www.common.utils.TokenHelper; 

/**
 *
 * @see TokenHelper
 */
public class TokenInterceptor extends HandlerInterceptorAdapter
{ 

  private static Logger log = Logger.getLogger(TokenInterceptor.class);
  private static Map<String , String> viewUrls = new HashMap<String , String>();
  private static Map<String , String> actionUrls = new HashMap<String , String>();
  private Object clock = new Object(); 

  @Autowired
  private IRedisCacheClient redisCacheClient;
  static
  {
    viewUrls.put("/user/regc/brandregnamecard/", "GET");
    viewUrls.put("/user/regc/regnamecard/", "GET"); 

    actionUrls.put("/user/regc/brandregnamecard/", "POST");
    actionUrls.put("/user/regc/regnamecard/", "POST");
  }
  {
    TokenHelper.setRedisCacheClient(redisCacheClient);
  } 

  /**
   * 拦截方法,添加or验证token
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  {
    String url = request.getRequestURI();
    String method = request.getMethod();
    if(viewUrls.keySet().contains(url) && ((viewUrls.get(url)) == null || viewUrls.get(url).equals(method)))
    {
      TokenHelper.setToken(request);
      return true;
    }
    else if(actionUrls.keySet().contains(url) && ((actionUrls.get(url)) == null || actionUrls.get(url).equals(method)))
    {
      log.debug("Intercepting invocation to check for valid transaction token.");
      return handleToken(request, response, handler);
    }
    return true;
  } 

  protected boolean handleToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  {
    synchronized(clock)
    {
      if(!TokenHelper.validToken(request))
      {
        System.out.println("未通过验证...");
        return handleInvalidToken(request, response, handler);
      }
    }
    System.out.println("通过验证...");
    return handleValidToken(request, response, handler);
  } 

  /**
   * 当出现一个非法令牌时调用
   */
  protected boolean handleInvalidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  {
    Map<String , Object> data = new HashMap<String , Object>();
    data.put("flag", 0);
    data.put("msg", "请不要频繁操作!");
    writeMessageUtf8(response, data);
    return false;
  } 

  /**
   * 当发现一个合法令牌时调用.
   */
  protected boolean handleValidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
  {
    return true;
  } 

  private void writeMessageUtf8(HttpServletResponse response, Map<String , Object> json) throws IOException
  {
    try
    {
      response.setCharacterEncoding("UTF-8");
      response.getWriter().print(JsonUtil.toJson(json));
    }
    finally
    {
      response.getWriter().close();
    }
  } 

}

TokenHelper.java

package com.xxx.www.common.utils; 

import java.math.BigInteger;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import com.xxx.cache.redis.IRedisCacheClient; 

/**
 * TokenHelper
 *
 */
public class TokenHelper
{ 

  /**
   * 保存token值的默认命名空间
   */
  public static final String TOKEN_NAMESPACE = "xxx.tokens"; 

  /**
   * 持有token名称的字段名
   */
  public static final String TOKEN_NAME_FIELD = "xxx.token.name";
  private static final Logger LOG = Logger.getLogger(TokenHelper.class);
  private static final Random RANDOM = new Random(); 

  private static IRedisCacheClient redisCacheClient;// 缓存调用,代替session,支持分布式 

  public static void setRedisCacheClient(IRedisCacheClient redisCacheClient)
  {
    TokenHelper.redisCacheClient = redisCacheClient;
  } 

  /**
   * 使用随机字串作为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 = generateGUID();
    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);
      redisCacheClient.listLpush(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[]) (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_NAME_FIELD))
    {
      LOG.warn("Could not find token name in params.");
      return null;
    } 

    String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
    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.debug("no token name found -> Invalid token ");
      return false;
    } 

    String token = getToken(request, tokenName); 

    if(token == null)
    {
      if(LOG.isDebugEnabled())
      {
        LOG.debug("no token found for token name " + tokenName + " -> Invalid token ");
      }
      return false;
    } 

    String tokenCacheName = buildTokenCacheAttributeName(tokenName);
    String cacheToken = redisCacheClient.listLpop(tokenCacheName); 

    if(!token.equals(cacheToken))
    {
      LOG.warn("xxx.internal.invalid.token Form token " + token + " does not match the session token " + cacheToken + ".");
      return false;
    } 

    // remove the token so it won't be used again 

    return true;
  } 

  public static String generateGUID()
  {
    return new BigInteger(165,RANDOM).toString(36).toUpperCase();
  } 

}

spring-mvc.xml

<!-- token拦截器-->
  <bean id="tokenInterceptor" class="com.xxx.www.common.interceptor.TokenInterceptor"></bean>
  <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="interceptors">
      <list>
        <ref bean="tokenInterceptor"/>
      </list>
    </property>
  </bean>

input.jsp 在form中加如下内容:

<input type="hidden" name="<%=request.getAttribute("xxx.token.name") %>" value="<%=token %>"/> 

<input type="hidden" name="xxx.token.name" value="<%=request.getAttribute("xxx.token.name") %>"/>

当前这里也可以用类似于struts2的自定义标签来做。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringMVC处理Form表单实例

    Spring MVC 表单处理例子下面的例子说明了如何编写一个简单的基于 web 的应用程序,它利用了使用 Spring 的 Web MVC 框架的 HTML 表单. 一 测试项目搭建 (1)新建Java Web项目,并引入几个SpringMVC项目所需要的jar包,项目结构和所需要的jar包如下: ①web.xml: <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3

  • SpringMVC实现数据绑定及表单标签

    首先理解数据绑定 为什么要使用数据绑定 基于HTTP特性,所有的用户输入的请求参数类型都是String,比如下面表单: 但我们提交后,为了将请求信息映射到模型中,还需要手动进行格式转换,此外还借助了一个中转对象productForm,其字段名称和Product一模一样,只是类型为String. @RequestMapping(value = "/product_save",method = RequestMethod.POST) public String saveProduct(Pr

  • SpringMVC中使用bean来接收form表单提交的参数时的注意点

    这是前辈们对于SpringMVC接收表单数据记录下来的总结经验: SpringMVC接收页面表单参数 springmvc请求参数获取的几种方法 下面是我自己在使用时发现的,前辈们没有记录的细节和注意点: 使用bean来接收form表单提交的参数时,pojo中必须含有默认的(即空的)构造函数,同时,需要设置到bean中的变量必须有setter方法. 注:以下代码均为示例代码,非本人实际运行代码,请自行补充. 例如:我有一个bean类是User,具有变量username和password.同时,表单

  • Spring MVC---数据绑定和表单标签详解

    数据绑定和表单标签 数据绑定 数据绑定是将用户输入绑定到领域模型的一种特性,在Spring MVC的controller和view数据传递中,基于HTTP请求的特性,所有HTTP请求参数的类型均为字符串,如果模型领域需要绑定的类型为double或int,则需要手动进行类型转换,而有了数据绑定后,就不需要手动将HTTP请求中的String类型转换为模型需要的类型了,数据绑定的另一个好处是,当输入验证失败时,会重新生成一个HTML表单,无需重新填写输入字段. 表单标签库 表单标签库中包含了可以用在J

  • Spring MVC中基于自定义Editor的表单数据处理技巧分享

    面向对象的编程方式极大地方便了程序员在管理数据上所花费的精力.在基于Spring MVC的Web开发过程当中,可以通过对象映射的方式来管理表单提交上来的数据,而不用去一个一个地从request中提取出来.另外,这一功能还支持基本数据类型的映射.例如in.long.float等等.这样我们就能从传统单一的String类型中解脱出来.然而,应用是灵活的.我们对数据的需求是千变万化的.有些时候我们需要对表单的数据进行兼容处理. 例如日期格式的兼容: 中国的日期标注习惯采用yyyy-MM-dd格式,欧美

  • [Spring MVC] -简单表单提交实例

    Spring MVC自带的表单标签比较简单,很多时候需要借助EL和JSTL来完成. 下面是一个比较简单的表单提交页面功能: 1.User model package com.my.controller.bean; import java.util.Date; import java.util.List; import javax.validation.constraints.Future; import javax.validation.constraints.Max; import javax

  • SpringMVC接收页面表单参数

    1.直接把表单的参数写在Controller相应的方法的形参中 @RequestMapping("/addUser1") public String addUser1(String userName,String password) { System.out.println("userName is:"+userName); System.out.println("password is:"+password); return "/us

  • SpringMVC表单标签使用详解

    在使用SpringMVC的时候我们可以使用Spring封装的一系列表单标签,这些标签都可以访问到ModelMap中的内容.下面将对这些标签一一介绍. 在正式介绍SpringMVC的表单标签之前,我们需要先在JSP中声明使用的标签,具体做法是在JSP文件的顶部加入以下指令: <%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %> 1.1.form标签 使用Sprin

  • springMVC中基于token防止表单重复提交方法

    本文介绍了springMVC中基于token防止表单重复提交方法,分享给大家,具体如下: 实现思路: 在springmvc配置文件中加入拦截器的配置,拦截两类请求,一类是到页面的,一类是提交表单的.当转到页面的请求到来时,生成token的名字和token值,一份放到Redis缓存中,一份放传给页面表单的隐藏域.(注:这里之所以使用redis缓存,是因为tomcat服务器是集群部署的,要保证token的存储介质是全局线程安全的,而redis是单线程的) 当表单请求提交时,拦截器得到参数中的toke

  • PHP使用token防止表单重复提交的方法

    本文实例讲述了PHP使用token防止表单重复提交的方法.分享给大家供大家参考,具体如下: <?php /* * PHP使用token防止表单重复提交 * 此处理方法纯粹是为了给初学者参考 */ session_start(); function set_token() { $_SESSION['token'] = md5(microtime(true)); } function valid_token() { $return = $_REQUEST['token'] === $_SESSION

  • php通过记录IP来防止表单重复提交方法分析

    本文实例分析了php通过记录IP来防止表单重复提交方法.分享给大家供大家参考.具体分析如下: 这个原理比较的简单就是用户第一次提交时我们记录提交用户的IP地址,这样如果用户在固定时间内再次提交表单就会提示重复提交了,这种做法通常用于在顶一下,支持一下这种应用中了,在防止数据重复提交是一个非常不好的选择. 例子,代码如下: 复制代码 代码如下: <?php session_start(); if(empty($_SESSION['ip']))//第一次写入操作,判断是否记录了IP地址,以此知道是否

  • PHP+Session防止表单重复提交的解决方法

     index.php 当前表单页面is_submit设为0 SESSION_START(); $_SESSION['is_submit'] = 0; <form id="reg" action="post.php" method="post"> <p>用户名:<input type="text" class="input" name="username" i

  • PHP实现防止表单重复提交功能【基于token验证】

    本文实例讲述了PHP实现防止表单重复提交功能.分享给大家供大家参考,具体如下: 防止表单重复提交的方法有很多种,那么今天就给大家介绍一种php如何有效的防止表单重复提交. 代码非常简单 我相信大家很聪明给大家分享一个小的demo,大家可以借鉴一下: 具体代码: <?php /* * 2016年9月29日08:09:13 */ session_start(); header("Content-Type: text/html;charset=utf-8"); function set

  • springmvc 防止表单重复提交的两种方法

    目录 1.通过session中的token验证 步骤1:创建自定义注解 步骤2:创建自定义拦截器(@slf4j是lombok的注解) 步骤3:将自定义拦截器添加到配置文件 2.通过当前用户上一次请求的url和参数验证重复提交 步骤1:创建自定义注解 步骤2:创建自定义拦截器 步骤3:将自定义拦截器添加到配置文件 最近在本地开发测试的时候,遇到一个表单重复提交的现象. 因为网络延迟的问题,我点击了两次提交按钮,数据库里生成了两条记录.其实这种现象以前也有遇到过,一般都是提交后把按钮置灰,无法再次提

  • 详解struts2的token机制和cookie来防止表单重复提交

    详解struts2的token机制和cookie来防止表单重复提交 今天在做一个投票系统时要实现防止表单重复提交! 当时就想到了用struts2提供的token机制 struts2的token机制防止表单重复提交: 首先需要在提交的jsp页面(要使用token机制,必须使用struts2提供的标签库)加上 <s:token></s:token> 这段代码,然后在struts.xml里面需要进行如下配置: <action name="token" class

  • springMVC如何防止表单重复提交详解

    目录  前言 防止表单重复提交 单机 实现的思路步骤 代码实现 分布式 实现的思路步骤 代码实现 总结  前言 在系统中,有些接口如果重复提交,可能会造成脏数据或者其他的严重的问题,所以我们一般会对与数据库有交互的接口进行重复处理 首先可以在前端做一层控制.当前端触发操作时,或弹出确认界面,或 disable 禁用按钮等等,但是这并不能彻底解决问题.假设我们不是从客户端提交,而是被其他的系统调用,还会遇到这种问题 为了彻底解决问题,还需要在后端对接口做防重处理 一般会引起表单重复提交的场景 在网

  • JSP使用自定义标签防止表单重复提交的方法

    本文实例讲述了JSP使用自定义标签防止表单重复提交的方法.分享给大家供大家参考.具体如下: 1. 编写servelt: package cn.itcast.apsliyuan.web.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletReques

  • 详谈表单重复提交的三种情况及解决方法

    第一种情况:提交完表单以后,不做其他操作,直接刷新页面,表单会提交多次. - 在servlet中写一句输出,用来判断是否提交多次 System.out.println("已经插入"); request.getRequestDispatcher("/login_success.jsp").forward(request, response); - 这样的话,刷新多少次,就会在控制器显示多少个"已经插入". - 根本原因:Servlet处理完请求以后

随机推荐