12306随想--防止恶意重复提交数据

  前几天在博客园看到一篇文章,讲述了某位程序猿收到银行卡诈骗短信,要求登录所谓的银行网站查询银行卡状态。很显然,骗子是想非法记录用户的银行卡卡号和密码来从事诈骗活动。或许大多数人只是一笑而过,当作垃圾短信给删除了。程序猿大部分都是闲得无聊,没事瞎折腾,这位园友自己写了个小程序,循环向该网站提交数据,结果大家都懂的,把别人网站搞崩了。<在这里也算是为民除害吧--仅代表个人观点>

       其实鄙人也曾有过这样的想法,但没有实施,在此思过,也对那些孜孜不倦编程,写博客,答疑解惑的各位园友表示钦佩和感谢,向你们看齐。

       联想到一直处于众矢之的深陷购票风波的12306,心里有些想法,不吐不快,欢迎吐槽。

       用没装抢票插件的浏览器打开12306的购票页面,每次点击蓝色的查询按钮后,按钮变灰无法点击,6秒钟之后恢复,可在此查询,很显然前端使用JS进行了处理。后来360,搜狗,猎豹浏览器都退出了自己的抢票插件,绕过了12306 JS控制,每次查询后立马可在次查询,一下子受到网友们的热捧,成为人人必备的抢票利器。<我一次都没抢到自己想要的票,够霉的吧!>

       这时,12306又跳出来了,约谈企业什么云云。再后来,使用抢票插件自动提交订单时没有任何提示,直接跳到登录页面,够让人崩溃的吧。

       我是中华人民共和国守法公民,不在此作过多评价,只谈谈我从网站设计方面的想法,抛砖引玉,欢迎讨论。

       首先,对数据的校验不能仅仅依靠前段,后台也应有相应的逻辑处理,防止非法操作,以保证数据的安全性和体统的可靠性。

       其次,在数据校验后,应当有相应的提示,特别是校验不通过时及时给用户以反馈,提升用户体验。

       下面使用Struts2开发小web模拟后台数据校验,防止同一IP重复恶意提交数据。

  1. 1.     Struts.xml配置

<package name="default" extends="struts-default">

       <default-action-ref name="index"/>

       <action name="index" >

           <result type="redirectAction">

              <param name="namespace">/Main</param>

              <param name="actionName">login_input</param>

           </result>

       </action>    

</package>

以上表示网站默认登录页,显示Login.jsp

<package name="Main" namespace="/Main" extends="struts-default">

       <action name="login_*" class="com.struts.action.LoginAction" method="{1}">

           <result name="input">/Login.jsp</result>

           <result name="success" type="redirectAction">main</result>

    </action>

<action name ="error">

           <result>/Error.jsp</result>

       </action>

</package>

以上表示发生错误时显示Error.jsp

说明:姑且对用户登录进行校验,如果短期内大量重复提交数据尝试登录就认为是恶意行为,30分钟内不允许登录,所提交的数据被拦截不会提交至数据库验证,直接导向至错误提示界面。

下面是错误提示界面,Error.jsp

<%@ page language="java" contentType="text/html; charset=GB18030"

    pageEncoding="GB18030"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=GB18030">

<title>error</title>

</head>

<body>

<p>由于你多次重复提交数据,被列为非法操作,请半小时后再尝试访问!</p>

<span style="color:red;">如有疑问,请联系管理员xxxxx!</span>

</body>

</html>

 

2.   Web.xml配置filter

<filter>

       <filter-name>IPFilter</filter-name>

       <filter-class>com.struts.filter.IPFilter</filter-class>

    </filter>

    <filter-mapping>

       <filter-name>IPFilter</filter-name>

       <url-pattern>/Main/login_login.action</url-pattern>

</filter-mapping>

以上表示对用户登录进行校验,在com.struts.filter.IPFilter类中进行处理。

 

3.   com.struts.filter.IPFilter

