开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

在JSP中动态生成随机验证码,登录时后台校验验证码,以及如何避免同一个验证码被重复提交爆破密码

简介:
+关注继续查看

只需几步就可以生成动态随机的验证码,最终效果如下图:

wKioL1YscJvQIeqDAAF7uMWlQBA873.jpg


一 前台显示页面login.jsp

  其中验证码显示的是一张图片,链接指向的是生成验证码的servlet,同时点击图片后触发changeImg()这个js函数,使其动态生成一个新的验证码,这个函数中的参数t=Math.random()并不会参与验证码的生成,它的作用仅仅只是表示每次提交的并不是同一个请求,需要单独处理,完整的login.jsp代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!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=UTF-8">
<base href="<%=basePath%>">
<%@ include file="head.txt"%>
<script type="text/javascript">
    //刷新验证码
    function changeImg(){
        document.getElementById("validateCodeImg").src="helpDrawValidateCode?t=" + Math.random();
    }
</script>
</head>
<body>
    <font size=3>
        <center>
            <form action="helpLogin" name="" method="post">
                <table>
                <tr><th>请您登陆:</th></tr>
                    <tr><td align=left>会员名称:</td><td><input type=text name="id"></td></tr>
                    <tr><td align=left>输入密码:</td><td><input type=password name="password"></td></tr>              
                </table
                <br>验证码:<input type="text" name="validateCode" style="width:50px">
                <img alt="看不清?换一张" src="helpDrawValidateCode" id="validateCodeImg" onclick="changeImg()">                        
                <br><input type="submit" value="提交">
            </form>
        </center>
    </font>
</body>
</html>


二 修改web.xml

  新增一个节点,代码如下:

1
2
3
4
5
6
7
8
9
<!-- 验证码绘制 -->
  <servlet>
    <servlet-name>drawValidateCode</servlet-name>
    <servlet-class>com.zifangsky.OnlineFriend.servlet.member.HandleDrawValidateCode</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>drawValidateCode</servlet-name>
    <url-pattern>/helpDrawValidateCode</url-pattern>
  </servlet-mapping>


三 后台的servlet文件HandleDrawValidateCode.java

  这个文件主要负责处理前台请求,并返回生成的验证码图片,同时将图片上的随机字符存入session中,以供登录时进行验证,HandleDrawValidateCode.java完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.zifangsky.OnlineFriend.servlet.member;
 
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
 
import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
/**
 * 生成随机图片用做验证码
 * */
public class HandleDrawValidateCode extends HttpServlet{
    private static final long serialVersionUID = 1L;
    private static final int WIDTH = 120;  //图片宽度
    private static final int HEIGHT = 30;  //图片高度
         
    public void init(ServletConfig config) throws ServletException{
        super.init(config);
    }
     
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
 
        HttpSession session = request.getSession(true);
         
        //创建一张图片
        BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        //得到图片
        Graphics graphics = bufferedImage.getGraphics();
        //设置图片背景色
        setBackGround(graphics);
        //设置图片边框
        setBordor(graphics);
        //在图片上画干扰线,用了4种颜色,共20条线条
        drawRandomLine(graphics,Color.GREEN);
        drawRandomLine(graphics,new Color(246,255,145));
        drawRandomLine(graphics,new Color(225,174,252));
        drawRandomLine(graphics,new Color(120,202,254));
        //在图片上写随机字符,并记录生成的序列
        String randomText = drawRandomText((Graphics2D) graphics);
        //将生成的字符存入session中
        session.setAttribute("checkcode", randomText);
        //设置响应头通知浏览器以图片的形式打开
        response.setContentType("image/jpeg");
        //设置响应头控制浏览器不要缓存
        response.setDateHeader("expries", -1);
        response.setHeader("Cache-Control""no-cache");
        response.setHeader("Pragma""no-cache");
        //将图片写给浏览器
        ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
 
    }
     
    /**
     * 设置图片背景色
     * */
    private void setBackGround(Graphics graphics) {
        graphics.setColor(Color.WHITE);
        graphics.fillRect(00, WIDTH, HEIGHT);
    }
 
    /**
     * 设置图片边框
     * */
    private void setBordor(Graphics graphics) {
        graphics.setColor(Color.BLUE);
        graphics.drawRect(11, WIDTH - 2, HEIGHT - 2);
    }
     
    /**
     * 在图片上画干扰线
     * */
    private void drawRandomLine(Graphics graphics,Color color) {
        graphics.setColor(color);
        //设置线条个数并画线
        for(int i = 0;i < 5;i++){
            int x1 = new Random().nextInt(WIDTH);
            int x2 = new Random().nextInt(WIDTH);
            int y1 = new Random().nextInt(HEIGHT);
            int y2 = new Random().nextInt(HEIGHT);
            graphics.drawLine(x1, y1, x2, y2);
        }
    }
     
    /**
     * 在图片上写随机字符,数字和字母的组合
     * @param length 字符串的长度
     
     * @return 返回生成的字符串序列
     * */
    private String drawRandomText(Graphics2D graphics) {
        graphics.setColor(Color.RED);
        graphics.setFont(new Font("宋体", Font.BOLD, 20));
         
        //数字和字母的组合
        String baseNumLetter = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";
        StringBuffer sBuffer = new StringBuffer();
         
        int x = 5;  //旋转原点的 x 坐标
        String ch = "";
        Random random = new Random();
        for(int i = 0;i < 4;i++){
            //设置字体旋转角度
            int degree = random.nextInt() % 30;  //角度小于30度
            int dot = random.nextInt(baseNumLetter.length());
            ch = baseNumLetter.charAt(dot) + ""
            sBuffer.append(ch);
             
            //正向旋转
            graphics.rotate(degree * Math.PI / 180, x, 20);
            graphics.drawString(ch, x, 20);
             
            //反向旋转
            graphics.rotate(-degree * Math.PI / 180, x, 20);
            x += 30;
        }
         
        return sBuffer.toString();
    }
 
     
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        doPost(request, response);
    }
}


