验证码技术的出现是为了防止对服务和数据库进行暴力攻击而设置的一道墙,客户端与服务端交互步骤如下图:

剩下的细节问题还有:

1,  验证码如何加噪成图片

2,  服务端如何维护验证码

案例代码在:https://github.com/yejingtao/forblog/tree/master/demo-securityCode

核心代码详解:

前端:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><head><title>Create user </title></head><body><form th:action="@{/login}" method="post"><div><label> User Name : <input type="text" name="name"/> </label></div><div><label> User Password : <input type="password" name="password"/> </label></div><img src="/security" οnclick="refreshSecurityCode(this);" /><input name="securityCode"  size="8" /><div><input type="submit" value="Login"/></div></form></body><script>
function refreshSecurityCode(obj) {obj.src = "/security?_t=" + Math.random();}
</script></html>

验证码code生成:原理很简单,就是随机字符串

public class SecurityCodeUtil {/*** 验证码难度级别,Simple只包含数字,Medium包含数字和小写英文,MediumPlus包含大小英文,Hard包含数字和大小写英文*/public enum SecurityCodeLevel {Simple, Medium, MediumPlus, Hard};/*** 产生默认验证码,4位中等难度* * @return String 验证码*/public static String getSecurityCode() {return getSecurityCode(4, SecurityCodeLevel.MediumPlus, false);}/*** 产生长度和难度任意的验证码* * @param length*            长度* @param level*            难度级别* @param isCanRepeat*            是否能够出现重复的字符,如果为true,则可能出现 5578这样包含两个5,如果为false,则不可能出现这种情况* @return String 验证码*/public static String getSecurityCode(int length, SecurityCodeLevel level, boolean isCanRepeat) {// 随机抽取len个字符int len = length;// 字符集合(除去易混淆的数字0、数字1、字母l、字母o、字母O)char[] codes = { '1', '2', '3', '4', '5', '6', '7', '8', '9', //'a', 'b', 'c', 'd', 'e', 'f', 'g', //'h', 'i', 'j', 'k', 'm', 'n', //'p', 'q', 'r', 's', 't', //'u', 'v', 'w', 'x', 'y', 'z', //'A', 'B', 'C', 'D', 'E', 'F', 'G', //'H', 'I', 'J', 'K', 'L', 'M', 'N', //'P', 'Q', 'R', 'S', 'T', //'U', 'V', 'W', 'X', 'Y', 'Z' };// 根据不同的难度截取字符数组if (level == SecurityCodeLevel.Simple) {codes = ArrayUtils.copyOfRange(codes, 0, 9);} else if (level == SecurityCodeLevel.Medium) {codes = ArrayUtils.copyOfRange(codes, 0, 33);} else if (level == SecurityCodeLevel.MediumPlus) {codes = ArrayUtils.copyOfRange(codes, 34, codes.length);}// 字符集合长度int n = codes.length;// 抛出运行时异常if (len > n && isCanRepeat == false) {throw new RuntimeException(String.format("调用SecurityCode.getSecurityCode(%1$s,%2$s,%3$s)出现异常," //+ "当isCanRepeat为%3$s时,传入参数%1$s不能大于%4$s", len, level, isCanRepeat, n));}// 存放抽取出来的字符char[] result = new char[len];// 判断能否出现重复的字符if (isCanRepeat) {for (int i = 0; i < result.length; i++) {// 索引 0 and n-1int r = (int) (Math.random() * n);// 将result中的第i个元素设置为codes[r]存放的数值result[i] = codes[r];}} else {for (int i = 0; i < result.length; i++) {// 索引 0 and n-1int r = (int) (Math.random() * n);// 将result中的第i个元素设置为codes[r]存放的数值result[i] = codes[r];// 必须确保不会再次抽取到那个字符,因为所有抽取的字符必须不相同。// 因此,这里用数组中的最后一个字符改写codes[r],并将n减1codes[r] = codes[n - 1];n--;}}return String.valueOf(result);}
}

前端技术很容易获取文本版的验证码,所以要以二进制流的形式返回加噪后的验证码,主要靠java.awt里的包:

public class SecurityImageSupport {/*** 返回验证码图片的流格式* * @param securityCode*            验证码* @return ByteArrayInputStream 图片流*/public static ByteArrayInputStream getImageAsInputStream(String securityCode) {BufferedImage image = createImage(securityCode);return convertImageToStream(image);}public static byte[] getImageAsByte(String securityCode) {BufferedImage image = createImage(securityCode);return convertImageToByte(image);}/*** 生成验证码图片* * @param securityCode*            验证码字符* @return BufferedImage 图片*/private static BufferedImage createImage(String securityCode) {// 验证码长度int codeLength = securityCode.length();// 字体大小int fSize = 13;int fWidth = fSize + 1;// 图片宽度int width = codeLength * fWidth + 15;// 图片高度int height = (int) (fSize * 1.5) + 1;// 图片BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D g = image.createGraphics();Color bgColor = new Color(239, 241, 249);// 设置背景色g.setColor(bgColor);// 填充背景g.fillRect(0, 0, width, height);// 设置边框颜色g.setColor(bgColor);// 边框字体样式g.setFont(new Font("Arial", Font.BOLD, height - 2));// 绘制边框g.drawRect(10, 10, width - 1, height - 1);// 绘制噪点Random rand = new Random();// 设置噪点颜色g.setColor(Color.LIGHT_GRAY);for (int i = 0; i < codeLength * 6; i++) {int x = rand.nextInt(width);int y = rand.nextInt(height);// 绘制1*1大小的矩形g.drawRect(x, y, 1, 1);}// 绘制验证码int codeY = height - 5;// 设置字体颜色和样式g.setColor(new Color(80, 25, 28));g.setFont(new Font("Georgia", Font.BOLD | Font.ITALIC, fSize));for (int i = 0; i < codeLength; i++) {g.drawString(String.valueOf(securityCode.charAt(i)), i * 16 + 5, codeY);}g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);Random r = new Random();CubicCurve2D cubic = new CubicCurve2D.Float(2, height / 2 + r.nextInt(8) - 4, //2 + width * 1 / 3, height / 2 + r.nextInt(8) - 4, //2 + width * 2 / 3, height / 2 + r.nextInt(8) - 4, //width - 2, height / 2 + r.nextInt(8) - 4);g.draw(cubic);// 关闭资源g.dispose();return image;}/*** 将BufferedImage转换成ByteArrayInputStream* * @param image*            图片* @return ByteArrayInputStream 流*/private static ByteArrayInputStream convertImageToStream(BufferedImage image) {byte[] bts = convertImageToByte(image);if(bts!=null) {return new ByteArrayInputStream(bts);}else {return null;}}private static byte[] convertImageToByte(BufferedImage image) {ByteArrayOutputStream bos = new ByteArrayOutputStream();try {ImageIO.write(image, "jpeg", bos);image.flush();byte[] bts = bos.toByteArray();return bts;} catch (IOException e) {e.printStackTrace();return null;}}}

与Redis交互:

@Service
public class SecurityCacheServiceImpl implements SecurityCacheService{public static final String REDIS_KEY = "sessionMap";@Autowiredprivate RedisTemplate<String,String> redisTemplate;@Overridepublic void setCodeCache(String sessionID, String securityCode) {HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();hashOp.put(REDIS_KEY,sessionID,securityCode);}@Overridepublic String getCodeCache(String sessionID) {HashOperations<String, String, String> hashOp = redisTemplate.opsForHash();return hashOp.get(REDIS_KEY, sessionID);}}

Controller在生成验证码之前需要维护下Redis缓存:

@RequestMapping("/security")public ResponseEntity<byte[]> securityCode(HttpServletRequest httpRequest) {//获取验证码文本String securityCode = SecurityCodeUtil.getSecurityCode();//Redis缓存验证码信息securityCacheService.setCodeCache(getSessionId(httpRequest), securityCode);byte[] bytes = SecurityImageSupport.getImageAsByte(securityCode);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.IMAGE_JPEG);return new ResponseEntity<byte[]>(bytes, headers,HttpStatus.OK);}

验证码效果图:

验证码原理详解与案例相关推荐

  1. EMD算法之Hilbert-Huang Transform原理详解和案例分析

    目录 Hilbert-Huang Transform 希尔伯特-黄变换 Section I 人物简介 Section II Hilbert-Huang的应用领域 Section III Hilbert ...

  2. 偏最小二乘回归分析原理详解和案例分析实例

    偏最小二乘回归分析原理详解 背景 偏最小二乘回归分析 Partial least squares regression analysis 基本思想 建模步骤 步骤一:分别提取两变量组的第一对成分,并使 ...

  3. 并联谐振电路工作原理详解,案例+计算公式,几分钟带你搞定

    昨天给大家分享了关于串联谐振的文章,今天给大家分享关于并联谐振的文章.(私信我的那个朋友,记得准备来看) 错过了串联谐振的朋友,可以直接点击下方标题跳转. 串联谐振是怎么工作的?案例+公式,几分钟,一 ...

  4. 清风数学建模学习笔记——系统(层次)聚类原理详解及案例分析

    系统聚类   系统聚类的合并算法通过计算两类数据点间的距离,对最为接近的两类数据点进行组合,并反复迭代这一过程,直到将所有数据点合成一类,并生成聚类谱系图.此外,系统聚类可以解决簇数 K 的取值问题, ...

  5. 清风数学建模学习笔记——主成分分析(PCA)原理详解及案例分析

    主成分分析   本文将介绍主成分分析(PCA),主成分分析是一种降维算法,它能将多个指标转换为少数几个主成分,这些主成分是原始变量的线性组合,且彼此之间互不相关,其能反映出原始数据的大部分信息. 一般 ...

  6. ant如何形成时间轴和图库_Python数据可视化常用4大绘图库原理详解_python

    这篇文章主要介绍了Python数据可视化常用4大绘图库原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天我们就用一篇文章,带大家梳理mat ...

  7. DL之GCN:GCN算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略

    DL之GCN:GCN算法的简介(论文介绍).架构详解.案例应用等配图集合之详细攻略 目录 GCN算法的简介(论文介绍) 0.实验结果 GCN算法的架构详解 GCN算法的案例应用 相关文章 DL之GCN ...

  8. DL之GoogleNet:GoogleNet(InceptionV1)算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略

    DL之GoogleNet:GoogleNet(InceptionV1)算法的简介(论文介绍).架构详解.案例应用等配图集合之详细攻略 目录 GoogleNet算法的简介 GoogleNet算法的架构详 ...

  9. 【深度学习系列】卷积神经网络CNN原理详解(一)——基本原理(1)

    上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...

  10. ASP.NET页面与IIS底层交互和工作原理详解(一)

    第一回: 引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实 ...

最新文章

  1. Linux下如何同时注释多行/同时取消多行注释
  2. android从放弃到精通 第七天 tomorrow
  3. ASCX呼叫ASPX.CS的方法
  4. HihoCode1032 最长回文子串 manacher算法
  5. maven项目部署打包
  6. LL-verilog语法:case用法
  7. javascript设置和获取cookie的方法
  8. 需求分析师与产品经理的区别
  9. 草地排水-网络流dinic
  10. php 上传文件工具类,PHP 图片上传工具类(支持多文件上传)
  11. 多台路由器堆叠_h3c路由器堆叠配置命令
  12. P1616 疯狂的采药+P1833 樱花+P1077 [NOIP2012 普及组] 摆花+P1064 [NOIP2006 提高组] 金明的预算方案
  13. 免费天气API,全国天气 JSON API接口,可以获取五天的天气预报
  14. MAC VScode 强制重启后保存的代码全没了
  15. 使用superset完成mysql数据库或者hive数据库的数据可视化
  16. 开源者的自我修养|为 ShardingSphere 贡献了千万行代码的程序员,后来当了 CEO...
  17. 小程序钉钉语音录入组件
  18. 网狐大联盟机器管理工具编译与使用
  19. 两个指针变量不可以做什么
  20. ACM—TC 联合招新赛 Round2

热门文章

  1. linux非root用户添加rzsz,Linux rz sz 安装
  2. 这几款好加密软件让你不再担心担心隐私泄露!
  3. strcpy_s函数
  4. doip 源码_汽车DoIP通讯协议的前世今生
  5. ctworklist php开发,DICOM worklist工作原理
  6. STM32单片机雨滴模块
  7. 总结之lowagie.text合并PDF文件
  8. 社交电商源码-带直播电商功能-可DIY前端
  9. 正确学习JavaScript知识和教程
  10. Visio_Premium_2010_VOL 和Project_Pro_2010。