后台验证码实现,附代码详解

验证码后台验证

登陆页面的验证码一直是大家头痛的问题,目前多数的验证码走的都是前端验证,但前端验证有许多危险漏洞,本文是一个简单的后台验证码实现,详细请看如下所述。

实现方式:我的框架是struts+spring+jdbc 为了越过struts 配置文件 选择使用serverlet 方式。

先附图看下验证码样式


第一步前端页面

jsp页面代码如下,其中引用了两个js文件(js中触发from表单提交)

<script type="text/javascript" src="js/login.js"></script>

<!-- 验证码图片 要自己写一个输入的文本框 -->
    <img src="/ZQZC-WEB/codeimage" id="Verify"  style="position: relative;top:333px;left:307px;width:68px;height:36px;" alt="看不清,换一张"/> 

<script src="js/JSCode.js"></script>
<script>


<form action="sys/loginAction.do"  method="post" id="loginForm">
//<input type="hidden" name="isIOS" id="isIOS" value=""/>
//<input type="hidden" name="password" id="password" value=""/>
//<input type="hidden" name="loginName" id="loginName" value=""/>
<input type="hidden" name="codeCon" id="codeCon_" value=""/>
</form>


$(function(){    
      //点击图片更换验证码  
     $("#Verify").click(function(){
            /* 点击图片发送请求,设置了struts拦截器允许该请求通过 */
            $(this).attr("src","/ZQZC-WEB/codeimage?timestamp="+new Date().getTime());  
        });
     });        
</script>


login.js 其中js中有些代码没用到可根据个人使用删除

/**
* 登陆调用的方法
*/
function checklogin(){
    if (!$("#inputloginName").val()){
        $("#message").html("请输入用户名!");
        $("#inputloginName").focus();
        return false;
    }
    var loginNameReg = /^[a-zA-Z0-9_]{3,32}$/i;
    if (!loginNameReg.test($("#inputloginName").val())){
        $("#message").html("用户名非法,请重新输入!");
        $("#inputloginName").focus();
        return false;
    }

    if (!$("#inputpassword").val()){
        $("#message").html("请输入密码!");
        $("#inputpassword").focus();
        return false;
    }

    if (!$("#codeCon").val()){
        $("#message").html("请输入验证码!");
        $("#codeCon").focus();
        return false;
    }
    
    $("#password").val($.md5($("#inputpassword").val()));
    $("#loginName").val($("#inputloginName").val());
    //新增验证码
    $("#codeCon_").val($("#codeCon").val());
    $("#loginForm").submit();
}

/**
* 为了在火狐和谷歌下支持回车可以调用登陆
*/
function keypress(e)
{
    var currKey=0,e=e||event;
    if(e.keyCode==13){
        //回车事件会自动触发对象的click事件,如果在这里直接调用checklogin会导致该方法执行两次,回车调用一次,click调用一次
        //结果会有两条登入日志,解决办法就是在回车事件中触发click
        $("input[type='image']").click();
        return false;
    }else return true;
}
document.οnkeypress=keypress;

/**
* 获得焦点
*/
function getFocus(str){
    $("#"+str).focus();
}

/**
* 增加常见问题页面的链接
*/
function problem(){//ZQL 20110707
    
    javascript:win=open('njsp/ExtJsp/problem/problem.jsp', 'content', 'width=802,height=450,left=230,top=130,location=no,toolbar=no,status=no,resizable=no,scrollbars=no,menubar=no,directories=no');
}
/**
* 用来得到字符串的长度
*/
function getlength(str){
    //str表示确认密码或者新密码
    var strvalue = document.getElementById(str).value;//用户输入的新密码或者确认密码的值
    var realLength = 0, len = strvalue.length, charCode = -1;
    for (var i = 0; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode >= 0 && charCode <= 128) realLength += 1;
        else realLength += 2;
    }
    return realLength*1;
    
}
/**
 * 用来检查密码必须包含大小写字母和数字
 * */
function CheckStr(s){
                return /[a-zA-Z0-9]*[a-z]+[a-zA-Z0-9]*/.test(s)&&/[a-zA-Z0-9]*[A-Z]+[a-zA-Z0-9]*/.test(s)&&/[a-zA-Z0-9]*[0-9]+[a-zA-Z0-9]*/.test(s);
}
/**
 * 修改密码提交时用到的验证
 */
function checkform(forma){
    var password = $("#password").val();//用户输入的旧密码
    var newpassword = $("#newpassword").val();//用户输入的新密码
    var verifypassword = $("#verifypassword").val();//用户输入的确认密码
    var dbpassword = $("#dbpassword").val();//用户的数据库密码
    if(!password){
        $("#message").html("请输入旧密码!");
        $("#password").focus();
        return false;
    }
    if(($.md5(password))!=dbpassword){
        $("#message").html("密码错误,请重新输入!");
        $("#password").focus();
        return false;
    }
    if(!newpassword){
        $("#message").html("请输入新密码!");
        $("#newpassword").focus();
        return false;
    }
    if(getlength("newpassword")<8){
        $("#message").html("密码长度至少为8!");
        $("#newpassword").focus();
        return false;
    }
    if(CheckStr(newpassword)==false){
        $("#message").html("密码必须包含大小写字母和数字!");
        $("#newpassword").focus();
        return false;
    }
    if(!verifypassword){
        $("#message").html("请输入确认密码!");
        $("#verifypassword").focus();
        return false;
    }
    if(newpassword!=verifypassword){
        $("#message").html("两次输入的密码不一致!");
        $("#verifypassword").focus();
        return false;
    }
//    $("#password").val($.md5(password));
//    $("#newpassword").val($.md5(newpassword));
//    $("#verifypassword").val($.md5(newpassword));
    var postData = new Object();
    postData[$("#password").attr("name")]=$.md5(password);
    postData[$("#newpassword").attr("name")]=$.md5(newpassword);
    postData[$("#verifypassword").attr("name")]=$.md5(verifypassword);
    $.ajax({
        url:$("#updatePwdForm").attr("action"),
        data:postData,
        type:"POST",
        success:function(data, textStatus, jqXHR){
            if(data.success){
                alert("密码修改成功,请重新登录!");
                window.top.location.href=$("#updatePwdForm").attr("doneurl");
            }else{
                alert(data.msg);
            }
        }
    });
//    $("#updatePwdForm").submit();
//    alert("密码修改成功,请重新登录!");
//    window.parent.location.href='../login.jsp';
}

JSCode.js

    //解决ie7/8下浏览器不兼容trim
    String.prototype.trim = function () {
        return this .replace(/^\s\s*/, '' ).replace(/\s\s*$/, '' );
     }
    //生成一定长度的随机数字和字母
    function randomString(len) {
      len = len || 32;
      var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';    /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
      var maxPos = $chars.length;
      var pwd = '';
      for (i = 0; i < len; i++) {
        pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
      }
      return pwd;
    }
    
    function randomColor() {  
    var rand = Math.floor(Math.random( ) * 0xFFFFFF).toString(16);  
    
        if(rand.length == 6){  
            return rand;  
        }else{  
            return randomColor();  
        }  
    }
    
    
    function createCode(n,obj){
        var codeHtml="";
        var codeVal="";
        codeHtml+="<div id='codeShade' style='border:1px solid red;position:absolute;width:66px;height:30px;z-index:10;background:#"+randomColor()+";opacity:0.2;filter:alpha(opacity=20)'></div>"
        for(var i=0;i<n;i++){
            var codeColor='#'+randomColor();
            var strCode = "<span style='color:"+codeColor+";display:inline-block;width:17px;height:27px;line-height:27px;font-size:30px;'>";
            var OneCode = randomString(1);
            strCode+=OneCode;
            codeVal+=OneCode;
            strCode+="</span>"
            codeHtml+=strCode;
        }
        codeHtml+="<span id='codeVal' style='display:none;'>"+codeVal+"</span>";
        obj.html(codeHtml);
        $("#codeShade").click(function(){
            createCode(n,obj);
        });
    }
    
    function CheckYN(obj){
        if(obj.val().trim().toUpperCase()==$("#codeVal").text().toUpperCase()){
            return true;
        }else{
            return false;
        }
    }
  

第二步 后台

会用到三个类文件 SecurityCode(生成验证码),SecurityImage(绘制验证码图片),CodeImage(验证码和图片合并到一起并存入session以便调用)

SecurityCode.java

package cn.cntomorrow.beidian.model;
import java.util.Arrays;
/**  
  * 工具类,生成随机验证码字符串  
  * @version 1.0   
  * @author Lyan by 2017-08-03  20:00  
  *  
  */  
public class SecurityCode {  
        /**  
          * 验证码难度级别,Simple只包含数字,Medium包含数字和小写英文,Hard包含数字和大小写英文  
          */  
         public enum SecurityCodeLevel {Simple,Medium,Hard};  
           
         /**  
          * 产生默认验证码,4位中等难度  
         * @return  String 验证码  
         */  
        public static String getSecurityCode(){  
             return getSecurityCode(4,SecurityCodeLevel.Medium,false);  
         }  
        /**  
         * 产生长度和难度任意的验证码  
         * @param length  长度  
          * @param level   难度级别  
          * @param isCanRepeat  是否能够出现重复的字符,如果为true,则可能出现 5578这样包含两个5,如果为false,则不可能出现这种情况  
         * @return  String 验证码  
         */  
         public static String getSecurityCode(int length,SecurityCodeLevel level,boolean isCanRepeat){  
             //随机抽取len个字符  
             int len=length;  
               
             //字符集合(除去易混淆的数字0、数字1、字母l、字母o、字母O)  
             char[] codes={'1','2','3','4','5','6','7','8','9',  
                           'a','b','c','d','e','f','g','h','i','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z',  
                           'A','B','C','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z'};  
               
             //根据不同的难度截取字符数组  
             if(level==SecurityCodeLevel.Simple){  
                 codes=Arrays.copyOfRange(codes, 0,9);  
             }else if(level==SecurityCodeLevel.Medium){  
                 codes=Arrays.copyOfRange(codes, 0,33);  
             }  
             //字符集合长度  
             int n=codes.length;  
              
             //抛出运行时异常  
             if(len>n&&isCanRepeat==false){  
                 throw new RuntimeException(  
                        String.format("调用SecurityCode.getSecurityCode(%1$s,%2$s,%3$s)出现异常,当isCanRepeat为%3$s时,传入参数%1$s不能大于%4$s",  
                                        len,level,isCanRepeat,n));  
            }  
            //存放抽取出来的字符  
             char[] result=new char[len];  
             //判断能否出现重复的字符  
            if(isCanRepeat){  
                 for(int i=0;i<result.length;i++){  
                     //索引 0 and n-1  
                     int r=(int)(Math.random()*n);  
                   
                     //将result中的第i个元素设置为codes[r]存放的数值  
                     result[i]=codes[r];  
                }  
             }else{  
                 for(int i=0;i<result.length;i++){  
                     //索引 0 and n-1  
                     int r=(int)(Math.random()*n);  
                      
                     //将result中的第i个元素设置为codes[r]存放的数值  
                     result[i]=codes[r];  
                       
                    //必须确保不会再次抽取到那个字符,因为所有抽取的字符必须不相同。  
                     //因此,这里用数组中的最后一个字符改写codes[r],并将n减1  
                    codes[r]=codes[n-1];  
                    n--;  
                 }  
            }  
             return String.valueOf(result);  
        }  
}

SecurityImage.java

package cn.cntomorrow.beidian.model;

import java.awt.Color;  
import java.awt.Font;  
import java.awt.Graphics;  
import java.awt.image.BufferedImage;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.util.Random;  
import com.sun.image.codec.jpeg.ImageFormatException;  
import com.sun.image.codec.jpeg.JPEGCodec;  
import com.sun.image.codec.jpeg.JPEGImageEncoder;  
/**  
 * 验证码生成器类,可生成数字、大写、小写字母及三者混合类型的验证码。  
 * 支持自定义验证码字符数量;  
 * 支持自定义验证码图片的大小;  
 * 支持自定义需排除的特殊字符;  
 * 支持自定义干扰线的数量;  
 * 支持自定义验证码图文颜色  
 * @author Lyan by 2017-08-03  20:00
 * @version 1.0   
 */  
public class SecurityImage {  
         /**  
           * 生成验证码图片  
           * @param securityCode   验证码字符  
           * @return  BufferedImage  图片  
          */  
         public static BufferedImage createImage(String securityCode){  
             //验证码长度  
             int codeLength=securityCode.length();  
             //字体大小  
             int fSize = 15;  
             int fWidth = fSize + 1;  
             //图片宽度  
             int width = 68;  
             //图片高度  
             int height = 36;  
              //图片  
              BufferedImage image=new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);  
              Graphics g=image.createGraphics();  
             //设置背景色  
              g.setColor(Color.WHITE);  
               //填充背景  
              g.fillRect(0, 0, width, height);  
               //设置边框颜色  
              g.setColor(Color.LIGHT_GRAY);  
               //边框字体样式  
              g.setFont(new Font("Arial", Font.BOLD, height - 2));  
               //绘制边框  
              g.drawRect(0, 0, width - 1, height -1);  
               //绘制噪点  
              Random rand = new Random();  
               //设置噪点颜色  
               g.setColor(Color.LIGHT_GRAY);  
               for(int i = 0;i < codeLength * 6;i++){  
                   int x = rand.nextInt(width);  
                   int y = rand.nextInt(height);  
                  //绘制1*1大小的矩形  
                   g.drawRect(x, y, 1, 1);  
               }  
              //绘制验证码  
             int codeY = height - 10;    
              //设置字体颜色和样式  
               g.setColor(new Color(19,148,246));  
               g.setFont(new Font("Georgia", Font.BOLD, fSize));  
               for(int i = 0; i < codeLength;i++){  
                   g.drawString(String.valueOf(securityCode.charAt(i)), i * 16 + 5, codeY);  
              }  
               //关闭资源  
               g.dispose();  
               return image;  
           }  
         
}


CodeImage.java

package cn.cntomorrow.beidian.action;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.cntomorrow.beidian.model.SecurityCode;
import cn.cntomorrow.beidian.model.SecurityImage;
/**
 * 页面请求生成验证码
 * by Lyan 2017-08-04 20:00
 * */

public class CodeImage extends HttpServlet{

    private static final long serialVersionUID = 1L;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
        //在doget中执行dopost方法
        doPost(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
        String securityCode = SecurityCode.getSecurityCode();
        BufferedImage createImage = SecurityImage.createImage(securityCode);
        // 将生成的验证码code放入sessoin中
        req.getSession().setAttribute("code", securityCode);
        // 通过ImageIO将图片输出
        ImageIO.write(createImage, "JPG", resp.getOutputStream());
        
    }
}

第三步 登陆的Action 去验证验证码  要生成codeCon的get,set 方法

  HttpServletRequest request = ServletActionContext.getRequest();
        HttpSession gsession = request.getSession();
        Object codeConq = gsession.getAttribute("code");
        if (!codeCon.equals(codeConq.toString())) {
            System.out.println(codeConq);
            throw new Exception("验证码错误!!!");
        }

第四步 web.xml 配置文件

    <!-- 配置登陆页面验证码请求 -->
    <servlet>
        <servlet-name>codeimage</servlet-name>

<!-- 下面为Codeimage的路径-->
        <servlet-class>cn.cntomorrow.beidian.action.CodeImage</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>codeimage</servlet-name>

<!--如果访问不到加上项目名 /项目名 /codeimage -->

        <url-pattern>/codeimage</url-pattern>
    </servlet-mapping>


<!-- struts不被拦截的请求 -->
    <filter>
        <filter-name>sessionFilter</filter-name>
        <filter-class>cn.cntomorrow.web.base.SessionFilter</filter-class>
        <init-param>
            <param-name>notCheckURLList</param-name>

<!--里面是不走struts配置文件的请求-->

            <param-value>loginAction.do,exemptLogin.do,.js,.css,codeimage</param-value>
        </init-param>
        <init-param>
            <param-name>redirectURL</param-name>
            <param-value>/login.jsp</param-value>
        </init-param>
    </filter>

第五步 Session.Filter类 可以用我的,也可以自己写一个

package cn.cntomorrow.web.base;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import mt.framework.util.Logger;
import share.Share;

/**
 * session过滤器
 * @author: Lyan
 */
public class SessionFilter implements Filter {

    protected FilterConfig filterConfig = null;
    private String notCheckURL = null;
    private String redirectURL = null;
    /**
     * 销毁操作
     */
    public void destroy() {
        notCheckURL = new String();
        redirectURL = new String();
    }

    /**
     * 判断session是否存在,不存在则跳转到登录页面
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException{
        // Session属于HTTP范畴,所以ServletRequest对象需要先转换成HttpServletRequest对象
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        HttpSession session = req.getSession();

        
        //如果访问的资源属于放行资源
        if (this.checkRequestURIIntNotFilterList(req)
                //或者是根路径
                || req.getRequestURI().length() == req.getContextPath().length() + 1) {
            chain.doFilter(req, res);
        } else {
            // 如果session不为空,则可以浏览其他页面
            if(session.getAttribute(Globals.KEY_USERINFO) != null){
                chain.doFilter(req, res);
            } else if(req.getRequestURI().indexOf("resourceReader") > -1){        
                chain.doFilter(req, res);
            }else if(req.getRequestURI().indexOf("insertCustomSar") > -1){        
                chain.doFilter(req, res);
            }else {
                Logger.info("!!!!!!!!!!!!!session time out!!!!!!!!!");
                res.setCharacterEncoding("UTF-8");
                res.setContentType("text/html; charset=UTF-8");
                String msg = Share.iAttr().getProperty("sessionTimeOut");
                // 跳转到登陆页
                res.getWriter().write(
                        "<script>alert('" + msg
                                + "');window.top.location.href='"
                                + req.getContextPath() + redirectURL
                                + "'</script>");
            }
        }
    }

    /**
     * 初始化filter,从web.xml中读取参数
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        redirectURL = filterConfig.getInitParameter("redirectURL");

        String notCheckURLListStr = filterConfig.getInitParameter("notCheckURLList");
        notCheckURL = notCheckURLListStr;

    }

    /**
     * 判断当前url是否可以访问
     * @param request
     * @return boolean
     * @author: zhlong
     * @version: 2013-3-20 下午04:01:40
     */
    private boolean checkRequestURIIntNotFilterList(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String str[] = notCheckURL.split(",");
        for(int i = 0;i<str.length;i++){
            if(uri.toLowerCase().contains(str[i].toLowerCase())){
                return true;
            }
        }
        //return uri.contains(notCheckURL);
        return false;
    }
}


到此也就完成了。
























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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值