11| 注册安全攻防-谷歌图形验证码 小滴课堂讲师 2024年07月31日 预计阅读 3 分钟 原文 ### 谷歌图形验证码kaptcha Kaptcha 框架介绍 谷歌开源的一个可高度配置的实用**验证码生成工具** - 验证码的`字体/大小/颜色` - 验证码内`容的范围(数字,字母,中文汉字`!) - 验证码`图片的大小,边框,边框粗细,边框颜色` - 验证码的`干扰线` - 验证码的`样式`(鱼眼样式、3D、普通模糊) 依赖:baomidou二次封装的springboot整合starter, 依赖添加到业务服务`nla-user-service` ```java com.baomidou kaptcha-spring-boot-starter 1.1.0 ``` 配置验证码大小央视范围等CaptchaConfig ```java package cn.nla.user.config @Configuration public class CaptchaConfig { /** * 验证码配置 Kaptcha配置类名 */ @Bean @Qualifier("captchaProducer") public DefaultKaptcha kaptcha() { DefaultKaptcha kaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); // properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "220,220,220"); // properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "38,29,12"); // properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "147"); // properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "34"); // properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "25"); // properties.setProperty(Constants.KAPTCHA_SESSION_KEY, "code"); //验证码个数 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Courier"); //字体间隔 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,"8"); //干扰线颜色 // properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white"); //干扰实现类 properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); //图片样式 properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple"); //文字来源 properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789"); Config config = new Config(properties); kaptcha.setConfig(config); return kaptcha; } } ``` ### 使用docker安装redis,用于存储验证码 redis做隔离, 多集群:核心集群和非核心集群,高并发集群和非高并发集群 - 资源隔离 - 数据保护 - 提高性能 - **key规范:业务划分,冒号隔离** - `user-service:captcha:xxxx` - 长度不能过长 ### 配置redis参数及依赖 ```yaml spring: application: name: nla-user-service redis: host: xx.xx.xx.xx password: 123456 port: 6379 ``` common聚合工程依赖配置**spring-redis下排除lettuce使用jedis** ```xml org.springframework.boot spring-boot-starter-data-redis io.lettuce lettuce-core redis.clients jedis ``` ### 通过HttpServletRequest请求头获取请求的真实ip 添加到公共的工具类中:`nla-common\src\main\java\cn\nla\common\util\CommonUtil.java` ```java /** * 获取请求的ip地址 */ public static String getIpAddr(HttpServletRequest request) { String ipAddress; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.error("getIpAddress error", e); } if (inet != null) { ipAddress = inet.getHostAddress(); } } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { log.error(" getIpAddress Exception", e); ipAddress = ""; } return ipAddress; } ``` ### MD5加密算法 同上:`CommonUtil` ```java /** * MD5加密 */ public static String MD5(String data) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.digest(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3); } return sb.toString().toUpperCase(); } catch (Exception e) { log.error("MD5 error", e); } return null; } ``` ### 实现获取图形验证码 - 图形验证码直接从baomidou实现的Producer获取。 - 设置超时时间已经redis存储 - redis存储的key是对应请求的ip进行MD5加密的,保障ip限制来访者邮箱轰炸。 ```java @RestController @RequestMapping("/user/v1") @Slf4j public class NotifyController { @Resource private Producer captchaProducer; @Autowired //不能使用Resource,会报错 private StringRedisTemplate redisTemplate; /** * 图形验证码有效期10分钟 */ private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000 * 10; /** * 获取图形验证码 * * @param request 请求参数 * @param response 响应 */ @ApiOperation("获取图形验证码") @GetMapping("captcha") public void getCaptcha(HttpServletRequest request, HttpServletResponse response) { String captchaText = captchaProducer.createText(); log.info("图形验证码:{}", captchaText); redisTemplate.opsForValue().set(getCaptchaKey(request), captchaText, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS); BufferedImage bufferedImage = captchaProducer.createImage(captchaText); ServletOutputStream outputStream; try { outputStream = response.getOutputStream(); ImageIO.write(bufferedImage, "jpg", outputStream); outputStream.flush(); outputStream.close(); } catch (IOException e) { log.error("获取图形验证码异常", e); } } /** * 获取缓存的key */ private String getCaptchaKey(HttpServletRequest request) { String ip = CommonUtil.getIpAddr(request); String userAgent = request.getHeader("User-Agent"); String key = "user-service:captcha:" + CommonUtil.MD5(ip + userAgent); log.info("ip:{}", ip); log.info("userAgent:{}", userAgent); log.info("key:{}", key); return key; } } ```
评论区