javaWeb中验证码的实现以及底层原理

本文探讨了表单重复提交的问题及解决方案,通过在表单中加入唯一标记并在服务器端验证来防止重复提交。同时,详细介绍了验证码的实现原理,包括生成随机验证码并存储于session,以及客户端与服务器端的验证流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

底层原理

 表单的重复提交

1)重读提交的情况:(就是说发了2次表单提交的请求,而且发送的请求参数一致)

①.在表单提交到一个Servlet,而Servlet又通过请求转发的方式响应了一个jsp或者html页面这个时候地址栏里面还保留着
Servlet的路径,而我在响应页面点击刷新。(http://localhost:8080/sessionx/sub.s?name=sdfe)一直刷新这个相当于一直给Servlet发送请求
②.在响应页面没有到达时重复点击提交按钮。(一直提交给Servlet)
③.点击返回,再点击提交。(以前写的参数还在)

2)如何避免表单的重复提交:

在表单中做一个标记,提交到Servlet时,检查标记是否存在且是否与预定义的标记一致,若一致,则受理请求,并销毁标记
	若不一致或者没有标记,则直接响应提交信息:
①.把标记放在request中,行不通,因为表单页面被刷新后,request被销毁了,再提交表单是一个新的request.(很重要 看submit1.jsp和success)
	
②把标记放在session中(很重要 看submit2.jsp和success)  form的get和post那个传递的参数名和值不管提交多少次都在的 比如op = ... 再servlet的提交url中不管刷新多少次再Servlet中都能获取

submit1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
   <%
   	request.setAttribute("arr", "abc");
   	System.out.println(request.getAttribute("arr"));
   %>
<!DOCTYPE html >
<html>
<head>
<meta  charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="<%=request.getContextPath() %>/subx.s" method="post">
	
		<input type="text" name="name">
		<input type="submit" value="Sub">
	</form>
</body>
</html>

SubxServlet(映射为subx.s)

package cn.itcast.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/subx.s")
public class SubxServlet extends BasicServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		String arr = (String)req.getAttribute("arr");
		System.out.println(arr);
		if(arr!=null){
			req.removeAttribute("arr");
		}else{
			resp.sendRedirect(req.getContextPath()+"/success/tt.jsp");
			return ;
		}
		String name = req.getParameter("name");
		System.out.println("hello "+name);
		req.getRequestDispatcher("/success/success.jsp");
	}
	
	
}

submit2.jsp

<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
   <%
   	String date = new Date().getTime()+"";
   	session.setAttribute("arr", date);
   	System.out.println(session.getAttribute("arr"));
   %>
<!DOCTYPE html >
<html>
<head>
<meta  charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="<%=request.getContextPath() %>/subxy.s" method="post">
		<input type="hidden" name="op" value="<%=date%>">
		<input type="text" name="name">
		<input type="submit" value="Sub">
	</form>
</body>
</html>

SubxyServlet(映射为subxy.s)

package cn.itcast.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/subxy.s")
public class SubxyServlet extends BasicServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		resp.setCharacterEncoding("utf-8");
		// TODO Auto-generated method stub
		String arr = req.getParameter("op");
		String arr2 = (String) req.getSession().getAttribute("arr");
		System.out.println("aqswd"+ arr2);
		if(arr!=null && arr.equals(arr2)){
			req.getSession().removeAttribute("arr");
		}else{
			resp.sendRedirect(req.getContextPath()+"/success/tt.jsp");
			return ;
		}
		String name = req.getParameter("name");
		System.out.println("hello2 "+name);
		req.getRequestDispatcher("/success/success.jsp");
	}
	
}

下面2个jsp在session文件夹下

success.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
   <%
   	Object name = request.getParameter("name");
   %>
<!DOCTYPE html >
<html>
<head>
<meta  charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h4>success <%=(name == null)?"":name %></h4>
</body>
</html>

tt.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html >
<html>
<head>
<meta  charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<h4>不好意思,您已经重读提交了</h4>
</body>
</html>

其实验证码的底层就是跟表单的重复提交一样,先将验证码的信息放入到session然后在Servlet中比较获取到的验证码值和session中的验证码值是否想通过-->若相同则表示验证码验证通过不相同则表示验证码验证不通过。

实现

文件目录结构

image.jsp 


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html >
<html>
<head>
<meta  charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<!-- 这里的new Date()是js中的 -->
	<form action="<%=request.getContextPath() %>/CheckServlet" method="post">
		name:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="text" name="name"/><br/>
		checkCode:<input type="text" name="code"><br/>
		<!-- img的src属性值是不能改变的  一定要映射到ValidateColorServlet文件-->
		<!-- 添加onclick是通过为src根据时间设置不同值从而实现每点击一次验证码换一个验证码 -->
		<img id="img" alt="" src="<%=request.getContextPath() %>/ValidateColorServlet"
		onclick='src="<%=request.getContextPath() %>/ValidateColorServlet?"+new Date().getTime()'><br/>
		<input type="submit" value="SUB">
	</form>
</body>


</html>

ValidateColorServlet.java

package cn.itcast.util;

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 = 100;
	private int height = 20;
	private int codeCount = 3;
	
	//验证码字体的高度
	private int fontHeight = 1;
	
	//验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (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);
		
		//随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到
		Random random = null;
		random = new Random();
		graphics.setColor(Color.GREEN);
		for(int i = 0; i < 50; 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.RED);
			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();
	}
}

CheckServlet.java

package cn.itcast.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.itcast.util.ValidateColorServlet;
@WebServlet("/CheckServlet")
public class CheckServlet extends BasicServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		String name = req.getParameter("name");
		String code = req.getParameter("code"); //获取输入的值
		
		//在ValidateColorServlet中将图片中的的验证码的值添加到session中
		/*
		 再把存放有所有随机字符的 StringBuffer 对应的字符串放入到 HttpSession 中
		request.getSession().setAttribute(CHECK_CODE_KEY, randomCode.toString());
		 */
		String vcode = (String) req.getSession().getAttribute(ValidateColorServlet.CHECK_CODE_KEY);
		
		if(code!=null&&code.equalsIgnoreCase(vcode)){
			req.getRequestDispatcher("/success/success.jsp").forward(req, resp);
		}else{
			resp.sendRedirect(req.getContextPath()+"/success/tt.jsp");
		}
	}
	
	
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值