注:

  在这里,干扰线的颜色和数目都可以自己设定,颜色可以使用随机色,同时显示的每个文字也可以使用随机的颜色,可以增加验证码识别难度,当然在这里我把比较容易混淆的0和O以及I都给去掉了。如果选用经过编码的中文字符的话,也是可以生成中文验证码的


四 login.jsp页面提交表单后,后台的servlet文件HandleLogin.java校验验证码,并进行登录验证

   经过上面的三个步骤后,login.jsp应该是可以正确显示验证码了,同时点击验证码图片后会生成一个新的验证码,login.jsp提交表单后HandleLogin.java文件验证验证码时主要是:将输入的验证码中的小写字母转换成大写,再和验证码生成时保存在session中的字符串比较,如果相同,则表示输入正确,同时移除session中设置的值,防止重复提交,爆破密码,HandleLogin.java相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.zifangsky.OnlineFriend.servlet.member;
 
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import com.zifangsky.OnlineFriend.model.member.Login;
import com.zifangsky.OnlineFriend.util.DbConn;
import com.zifangsky.OnlineFriend.util.StringUtil;
 
public class HandleLogin extends HttpServlet{
    private String backNews = "";  //登录状态返回信息
     
    public void init(ServletConfig config) throws ServletException{
        super.init(config);
    }
     
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        HttpSession session = request.getSession(true);
         
        //获取验证码
        String validateCode = StringUtil.xssEncode(request.getParameter("validateCode").trim());
        Object checkcode = session.getAttribute("checkcode");
        //将输入的验证码中的小写字母转换成大写,再和验证码生成时保存在session中的字符串比较     
        if(checkcode != null && checkcode.equals(StringUtil.convertToCapitalString(validateCode))){
            session.removeAttribute("checkcode");
            continueDoPost(request,response);
        }
        else{          
            response.sendRedirect("login.jsp");
            return;
        }
             
    }
     
    private void continueDoPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        //这里的登录后续操作省略
    }
 
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        doPost(request, response);
    }
}


注1:上面用到的StringUtil是我写的一个字符串相关的方法类,其中用到的方法convertToCapitalString()目的是将一个字符串中的小写字母转换为大写字母,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
     * 将一个字符串中的小写字母转换为大写字母
     
     * */
    public static String convertToCapitalString(String src)
    {
         char[] array = src.toCharArray();
          int temp = 0;
          for (int i = 0; i < array.length; i++)
          {
              temp = (int) array[i];
              if (temp <= 122 && temp >= 97){ // array[i]为小写字母
                   array[i] = (char) (temp - 32);
              }
           }
           return String.valueOf(array);
       }


注2:其中方法xssEncode()是将输入的字符串进行编码,以此避免产生XSS跨站脚本攻击,用的是commons-lang-2.4.jar里的StringEscapeUtils,代码如下(PS:当然在这里没有什么必要,可以省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
     * 字符串XSS过滤,JavaScript过滤,Sql过滤
     
     * @param str 传入的字符串
     
     * @return 转义后的字符串
     * */
    public static String xssEncode(String str){
        String s = StringEscapeUtils.escapeHtml(str);
//      s = StringEscapeUtils.escapeJavaScript(s);
//      s = StringEscapeUtils.escapeSql(s);      
        return s; 
    }




本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1706029,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
第二章 TCP/IP-IOS七层模型
前言:学习OSI七层模型,了解各层上的协议 ,数据报(协议数据)单元 ,各层对应的设备
10 0
.net实现动态验证码
绪论:本文是.net实现动态验证码的显示,需使用到一般处理程序(ashx)
30 0
laravle对密码验证,密码和重复密码
laravle对密码验证,密码和重复密码
154 0
什么是5G NR-U?5G将首先替代企业级Wi-Fi?
什么是5G NR-U?5G将首先替代企业级Wi-Fi?
298 0
js生成验证码并验证
JS生成验证码 .code { background-image: url(code.jpg); font-family: Arial; font-style: italic; color: Red; border: 0; padding: 2px 3px; letter-spacing: 3px; font-weight: bolder; } .
793 0
1.1、Libgdx目标和特性
Libgdx是一个通过统一的API工作在所有支持平台(Windows,Linux,Mac OS X,Android,黑莓,HTML/WebGL)的一个Java游戏开发框架。
910 0
文章
问答
文章排行榜
最热
最新
相关课程
更多
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载