在说验证码的实现之前,我先说一下表单的重复提交的问题。
一、表单的重复提交
1). 重复提交的情况:
①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
写一个小项目说一下:
页面结构:
index.jsp向servlet发送请求,servlet处理后转发到success.jsp页面
index.jsp:
<form action="<%=request.getContextPath() %>/TokenServlet" method="post">
用户名:<input name="name" type="text">
<input type="submit" value="提交">
</form>
TokenServlet:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name=request.getParameter("name");
System.out.println(name);
request.getRequestDispatcher("token/success.jsp").forward(request, response);
}
success.jsp:
<body>
<h1>成功页面</h1>
</body>
执行,当从index.jsp到达TokenServlet处理后转发到success.jsp后,在success.jsp中刷新页面,会出现下面这个:
在TokenServlet中的doPost()方法里添加一段代码,让提交过程延缓2秒:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
这样在index.jsp中会重复点击提交按钮,然后过了时间才会到达指定页面,这也是重复提交。
③. 点击 "返回", 再点击 "提交"
2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。
这个不就相当于重新开了一个 浏览器页面吗?显然不是重复提交了。
3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"
①. 仅提供一个隐藏域: <input type="hidden" name="token" value="atguigu"/>. 行不通: 没有方法清除固定的请求参数.
②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
③. 把标记放在 session 中. 可以!
在index.jsp中放一个session,里面注值
<%
session.setAttribute("token", "tokenValue");
%>
然后在TokenServlet中的doPost()方法中处理:
HttpSession session = request.getSession();
Object token = session.getAttribute("token");
if(token!=null){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/token/token.jsp");
return ;
}
然后你到了success.jsp再次刷新的时候就会转到token.jsp:
<body>
<h1>Im so sorry ,你已经提交过了</h1>
</body>
结果也的确是这样:
这个方法的确能够处理重复提交这个问题,但是不可能每次session里面都放固定值吧,好了,下面我们用随机值来处理,解决步骤如下:
> 在原表单页面, 生成一个随机值 token
> 在原表单页面, 把 token 值放入 session 属性中
> 在原表单页面, 把 token 值放入到 隐藏域 中.
> 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
> 若不一致, 则直接响应提示页面: "重复提交"
重点代码:
在index.jsp中添加几段代码:
在form表单上面写:
<%
String tokenValue= new Date().getTime() + "";
session.setAttribute("token", tokenValue);
%>
form表单里面添加一个隐含域:
<!-- 用一个隐藏域 -->
<input type="hidden" name="token" value="<%=tokenValue %>" />
你执行的时候,点开index.jsp的源代码,就可以看到tolenValue为一段随机数:
然后在TokenServlet中的doPost()方法中处理:
HttpSession session = request.getSession();
Object token = session.getAttribute("token");
System.out.println(token);
String tokenValue=request.getParameter("token");
System.out.println(tokenValue);
if(token!=null && token.equals(tokenValue)){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/token/token.jsp");
return ;
}
执行,发现控制台输出的两个数是一样的。这样就很好的处理了重复提交。
好了,接下来就讲一下验证码的生成
二、验证码
1)、为什么要验证码
防止恶意提交,就比如你写一个for循环一直提交一个get请求,这样会出问题,所以我们
添加一个验证码来防止恶意提交。
2)怎么来实现验证码
使用 HttpSession 实现验证码
1)、基本原理: 和表单重复提交一致:
> 在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
> 在原表单页面, 定义一个文本域, 用于输入验证码.
> 在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
> 若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"
2)、代码实现
首先,我们肯定需要一个提交表单index.jsp
index.jsp:
<body>
<form action="CheckCodeServlet">
用户名:<input name="ymfwj" type="text"/>
验证码:<input type="text" name="code"/>
<img alt="" src="<%=request.getContextPath() %>/ValidateColorServlet">
<input type="submit" value="提交"/>
</form>
</body>
处理图片的ValidateColorServlet:
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 = 4;
//验证码字体的高度
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);
//随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到
Random random = null;
random = new Random();
graphics.setColor(Color.GREEN);
for(int i = 0; i < 55; 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();
}
}
对于这个servlet的作用都有注释。
然后在CheckCodeServlet处理:
public class CheckCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取请求参数:CHECK_CODE_PARAM_NAME
String paramCode=request.getParameter("CHECK_CODE_PARAM_NAME");
//2.获取session中的CHECK_CODE_KEY 属性值
String sessionCode = (String) request.getSession().getAttribute("CHECK_CODE_KEY");
System.out.println(paramCode+"和"+sessionCode);
//3.对比看是否一直,若一直说明验证码正确,若不一致,说明验证码错误
if(!(paramCode != null && paramCode.equals(sessionCode))){
request.getSession().setAttribute("message", "验证码不正确!");
response.sendRedirect(request.getContextPath()+"/check/index.jsp");
return ;
}
System.out.println("接受请求。。。");
}
}
最后,如果验证码输入框和图片显示的一致,就接受请求。
一、表单的重复提交
1). 重复提交的情况:
①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
写一个小项目说一下:
页面结构:
index.jsp向servlet发送请求,servlet处理后转发到success.jsp页面
index.jsp:
<form action="<%=request.getContextPath() %>/TokenServlet" method="post">
用户名:<input name="name" type="text">
<input type="submit" value="提交">
</form>
TokenServlet:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name=request.getParameter("name");
System.out.println(name);
request.getRequestDispatcher("token/success.jsp").forward(request, response);
}
success.jsp:
<body>
<h1>成功页面</h1>
</body>
执行,当从index.jsp到达TokenServlet处理后转发到success.jsp后,在success.jsp中刷新页面,会出现下面这个:
点击重新发送,控制台会再次输出结果,这就是重复提交了。
在TokenServlet中的doPost()方法里添加一段代码,让提交过程延缓2秒:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
这样在index.jsp中会重复点击提交按钮,然后过了时间才会到达指定页面,这也是重复提交。
③. 点击 "返回", 再点击 "提交"
2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。
这个不就相当于重新开了一个 浏览器页面吗?显然不是重复提交了。
3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"
①. 仅提供一个隐藏域: <input type="hidden" name="token" value="atguigu"/>. 行不通: 没有方法清除固定的请求参数.
②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
③. 把标记放在 session 中. 可以!
在index.jsp中放一个session,里面注值
<%
session.setAttribute("token", "tokenValue");
%>
然后在TokenServlet中的doPost()方法中处理:
HttpSession session = request.getSession();
Object token = session.getAttribute("token");
if(token!=null){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/token/token.jsp");
return ;
}
然后你到了success.jsp再次刷新的时候就会转到token.jsp:
<body>
<h1>Im so sorry ,你已经提交过了</h1>
</body>
结果也的确是这样:
这个方法的确能够处理重复提交这个问题,但是不可能每次session里面都放固定值吧,好了,下面我们用随机值来处理,解决步骤如下:
> 在原表单页面, 生成一个随机值 token
> 在原表单页面, 把 token 值放入 session 属性中
> 在原表单页面, 把 token 值放入到 隐藏域 中.
> 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
> 若不一致, 则直接响应提示页面: "重复提交"
重点代码:
在index.jsp中添加几段代码:
在form表单上面写:
<%
String tokenValue= new Date().getTime() + "";
session.setAttribute("token", tokenValue);
%>
form表单里面添加一个隐含域:
<!-- 用一个隐藏域 -->
<input type="hidden" name="token" value="<%=tokenValue %>" />
你执行的时候,点开index.jsp的源代码,就可以看到tolenValue为一段随机数:
然后在TokenServlet中的doPost()方法中处理:
HttpSession session = request.getSession();
Object token = session.getAttribute("token");
System.out.println(token);
String tokenValue=request.getParameter("token");
System.out.println(tokenValue);
if(token!=null && token.equals(tokenValue)){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/token/token.jsp");
return ;
}
执行,发现控制台输出的两个数是一样的。这样就很好的处理了重复提交。
好了,接下来就讲一下验证码的生成
二、验证码
1)、为什么要验证码
防止恶意提交,就比如你写一个for循环一直提交一个get请求,这样会出问题,所以我们
添加一个验证码来防止恶意提交。
2)怎么来实现验证码
使用 HttpSession 实现验证码
1)、基本原理: 和表单重复提交一致:
> 在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
> 在原表单页面, 定义一个文本域, 用于输入验证码.
> 在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
> 若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"
2)、代码实现
首先,我们肯定需要一个提交表单index.jsp
index.jsp:
<body>
<form action="CheckCodeServlet">
用户名:<input name="ymfwj" type="text"/>
验证码:<input type="text" name="code"/>
<img alt="" src="<%=request.getContextPath() %>/ValidateColorServlet">
<input type="submit" value="提交"/>
</form>
</body>
处理图片的ValidateColorServlet:
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 = 4;
//验证码字体的高度
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);
//随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到
Random random = null;
random = new Random();
graphics.setColor(Color.GREEN);
for(int i = 0; i < 55; 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();
}
}
对于这个servlet的作用都有注释。
然后在CheckCodeServlet处理:
public class CheckCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取请求参数:CHECK_CODE_PARAM_NAME
String paramCode=request.getParameter("CHECK_CODE_PARAM_NAME");
//2.获取session中的CHECK_CODE_KEY 属性值
String sessionCode = (String) request.getSession().getAttribute("CHECK_CODE_KEY");
System.out.println(paramCode+"和"+sessionCode);
//3.对比看是否一直,若一直说明验证码正确,若不一致,说明验证码错误
if(!(paramCode != null && paramCode.equals(sessionCode))){
request.getSession().setAttribute("message", "验证码不正确!");
response.sendRedirect(request.getContextPath()+"/check/index.jsp");
return ;
}
System.out.println("接受请求。。。");
}
}
最后,如果验证码输入框和图片显示的一致,就接受请求。