图片验证码的生成和使用

时间 2018/12/23 17:44:21 加载中...

原理

后台生成 一张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实现

图片验证码

生成一个带有验证码的图片,涉及到对图片的操作,可参考 图片操作练习

生成验证码图片

  1. private String darwImg(String content, int imgWidth, int imgHeight) throws IOException {
  2. // 创建一个画布
  3. BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
  4. // 创建一个画笔,要开始画东西了
  5. Graphics2D g = bufferedImage.createGraphics();
  6. // 画布背景色
  7. g.setBackground(Color.WHITE);
  8. g.clearRect(0, 0, imgWidth, imgHeight);
  9. g.setPaint(new GradientPaint(0, 0, new Color(28, 64, 124), imgWidth, imgHeight, new Color(46, 109, 163)));
  10. // g.setPaint(Color.BLUE);
  11. g.fillRect(0, 0, imgWidth, imgHeight);
  12. // 画笔颜色
  13. g.setColor(Color.white);
  14. // 画笔字体
  15. g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 10));
  16. String text = content;
  17. FontMetrics metrics = g.getFontMetrics(g.getFont());
  18. int xPos = (imgWidth - metrics.stringWidth(text)) / 2;
  19. int yPos = ((imgHeight - metrics.getHeight()) / 2) + metrics.getAscent();
  20. g.drawString(text, xPos, yPos);
  21. // 干扰
  22. addLine(g, imgWidth, imgHeight);
  23. //addOtherText(g, imgWidth, imgHeight, text);
  24. g.dispose();
  25. return saveToBase64Img(bufferedImage);
  26. }
  27. private void addOtherText(Graphics2D g, int imgWidth, int imgHeight, String text) {
  28. Random randomer = new Random();
  29. for (int i = 0; i < text.length(); i++) {
  30. char item = text.charAt(i);
  31. int pointX = randomer.nextInt(imgWidth);
  32. int pointY = randomer.nextInt(imgHeight);
  33. g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 25));
  34. g.drawString(Character.toString(item), pointX, pointY);
  35. }
  36. }
  37. private void addLine(Graphics2D g, int imgWidth, int imgHeight) {
  38. int pointNum = 10;
  39. Random randomer = new Random();
  40. for (int i = 0; i < pointNum; i++) {
  41. int pointX1 = randomer.nextInt(imgWidth);
  42. int pointY1 = randomer.nextInt(imgHeight);
  43. int pointX2 = randomer.nextInt(imgWidth);
  44. int pointY2 = randomer.nextInt(imgHeight);
  45. g.drawLine(pointX1, pointY1, pointX2, pointY2);
  46. }
  47. }
  48. private String saveToBase64Img(BufferedImage bufferedImage) throws IOException {
  49. ByteArrayOutputStream output = new ByteArrayOutputStream();
  50. ImageIO.write(bufferedImage, "png", output);
  51. output.flush();
  52. output.close();
  53. byte[] byteArr = output.toByteArray();
  54. String str = Base64.getEncoder().encodeToString(byteArr);
  55. String imgStr = new StringBuilder().append("data:image/png;base64,").append(str).toString();
  56. return imgStr;
  57. }

随机数

  1. public String getCode(int codeLength) {
  2. Random randomer = new Random();
  3. StringBuilder builder = new StringBuilder();
  4. char[] elements = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
  5. 'S', 'T', 'V', 'U', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  6. 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8',
  7. '9' };
  8. for (int i = 0; i < codeLength; i++) {
  9. int index = randomer.nextInt(elements.length);
  10. builder.append(elements[index]);
  11. }
  12. return builder.toString();
  13. }

返回一个验证码对象

  1. /**
  2. * @author admin
  3. * 图像验证码dto
  4. */
  5. public class ImageCaptchaDTO {
  6. private String image;
  7. private String md5;
  8. private String time;
  9. public ImageCaptchaDTO(String image, String md5, String time) {
  10. super();
  11. this.image = image;
  12. this.md5 = md5;
  13. this.time = time;
  14. }
  15. public String getImage() {
  16. return image;
  17. }
  18. public void setImage(String image) {
  19. this.image = image;
  20. }
  21. public String getMd5() {
  22. return md5;
  23. }
  24. public void setMd5(String md5) {
  25. this.md5 = md5;
  26. }
  27. public String getTime() {
  28. return time;
  29. }
  30. public void setTime(String time) {
  31. this.time = time;
  32. }
  33. }
  34. public ImageCaptchaDTO getImageCaptcha() throws IOException {
  35. String code = getCode(4);
  36. String img = darwImg(code, 113, 45);
  37. String time = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());
  38. String md5 = md5Cal(code, time);
  39. return new ImageCaptchaDTO(img, md5, time);
  40. }
  41. public boolean checkCode(String code, String time, String md5) {
  42. if (md5Cal(code, time).equals(md5))
  43. return true;
  44. else
  45. return false;
  46. }
  47. private String md5Cal(String code, String time) {
  48. return DigestUtils.md5Hex(code.toLowerCase() + time + "540de77bc32847828b85c84217ca4c32");
  49. }

Spring Boot 下使用

codeService.java

将上面代码放到一个 codeService.java 中。

ICodeService.java

  1. import java.io.IOException;
  2. import com.sqber.personMgr.entity.ImageCaptchaDTO;
  3. public interface ICodeService {
  4. /**
  5. * @param codeLength
  6. * 随机验证码的长度
  7. * @return 由大小写字母和数字组成的字符串
  8. */
  9. String getCode(int codeLength);
  10. boolean checkCode(String code, String time, String md5);
  11. ImageCaptchaDTO getImageCaptcha() throws IOException;
  12. }

CodeService.java

  1. import java.awt.Color;
  2. import java.awt.Font;
  3. import java.awt.FontMetrics;
  4. import java.awt.GradientPaint;
  5. import java.awt.Graphics2D;
  6. import java.awt.image.BufferedImage;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.IOException;
  9. import java.text.SimpleDateFormat;
  10. import java.util.Base64;
  11. import java.util.Date;
  12. import java.util.Random;
  13. import javax.imageio.ImageIO;
  14. import com.sqber.personMgr.bll.ICodeService;
  15. import org.apache.commons.codec.digest.DigestUtils;
  16. import org.springframework.stereotype.Service;
  17. import com.sqber.personMgr.entity.ImageCaptchaDTO;
  18. @Service
  19. public class CodeService implements ICodeService {
  20. @Override
  21. public String getCode(int codeLength) {
  22. Random randomer = new Random();
  23. StringBuilder builder = new StringBuilder();
  24. char[] elements = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
  25. 'S', 'T', 'V', 'U', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  26. 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8',
  27. '9' };
  28. for (int i = 0; i < codeLength; i++) {
  29. int index = randomer.nextInt(elements.length);
  30. builder.append(elements[index]);
  31. }
  32. return builder.toString();
  33. }
  34. public ImageCaptchaDTO getImageCaptcha() throws IOException {
  35. String code = getCode(4);
  36. String img = darwImg(code, 113, 45);
  37. String time = new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());
  38. String md5 = md5Cal(code, time);
  39. return new ImageCaptchaDTO(img, md5, time);
  40. }
  41. public boolean checkCode(String code, String time, String md5) {
  42. if (md5Cal(code, time).equals(md5))
  43. return true;
  44. else
  45. return false;
  46. }
  47. private String md5Cal(String code, String time) {
  48. return DigestUtils.md5Hex(code.toLowerCase() + time + "540de77bc32847828b85c84217ca4c32");
  49. }
  50. private String darwImg(String content, int imgWidth, int imgHeight) throws IOException {
  51. // 创建一个画布
  52. BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
  53. // 创建一个画笔,要开始画东西了
  54. Graphics2D g = bufferedImage.createGraphics();
  55. // 画布背景色
  56. g.setBackground(Color.WHITE);
  57. g.clearRect(0, 0, imgWidth, imgHeight);
  58. g.setPaint(new GradientPaint(0, 0, new Color(28, 64, 124), imgWidth, imgHeight, new Color(46, 109, 163)));
  59. // g.setPaint(Color.BLUE);
  60. g.fillRect(0, 0, imgWidth, imgHeight);
  61. // 画笔颜色
  62. g.setColor(Color.white);
  63. // 画笔字体
  64. g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 10));
  65. String text = content;
  66. FontMetrics metrics = g.getFontMetrics(g.getFont());
  67. int xPos = (imgWidth - metrics.stringWidth(text)) / 2;
  68. int yPos = ((imgHeight - metrics.getHeight()) / 2) + metrics.getAscent();
  69. g.drawString(text, xPos, yPos);
  70. // 干扰
  71. addLine(g, imgWidth, imgHeight);
  72. //addOtherText(g, imgWidth, imgHeight, text);
  73. g.dispose();
  74. return saveToBase64Img(bufferedImage);
  75. }
  76. private void addOtherText(Graphics2D g, int imgWidth, int imgHeight, String text) {
  77. Random randomer = new Random();
  78. for (int i = 0; i < text.length(); i++) {
  79. char item = text.charAt(i);
  80. int pointX = randomer.nextInt(imgWidth);
  81. int pointY = randomer.nextInt(imgHeight);
  82. g.setFont(new Font("Arial", Font.PLAIN, imgHeight - 25));
  83. g.drawString(Character.toString(item), pointX, pointY);
  84. }
  85. }
  86. private void addLine(Graphics2D g, int imgWidth, int imgHeight) {
  87. int pointNum = 10;
  88. Random randomer = new Random();
  89. for (int i = 0; i < pointNum; i++) {
  90. int pointX1 = randomer.nextInt(imgWidth);
  91. int pointY1 = randomer.nextInt(imgHeight);
  92. int pointX2 = randomer.nextInt(imgWidth);
  93. int pointY2 = randomer.nextInt(imgHeight);
  94. g.drawLine(pointX1, pointY1, pointX2, pointY2);
  95. }
  96. }
  97. private String saveToBase64Img(BufferedImage bufferedImage) throws IOException {
  98. ByteArrayOutputStream output = new ByteArrayOutputStream();
  99. ImageIO.write(bufferedImage, "png", output);
  100. output.flush();
  101. output.close();
  102. byte[] byteArr = output.toByteArray();
  103. String str = Base64.getEncoder().encodeToString(byteArr);
  104. String imgStr = new StringBuilder().append("data:image/png;base64,").append(str).toString();
  105. return imgStr;
  106. }
  107. }

验证码对象

  1. package com.sqber.personMgr.entity;
  2. /**
  3. * @author admin
  4. * 图像验证码dto
  5. */
  6. public class ImageCaptchaDTO {
  7. private String image;
  8. private String md5;
  9. private String time;
  10. public ImageCaptchaDTO(String image, String md5, String time) {
  11. super();
  12. this.image = image;
  13. this.md5 = md5;
  14. this.time = time;
  15. }
  16. public String getImage() {
  17. return image;
  18. }
  19. public void setImage(String image) {
  20. this.image = image;
  21. }
  22. public String getMd5() {
  23. return md5;
  24. }
  25. public void setMd5(String md5) {
  26. this.md5 = md5;
  27. }
  28. public String getTime() {
  29. return time;
  30. }
  31. public void setTime(String time) {
  32. this.time = time;
  33. }
  34. }

控制器调用

假设控制器为 HomeCtroller,引入 ICodeService 后,新增方法

  1. @ResponseBody
  2. @GetMapping("home/captcha")
  3. public BaseResponse<ImageCaptchaDTO> getCaptcha() {
  4. BaseResponse<ImageCaptchaDTO> response = new BaseResponse<ImageCaptchaDTO>();
  5. ImageCaptchaDTO imageCap = null;
  6. try {
  7. imageCap = codeService.getImageCaptcha();
  8. } catch (IOException e) {
  9. response.setCode(500);
  10. response.setMsg("内部错误");
  11. log.error(e.getStackTrace() + e.getMessage());
  12. }
  13. response.setCode(200);
  14. response.setData(imageCap);
  15. return response;
  16. }

另外提供下 BaseResponse.java

  1. public class BaseResponse<T> {
  2. private int code;
  3. private String msg;
  4. private T data;
  5. public BaseResponse(){
  6. this.code = 200;
  7. }
  8. public int getCode() {
  9. return code;
  10. }
  11. public void setCode(int code) {
  12. this.code = code;
  13. }
  14. public String getMsg() {
  15. return msg;
  16. }
  17. public void setMsg(String msg) {
  18. this.msg = msg;
  19. }
  20. public T getData() {
  21. return data;
  22. }
  23. public void setData(T data) {
  24. this.data = data;
  25. }
  26. }

前台

前台通过控制器接口 home/captcha 来获取到控制器对象
html

  1. <img src="" id="ImageCatpcha" alt="正在加载" class="yzm"/>
  2. <el-input placeholder="验证码" class="dis-table" name="code">
  3. <input type='hidden' name='time'/>
  4. <input type='hidden' name='md5'/>

javascript

  1. var getCaptcha = function () {
  2. $.get(contextRoot + "home/captcha?v=" + new Date(), function (data) {
  3. if (data.code == 200) {
  4. $("#ImageCatpcha").attr("src", data.data.image);
  5. $("input[name=time]").val(data.data.time);
  6. $("input[name=md5]").val(data.data.md5);
  7. }
  8. else {
  9. alert("获取图片验证码失败,请重试!");
  10. }
  11. });
  12. }
  13. getCaptcha();
  14. $("#ImageCatpcha").click(getCaptcha);

表单提交的时候,将 验证码md5值时间 传递到后台,通过 codeServicecheckCode 方法来验证即可。

扫码分享
版权说明
作者:SQBER
文章来源:http://www.sqber.com/articles/image-verification-code.html
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。