1.什么是验证码?
验证码(CAPTCHA) 是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解 密码、刷票、论坛灌水。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
2.为什么要使用验证码?
验证码可以有效防止攻击者对某一场景使用暴力方式进行不断的攻击尝试。验证码主要是运用于登录,注册,评论发帖及业务安全防刷等场景。
2.1 登录场景
防恶意程序采用暴力破解的方式进行不断的登录尝试,来破解用户的密码(撞库)。
图片类验证码
通过在图片上随机产生数字或者是英文字母或者是汉字,一般有四位或者六位验证码字符。通过添加干扰线,添加噪点以及增加字符的粘连程度和旋转角度来增加机器识别的难度。但是这种传统的验证码随着OCR技术的发展,能够轻易的被破解。
此处的后台使用的技术是springboot框架(也可以换成Servlet等)
【开发步骤】:
编写控制器的方法(生成验证码的方法)随机生成4个字符(字母或数字)将字符绘制到图片中生成随机的线条将验证码字符串保存到Session中将图片文件流发送给浏览器在页面中通过img标签的src属性读取验证码图片登录时对用户输入的验证码进行判断点击图片时能刷新验证码
【后台代码】:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 生成验证码
*
* @param session 保存验证码
* @param response 发送验证码到浏览器
*/
@RequestMapping("/code")
public void createCode(HttpSession session, HttpServletResponse response) {
// 图片的宽和高
int width = 80;
int height = 35;
// 定义字体大小
int fontSize = 20;
// 创建缓冲图片(存储在内存中)
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// 获得图片的绘图对象
Graphics graphics = image.getGraphics();
// 设置背景颜色
graphics.setColor(Color.WHITE);
// 填充背景颜色
graphics.fillRect(0, 0, width, height);
// 设置字体
graphics.setFont(new Font("宋体", Font.PLAIN, fontSize));
Random random = new Random();
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Color[] colors = {Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA,
Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
StringBuilder builder = new StringBuilder();
// 随机生成4个字符
for (int i = 0; i < 4; i++) {
char ch = str.charAt(random.nextInt(str.length()));
graphics.setColor(colors[random.nextInt(colors.length)]);
// 将随机生成的字符绘制到图片上
graphics.drawString(String.valueOf(ch), fontSize * i + 5, fontSize + 5);
// 绘制随机线条
graphics.setColor(colors[random.nextInt(colors.length)]);
graphics.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
builder.append(ch);
}
// 将验证码存储到session中
session.setAttribute("code", builder.toString());
// 设置响应头 让浏览器不缓存图片
response.setHeader("Cache-Control", "no-cache");
try {
// 将验证码通过流发送到浏览器
ImageIO.write(image, "PNG", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
...
}
【前端代码】:
<html>
<head>
<meta charset="utf-8">
<title>会员登录title>
head>
<body>
<form action="/user/login" method="post">
<div class="user-form-item">
div>
<div class="user-form-item">
<input class="user-input" name="password" type="password" max="15" maxlength="15" placeholder="密码">
div>
<div class="user-form-item">
<input class="user-input user-input-adjust" name="code" type="text" max="4" placeholder="验证码">
<img id="code" class="qrcode" style="cursor:pointer" src="/user/code" onclick="changeCode()" />
div>
<div class="user-form-item">
<label>
<input class="user-check" type="checkbox" checked="false" value="yes">
<span class="cos_span">
登录即表示同意<a class="keyword-blue-pale">《119婚庆网用户协议》a>
span>
label>
div>
<div class="user-form-item">
<input class="user-form-button" style="font-weight: bold;" type="submit" value="登 录" />
div>
<div class="user-form-item us_text_right">
<span class="cos_span"><a>忘记密码?a>span> <span class="cos_span_empty"><a>注册账号a>span>
div>
form>
body>
<script type="text/javascript">
// 修改验证码
function changeCode() {
// 获取验证码图片,修改其src属性。如果src指向的url不改变,浏览器不会重新调用验证码的后台
document.getElementById('code').src = '/user/code?random='+Math.random();
}
script>
html>
img标签的src属性的值为创建验证码的方法的url。
页面显示如下:
有时看不清图片上的验证码,需要点击那张图片来刷新验证码。所以需要在img标签上添加一个onclick()点击事件。这就会触发js中的changeCode()函数。
后台登录验证代码:
@PostMapping("/login")
public String login(Model model, String username, String password, String code, HttpSession session) {
String rightCode = (String) session.getAttribute("code");
if (null == code || null == rightCode || !rightCode.equalsIgnoreCase(code)){
model.addAttribute("msg", "验证码错误");
return "forward:/login.html";
}
User user = userService.login(username, password);
if (null != user) {
return "redirect:/index.html";
}
model.addAttribute("msg", "账号或密码登录错误");
return "forward:/login.html";
}
首先判断验证码是否正确(equalsIgnoreCase()方法忽视验证码英文字符大小写)。如果,正确,再来验证账户和密码;否则,直接返回。
好了,这样就简单实现了一个用图形验证码校验的登录操作
当然,如果一个项目中有多个页面需要验证码,就可以把创建图形验证码方法单独封装成一个工具类:
@Component
public class VerifyCode {
// 图片的宽和高
int width = 80;
int height = 35;
// 定义字体大小
int fontSize = 20;
// 可选字体
private String[] fontNames = {"宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312"};
// 可选字符
private String codes = "0123456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ";
private Color[] colors = {Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA,
Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
// 背景色
private Color bgColor = new Color(255, 255, 255);
// 验证码上的文本
private String text ;
private Random random = new Random();
/**
* 调用这个方法得到 BufferedImage
*
* @return
*/
public BufferedImage getImage () {
BufferedImage image = createImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics graphics = image.getGraphics();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 4; i++) {
char ch = randomChar();
graphics.setColor(randomColor());
graphics.drawString(String.valueOf(ch), fontSize * i + 5, fontSize + 5);
drawLine(graphics);
builder.append(ch);
}
this.text = builder.toString();
return image;
}
/**
* 保存图片到指定的输出流
*
* @param image
* @param out
* @throws IOException
*/
public void write(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "PNG", out);
}
/**
* 创建BufferedImage
*
* @param width
* @param height
* @param type
* @return
*/
private BufferedImage createImage(int width, int height, int type) {
BufferedImage image = new BufferedImage(width, height, type);
Graphics graphics = image.getGraphics();
graphics.setColor(this.bgColor);
graphics.fillRect(0, 0, width, height);
graphics.setFont(randomFont());
return image;
}
/**
* 生成随机的字体
*
* @return
*/
private Font randomFont() {
return new Font(fontNames[random.nextInt(fontNames.length)], Font.PLAIN, fontSize);
}
/**
* 生成一个随机字符
*
* @return
*/
private char randomChar() {
return codes.charAt(random.nextInt(codes.length()));
}
/**
* 生成随机的颜色
*
* @return
*/
private Color randomColor() {
return colors[random.nextInt(colors.length)];
}
/**
* 画干扰线
*
* @param graphics
*/
private void drawLine(Graphics graphics) {
graphics.setColor(randomColor());
graphics.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
}
/**
* 返回验证码图片上的文本
*
* @return
*/
public String getText () {
return text;
}
}
那么后台代码修改为:
/**
* 生成验证码
*
* @param session 保存验证码
* @param response 发送验证码到浏览器
*/
@RequestMapping("/code")
public void createCode(HttpSession session, HttpServletResponse response) {
BufferedImage image = verifyCode.getImage();
// 将验证码存储到session中
session.setAttribute("code", verifyCode.getText());
// 设置响应头 让浏览器不缓存图片
response.setHeader("Cache-Control", "no-cache");
try {
// 将验证码通过流发送到浏览器
//ImageIO.write(image, "PNG", response.getOutputStream());
verifyCode.write(image, response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
这样代码就足够简洁了~~~