图片验证码的生成和使用
原理
后台生成 一张base64的图片,一个当前时间,还有一个token,token生成方式为 md5加密(图片内容+当前时间+一个固定的随机数),
然后将图片,时间,和 token 传递到前端页面。图片是显示给用户看的,用户输入正确的图片验证码后,将“用户输入的验证码”、“时间”、“token”传递给后台,
根据之前的token计算方式,计算md5加密(用户输入的验证码+时间+一个固定的随机数),如果生成的token和页面传递回来的token相同则说明 验证码是正确的。
前端页面只是知道 时间 和 token,如果随意仿造一个 验证码,必须将此验证码对应的 token也伪造出来。
因为有一个固定的随机数,且如果不知道算法的话,根本无法伪造。
加密算法(图片内容 + 时间 + 一个固定的随机数)=token
加密算法(用户输入图片内容 + 时间 + 一个固定的随机数)=用户输入内容得到的token
这里的【一个固定的随机数】就是密钥了,当然,如果算法和随机数被内部开发泄露出去,
那也相当于完蛋了,所以,这个随机数可以定期并更。
同样的,此种思想还经常用在 接口访问 限制中,为了防止接口的乱调用,每个调用方有会分配一个appId 和 appKey, 相当于给你小明一个密钥是abc,给你小花一个密钥是dfe
这里的appId 指的就是小明, appKey 就是abc。
原来调用接口,一个地址+传递的请求参数 大伙都可以调用,
现在调用成了 小明调用的时候 还需要传递过来 appId 和 根据自己的 appKey 生成的一个 Token。
当然了,这里就需要小明也知道 Token 的生成方法了,同理 小花 也知道,即大家都知道,
所以说 如果小明的 appKey 被别人知道,那别人就可以假冒他了。
token 的计算方法假设为
加密算法(参数内容 + 时间 + appId + appKey )=token
现在调用方传递的过来的有
参数内容
时间
appId
token
appKey是保密的,后台得到传递过来的数据后,再根据加密算法(参数内容 + 时间 + appId + 服务器端根据appId查询的appKey )= 服务器端计算的token
计算一下 token 和 传过来的 token 是否一致,如果不一致,则表明请求参数可能篡改过,或者 appKey 不对
这个 token 是随便写的一个,这个人不是我们授权的调用方。
Java实现
图片验证码
生成一个带有验证码的图片,涉及到对图片的操作,可参考 图片操作练习
生成验证码图片
private String darwImg(String content, int imgWidth, int imgHeight) throws IOException {
// 创建一个画布
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
// 创建一个画笔,要开始画东西了
Graphics2D g = bufferedImage.createGraphics();
// 画布背景色
g.setBackground(Color.WHITE);
g.clearRect(0, 0, imgWidth, imgHeight);
g.setPaint(new GradientPaint(0, 0, new Color(28, 64, 124), imgWidth, imgHeight, new Color(46, 109, 163)));
// g.setPaint(Color.BLUE);
g.fillRect(0, 0, imgWidth, imgHeight);
// 画笔颜色
g.setColor(Color.white);
// 画笔字体
g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 10));
String text = content;
FontMetrics metrics = g.getFontMetrics(g.getFont());
int xPos = (imgWidth - metrics.stringWidth(text)) / 2;
int yPos = ((imgHeight - metrics.getHeight()) / 2) + metrics.getAscent();
g.drawString(text, xPos, yPos);
// 干扰
addLine(g, imgWidth, imgHeight);
//addOtherText(g, imgWidth, imgHeight, text);
g.dispose();
return saveToBase64Img(bufferedImage);
}
private void addOtherText(Graphics2D g, int imgWidth, int imgHeight, String text) {
Random randomer = new Random();
for (int i = 0; i < text.length(); i++) {
char item = text.charAt(i);
int pointX = randomer.nextInt(imgWidth);
int pointY = randomer.nextInt(imgHeight);
g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 25));
g.drawString(Character.toString(item), pointX, pointY);
}
}
private void addLine(Graphics2D g, int imgWidth, int imgHeight) {
int pointNum = 10;
Random randomer = new Random();
for (int i = 0; i < pointNum; i++) {
int pointX1 = randomer.nextInt(imgWidth);
int pointY1 = randomer.nextInt(imgHeight);
int pointX2 = randomer.nextInt(imgWidth);
int pointY2 = randomer.nextInt(imgHeight);
g.drawLine(pointX1, pointY1, pointX2, pointY2);
}
}
private String saveToBase64Img(BufferedImage bufferedImage) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", output);
output.flush();
output.close();
byte[] byteArr = output.toByteArray();
String str = Base64.getEncoder().encodeToString(byteArr);
String imgStr = new StringBuilder().append("data:image/png;base64,").append(str).toString();
return imgStr;
}
随机数
public String getCode(int codeLength) {
Random randomer = new Random();
StringBuilder builder = new StringBuilder();
char[] elements = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'V', 'U', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8',
'9' };
for (int i = 0; i < codeLength; i++) {
int index = randomer.nextInt(elements.length);
builder.append(elements[index]);
}
return builder.toString();
}
返回一个验证码对象
/**
* @author admin
* 图像验证码dto
*/
public class ImageCaptchaDTO {
private String image;
private String md5;
private String time;
public ImageCaptchaDTO(String image, String md5, String time) {
super();
this.image = image;
this.md5 = md5;
this.time = time;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
public ImageCaptchaDTO getImageCaptcha() throws IOException {
String code = getCode(4);
String img = darwImg(code, 113, 45);
String time = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());
String md5 = md5Cal(code, time);
return new ImageCaptchaDTO(img, md5, time);
}
public boolean checkCode(String code, String time, String md5) {
if (md5Cal(code, time).equals(md5))
return true;
else
return false;
}
private String md5Cal(String code, String time) {
return DigestUtils.md5Hex(code.toLowerCase() + time + "540de77bc32847828b85c84217ca4c32");
}
Spring Boot 下使用
codeService.java
将上面代码放到一个 codeService.java 中。
ICodeService.java
import java.io.IOException;
import com.sqber.personMgr.entity.ImageCaptchaDTO;
public interface ICodeService {
/**
* @param codeLength
* 随机验证码的长度
* @return 由大小写字母和数字组成的字符串
*/
String getCode(int codeLength);
boolean checkCode(String code, String time, String md5);
ImageCaptchaDTO getImageCaptcha() throws IOException;
}
CodeService.java
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
import com.sqber.personMgr.bll.ICodeService;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
import com.sqber.personMgr.entity.ImageCaptchaDTO;
@Service
public class CodeService implements ICodeService {
@Override
public String getCode(int codeLength) {
Random randomer = new Random();
StringBuilder builder = new StringBuilder();
char[] elements = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'V', 'U', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8',
'9' };
for (int i = 0; i < codeLength; i++) {
int index = randomer.nextInt(elements.length);
builder.append(elements[index]);
}
return builder.toString();
}
public ImageCaptchaDTO getImageCaptcha() throws IOException {
String code = getCode(4);
String img = darwImg(code, 113, 45);
String time = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());
String md5 = md5Cal(code, time);
return new ImageCaptchaDTO(img, md5, time);
}
public boolean checkCode(String code, String time, String md5) {
if (md5Cal(code, time).equals(md5))
return true;
else
return false;
}
private String md5Cal(String code, String time) {
return DigestUtils.md5Hex(code.toLowerCase() + time + "540de77bc32847828b85c84217ca4c32");
}
private String darwImg(String content, int imgWidth, int imgHeight) throws IOException {
// 创建一个画布
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
// 创建一个画笔,要开始画东西了
Graphics2D g = bufferedImage.createGraphics();
// 画布背景色
g.setBackground(Color.WHITE);
g.clearRect(0, 0, imgWidth, imgHeight);
g.setPaint(new GradientPaint(0, 0, new Color(28, 64, 124), imgWidth, imgHeight, new Color(46, 109, 163)));
// g.setPaint(Color.BLUE);
g.fillRect(0, 0, imgWidth, imgHeight);
// 画笔颜色
g.setColor(Color.white);
// 画笔字体
g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 10));
String text = content;
FontMetrics metrics = g.getFontMetrics(g.getFont());
int xPos = (imgWidth - metrics.stringWidth(text)) / 2;
int yPos = ((imgHeight - metrics.getHeight()) / 2) + metrics.getAscent();
g.drawString(text, xPos, yPos);
// 干扰
addLine(g, imgWidth, imgHeight);
//addOtherText(g, imgWidth, imgHeight, text);
g.dispose();
return saveToBase64Img(bufferedImage);
}
private void addOtherText(Graphics2D g, int imgWidth, int imgHeight, String text) {
Random randomer = new Random();
for (int i = 0; i < text.length(); i++) {
char item = text.charAt(i);
int pointX = randomer.nextInt(imgWidth);
int pointY = randomer.nextInt(imgHeight);
g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 25));
g.drawString(Character.toString(item), pointX, pointY);
}
}
private void addLine(Graphics2D g, int imgWidth, int imgHeight) {
int pointNum = 10;
Random randomer = new Random();
for (int i = 0; i < pointNum; i++) {
int pointX1 = randomer.nextInt(imgWidth);
int pointY1 = randomer.nextInt(imgHeight);
int pointX2 = randomer.nextInt(imgWidth);
int pointY2 = randomer.nextInt(imgHeight);
g.drawLine(pointX1, pointY1, pointX2, pointY2);
}
}
private String saveToBase64Img(BufferedImage bufferedImage) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", output);
output.flush();
output.close();
byte[] byteArr = output.toByteArray();
String str = Base64.getEncoder().encodeToString(byteArr);
String imgStr = new StringBuilder().append("data:image/png;base64,").append(str).toString();
return imgStr;
}
}
验证码对象
package com.sqber.personMgr.entity;
/**
* @author admin
* 图像验证码dto
*/
public class ImageCaptchaDTO {
private String image;
private String md5;
private String time;
public ImageCaptchaDTO(String image, String md5, String time) {
super();
this.image = image;
this.md5 = md5;
this.time = time;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
控制器调用
假设控制器为 HomeCtroller,引入 ICodeService 后,新增方法
@ResponseBody
@GetMapping("home/captcha")
public BaseResponse<ImageCaptchaDTO> getCaptcha() {
BaseResponse<ImageCaptchaDTO> response = new BaseResponse<ImageCaptchaDTO>();
ImageCaptchaDTO imageCap = null;
try {
imageCap = codeService.getImageCaptcha();
} catch (IOException e) {
response.setCode(500);
response.setMsg("内部错误");
log.error(e.getStackTrace() + e.getMessage());
}
response.setCode(200);
response.setData(imageCap);
return response;
}
另外提供下 BaseResponse.java
public class BaseResponse<T> {
private int code;
private String msg;
private T data;
public BaseResponse(){
this.code = 200;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
前台
前台通过控制器接口 home/captcha
来获取到控制器对象
html
<img src="" id="ImageCatpcha" alt="正在加载" class="yzm"/>
<el-input placeholder="验证码" class="dis-table" name="code">
<input type='hidden' name='time'/>
<input type='hidden' name='md5'/>
javascript
var getCaptcha = function () {
$.get(contextRoot + "home/captcha?v=" + new Date(), function (data) {
if (data.code == 200) {
$("#ImageCatpcha").attr("src", data.data.image);
$("input[name=time]").val(data.data.time);
$("input[name=md5]").val(data.data.md5);
}
else {
alert("获取图片验证码失败,请重试!");
}
});
}
getCaptcha();
$("#ImageCatpcha").click(getCaptcha);
表单提交的时候,将 验证码、md5值、时间 传递到后台,通过 codeService 的 checkCode
方法来验证即可。
*昵称:
*邮箱:
个人站点:
*想说的话: