zoukankan      html  css  js  c++  java
  • HttpSession之表单的重复提交 & 验证码

    如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复提交问题

    1.表单的重复提交

    1). 重复提交的情况:
    ①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
          此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
    ②. 在响应页面没有到达时重复点击 "提交按钮".(网络问题)
    ③. 点击 "返回", 再点击 "提交"

    2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。

    3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致,

           若一致, 则受理请求,并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"

    ①. 仅提供一个隐藏域: <input type="hidden" name="token" value="atguigu"/>. 行不通: 没有方法清除固定的请求参数.
    ②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
    ③. 把标记放在 session 中. 可以!

    jsp中

    > 在原表单页面, 生成一个随机值 token
    > 在原表单页面, 把 token 值放入 session 属性中
    > 在原表单页面, 把 token 值放入到 隐藏域 中. 

    servlet中

    > 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
    > 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
    > 若不一致, 则直接响应提示页面: "重复提交"

    4).利用Session防止表单重复提交 

    TokenProcessor.java:用于管理表单标识号的工具类,它主要用于产生、比较和清除存储在当前用户Session中的表单标识号。

    为了保证表单标识号的唯一性,每次将当前SessionID和系统时间的组合值按MD5算法计算的结果作为表单标识号,

    并且将TokenProcessor类设计为单件类

    实验

    TokenProcessor.java

    package com.aff.javaweb;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    public class TokenProcessor {
    
        private static final String TOKEN_KEY = "TOKEN_KEY";
    
        private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";
    
        private static TokenProcessor instance = new TokenProcessor();
    
        private long previous;
    
        protected TokenProcessor() {
            super();
        }
    
        public static TokenProcessor getInstance() {
            return instance;
        }
    
        public synchronized boolean isTokenValid(HttpServletRequest request) {
            return this.isTokenValid(request, false);
        }
    
        public synchronized boolean isTokenValid(HttpServletRequest request, boolean reset) {
            HttpSession session = request.getSession(false);
    
            if (session == null) {
                return false;
            }
    
            String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY);
    
            if (saved == null) {
                return false;
            }
    
            if (reset) {
                this.resetToken(request);
            }
    
            String token = request.getParameter(TOKEN_KEY);
    
            if (token == null) {
                return false;
            }
    
            return saved.equals(token);
        }
    
        public synchronized void resetToken(HttpServletRequest request) {
            HttpSession session = request.getSession(false);
    
            if (session == null) {
                return;
            }
    
            session.removeAttribute(TRANSACTION_TOKEN_KEY);
        }
    
        public synchronized String saveToken(HttpServletRequest request) {
            HttpSession session = request.getSession();
            String token = generateToken(request);
    
            if (token != null) {
                session.setAttribute(TRANSACTION_TOKEN_KEY, token);
            }
    
            return token;
        }
    
        public synchronized String generateToken(HttpServletRequest request) {
            HttpSession session = request.getSession();
    
            return generateToken(session.getId());
        }
    
        public synchronized String generateToken(String id) {
            try {
                long current = System.currentTimeMillis();
    
                if (current == previous) {
                    current++;
                }
    
                previous = current;
    
                byte[] now = new Long(current).toString().getBytes();
                MessageDigest md = MessageDigest.getInstance("MD5");
    
                md.update(id.getBytes());
                md.update(now);
    
                return toHex(md.digest());
            } catch (NoSuchAlgorithmException e) {
                return null;
            }
        }
    
        private String toHex(byte[] buffer) {
            StringBuffer sb = new StringBuffer(buffer.length * 2);
    
            for (int i = 0; i < buffer.length; i++) {
                sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
                sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
            }
    
            return sb.toString();
        }
    }

    TokenServlet.java

    package com.aff.javaweb;
    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/tokenServlet")
    public class TokenServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean valid = TokenProcessor.getInstance().isTokenValid(request);
            if(valid){
                TokenProcessor.getInstance().resetToken(request);//清除标识号
            }else{
                response.sendRedirect(request.getContextPath() + "/token/token.jsp");
                return;
            }
            String name = request.getParameter("name");
            System.out.println("name:" + name);
            //request.getRequestDispatcher("/token/success.jsp").forward(request, response);
            response.sendRedirect(request.getContextPath()+"/token/success.jsp");
            
        }
    
    }

    index.jsp

    <%@page import="com.aff.javaweb.TokenProcessor"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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">
    <title>Insert title here</title>
    </head>
    <body>
    <form action="<%=request.getContextPath() %>/tokenServlet"  method="post">
    <input type="hidden" name="TOKEN_KEY" 
                         value="<%= TokenProcessor.getInstance().saveToken(request) %>"/>
    
    name: <input  type="text" name="name"/>
    <input type="submit" value="Submit"/>
    </form>
    
    </body>
    </html>

    success.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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">
    <title>Insert title here</title>
    </head>
    <body>
        <h3>Success Page</h3>
    </body>
    </html>

    token.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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">
    <title>Insert title here</title>
    </head>
    <body>
        <h4>对不起, 已经提交过了!</h4>
    </body>
    </html>

    2.使用 HttpSession 实现验证码

    1). 基本原理: 和表单重复提交一致:

    > 在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
    > 在原表单页面, 定义一个文本域, 用于输入验证码.

    > 在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
    > 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
    > 若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"

    ValidateColorServlet.java

    package com.aff.javaweb;
    
    import java.awt.Color;
    import java.awt.Font;
    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.Random;
    
    import javax.imageio.ImageIO;
    import javax.servlet.ServletException;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class ValidateColorServlet extends HttpServlet {
    
        public static final String CHECK_CODE_KEY = "CHECK_CODE_KEY";
    
        private static final long serialVersionUID = 1L;
    
        // 设置验证图片的宽度, 高度, 验证码的个数
        private int width = 152;
        private int height = 40;
        private int codeCount = 6;
    
        // 验证码字体的高度
        private int fontHeight = 4;
    
        // 验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (codeX, codeY) 位置处
        private int codeX = 0;
        private int codeY = 0;
    
        // 验证码由哪些字符组成
        char[] codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789".toCharArray();
    
        // 初始化验证码图形属性
        public void init() {
            fontHeight = height - 2;
            codeX = width / (codeCount + 2);
            codeY = height - 4;
        }
    
        public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 定义一个类型为 BufferedImage.TYPE_INT_BGR 类型的图像缓存
            BufferedImage buffImg = null;
            buffImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    
            // 在 buffImg 中创建一个 Graphics2D 图像
            Graphics2D graphics = null;
            graphics = buffImg.createGraphics();
    
            // 设置一个颜色, 使 Graphics2D 对象的后续图形使用这个颜色
            graphics.setColor(Color.WHITE);
    
            // 填充一个指定的矩形: x - 要填充矩形的 x 坐标; y - 要填充矩形的 y 坐标; width - 要填充矩形的宽度; height
            // - 要填充矩形的高度
            graphics.fillRect(0, 0, width, height);
    
            // 创建一个 Font 对象: name - 字体名称; style - Font 的样式常量; size - Font 的点大小
            Font font = null;
            font = new Font("", Font.BOLD, fontHeight);
            // 使 Graphics2D 对象的后续图形使用此字体
            graphics.setFont(font);
    
            graphics.setColor(Color.BLACK);
    
            // 绘制指定矩形的边框, 绘制出的矩形将比构件宽一个也高一个像素
            graphics.drawRect(0, 0, width - 1, height - 1);
    
            // 随机产生 40 条干扰线, 使图像中的认证码不易被其它程序探测到
            Random random = null;
            random = new Random();
            graphics.setColor(Color.GREEN);
            for (int i = 0; i < 40; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int x1 = random.nextInt(20);
                int y1 = random.nextInt(20);
                graphics.drawLine(x, y, x + x1, y + y1);
            }
    
            // 创建 randomCode 对象, 用于保存随机产生的验证码, 以便用户登录后进行验证
            StringBuffer randomCode;
            randomCode = new StringBuffer();
    
            for (int i = 0; i < codeCount; i++) {
                // 得到随机产生的验证码数字
                String strRand = null;
                strRand = String.valueOf(codeSequence[random.nextInt(36)]);
    
                // 把正在产生的随机字符放入到 StringBuffer 中
                randomCode.append(strRand);
    
                // 用随机产生的颜色将验证码绘制到图像中
                graphics.setColor(Color.BLUE);
                graphics.drawString(strRand, (i + 1) * codeX, codeY);
            }
    
            // 再把存放有所有随机字符的 StringBuffer 对应的字符串放入到 HttpSession 中
            request.getSession().setAttribute(CHECK_CODE_KEY, randomCode.toString());
    
            // 禁止图像缓存
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
    
            // 将图像输出到输出流中
            ServletOutputStream sos = null;
            sos = response.getOutputStream();
            ImageIO.write(buffImg, "jpeg", sos);
            sos.close();
        }
    }

    CheckCodeServlet.java

    package com.aff.javaweb;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/checkCodeServlet")
    public class CheckCodeServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 获取请求参数: CHECK_CODE_PARAM_NAME
            String paramCode = request.getParameter("CHECK_CODE_PARAM_NAME");
            // 获取session 中的CHECK_CODE_KEY 属性值
            String sessionCode = (String) request.getSession().getAttribute("CHECK_CODE_KEY");
    
            System.out.println(paramCode);
            System.out.println(sessionCode);
            if (!(paramCode != null && paramCode.equals(sessionCode))) {
                // 比对 看是否一致,若一致 说明验证码正确, 若不一致说明验证码错误
                request.getSession().setAttribute("message", "验证码不一致");
                response.sendRedirect(request.getContextPath() + "/check/index.jsp");
                return;
    
            }
            System.out.println("受理请求!");
        }
    
    }

    index.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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">
    <title>Insert title here</title>
    </head>
    <body>
        <font color="red">
        <%= session.getAttribute("message") == null ? "" : session.getAttribute("message")%>
        </font>
    
        <form action="<%=request.getContextPath()%>/checkCodeServlet"
            method="post">
            name: <input type="text" name="name" /> 
            checkCode:<input type="text" name="CHECK_CODE_PARAM_NAME" /> 
            <img alt="" src="<%=request.getContextPath()%>/validateColorServlet"> 
            <input type="submit" value="Submit" />
        </form>
    
    </body>
    </html>
    All that work will definitely pay off
  • 相关阅读:
    PAT顶级 1015 Letter-moving Game (35分)
    PAT顶级 1008 Airline Routes (35分)(有向图的强连通分量)
    PAT顶级 1025 Keep at Most 100 Characters (35分)
    PAT顶级 1027 Larry and Inversions (35分)(树状数组)
    PAT 顶级 1026 String of Colorful Beads (35分)(尺取法)
    PAT顶级 1009 Triple Inversions (35分)(树状数组)
    Codeforces 1283F DIY Garland
    Codeforces Round #438 A. Bark to Unlock
    Codeforces Round #437 E. Buy Low Sell High
    Codeforces Round #437 C. Ordering Pizza
  • 原文地址:https://www.cnblogs.com/afangfang/p/12748611.html
Copyright © 2011-2022 走看看