思路:记录每个IP访问的时间,次数,状态,这里时间记录第一次访问和最近一次访问的时间,次数为这段时间间隔内的访问次数,需要自定义对象进行封装。如果第一分钟内请求超过30次,或平均每分钟超过15次,IP为无效。<为什么不直接计算平均每分钟请求的次数,而要分为一分钟以内和大于一分钟呢?如果我点击的频率很高,但是只点击几次登陆,有可能频率超过15次/分>。每接收到客户端请求时,判断是否是全新的请求,如果是全新请求,将该IP和访问时间记录下来存入容器,否则判断距离上次访问是否超过半小时,再判断状态是否有效,再判断请求频率。

 

部分代码:

package com.struts.filter;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.FilterConfig;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

 

import java.util.HashMap;

import java.util.Map;

 

public class IPFilter implements Filter {

    IPContainer IPCon = null;

 

    public void destroy() {

       IPCon = null;

    }

 

    public void doFilter(ServletRequest req, ServletResponse res,

           FilterChain chain) throws IOException, ServletException {

       HttpServletRequest httpReq = (HttpServletRequest) req;

       String ClientIP = getIPAddress(httpReq);//获取IP

       System.out.println(ClientIP);

       Visiter visiter = new Visiter();

       visiter.setLatestlogin(System.currentTimeMillis());

       if (!IPCon.CheckVisiterIP(ClientIP, visiter)) {

           //校验IP

           ((HttpServletResponse)res).sendRedirect("error");

       } else {

           chain.doFilter(req, res);

       }

    }

 

    public void init(FilterConfig filterconfig) throws ServletException {

       IPCon = new IPContainer();

    }

 

    public String getIPAddress(HttpServletRequest req) {

       String ip = req.getHeader("x-forwarded-for");

       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

           ip = req.getHeader("Proxy-Client-IP");

       }

       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

           ip = req.getHeader("WL-Proxy-Client-IP");

       }

       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

           ip = req.getRemoteAddr();

        }

       return ip;

    }

 

    private class IPContainer {

       Map<String, Visiter> visiterMap;

 

       IPContainer() {

           visiterMap = new HashMap<String, Visiter>();

       }

 

       boolean CheckVisiterIP(String IP, Visiter visiter) {

           boolean validate = false;

           if (visiterMap.containsKey(IP)) {

              //不是全新请求

              Visiter vis = visiterMap.get(IP);

 

              if (visiter.getLatestlogin() - vis.getLatestlogin() > 1800000) {

                  // 距离上次访问超过30分钟

                  vis.setFirstlogin(visiter.getLatestlogin());

                  vis.setLatestlogin(visiter.getLatestlogin());

                  vis.setLogintimes(1L);

                  vis.setState(0);

                  validate = true;

              } else if (vis.getState() == 1) {

                  // IP 无效

                  validate = false;

              } else if ((visiter.getLatestlogin() - vis.getFirstlogin() < 60000 && vis

                     .getLogintimes() > 30)

                     || (visiter.getLatestlogin() - vis.getFirstlogin() > 60000 && vis

                            .getLogintimes()

                            * 60000

                            / (visiter.getLatestlogin() - vis

                                   .getFirstlogin()) > 15L)) {

                  // 第一分钟内请求超过三十次,或平均每分钟请求次数多余15次,当作非法请求

                  vis.setLatestlogin(visiter.getLatestlogin());

                  vis.setState(1);

                  validate = false;

              } else {

                  //请求处于正常频率,合法

                  vis.setLatestlogin(visiter.getLatestlogin());

                  vis.incrLoginTimes();

                  validate = true;

              }

           } else {

              //第一次访问

              visiter.setFirstlogin(visiter.getLatestlogin());

              visiter.incrLoginTimes();

              visiterMap.put(IP, visiter);

              validate = true;

           }

           return validate;

       }

    }

 

    class IPAudit {

       //略,监控Container,移除长时间不访问的IP,释放内存

    }

 

    class Visiter {

       private long firstlogin; // 第一次请求时间

       private long latestlogin; // 最近请求时间

       private long logintimes; // 请求次数

       private int state; // 状态,0 有效,1 无效

 

       Visiter() {

           firstlogin = 0L;

           latestlogin = 0L;

           logintimes = 0L;

           setState(0);

       }

 

       //略,get set

 

       public void incrLoginTimes() {

           logintimes += 1;

       }

    }

}

 

 

转载于:https://www.cnblogs.com/taosx/p/3331691.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值