【快速上手系列】用于登录的验证码制作(ValidateCode)和Javaweb自带的老式验证码快速上手
验证码
简介
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。一种常用的CAPTCHA测试是让用户输入一个扭曲变形的图片上所显示的文字或数字,扭曲变形是为了避免被光学字符识别(OCR, Optical Character Recognition)之类的电脑程序自动辨识出图片上的文字而失去效果。为了无法看到图像的身心障碍者,替代的方法是改用语音读出文数字,为了防止语音辨识分析声音,声音的内容会有杂音。
作用
- 防止批量注册、刷票、论坛灌水、刷页。
- 有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试。
- 验证码通常使用一些线条和一些不规则的字符组成,主要作用是为了防止一些黑客把密码数据化盗取。
常见的验证码
- 传统输入式验证码
主要是通过用户输入图片中的字母、数字、汉字等进行验证。
代表:大多数网站采用此种验证形式。
特点:简单易操作,人机交互性较好。但安全系数低,容易被破解。 - 输入式的图形验证码
有精美图案,识别文本也清晰可认,专注于广告。
代表:Solve Media,宇初验证码
特点:与其说是验证码,倒不如说是广告位。 - 纯行为验证码
照要求将备选碎片直线滑动到正确的位置
代表:极验验证码
特点:操作简单,体验好。单一维度,容易被逆向模拟,与移动端页面切换不兼容。 - 图标选择与行为辅助
给出一组图片,按要求点击其中一张或者多张。借用万物识别的难度阻挡机器。
代表:点触验证码、Google新型验证码、12306验证码
特点:安全性强。对于图片、图库、技术要求高。 - 点击式的图文验证与行为辅助
通过文字提醒用户点击图中相同字的位置进行验证。
代表:淘宝新型验证码、点触验证码
特点:操作简单,体验良好,单一图片区域较大,破解难度大。 - 智能验证码
通过行为特征、设备指纹、数据风控等技术,正常用户免验证,异常用户强制验证
代表:点触智能验证码
特点:简单便捷,区分人与机器、人与人、设备与设备。
具体使用
Servlet版本
步骤
1、导入jar包到你的web项目
ValidateCode.jar
maven依赖:
<!-- ValidateCode验证码 -->
<dependency>
<groupId>cn.dsna</groupId>
<artifactId>ValidateCode</artifactId>
<version>1.0.0</version>
</dependency>
2、编码过滤器中多一个配置(防止乱码)
//设置图像编码集
response.setContentType("image/jpeg;charset=utf-8");
3、配置验证码Servlet文件ValidateCodeServlet.java
@WebServlet("/ValidateCodeServlet")
public class ValidateCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//四个参数:验证码宽度、验证码高度、验证码有几位,验证码有几个干扰项
ValidateCode validateCode = new ValidateCode(110,26,4,10);
String code = validateCode.getCode();
System.out.println(code);
//在这一步存入code值(就是图片中验证码的字母和数字),用于一会在登录Servlet中接收进行判断用户输入的验证码是否和图片中的验证码一致
request.getSession().setAttribute("code", code);
//图片响应给客户端
validateCode.write(response.getOutputStream());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
如果直接访问此Servlet页面就可以看到即将显示的验证码图片:
4、配置前端jsp页面(html都行,看着改)
<!-- 配置cookie(html不用这) -->
<%
String ucode = "";
String upwd = "";
//用request获取cookie
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("usercode")) {
//获得cookie值
ucode = cookie.getValue();
//如果其值存在中文,就将其解码
ucode = URLDecoder.decode(ucode,"utf-8");
}
if (cookie.getName().equals("upwd")) {
//获得cookie值
upwd = cookie.getValue();
}
}
}
%>
<!-- html代码(value不用要) -->
<!-- 用value设置cookie默认值 -->
账号:<input type="text" id="usercode" name="ucode" value="<%=ucode%>"/><br/>
密码:<input type="text" id="userpwd" name="upwd" value="<%=upwd%>"/><br/>
<!-- 这里是验证码 -->
验证码:<input name="validatecode" id="validatecode" placeholder="验证码" type="text">
<a href="javascript:fn1()" style="text-decoration:none;">看不清,换一张<br/><img id="imageCode" src="/web08/ValidateCodeServlet(访问验证码Servlet的路径)" /></a>
<br/><br/>
<input type="button" id="loginbtn" value="提交"/>
js代码(我这都写一起了,单独写的粘所需即可):
<script type="text/javascript" src="js/jquery-3.6.0.js" charset="UTF-8"></script>
<script type="text/javascript">
//改变验证码
function fn1(){
//随便一个参数或者随机数,使图片转换 (可能因为验证码缓存所以不设置的话只能换一次)
var d = new Date(); //(因为日期时间比较唯一)
document.getElementById("imageCode").src = "/web08/ValidateCodeServlet?t="+d.getTime();
}
//登录的点击事件:
$(function(){
$("#loginbtn").click(function(){
//通过id得到值
var usercode = $("#usercode").val();
var userpwd = $("#userpwd").val();
var validatecode = $("#validatecode").val();
$.get("LoginServlet",
{
usercode:usercode,userpwd:userpwd,validatecode:validatecode},
function(res){
alert(res.msg); //判断登录对错状态
if(res.code==1){
location = "/web08/web/ProviderAllServlet(跳到成功登录后的页面)"
}else {
location = "/web08/login.jsp(原地刷新)"
}
},
"json");
});
});
</script>
写好后的前端效果:
扫描二维码关注公众号,回复:
15296591 查看本文章
然后配置自己项目中的登录Servlet(我这里是LoginServlet.ava):
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public LoginServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String json=null;
//设置网页字符编码集
response.setContentType("text/html;charset=utf-8");
//设置接收参数
String ucode = request.getParameter("usercode");
String upwd = request.getParameter("userpwd");
String validatecode = request.getParameter("validatecode");
//登录方法
UserService us = new UserServiceImpl();
User user = new User();
user = us.login(ucode, upwd);
//1、看验证码是否为null或是空字符串,如果不是进行下一步判断
if(validatecode == null || validatecode.equals("")) {
json = "{\"code\":2,\"msg\":\"验证码不能为空!!\"}";
}else {
//2、拿取当时从验证码Servlet中得到的code(也就是验证码信息),然后与用户输入的验证码进行比较,若比较成功则进行下一步用户登录验证的判断,否则返回验证码错误的信息
String code = (String)request.getSession().getAttribute("code");
//忽略大小写进行比较
if(validatecode.equalsIgnoreCase(code)) {
//3、比较相等后,开始写登录的业务判断
if(user != null) {
String uname = user.getUserName();
request.setAttribute("ucode",ucode);
request.setAttribute("uname",uname);
//设置Cookie
//如果Cookie中值存在中文,就将其编码设置成utf-8(也可能因为设置了反而乱码,看具体情况)
String usercode = URLEncoder.encode(ucode, "utf-8");
Cookie cookie = new Cookie("usercode",usercode);
//设置路径为根目录(方便使用)
cookie.setPath("/");
//设置cookie生命周期
cookie.setMaxAge(20); //s
//添加cookie(response)
response.addCookie(cookie);
Cookie cookie1 = new Cookie("upwd",upwd);
//设置路径为根目录(方便使用)
cookie1.setPath("/");
//设置cookie生命周期
cookie1.setMaxAge(20); //s
//添加cookie(response)
response.addCookie(cookie1);
//创建session
HttpSession session = request.getSession();
//存入用户session
session.setAttribute("uname", uname);
//设置session过期时间
session.setMaxInactiveInterval(60 * 60);
json = "{\"code\":1,\"msg\":\"登录成功\"}";
//response.sendRedirect("web/ProviderAllServlet");
} else {
json = "{\"code\":2,\"msg\":\"登录失败\"}";
//response.sendRedirect("/login.jsp");
}
}else {
json = "{\"code\":2,\"msg\":\"验证码错误!!\"}";
}
}
//回应控制台输出:
PrintWriter out = response.getWriter();
out.print(json);
System.out.println(json);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
这样就整好了
另一种老式验证码
用的是java.awt下自带的验证码,很麻烦,需要自己一步一步算是画个验证码出来,不过一些特性都可以自定义设置:
package com.r.servlet;
import java.io.IOException;
import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.*;
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;
/**
* 验证码
* @author
*
*/
@WebServlet("/ValidateCodeServlet2")
public class ValidateCodeServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
private void codeChange(HttpServletResponse response) throws IOException{
int width=120;
int height=27;
/**
* 1.在内存中创建一个图像对象
*/
//构造一个类型为预定义图像类型之一的 BufferedImage。
//表示一个具有 8 位 RGB 颜色分量的图像,对应于 Windows 或 Solaris 风格的 BGR 颜色模型,具有打包为整数像素的 Blue、Green 和 Red 三种颜色。
BufferedImage img=new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
//创建一个画笔
Graphics g=img.getGraphics();
//给背景图片添加一个颜色
g.setColor(Color.ORANGE);
g.fillRect(1, 1, width-2, width-2);//建议使用IE浏览器访问,Google浏览器和获取均显示为居中的图片,ie显示左上角
//给边框一个色
g.setColor(Color.GREEN);
g.drawRect(0, 0, width-1, height-1);
//设置文本样式
Random rr = new Random();
g.setColor(Color.BLACK);
g.setFont(new Font("宋体",Font.BOLD|Font.ITALIC,15));//Font对象 ----根据指定名称、样式和磅值大小,创建一个新 Font。
g.setColor(new Color(rr.nextInt(255),rr.nextInt(255),rr.nextInt(255))); //设置颜色,它的红,绿,蓝都在0~255之间随机
//给图片添加文本
Random rand=new Random();
int position=20;
for(int i=0;i<4;i++){
g.drawString(rand.nextInt(10)+"", position, 20);//给图片填充文本
position+=20;
}
//给图片加噪点
for(int i=0;i<20;i++){
Random r = new Random();
int x = r.nextInt(width);
int y = r.nextInt(height);
g.drawOval(x,y,5,5);
}
//将图片以对象流的方式输出到客户端
ImageIO.write(img, "jpg", response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
codeChange(response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
这是效果:
也可以每个字符都变颜色
package com.r.servlet;
import java.io.IOException;
import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.*;
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;
/**
* Servlet implementation class ValidateCodeServlet3
*/
@WebServlet("/ValidateCodeServlet3")
public class ValidateCodeServlet3 extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public ValidateCodeServlet3() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
//图片的高度,宽度,文本的个数
private static final int IMG_HEIGHT = 30;
private static final int IMG_WIDTH = 100;
private static final int IMG_NUM = 4;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置响应头,图片的缓存
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Cache-Control", "no-cache");
//设置响应的内容类型,MIME TYPE
resp.setContentType("image/jpeg");
//创建绘制图片的缓存流,BufferedImage.TYPE_INT_RGB图片为三原色图片
BufferedImage bi = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
//获取画笔对象
Graphics g = bi.getGraphics();
//设置画笔的颜色
g.setColor(new Color(100,230,200));
//绘制图片的背景色
g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);
//验证码中使用字符
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
//创建随机数
Random random = new Random();
//创建StringBuffer存储随机字符
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < IMG_NUM; i++) {
//生成随机数
int index = random.nextInt(chars.length);
//取出随机字符
char randomChar = chars[index];
//设置画笔颜色
g.setColor(new Color(random.nextInt(150),random.nextInt(200),random.nextInt(255)));
//设置文本字体
g.setFont(new Font("Arial",Font.PLAIN,18));
//给图片加噪点
for(int j=0;j<7;j++){
Random r = new Random();
int x = r.nextInt(IMG_WIDTH);
int y = r.nextInt(IMG_HEIGHT);
g.drawOval(x,y,5,5);
}
//将字符绘制到图片上
g.drawString(String.valueOf(randomChar), 15+(i*20), 20);
//将随机字符放入StringBuffer中
buffer.append(randomChar);
}
//将存储随机字符的String对象放入Session中
req.getSession().setAttribute("validateCode", buffer.toString());
//通过ImageIo流,将图片输出到页面中
ImageIO.write(bi, "jpg", resp.getOutputStream());
}
}
效果: