验证码的介绍
验证码大家都很熟悉了,一般情况下,互联网上的系统都会使用验证码,但是一般企业内网不需要验证码。为了就是恶意破解密码和脚本恶意篡改信息等。
首先写一个 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">
账 号:<input type="text" name="username" required></br>
密 码:<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.跳转界面
}
}
效果如下:
防止表单重复提交
根本原因:
没有完整的进行一次请求页面->提交页面的过程
现象:
- 由于服务器的缓慢或延迟重复点击按钮;
- 通过回退到原来表单提交页面再次提交;
- 已经提交成功,刷新成功页面。
解决方案:
使用令牌机制(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>
效果如下: