JavaWeb世界(十二):验证码的实现和防止表单重复提交

验证码的介绍

验证码大家都很熟悉了,一般情况下,互联网上的系统都会使用验证码,但是一般企业内网不需要验证码。为了就是恶意破解密码和脚本恶意篡改信息等。
首先写一个 Servlet 来响应验证码 JSP:

RandomCodeServlet.java:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.Buffer;
import java.util.Random;
import java.util.UUID;

import javax.imageio.ImageIO;
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 javax.swing.plaf.metal.MetalIconFactory.FolderIcon16;
import javax.xml.ws.RespectBinding;

@WebServlet("/randomCode")
public class RandomCodeServlet extends HttpServlet {

	private static final long serialVersionUID = -2717466490897149900L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		//1.生成随机数
		String randomcode = UUID.randomUUID().toString().substring(0, 5);
		//2.将随机数放进session
		req.getSession().setAttribute("RANDOM_IN_SESSION", randomcode);
		show(randomcode, resp);
	}
	
	private void show(String randomcode, HttpServletResponse resp) throws IOException {
		//创建图片对象
		int width = 80;
		int height = 30;
		int imageType = BufferedImage.TYPE_INT_RGB;
		BufferedImage image = new BufferedImage(width, height, imageType);
		
		//画板
		Graphics graphics = image.getGraphics();
		graphics.setColor(Color.YELLOW);
		
		//绘制一个实心矩形
		graphics.fillRect(1, 1, width - 2, height - 2);
		//将随机数画进图片中
		graphics.setColor(Color.BLACK);
		Font font = new Font("宋体", Font.BOLD + Font.ITALIC, 20);
		graphics.setFont(font);
		graphics.drawString(randomcode, 10, 20);
		
		//干扰线
		graphics.setColor(Color.GRAY);
		Random random = new Random();
		for (int i = 0; i < 100; i++) {
			graphics.fillRect(random.nextInt(width), random.nextInt(height), 2, 2);	
		}
		graphics.dispose();
		ImageIO.write(image, "jpg", resp.getOutputStream());
	}
}

login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
<script type="text/javascript">
	function change() {
		//重新设置Img元素的scr属性
		document.getElementById("randomCodeImg").src = "/randomCode?"
				+ new Date().getTime();
	}
</script>

</head>
<body>
	<h3>用户登录</h3>
	<span style="color: red">${error}</span>
	<form action="" method="POST">&emsp;:<input type="text" name="username" required></br>&emsp;:<input type="password" name="password" required></br> 
		验证码:<input type="text" name="randomCode" size="5" maxlength="5"> 
			 <img src="/randomCode" id="randomCodeImg" style="cursor: pointer;"
				title="看不清,换一张" onclick="change()"></br> 
		<input type="submit" value="登录">
	</form>
</body>
</html>

需要给图片设置点击事件,通过内嵌 JavaScript 代码实现,通过设置资源后面的参数来实现每次点击内容不一样,否则每次都会加载之前的缓存。

最终效果如下:
在这里插入图片描述

验证码的实现

有几个步骤:
首先,判断验证码是否输入正确:将输入的验证码和 Session 中存放的验证码进行比较,其次,若两者不相等提示验证码错误会话过期,若相等,则做登录检查,并销毁 Session 中的验证码(一个验证码只能用一次)

RandomCodeLoginServlet.java:

package com.cherry._03_randomcode;

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("/randomLogin")
public class RandomCodeLoginServlet extends HttpServlet {

	private static final long serialVersionUID = 9020939103399052034L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		//1.接收请求参数
		String username = req.getParameter("username");
		String password = req.getParameter("password");
		String randomCode = req.getParameter("randomCode");
		
		//获取session中的验证码
		String randomCodeInSession = (String) req.getSession()
				.getAttribute("RANDOMCODE_IN_SESSION");
		
		if (!randomCode.equalsIgnoreCase(randomCodeInSession)) {
			req.setAttribute("error", "验证码不正确或会话已过期");
			req.getRequestDispatcher("/randomcode/login.jsp").forward(req, resp);	
			return;
		}
		
		req.getSession().removeAttribute("RANDOMCODE_IN_SESSION");
		
		//登录判断
		
		//2.调用业务方法
		
		//3.跳转界面
	}
}

效果如下:
样例

防止表单重复提交

根本原因:
没有完整的进行一次请求页面->提交页面的过程
现象:

  1. 由于服务器的缓慢或延迟重复点击按钮;
  2. 通过回退到原来表单提交页面再次提交;
  3. 已经提交成功,刷新成功页面。

解决方案:
使用令牌机制(token

我们在 jsp 中生成一个随机数:

<%
	//生成一个随机数(口令)
	String token = UUID.randomUUID().toString();
	//存放于session中,将来用于做判断
	session.setAttribute("TOKEN_IN_SESSION", token);
%>

在表单中隐藏显示:

<input type="hidden" name="token" value="<%=token%>"/>

在这里插入图片描述
原理便是:在请求表单页面的时候,生成一个随机数(口令),然后在 Servlet 中获取表单中的口令,获取 Session 中的口令,判断是否相等,若相等,则销毁,然后执行操作;若不等,口令不对,说明重复提交。
原理图

完整代码:

RepeatLogin.java:

import java.io.IOException;
import java.io.PrintWriter;

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("/repeatLogin")
public class RepeatServlet extends HttpServlet {

	private static final long serialVersionUID = -2618731683900219578L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		//表单中的口令
		String tokenInRequest = req.getParameter("token");
		//session中的口令
		String tokenInSession = (String) req.getSession().getAttribute("TOKEN_IN_SESSION");
		if (tokenInRequest.equals(tokenInSession)) {
			//销毁session中的口令
			req.getSession().removeAttribute("TOKEN_IN_SESSION");
			resp.setContentType("text/html;charset=utf-8");
			PrintWriter out = resp.getWriter();
			String num = req.getParameter("num");
			System.out.println("转账:" + num);
			out.print("转账成功!");	
		} else {
			System.out.println("不要重复提交表单!!!");
		}
	}
}

LoginServlet.jsp:

import java.io.IOException;
import java.io.PrintWriter;

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("/repeatLogin")
public class RepeatServlet extends HttpServlet {

	private static final long serialVersionUID = -2618731683900219578L;

	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		//表单中的口令
		String tokenInRequest = req.getParameter("token");
		//session中的口令
		String tokenInSession = (String) req.getSession().getAttribute("TOKEN_IN_SESSION");
		if (tokenInRequest.equals(tokenInSession)) {
			//销毁session中的口令
			req.getSession().removeAttribute("TOKEN_IN_SESSION");
			resp.setContentType("text/html;charset=utf-8");
			PrintWriter out = resp.getWriter();
			String num = req.getParameter("num");
			System.out.println("转账:" + num);
			out.print("转账成功!");	
		} else {
			System.out.println("不要重复提交表单!!!");
		}
	}
}

login.jsp:

<%@page import="java.util.UUID"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
</head>
<body>
	<span style="color: red">${error}</span>
	<form action="/repeatLogin" method="POST">
		<input type="hidden" name="token" value="${token}"/>
		转账金额: <input type="text" name="num" required></br>
		<input type="submit" value="提交"/>
	</form>
</body>
</html>

效果如下:
在这里插入图片描述

发布了56 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42650988/article/details/104115580