最近在学用JAVA Servlet/JSP来开发web应用,于是练手来做一个通过Servlet后台来生成图片验证码,并实现验证功能。

实现思路

先用单例模式写一个生成验证码图片的GenerateCode类,该类的createImage()方法返回BufferImage类,然后用Servlet来读取图片,GenerateCode对象在生成图片的同时也会生成一个验证码的字符串,可供后台验证的Servlet来验证前台post上去的验证码是否相同.

实现代码

Servlet类

  • VerifyCode.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
    package servlet;

    import Utils.GenerateCode;
    import com.sun.image.codec.jpeg.JPEGCodec;
    import com.sun.image.codec.jpeg.JPEGImageEncoder;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.OutputStream;

    @WebServlet(“/VerifyCode.do”)
    public class VerifyCode extends HttpServlet {

    private void processRequest(HttpServletRequest req, HttpServletResponse resp) {
    resp.setContentType(“image/jpg”);
    GenerateCode generateCode = GenerateCode.getInstance();
    try {
    OutputStream outputStream = resp.getOutputStream();
    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
    encoder.encode(generateCode.createImage());
    outputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    processRequest(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    processRequest(req, resp);
    }
    }
  • Verify.java(验证post上来的验证码)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package servlet;

    import Utils.GenerateCode;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    @WebServlet(“/Verify.do”)
    public class Verify extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType(“text/html;charset=UTF-8”);
    String reallycode = GenerateCode.getInstance().getCode();
    String postcode = req.getParameter(“verifycode”);
    if(reallycode.equalsIgnoreCase(postcode)){
    req.getRequestDispatcher(“success.jsp”).forward(req,resp);
    }else
    resp.sendRedirect(“index.jsp”);
    }
    }

Utils

  • GenerateCode.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
    package Utils;

    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.util.Random;

    public class GenerateCode{
    private static final int PW = 70;
    private static final int PH = 35;
    private static Random random = new Random();
    private static String[] fontnames = {“Chalkboard”,“Cochin”,“Skia”,“Apple Symbols”};
    private String codes=“012345678901234567890123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ”;
    private static Color bgColor = Color.BLACK;
    private String code;
    private volatile static GenerateCode generateCode;

    public static GenerateCode getInstance(){
    if(generateCode ==null){
    synchronized (GenerateCode.class){
    if(generateCode ==null){
    generateCode = new GenerateCode();
    }
    }
    }
    return generateCode;
    }

    public String getCode() {
    return code;
    }

    public void setCode(String code) {
    this.code = code;
    }

    private Color randomColor(){
    return new Color(random.nextInt(150),random.nextInt(150),random.nextInt(150));
    }

    private Font randomFont(){
    int index = random.nextInt(fontnames.length);
    String fontname = fontnames[index];
    int style = random.nextInt(4);
    int size = random.nextInt(5) + 25;
    return new Font(fontname,style,size);
    }

    private String randomCode(){
    int len = codes.length();
    String code = “”;
    for(int i=0;i<4;i++){
    int a = random.nextInt(len);
    code += codes.charAt(a);
    }
    return code;
    }

    public BufferedImage createImage(){
    BufferedImage image = new BufferedImage(PW,PH,BufferedImage.TYPE_INT_BGR);
    Graphics g = image.getGraphics();
    g.setColor(bgColor);
    g.fillRect(0,0,PW,PH);
    String num = randomCode();
    this.setCode(num);
    g.setColor(randomColor());
    g.setFont(randomFont());
    g.drawString(num,0,25);
    return image;
    }
    }

jsp

  • index.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
    <%@ page import=“java.awt.image.BufferedImage” %>
    <%@ page import=“servlet.VerifyCode” %><%–
    Created by IntelliJ IDEA.
    User: onlyless
    Date: 11/17/18
    Time: 11:34
    To change this template use File | Settings | File Templates.
    –%>
    <%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <form action=“/Verify.do” method=“post”>
    <table>
    <tr>
    <td>
    <img src=“/VerifyCode.do”>
    <input type=“text” name=“verifycode” maxlength=“4”>
    <input type=“submit” name=“submit”>
    </td>
    </tr>
    </table>
    </form>
    </body>
    </html>

  • success.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <%–
    Created by IntelliJ IDEA.
    User: onlyless
    Date: 11/16/18
    Time: 16:46
    To change this template use File | Settings | File Templates.
    –%>
    <%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
    <html>
    <head>
    <title>success</title>
    </head>
    <body>
    <h1>success</h1>
    </body>
    </html>

不足

思路很简单,最终也实现出来了,但是我发现了一个BUG,在多个窗口同时请求验证时,由于后台很简单的读取GenerateCode的验证码,因此这多个窗口post上来的验证码就算全部都是正确的,但是只能验证最后生成验证码图片的那个post,要解决这个问题,我想应该要用到Cookie的知识,现在还在学习,待以后再解决这个问题。


后台查看验证码不一致,后台正确的验证码是最后生成的验证码,导致之前的生成的验证码提交上去不匹配

后续

第二天学到了Cookie和Session的运用,发现用Session很简单就能解决上面的那个不足。

  • VerifyCode.java
    1
    2
    3
    4
    5
    6

    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(outputStream);
    encoder.encode(generateCode.createImage());
    req.getSession().setAttribute(“verify”,generateCode.getCode()); //添加的代码
    outputStream.close();

  • Verify.java
    1
    2
    3
    4
    5
    6
    7

    resp.setContentType(“text/html;charset=UTF-8”);
    // String reallycode = GenerateCode.getInstance().getCode();
    String reallycode = (String) req.getSession().getAttribute(“verify”); //修改的代码
    String postcode = req.getParameter(“verifycode”);
    System.out.println(reallycode + “—“ + postcode);
    ….

两个浏览器同时登陆都成功登陆,后台查看验证码一致



不过我发现这个问题在同一个浏览器上还是存在,原因很简单,用一个浏览器不同窗口的Session都是一样的,这样就导致新生成的验证码覆盖掉以前Session绑定的验证码,导致问题产生,这个问题我测试了一下学校的教务系统和12306的登录验证码,都存在这样的问题。不过这个一般谁也不会同时开多个窗口登录,登录失败一个,换以前的窗口登录,这样导致一直匹配到的不是产生最新的验证码。