Spring框架生成图片验证码实例讲解

合集下载

SpringBoot使用Captcha生成验证码

SpringBoot使用Captcha生成验证码

SpringBoot使⽤Captcha⽣成验证码⽬录1. 基本结构2. Kaptcha的依赖3. 配置SpringBoot4. 配置Captcha5. ⼯具类6. 接⼝以及实现类7. Controller8. 前端页⾯的实现⽰例1. 基本结构使⽤Captcha⽣成验证码, 利⽤Redis存储验证码Redis中的结构为, Key是32位的UUID, Value为Captcha的4位随机字母以及数字的集合设定Redis过期时间为1min, 即可实现过期验证码的⾃动失效2. Kaptcha的依赖基本的依赖这⾥不再叙述, 主要说⼀下要导⼊Captcha的依赖<!--Kaptcha--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>所有的依赖如下<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 https:///xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.wang</groupId><artifactId>spring_security_framework</artifactId><version>0.0.1-SNAPSHOT</version><name>spring_security_framework</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!--Redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--JDBC--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!--SpringSecurity--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--Thymeleaf--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--Validation--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--SpringBoot Web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--SpringSecurity with thymeleaf--><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency><!--MySQL connector--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--Lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--Test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><!--Druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.2</version></dependency><!--FastJSON--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.74</version></dependency><!--log4j--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><!--Swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!--HuTool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.4.7</version></dependency><!--Kaptcha--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>3. 配置SpringBoot配置SpringBoot的配置⽂件, 这⾥主要关注⼀个session的过期时间#Portserver:port: 80servlet:session:timeout: 1spring:application:name: SpringSecurityFramework#dataBase Settingdatasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#Druid Settingdruid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 30000validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: true#Setting For Druid StatView and Filterfilters: stat,wall,log4jmax-pool-prepared-statement-per-connection-size: 20use-global-data-source-stat: trueconnection-properties: druid.stat.mergeSql=true;druid.stat.slowSql#Redis Settingredis:host: 127.0.0.1port: 6379#Thymeleafthymeleaf:cache: false#Mybatismybatis:type-aliases-package: com.wang.entitymapper-locations: classpath:Mybatis/mapper/*.xmlconfiguration:map-underscore-to-camel-case: true其余的配置, 如log4j, druid, SpringSecurity, RedisTemplate,这⾥就不再赘述4. 配置Captcha我们可以通过JAVA的配置类来配置Captcha⽣成验证码的⼀些规则package com.wang.spring_security_framework.config;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.google.code.kaptcha.util.Config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Properties;//Kaptcha配置@Configurationpublic class KaptchaConfig {@Beanpublic DefaultKaptcha producer() {//Properties类Properties properties = new Properties();// 图⽚边框properties.setProperty("kaptcha.border", "yes");// 边框颜⾊properties.setProperty("kaptcha.border.color", "105,179,90");// 字体颜⾊properties.setProperty("kaptcha.textproducer.font.color", "blue");// 图⽚宽properties.setProperty("kaptcha.image.width", "110");// 图⽚⾼properties.setProperty("kaptcha.image.height", "40");// 字体⼤⼩properties.setProperty("kaptcha.textproducer.font.size", "30");// session keyproperties.setProperty("kaptcha.session.key", "code");// 验证码长度properties.setProperty("kaptcha.textproducer.char.length", "4");// 字体properties.setProperty("s", "宋体,楷体,微软雅⿊");//图⽚⼲扰properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.DefaultNoise");//Kaptcha 使⽤上述配置Config config = new Config(properties);//DefaultKaptcha对象使⽤上述配置, 并返回这个BeanDefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}}5. ⼯具类使⽤UUID作为key, 同时考虑到对验证码的输出结果可能有不同的要求, 这⾥写两个⼯具类来处理它们UUIDUtilpackage com.wang.spring_security_framework.util;import org.springframework.context.annotation.Bean;import ponent;import java.util.UUID;@Component public class UUIDUtil {/** * ⽣成32位的随机UUID * @return 字符形式的⼩写UUID */@Bean public String getUUID32() {return UUID.randomUUID().toString() .replace("-", "").toLowerCase();}}CaptchaUtilpackage com.wang.spring_security_framework.util;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.wang.spring_security_framework.service.CaptchaService;import ty.handler.codec.base64.Base64Encoder;import org.springframework.beans.factory.annotation.Autowired;import ponent;import sun.misc.BASE64Encoder;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.util.Map;@Component//Captcha ⽣成⼯具public class CaptchaUtil {@Autowiredprivate DefaultKaptcha producer;@Autowiredprivate CaptchaService captchaService;//⽣成catchCreator的mappublic Map<String, Object> catchaImgCreator() throws IOException {//⽣成⽂字验证码String text = producer.createText();//⽣成⽂字对应的图⽚验证码BufferedImage image = producer.createImage(text);//将图⽚写出ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream);//对写出的字节数组进⾏Base64编码 ==> ⽤于传递8⽐特字节码BASE64Encoder encoder = new BASE64Encoder();//⽣成tokenMap<String, Object> token = captchaService.createToken(text);token.put("img", encoder.encode(outputStream.toByteArray()));return token;}}6. 接⼝以及实现类1. 接⼝package com.wang.spring_security_framework.service;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.Map;public interface CaptchaService {//⽣成tokenMap<String, Object> createToken(String captcha);//⽣成captcha验证码Map<String, Object> captchaCreator() throws IOException;//验证输⼊的验证码是否正确String versifyCaptcha (String token, String inputCode);}2. 实现类package com.wang.spring_security_framework.service.serviceImpl;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.wang.spring_security_framework.service.CaptchaService; import com.wang.spring_security_framework.util.CaptchaUtil;import com.wang.spring_security_framework.util.UUIDUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;@Servicepublic class CaptchaServiceImpl implements CaptchaService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate UUIDUtil uuidUtil;@Autowiredprivate CaptchaUtil captchaUtil;//从SpringBoot的配置⽂件中取出过期时间@Value("${server.servlet.session.timeout}")private Integer timeout;//UUID为key, 验证码为Value放在Redis中@Overridepublic Map<String, Object> createToken(String captcha) {//⽣成⼀个tokenString key = uuidUtil.getUUID32();//⽣成验证码对应的token 以token为key 验证码为value存在redis中ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();valueOperations.set(key, captcha);//设置验证码过期时间redisTemplate.expire(key, timeout, TimeUnit.MINUTES);Map<String, Object> map = new HashMap<>();map.put("token", key);map.put("expire", timeout);return map;}//⽣成captcha验证码@Overridepublic Map<String, Object> captchaCreator() throws IOException {return captchaUtil.catchaImgCreator();}//验证输⼊的验证码是否正确@Overridepublic String versifyCaptcha(String token, String inputCode) {//根据前端传回的token在redis中找对应的valueValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();if (redisTemplate.hasKey(token)) {//验证通过, 删除对应的keyif (valueOperations.get(token).equals(inputCode)) {redisTemplate.delete(token);return "true";} else {return "false";}} else {return "false";}}}这⾥的验证, 只是简单的验证了输⼊是否能从Redis中匹配, 返回了字符串真实的验证中, 我们还要在逻辑中添加⽤户名和密码的考虑7. Controllerpackage com.wang.spring_security_framework.controller;import com.wang.spring_security_framework.service.CaptchaService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import java.io.IOException;import java.util.Map;@RestControllerpublic class LoginController {@AutowiredCaptchaService captchaService;@GetMapping("/captcha")public Map<String, Object> captcha() throws IOException {return captchaService.captchaCreator();}@GetMapping("/login1")public String login(@RequestParam("token") String token,@RequestParam("inputCode") String inputCode) {return captchaService.versifyCaptcha(token, inputCode);}}captcha ⽤于获取⼀个验证码login1 ⽤于接收到前端的请求后验证并返回结果login1 这⾥为了测试简便实⽤了GET⽅法, ⽽实际中最好使⽤POST⽅法, 这样安全性更⾼8. 前端页⾯的实现前端结构如图, 实现了⼀个简单的验证码<!DOCTYPE html><html lang="en" xmlns:th=""><head><meta charset="UTF-8"><title>登录</title><script src="https:///jquery/3.4.1/jquery.js"></script></head><body><div><div><form th:action="@{/login1}" method="get"><input type="text" id="userName" placeholder="请输⼊⽤户名" name="userName"><br><input type="password" id="password" placeholder="请输⼊密码" name="password"><br><input type="text" id="inputCode" placeholder="请输⼊验证码" maxlength="4" name="inputCode"><!--通过隐藏域传递值, 在下⾯的验证码点击事件中, 将值绑定过来, 这样就可以获得最新的验证码对应的值了!--><input id="token" value="" type="hidden" name="token"><input type="submit" value="登录"></form></div><div><!-- 当⽤户链接时,void(0)计算为0,⽤户点击不会发⽣任何效果 --><a href="javascript:void(0);" rel="external nofollow" title="点击更换验证码"><!--this参数, 返回当前的DOM元素--><img src="" alt="更换验证码" id="imgVerify" onclick="getVerify(this)"></a></div></div><script>//获得img对象let imgVerify = $("#imgVerify").get(0);//$(function())等同于$(document).ready(function()) ==> 页⾯加载完毕之后, 才执⾏函数$(function () {getVerify(imgVerify);});//onclick时间绑定的getVerify函数function getVerify(obj) {$.ajax({type: "GET",url: "/captcha",success: function (result) {obj.src = "data:image/jpeg;base64," + result.img;$("#token").val(result.token);}});}</script></body></html>⽤⼀个 a 标签包围 img 标签, 这样如果图⽚没有加载出来也有⼀个超链接, 不过点了以后没有效果(function())等同于(function())等同于(document).ready(function()) ==> 页⾯加载完毕之后, 才执⾏函数, 这⾥必须要写这个函数, 否则第⼀次加载不会调⽤ onclick ⽅法, 也就不会⽣成验证码!我们利⽤隐藏域将验证码的key传递到表单中, 我们在 img 的点击事件对应的函数的ajax回调函数中可以利⽤jQuery操作DOM, 顺带取出key值放到我们的隐藏域中, 这样提交的时候就会提交 key 和⽤户输⼊的 value 了⽰例验证通过以上就是SpringBoot使⽤Captcha⽣成验证码的详细内容,更多关于SpringBoot⽣成验证码的资料请关注其它相关⽂章!。

SpringBoot实现前端验证码图片生成和校验

SpringBoot实现前端验证码图片生成和校验

SpringBoot实现前端验证码图⽚⽣成和校验SpringBoot下实现前端验证码图⽚的⽣成和校验,供⼤家参考,具体内容如下1.效果点击验证码可以获取新的验证码2.原理后台⽣成验证码图⽚,将图⽚传到前台。

后台在session中保存验证码内容。

前台输⼊验证码后传到后台在后台取出session中保存的验证码进⾏校验。

注意,验证码的明⽂是不能传送到前端的。

前端内容都是透明的,不安全。

验证码是⽤来防机器⼈并不是单单防⼈。

如果把验证码明⽂传到前端很容易就会被破解。

3.图⽚⽣成验证码⽣成⼯具类RandomValidateCodeUtilpublic class RandomValidateCodeUtil {public static final String RANDOMCODEKEY= "RANDOMVALIDATECODEKEY";//放到session中的keyprivate String randString = "0123456789";//随机产⽣只有数字的字符串 private String//private String randString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产⽣只有字母的字符串//private String randString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产⽣数字与字母组合的字符串private int width = 95;// 图⽚宽private int height = 25;// 图⽚⾼private int lineSize = 40;// ⼲扰线数量private int stringNum = 4;// 随机产⽣字符数量private static final Logger logger = LoggerFactory.getLogger(RandomValidateCodeUtil.class);private Random random = new Random();/*** 获得字体*/private Font getFont() {return new Font("Fixedsys", Font.CENTER_BASELINE, 18);}/*** 获得颜⾊*/private Color getRandColor(int fc, int bc) {if (fc > 255)fc = 255;if (bc > 255)bc = 255;int r = fc + random.nextInt(bc - fc - 16);int g = fc + random.nextInt(bc - fc - 14);int b = fc + random.nextInt(bc - fc - 18);return new Color(r, g, b);}/*** ⽣成随机图⽚*/public void getRandcode(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession();// BufferedImage类是具有缓冲区的Image类,Image类是⽤于描述图像信息的类BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);Graphics g = image.getGraphics();// 产⽣Image对象的Graphics对象,改对象可以在图像上进⾏各种绘制操作g.fillRect(0, 0, width, height);//图⽚⼤⼩g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));//字体⼤⼩g.setColor(getRandColor(110, 133));//字体颜⾊// 绘制⼲扰线for (int i = 0; i <= lineSize; i++) {drowLine(g);}// 绘制随机字符String randomString = "";for (int i = 1; i <= stringNum; i++) {randomString = drowString(g, randomString, i);}(randomString);//将⽣成的随机字符串保存到session中session.removeAttribute(RANDOMCODEKEY);session.setAttribute(RANDOMCODEKEY, randomString);g.dispose();try {// 将内存中的图⽚通过流动形式输出到客户端ImageIO.write(image, "JPEG", response.getOutputStream());} catch (Exception e) {logger.error("将内存中的图⽚通过流动形式输出到客户端失败>>>> ", e);}}/*** 绘制字符串*/private String drowString(Graphics g, String randomString, int i) {g.setFont(getFont());g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));String rand = String.valueOf(getRandomString(random.nextInt(randString.length())));randomString += rand;g.translate(random.nextInt(3), random.nextInt(3));g.drawString(rand, 13 * i, 16);return randomString;}/*** 绘制⼲扰线*/private void drowLine(Graphics g) {int x = random.nextInt(width);int y = random.nextInt(height);int xl = random.nextInt(13);int yl = random.nextInt(15);g.drawLine(x, y, x + xl, y + yl);}/*** 获取随机的字符*/public String getRandomString(int num) {return String.valueOf(randString.charAt(num));}}在Controller调⽤⽣成验证码图⽚⽅法并将图⽚传到前端/*** ⽣成验证码*/@RequestMapping(value = "/getVerify")public void getVerify(HttpServletRequest request, HttpServletResponse response) {try {response.setContentType("image/jpeg");//设置相应类型,告诉浏览器输出的内容为图⽚response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容 response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expire", 0);RandomValidateCodeUtil randomValidateCode = new RandomValidateCodeUtil();randomValidateCode.getRandcode(request, response);//输出验证码图⽚⽅法} catch (Exception e) {logger.error("获取验证码失败>>>> ", e);}}前端获取验证码图⽚html<div class="row"><div class="col-xs-6 pull_left"><div class="form-group"><input class="form-control" type="tel" id="verify_input" placeholder="请输⼊验证码" maxlength="4"></div></div><div class="col-xs-6 pull_left"><a href="javascript:void(0);" rel="external nofollow" title="点击更换验证码"><img id="imgVerify" src="" alt="更换验证码" height="36" width="100%" onclick="getVerify(this);"></a></div></div>js//获取验证码function getVerify(obj){obj.src = httpurl + "/sys/getVerify?"+Math.random();}每次点击图⽚重新刷新验证码界⾯初次加载时,调⽤getVerify()⽅法即可。

SpringBoot使用Captcha生成验证码

SpringBoot使用Captcha生成验证码

SpringBoot使用Captcha生成验证码验证码是一种用于验证用户身份的常见技术。

使用验证码可以防止恶意用户自动注册、暴力破解密码、刷票等攻击。

在Spring Boot中,我们可以使用Captcha库来生成验证码。

Captcha是一个Java库,用于生成图像验证码。

它可以生成不同类型的验证码,包括数字、字母、算术运算符等。

首先,我们需要在项目的pom.xml文件中添加Captcha库的依赖:```xml<dependency><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>```然后,在Spring Boot的配置文件中添加以下配置:```yamlkaptcha:border:color: blacktextproducer:char.length: 4image.width: 150image.height: 50```这些配置可以根据需求进行调整。

例如,可以更改验证码的字符集、长度、图像大小等。

接下来,在Spring Boot的控制器中添加生成验证码的方法:```javapublic void getCaptcha(HttpServletResponse response, HttpSession session) throws IOException//创建验证码生成器DefaultKaptcha captchaProducer = new DefaultKaptcha(;//生成验证码文本String codeText = captchaProducer.createText(;// 将验证码文本保存到session中session.setAttribute("captcha", codeText);//创建验证码图像BufferedImage image = captchaProducer.createImage(codeText);//将图像输出到响应流中response.setContentType("image/jpeg");try (ServletOutputStream outputStream =response.getOutputStream()ImageIO.write(image, "jpeg", outputStream);}```在上面的方法中,我们使用DefaultKaptcha类来创建验证码生成器,并使用createText(方法生成验证码文本。

图形验证码的两种实现方式

图形验证码的两种实现方式

图形验证码的两种实现⽅式情形⼀:图形验证码跟短信验证码⼀起,只需要将后台提供的动态链接填到(id="img")的src中即可⽣成动态验证码。

然后,在需要请求接⼝的地⽅,只需把(id="imgCode")中⽤户输⼊的信息通过ajax传给后台,验证验证码是否正确。

原理(后台):后台通过session存储图⽚上的字符串,和之后前台请求过来的带的输⼊的字符串参数,做⽐较,判断是否⼀样。

<!doctype html><html><head><meta charset="UTF-8"><title>图形验证码</title></head><body><form><div class="imgCodeBox"><label for="imgCode">图形验证码</label><input type="text" placeholder="请输⼊验证码" id="imgCode"><img src="" id="img"></div></form></body></html>情形⼆:⽤cavas,但是没有安全性,考虑到实⽤性的话,还是⽤情形⼀的好<!doctype html><html><head><meta charset="UTF-8"><title>测试</title></head><body><canvas id="canvas" width="120" height="40"></canvas><a href="#" id="changeImg">看不清,换⼀张</a><script>/**⽣成⼀个随机数**/function randomNum(min,max){return Math.floor( Math.random()*(max-min)+min);}/**⽣成⼀个随机⾊**/function randomColor(min,max){var r = randomNum(min,max);var g = randomNum(min,max);var b = randomNum(min,max);return "rgb("+r+","+g+","+b+")";}drawPic();document.getElementById("changeImg").onclick = function(e){e.preventDefault();drawPic();}/**绘制验证码图⽚**/function drawPic(){var canvas=document.getElementById("canvas");var width=canvas.width;var height=canvas.height;var ctx = canvas.getContext('2d');ctx.textBaseline = 'bottom';/**绘制背景⾊**/ctx.fillStyle = randomColor(180,240); //颜⾊若太深可能导致看不清ctx.fillRect(0,0,width,height);/**绘制⽂字**/var str = 'ABCEFGHJKLMNPQRSTWXY123456789';for(var i=0; i<4; i++){var txt = str[randomNum(0,str.length)];ctx.fillStyle = randomColor(50,160); //随机⽣成字体颜⾊ctx.font = randomNum(15,40)+'px SimHei'; //随机⽣成字体⼤⼩var x = 10+i*25;var y = randomNum(25,45);var deg = randomNum(-45, 45);//修改坐标原点和旋转⾓度ctx.translate(x,y);ctx.rotate(deg*Math.PI/180);ctx.fillText(txt, 0,0);//恢复坐标原点和旋转⾓度ctx.rotate(-deg*Math.PI/180);ctx.translate(-x,-y);}/**绘制⼲扰线**/for(var i=0; i<8; i++){ctx.strokeStyle = randomColor(40,180);ctx.beginPath();ctx.moveTo( randomNum(0,width), randomNum(0,height) );ctx.lineTo( randomNum(0,width), randomNum(0,height) );ctx.stroke();}/**绘制⼲扰点**/for(var i=0; i<100; i++){ctx.fillStyle = randomColor(0,255);ctx.beginPath();ctx.arc(randomNum(0,width),randomNum(0,height), 1, 0, 2*Math.PI);ctx.fill();}}</script></body></html>情形⼆转⾃:https:///meishuixingdeququ/article/details/52386542情形三、⽤js产⽣随机数实现1、创建图形码容器<label class="myLabel">图形码:<input type = "button" id="code" onclick="createCode()" style="border: 0;background-color: transparent;padding: 0;"/> </label>2、产⽣验证码并在页⾯加载时和点击时调⽤ // 图形验证码var code ; //在全局定义验证码//产⽣验证码window.onload = function createCode(){code = "";var codeLength = 4;//验证码的长度var checkCode = document.getElementById("code");var random = new Array(0,1,2,3,4,5,6,7,8,9,'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');//随机数for(var i = 0; i < codeLength; i++) {//循环操作var index = Math.floor(Math.random()*36);//取得随机数的索引(0~35)code += random[index];//根据索引取得随机数加到code上}checkCode.value = code;//把code值赋给验证码}。

SpringBoot+Vue实现请求后台获取Base64编码的图片验证码并使用Redis缓。。。

SpringBoot+Vue实现请求后台获取Base64编码的图片验证码并使用Redis缓。。。

SpringBoot+Vue实现请求后台获取Base64编码的图⽚验证码并使⽤Redis缓。

场景前端Vue的登录页⾯,验证码请求后台,后台⽣成验证码照⽚后使⽤Base64编码后,返回给前端,前端进⾏显⽰。

注:实现⾸先看前端页⾯login.vue<el-form-item prop="code"><el-inputv-model="loginForm.code"auto-complete="off"placeholder="验证码"style="width: 63%"@keyup.enter.native="handleLogin"><svg-icon slot="prefix" icon-class="validCode"class="el-input__icon input-icon" /></el-input><div class="login-code"><img :src="codeUrl" @click="getCode" /></div>这⾥验证码的图⽚的src属性是绑定的codeUrl,此属性来⾃于请求后台,并且绑定点击事件为getCode此⽅法也是请求后台获取验证码。

所以需要提前声明codeUrl属性data() {return {codeUrl: "",然后在页⾯加载完时请求后台接⼝获取验证码图⽚。

所以在created函数中执⾏请求验证码图⽚的⽅法created() {this.getCode();},此⽅法的具体实现methods: {getCode() {getCodeImg().then(res => {this.codeUrl = "data:image/gif;base64," + res.img;this.loginForm.uuid = res.uuid;});},在此⽅法中调⽤了请求后台接⼝获取验证码图⽚编码后的代码并在前⾯拼接上"data:image/gif;base64,"data:image/png;base64的⽤法详解⽹页上有些图⽚的src或css背景图⽚的url后⾯跟了⼀⼤串字符,⽐如:<img src='data:img/jpg;base64,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。

java如何生成验证码呢

java如何生成验证码呢

java如何生成验证码呢java如何生成验证码呢Java生成验证码的流程是:收到请求->生成验证码所用的随机数->使用随机数写出图片->将随机数记录到Session中->输出验证码Java验证验证码的流程是:收到请求->获取用户传过来的验证码数字->验证是否正确->输出验证结果下面通过一个例子来展示验证码的生成流程,该例子使用基本Java Spring框架的'Rest接口,可以使用任何平台来获取验证码:服务器处理验证码的例子:1.接收验证码请求:/*** 接收验证码请求*/@RequestMapping(value="captchacode")public void CaptchaCode(){ try { CaptchaCodeModel captchaCodeModel=new CaptchaCode().getCode(); //将验证码放到Session中HttpServletRequest httpServletRequest=super.getRequest(); httpServletRequest.getSession().setAttribute("captchacodekey", captchaCodeModel.getCaptchaCode()); //将图片写到客户端HttpServletResponse httpServletResponse=super.getResponse(); //禁止缓存httpServletResponse.setHeader("Pragma", "no-cache"); httpServletResponse.setHeader("Cache-Control", "no-cache"); httpServletResponse.setDateHeader("Expires", 0); ServletOutputStreamservletOutputStream=httpServletResponse.getOutputStream(); //输出图片 ImageIO.write(captchaCodeModel.getCaptchaImage(), "jpeg", servletOutputStream); servletOutputStream.close(); } catch (Exception e) { ("验证码生成失败:"+e.getMessage()); }}2.生成验证码并生成图片:public class CaptchaCode {private int width = 90;// 定义图片的widthprivate int height = 20;// 定义图片的heightprivate int codeCount = 4;// 定义图片上显示验证码的个数private int xx = 15;private int fontHeight = 18;private int codeY = 16;char[] codeSequence = { 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, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };【java如何生成验证码呢】。

在JSP页面中动态生成图片验证码的方法实例

在JSP页面中动态生成图片验证码的方法实例

在JSP页⾯中动态⽣成图⽚验证码的⽅法实例在JSP页⾯中动态⽣成图⽚验证码<%@ page language="java" pageEncoding="UTF-8"%><%@ pageimport="java.awt.*,java.awt.image.*,com.sun.image.codec.jpeg.*,java.util.*" %><%@ taglib /tags-bean">/tags-bean"prefix="bean" %><%@ taglib /tags-html">/tags-html"prefix="html" %><%@ taglib /tags-logic">/tags-logic"prefix="logic" %><%@ taglib /tags-tiles">/tags-tiles"prefix="tiles" %><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html:html lang="true"><head><html:base /><title>MyJsp.jsp</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"><meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page"><!--<link rel="stylesheet" type="text/css" href="styles.css" rel="external nofollow" >--></head><body><h3>在jsp页⾯⽣成验证码</h3><hr/><%//out.clear();//response.setContentType("image/jpeg");//设置响应类型//response.addHeader("pragma","NO-cache");//response.addHeader("Cache-Control","no-cache");//response.addDateHeader("Expries",0);int width=400, height=30;//图⽚的⼤⼩(宽和⾼)//构架画布,第⼀个参数表⽰画布的宽,第⼆个参数表⽰画布的⾼,第三个参数的含义有待确定BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics g = image.getGraphics();//实例化画图对象//以下设置背景⾊g.setColor(Color.yellow);Font DeFont=new Font("宋体", Font.ITALIC, 20);g.setFont(DeFont);//将已经设置好的背景颜⾊填充到指定的画布区域g.fillRect(0,0, width, height);//置字体⾊g.setColor(Color.blue);int x=10,y=10,xl=550,yl=15;g.drawLine(x,y,x+xl,y+yl);//在画布中画椭圆形,参数为椭圆的坐标,⽤于确定椭圆的⼤⼩g.drawOval(0,10,200,10);//在画布上输出⽂字信息,第⼀个参数表⽰要显⽰的⽂字,第⼆和第三个参数表⽰起始点的X、Y坐标g.drawString("想要输出的⽂字-我是陈杉",70,20);g.dispose();ServletOutputStream outStream = response.getOutputStream();JPEGImageEncoder encoder =JPEGCodec.createJPEGEncoder(outStream);encoder.encode(image);outStream.close();%></body></html:html>--将该⽂件保存为pic.jsp,该⽂件负责⽣成图⽚!如果要在其他的页⾯显⽰该图⽚只需要写上<img src="pic.jsp"/>仅此⼀句就ok了,适⽤于⽣成各种验证码!总结以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。

SpringBoot+EasyCaptcha实现验证码功能

SpringBoot+EasyCaptcha实现验证码功能

SpringBoot+EasyCaptcha实现验证码功能⼆、SpringBoot项⽬如何使⽤1、初始化⼀个基本的SpringBoot项⽬2、引⼊EasyCaptcha 依赖<dependency> <groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency>3、⽣成验证码@RequestMapping("/captcha")public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception{GifCaptcha gifCaptcha = new GifCaptcha(130,48,4);CaptchaUtil.out(gifCaptcha, request, response);String verCode = gifCaptcha.text().toLowerCase();request.getSession().setAttribute("CAPTCHA",verCode); //存⼊session}4、校验验证码public class LoginController {private static final Logger log = LoggerFactory.getLogger(LoginController.class);@GetMapping("/")public String login() {return"/test";}@PostMapping("/test")public String test(HttpServletRequest request, HttpServletResponse response) {String username = request.getParameter("username");String password = request.getParameter("password");String captcha = request.getParameter("code");String sessionCode = request.getSession().getAttribute("CAPTCHA").toString();if(sessionCode == null ||StringUtils.isEmpty(sessionCode)){return"验证码为空";}if(captcha.equals(sessionCode)){return"验证通过";}return"验证失败";}}5、前端html中页⾯代码<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="./lib/jquery/1.9.1/jquery.min.js"></script></head><body><div><form action="http://localhost:8088/test" method="post"><div><label>⽤户名</label><input type="text" name="username" id="username"><label>密码</label><input type="password" name="password" id="password"><label>验证码</label><img src="/captcha" height="48px" width="130px"><input type="text" name="code" id="code"><input type="submit" value="登录"></div></form></div></body></html>ref:。

springboot实现验证码功能

springboot实现验证码功能

springboot实现验证码功能本⽂实例为⼤家分享了spring boot实现验证码功能的具体代码,供⼤家参考,具体内容如下流程是按照交互顺序。

1.controller层代码,获取验证码,以及⽣成验证码图⽚。

1.1获取html@RequestMapping(value="/authImage",method=RequestMethod.GET)public String authImage(){return "authImage";}1.2 html<!DOCTYPE html><html><head><title>验证码</title></head><body><table><tr><td nowrap width="437"></td><td><img id="img" src="/image" /><a href='#' οnclick="javascript:changeImg()" style="color:white;"><label style="color:black;">看不清?</label></a> </td></tr></table><!-- 触发JS刷新--><script type="text/javascript">function changeImg(){var img = document.getElementById("img");img.src = "/image?date=" + new Date();}</script></body></html>1.3.获取验证码图⽚@RequestMapping(value="/getImage",method=RequestMethod.GET)public void authImage(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setHeader("Pragma", "No-cache");response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expires", 0);response.setContentType("image/jpeg");// ⽣成随机字串String verifyCode = VerifyCodeUtils.generateVerifyCode(4);// 存⼊会话sessionHttpSession session = request.getSession(true);// 删除以前的session.removeAttribute("verCode");session.removeAttribute("codeTime");session.setAttribute("verCode", verifyCode.toLowerCase());session.setAttribute("codeTime", LocalDateTime.now());// ⽣成图⽚int w = 100, h = 30;OutputStream out = response.getOutputStream();VerifyCodeUtils.outputImage(w, h, out, verifyCode);}1.4 核对验证码@RequestMapping(value="validImage",method=RequestMethod.GET)public String validImage(HttpServletRequest request,HttpSession session){String code = request.getParameter("code");Object verCode = session.getAttribute("verCode");return "验证码已失效,请重新输⼊";}String verCodeStr = verCode.toString();LocalDateTime localDateTime = (LocalDateTime)session.getAttribute("codeTime");long past = localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();if(verCodeStr == null || code == null || code.isEmpty() || !verCodeStr.equalsIgnoreCase(code)){request.setAttribute("errmsg", "验证码错误");return "验证码错误";} else if((now-past)/1000/60>5){request.setAttribute("errmsg", "验证码已过期,重新获取");return "验证码已过期,重新获取";} else {//验证成功,删除存储的验证码session.removeAttribute("verCode");return "200";}}2、VerifyCodeUtils的⼯具类package com.example.springboot.demo.util.varcode;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import java.awt.geom.AffineTransform;import java.awt.image.BufferedImage;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStream;import java.util.Arrays;import java.util.Random;import javax.imageio.ImageIO;public class VerifyCodeUtils{//使⽤到Algerian字体,系统⾥没有的话需要安装字体,字体只显⽰⼤写,去掉了1,0,i,o⼏个容易混淆的字符 public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";private static Random random = new Random();/*** 使⽤系统默认字符源⽣成验证码* @param verifySize 验证码长度* @return*/public static String generateVerifyCode(int verifySize){return generateVerifyCode(verifySize, VERIFY_CODES);}/*** 使⽤指定源⽣成验证码* @param verifySize 验证码长度* @param sources 验证码字符源* @return*/public static String generateVerifyCode(int verifySize, String sources){if(sources == null || sources.length() == 0){sources = VERIFY_CODES;}int codesLen = sources.length();Random rand = new Random(System.currentTimeMillis());StringBuilder verifyCode = new StringBuilder(verifySize);for(int i = 0; i < verifySize; i++){verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));}return verifyCode.toString();}/*** @param h* @param outputFile* @param verifySize* @return* @throws IOException*/public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{ String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, outputFile, verifyCode);return verifyCode;}/*** 输出随机验证码图⽚流,并返回验证码值* @param w* @param h* @param os* @param verifySize* @return* @throws IOException*/public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{ String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, os, verifyCode);return verifyCode;}/*** ⽣成指定验证码图像⽂件* @param w* @param h* @param outputFile* @param code* @throws IOException*/public static void outputImage(int w, int h, File outputFile, String code) throws IOException{if(outputFile == null){return;}File dir = outputFile.getParentFile();if(!dir.exists()){dir.mkdirs();}try{outputFile.createNewFile();FileOutputStream fos = new FileOutputStream(outputFile);outputImage(w, h, fos, code);fos.close();} catch(IOException e){throw e;}}/*** 输出指定验证码图⽚流* @param w* @param h* @param os* @param code* @throws IOException*/public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{int verifySize = code.length();BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);Random rand = new Random();Graphics2D g2 = image.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); Color[] colors = new Color[5];Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,Color.PINK, Color.YELLOW };float[] fractions = new float[colors.length];for(int i = 0; i < colors.length; i++){colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];fractions[i] = rand.nextFloat();Arrays.sort(fractions);g2.setColor(Color.GRAY);// 设置边框⾊g2.fillRect(0, 0, w, h);Color c = getRandColor(200, 250);g2.setColor(c);// 设置背景⾊g2.fillRect(0, 2, w, h-4);//绘制⼲扰线Random random = new Random();g2.setColor(getRandColor(160, 200));// 设置线条的颜⾊for (int i = 0; i < 20; i++) {int x = random.nextInt(w - 1);int y = random.nextInt(h - 1);int xl = random.nextInt(6) + 1;int yl = random.nextInt(12) + 1;g2.drawLine(x, y, x + xl + 40, y + yl + 20);}// 添加噪点float yawpRate = 0.05f;// 噪声率int area = (int) (yawpRate * w * h);for (int i = 0; i < area; i++) {int x = random.nextInt(w);int y = random.nextInt(h);int rgb = getRandomIntColor();image.setRGB(x, y, rgb);}shear(g2, w, h, c);// 使图⽚扭曲g2.setColor(getRandColor(100, 160));int fontSize = h-4;Font font = new Font("Algerian", Font.ITALIC, fontSize);g2.setFont(font);char[] chars = code.toCharArray();for(int i = 0; i < verifySize; i++){AffineTransform affine = new AffineTransform();affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2); g2.setTransform(affine);g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);}g2.dispose();ImageIO.write(image, "jpg", os);}private static Color getRandColor(int fc, int bc) {if (fc > 255)fc = 255;if (bc > 255)bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}private static int getRandomIntColor() {int[] rgb = getRandomRgb();int color = 0;for (int c : rgb) {color = color << 8;color = color | c;}return color;}private static int[] getRandomRgb() {int[] rgb = new int[3];for (int i = 0; i < 3; i++) {rgb[i] = random.nextInt(255);}return rgb;private static void shear(Graphics g, int w1, int h1, Color color) {shearX(g, w1, h1, color);shearY(g, w1, h1, color);}private static void shearX(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(2);boolean borderGap = true;int frames = 1;int phase = random.nextInt(2);for (int i = 0; i < h1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(0, i, w1, 1, (int) d, 0);if (borderGap) {g.setColor(color);g.drawLine((int) d, i, 0, i);g.drawLine((int) d + w1, i, w1, i);}}}private static void shearY(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(40) + 10; // 50;boolean borderGap = true;int frames = 20;int phase = 7;for (int i = 0; i < w1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(i, 0, 1, h1, 0, (int) d);if (borderGap) {g.setColor(color);g.drawLine(i, (int) d, i, 0);g.drawLine(i, (int) d + h1, i, h1);}}}}}以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

前端开发实训案例网页验证码的生成与验证

前端开发实训案例网页验证码的生成与验证

前端开发实训案例网页验证码的生成与验证前端开发实训案例网页验证码的生成与验证为了保证网页的安全性和防止机器人恶意攻击,很多网站在用户进行注册、登录或者提交表单等操作时会要求用户输入验证码。

验证码是一种基于图像识别的技术,通过生成和验证验证码,可以有效防止机器人的自动化行为,确保用户身份的真实性。

本文将介绍前端开发中实现网页验证码的生成与验证的案例,并提供相应的代码实例。

1. 网页验证码的生成在前端开发中,验证码的生成是通过服务器端应用和前端代码联动来实现的。

以下是一种常见的验证码生成方法:首先,我们需要在服务器端生成验证码图片。

可以使用第三方库、自定义脚本或开源工具来实现这一步骤。

生成验证码图片的关键是要确保图片上的字符是随机的、干扰元素适当,并且字体、大小、颜色等可配置。

接下来,在前端页面中使用<img>标签将验证码图片展示给用户。

同时,在用户填写验证码的输入框旁边提供一个刷新按钮,点击按钮可以重新加载生成新的验证码图片。

2. 网页验证码的验证用户输入验证码后,前端需要将验证码的值发送给服务器端进行验证。

下面是一个网络验证码验证的示例过程:首先,前端需要获取用户输入的验证码值,可以使用JavaScript的相关API来获取。

然后,前端通过AJAX等方式将验证码的值发送给服务器端。

服务器端会将用户输入的验证码值与之前生成的验证码值进行比对。

如果验证码验证成功,服务器端会返回一个验证通过的信息(如JSON格式的数据)给前端;否则,返回一个验证失败的信息。

前端根据服务器端返回的响应,可以展示相应的提示信息给用户,例如验证码正确、验证码错误等提示。

3. 安全性考虑为了保证验证码的安全性,防止机器人攻击,有以下几点需要注意:首先,生成的验证码要足够随机,不能出现重复的情况,且需要有一定复杂度。

可以使用混合字母、数字和特殊字符的组合,增加验证码的难度。

其次,验证码的图片要有适当的干扰元素,使机器无法简单识别。

springMVC实现图形验证码(kaptcha)

springMVC实现图形验证码(kaptcha)

springMVC实现图形验证码(kaptcha)springMVC项⽬中实现图形验证码功能,可以使⽤kaptcha来实现,下⾯是步骤⼀、引⼊架包,pom.xml<dependency><groupId>com.google.code</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>⼆、kaptchaProducer配置,需要在spring-mvc.xml中对kaptchaProducer的bean进⾏配置,可以对验证码图形的属性进⾏配置,⽹上也有很多可以参考的,我这是放我项⽬中的相关代码<!-- 使⽤Kaptcha⽣成验证码 --><bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha"><property name="config"><bean class="com.google.code.kaptcha.util.Config"><constructor-arg><props><prop key="kaptcha.border">yes</prop><prop key="kaptcha.border.color">105,179,90</prop><prop key="kaptcha.textproducer.font.color">blue</prop><prop key="kaptcha.image.width">125</prop><prop key="kaptcha.image.height">60</prop><prop key="kaptcha.textproducer.font.size">40</prop><prop key="kaptcha.session.key">code</prop><prop key="kaptcha.textproducer.char.length">4</prop><prop key="s">宋体,楷体,微软雅⿊</prop></props></constructor-arg></bean></property></bean>三、kaptchaProducer配置完毕后,就只可以写⽣成验证码的⽅法了。

SpringBoot之后端图形验证码实现

SpringBoot之后端图形验证码实现

SpringBoot之后端图形验证码实现此验证码的实现没有⽤到太多的插件,话不多说直接上代码,⼤家拿过去就可以⽤。

1.验证码类package com.youyou.login.util.validatecode;import lombok.Data;/*** 验证码类*/@Datapublic class VerifyCode {private String code;private byte[] imgBytes;private long expireTime;}2.验证码⽣成接⼝package com.youyou.login.util.validatecode;import java.io.IOException;import java.io.OutputStream;/*** 验证码⽣成接⼝*/public interface IVerifyCodeGen {/*** ⽣成验证码并返回code,将图⽚写的os中** @param width* @param height* @param os* @return* @throws IOException*/String generate(int width, int height, OutputStream os) throws IOException;/*** ⽣成验证码对象** @param width* @param height* @return* @throws IOException*/VerifyCode generate(int width, int height) throws IOException;}3.验证码⽣成实现类package com.youyou.login.util.validatecode;import com.youyou.util.RandomUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.imageio.ImageIO;import java.awt.*;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.util.Random;/*** 验证码实现类*/public class SimpleCharVerifyCodeGenImpl implements IVerifyCodeGen {private static final Logger logger = LoggerFactory.getLogger(SimpleCharVerifyCodeGenImpl.class);private static final String[] FONT_TYPES = { "\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53", "\u6977\u4f53", "\u96b6\u4e66" }; private static final int VALICATE_CODE_LENGTH = 4;/*** 设置背景颜⾊及⼤⼩,⼲扰线** @param graphics* @param width* @param height*/private static void fillBackground(Graphics graphics, int width, int height) {// 填充背景graphics.setColor(Color.WHITE);//设置矩形坐标x y 为0graphics.fillRect(0, 0, width, height);// 加⼊⼲扰线条for (int i = 0; i < 8; i++) {//设置随机颜⾊算法参数graphics.setColor(RandomUtils.randomColor(40, 150));Random random = new Random();int x = random.nextInt(width);int y = random.nextInt(height);int x1 = random.nextInt(width);int y1 = random.nextInt(height);graphics.drawLine(x, y, x1, y1);}}/*** ⽣成随机字符** @param width* @param height* @param os* @return* @throws IOException*/@Overridepublic String generate(int width, int height, OutputStream os) throws IOException {BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics graphics = image.getGraphics();fillBackground(graphics, width, height);String randomStr = RandomUtils.randomString(VALICATE_CODE_LENGTH);createCharacter(graphics, randomStr);graphics.dispose();//设置JPEG格式ImageIO.write(image, "JPEG", os);return randomStr;}/*** 验证码⽣成** @param width* @param height* @return*/@Overridepublic VerifyCode generate(int width, int height) {VerifyCode verifyCode = null;try (//将流的初始化放到这⾥就不需要⼿动关闭流ByteArrayOutputStream baos = new ByteArrayOutputStream();) {String code = generate(width, height, baos);verifyCode = new VerifyCode();verifyCode.setCode(code);verifyCode.setImgBytes(baos.toByteArray());} catch (IOException e) {logger.error(e.getMessage(), e);verifyCode = null;}return verifyCode;}/*** 设置字符颜⾊⼤⼩** @param g* @param randomStr*/private void createCharacter(Graphics g, String randomStr) {char[] charArray = randomStr.toCharArray();for (int i = 0; i < charArray.length; i++) {//设置RGB颜⾊算法参数g.setColor(new Color(50 + RandomUtils.nextInt(100),50 + RandomUtils.nextInt(100), 50 + RandomUtils.nextInt(100)));//设置字体⼤⼩,类型g.setFont(new Font(FONT_TYPES[RandomUtils.nextInt(FONT_TYPES.length)], Font.BOLD, 26));//设置x y 坐标g.drawString(String.valueOf(charArray[i]), 15 * i + 5, 19 + RandomUtils.nextInt(8));}}}4.⼯具类package com.youyou.util;import java.awt.*;import java.util.Random;public class RandomUtils extends ng3.RandomUtils {private static final char[] CODE_SEQ = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J','K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W','X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };private static final char[] NUMBER_ARRAY = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };private static Random random = new Random();public static String randomString(int length) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {sb.append(String.valueOf(CODE_SEQ[random.nextInt(CODE_SEQ.length)]));}return sb.toString();}public static String randomNumberString(int length) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {sb.append(String.valueOf(NUMBER_ARRAY[random.nextInt(NUMBER_ARRAY.length)]));}return sb.toString();}public static Color randomColor(int fc, int bc) {int f = fc;int b = bc;Random random = new Random();if (f > 255) {f = 255;}if (b > 255) {b = 255;}return new Color(f + random.nextInt(b - f), f + random.nextInt(b - f), f + random.nextInt(b - f));}public static int nextInt(int bound) {return random.nextInt(bound);}}经过以上代码,我们的验证码⽣成功能基本上已经实现了,现在还需要⼀个controller来调⽤它。

SpringBoot验证码的生成和验证详解

SpringBoot验证码的生成和验证详解

SpringBoot验证码的⽣成和验证详解前⾔本⽂介绍的imagecode⽅法是⼀个⽣成图形验证码的请求,checkcode⽅法实现了对这个图形验证码的验证。

从验证码的⽣成到验证的过程中,验证码是通过Session来保存的,并且设定⼀个验证码的最长有效时间为5分钟。

验证码的⽣成规则是从0~9的数字中,随机产⽣⼀个4位数,并增加⼀些⼲扰元素,最终组合成为⼀个图形输出1、验证码⽣成类import java.awt.*;import java.awt.image.BufferedImage;import java.io.OutputStream;import java.util.HashMap;import java.util.Map;import java.util.Random;public class ImageCode {private static char mapTable[] = {'0', '1', '2', '3', '4', '5','6', '7', '8', '9', '0', '1','2', '3', '4', '5', '6', '7','8', '9'};public static Map<String, Object> getImageCode(int width, int height, OutputStream os) {Map<String,Object> returnMap = new HashMap<String, Object>();if (width <= 0) width = 60;if (height <= 0) height = 20;BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 获取图形上下⽂Graphics g = image.getGraphics();//⽣成随机类Random random = new Random();// 设定背景⾊g.setColor(getRandColor(200, 250));g.fillRect(0, 0, width, height);//设定字体g.setFont(new Font("Times New Roman", Font.PLAIN, 18));// 随机产⽣168条⼲扰线,使图象中的认证码不易被其它程序探测到g.setColor(getRandColor(160, 200));for (int i = 0; i < 168; i++) {int x = random.nextInt(width);int y = random.nextInt(height);int xl = random.nextInt(12);int yl = random.nextInt(12);g.drawLine(x, y, x + xl, y + yl);}//取随机产⽣的码String strEnsure = "";//4代表4位验证码,如果要⽣成更多位的认证码,则加⼤数值for (int i = 0; i < 4; ++i) {strEnsure += mapTable[(int) (mapTable.length * Math.random())];// 将认证码显⽰到图象中g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));//直接⽣成String str = strEnsure.substring(i, i + 1);g.drawString(str, 13 * i + 6, 16);}// 释放图形上下⽂g.dispose();returnMap.put("image",image);returnMap.put("strEnsure",strEnsure);return returnMap;}//给定范围获得随机颜⾊static Color getRandColor(int fc, int bc) {Random random = new Random();if (fc > 255) fc = 255;if (bc > 255) bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}}2、获取验证码API@RequestMapping(value = "/images/imagecode")public String imagecode(HttpServletRequest request, HttpServletResponse response) throws Exception {OutputStream os = response.getOutputStream();Map<String,Object> map = ImageCode.getImageCode(60, 20, os);String simpleCaptcha = "simpleCaptcha";request.getSession().setAttribute(simpleCaptcha, map.get("strEnsure").toString().toLowerCase());request.getSession().setAttribute("codeTime",new Date().getTime());try {ImageIO.write((BufferedImage) map.get("image"), "JPEG", os);} catch (IOException e) {return "";}return null;}3、验证验证码API@RequestMapping(value = "/checkcode")@ResponseBodypublic String checkcode(HttpServletRequest request, HttpSession session) throws Exception {String checkCode = request.getParameter("checkCode");Object cko = session.getAttribute("simpleCaptcha") ; //验证码对象if(cko == null){request.setAttribute("errorMsg", "验证码已失效,请重新输⼊!");return "验证码已失效,请重新输⼊!";}String captcha = cko.toString();Date now = new Date();Long codeTime = Long.valueOf(session.getAttribute("codeTime")+"");if(StringUtils.isEmpty(checkCode) || captcha == null || !(checkCode.equalsIgnoreCase(captcha))) {request.setAttribute("errorMsg", "验证码错误!");return "验证码错误!";} else if ((now.getTime()-codeTime)/1000/60>5) {//验证码有效时长为5分钟request.setAttribute("errorMsg", "验证码已失效,请重新输⼊!");return "验证码已失效,请重新输⼊!";}else {session.removeAttribute("simpleCaptcha");return "1";}}总结以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作能带来⼀定的帮助,如果有疑问⼤家可以留⾔交流,谢谢⼤家对的⽀持。

SpringSecurity实现图形验证码功能

SpringSecurity实现图形验证码功能

SpringSecurity实现图形验证码功能⒈封装验证码类1package cn.coreqi.security.validate;23import java.awt.image.BufferedImage;4import java.time.LocalDateTime;56public class ImageCode {7private BufferedImage image;8private String code;9private LocalDateTime expireTime; //过期时间1011public ImageCode(BufferedImage image, String code, Integer expireIn) {12this.image = image;13this.code = code;14this.expireTime = LocalDateTime.now().plusSeconds(expireIn);15 }1617public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {18this.image = image;19this.code = code;20this.expireTime = expireTime;21 }2223public boolean isExpried(){24return LocalDateTime.now().isAfter(expireTime);25 }2627public BufferedImage getImage() {28return image;29 }3031public void setImage(BufferedImage image) {32this.image = image;33 }3435public String getCode() {36return code;37 }3839public void setCode(String code) {40this.code = code;41 }4243public LocalDateTime getExpireTime() {44return expireTime;45 }4647public void setExpireTime(LocalDateTime expireTime) {48this.expireTime = expireTime;49 }50 }⒉封装验证码控制器1package cn.coreqi.security.controller;23import cn.coreqi.security.validate.ImageCode;4import com.sun.image.codec.jpeg.JPEGCodec;5import com.sun.image.codec.jpeg.JPEGImageEncoder;6import org.springframework.social.connect.web.HttpSessionSessionStrategy;7import org.springframework.social.connect.web.SessionStrategy;8import org.springframework.web.bind.annotation.GetMapping;9import org.springframework.web.bind.annotation.RestController;10import org.springframework.web.context.request.ServletWebRequest;1112import javax.imageio.ImageIO;13import javax.servlet.http.HttpServletRequest;14import javax.servlet.http.HttpServletResponse;15import java.awt.*;16import java.awt.image.BufferedImage;17import java.io.IOException;18import java.util.Random;1920 @RestController21public class ValidateController {2223public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";24private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();2526 @GetMapping("code/image")27public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {28 ImageCode imageCode = createImageCode(request);29 sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);3031 response.setHeader("Pragma","No-cache");32 response.setHeader("Cache-Control","no-cache");33//response.setDateHeader("Expires", 0);3435 JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(response.getOutputStream());36 encoder.encode(imageCode.getImage());3738//ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream()); //当tomcat下temp⽂件夹不存在则"Can't create output stream"39 }4041private ImageCode createImageCode(HttpServletRequest request) {42int width = 67;43int height = 23;44 BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);4546 Graphics g = image.getGraphics();4748 Random random = new Random();4950 g.setColor(getRandColor(200,250));51 g.fillRect(0,0,width,height);52 g.setFont(new Font("Times New Roman",Font.ITALIC,20));53 g.setColor(getRandColor(160,200));54for (int i = 0;i < 155; i++){55int x = random.nextInt(width);56int y = random.nextInt(height);57int xl = random.nextInt(12);58int yl = random.nextInt(12);59 g.drawLine(x,y,x+xl,y+yl);60 }61 String sRand = "";62for(int i = 0;i < 4; i++){63 String rand = String.valueOf(random.nextInt(10));64 sRand += rand;65 g.setColor(new Color(20 + random.nextInt(110),20 + random.nextInt(110),20 + random.nextInt(110)));66 g.drawString(rand,13 * i + 6,16);67 }68 g.dispose();69return new ImageCode(image,sRand,60);70 }7172/**73 * ⽣成随机背景条纹74 * @param fc75 * @param bc76 * @return77*/78private Color getRandColor(int fc, int bc) {79 Random random = new Random();80if(fc > 255){81 fc = 255;82 }83if(bc > 255){84 bc = 255;85 }86int r = fc + random.nextInt(bc - fc);87int g = fc + random.nextInt(bc - fc);88int b = fc + random.nextInt(bc - fc);89return new Color(r,g,b);90 }91 }⒊放⾏验证码的Rest地址⒋表单添加验证码1<tr>2<td>图形验证码:</td>3<td>4<input type="text" name="imageCode">5<img src="/code/image">6</td>7</tr>⒌声明⼀个验证码异常,⽤于抛出特定的验证码异常1package cn.coreqi.security.validate;23import org.springframework.security.core.AuthenticationException;45public class ValidateCodeException extends AuthenticationException {6public ValidateCodeException(String msg) {7super(msg);8 }9 }⒍创建⼀个过滤器,⽤于验证请求中的验证码是否正确1package cn.coreqi.security.Filter;23import cn.coreqi.security.validate.ImageCode;4import cn.coreqi.security.validate.ValidateCodeException;5import org.springframework.security.web.authentication.AuthenticationFailureHandler;6import org.springframework.social.connect.web.HttpSessionSessionStrategy;7import org.springframework.social.connect.web.SessionStrategy;8import org.springframework.util.StringUtils;9import org.springframework.web.bind.ServletRequestBindingException;10import org.springframework.web.bind.ServletRequestUtils;11import org.springframework.web.context.request.ServletWebRequest;12import org.springframework.web.filter.OncePerRequestFilter;13import cn.coreqi.security.controller.*;1415import javax.servlet.FilterChain;16import javax.servlet.ServletException;17import javax.servlet.http.HttpServletRequest;18import javax.servlet.http.HttpServletResponse;19import java.io.IOException;2021public class ValidateCodeFilter extends OncePerRequestFilter {2223private AuthenticationFailureHandler authenticationFailureHandler;2425private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();2627public AuthenticationFailureHandler getAuthenticationFailureHandler() {28return authenticationFailureHandler;29 }3031public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {32this.authenticationFailureHandler = authenticationFailureHandler;33 }3435public SessionStrategy getSessionStrategy() {36return sessionStrategy;37 }3839public void setSessionStrategy(SessionStrategy sessionStrategy) {40this.sessionStrategy = sessionStrategy;41 }4243 @Override44protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 45if (httpServletRequest.equals("/authentication/form") && httpServletRequest.getMethod().equals("post")) {46try {47 validate(new ServletWebRequest(httpServletRequest));4849 }catch (ValidateCodeException e){50 authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);51return;52 }53 }54 filterChain.doFilter(httpServletRequest,httpServletResponse); //如果不是登录请求,直接调⽤后⾯的过滤器链55 }5657private void validate(ServletWebRequest request) throws ServletRequestBindingException {58 ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateController.SESSION_KEY);59 String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");60if(!StringUtils.hasText(codeInRequest)){61throw new ValidateCodeException("验证码的值不能为空!");62 }63if(codeInSession == null){64throw new ValidateCodeException("验证码不存在!");65 }66if(codeInSession.isExpried()){67 sessionStrategy.removeAttribute(request,ValidateController.SESSION_KEY);68throw new ValidateCodeException("验证码已过期!");69 }70if(!codeInSession.getCode().equals(codeInRequest)){71throw new ValidateCodeException("验证码不正确!");72 }73 sessionStrategy.removeAttribute(request,ValidateController.SESSION_KEY);74 }75 }⒎在SpringSecurity过滤器链中注册我们的过滤器1package cn.coreqi.security.config;23import cn.coreqi.security.Filter.ValidateCodeFilter;4import org.springframework.beans.factory.annotation.Autowired;5import org.springframework.context.annotation.Bean;6import org.springframework.context.annotation.Configuration;7import org.springframework.security.config.annotation.web.builders.HttpSecurity;8import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;9import org.springframework.security.crypto.password.NoOpPasswordEncoder;10import org.springframework.security.crypto.password.PasswordEncoder;11import org.springframework.security.web.authentication.AuthenticationFailureHandler;12import org.springframework.security.web.authentication.AuthenticationSuccessHandler;13import ernamePasswordAuthenticationFilter;1415 @Configuration16public class WebSecurityConfig extends WebSecurityConfigurerAdapter {1718 @Autowired19private AuthenticationSuccessHandler coreqiAuthenticationSuccessHandler;2021 @Autowired22private AuthenticationFailureHandler coreqiAuthenticationFailureHandler;2324 @Bean25public PasswordEncoder passwordEncoder(){26return NoOpPasswordEncoder.getInstance();27 }2829 @Override30protected void configure(HttpSecurity http) throws Exception {31 ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();32 validateCodeFilter.setAuthenticationFailureHandler(coreqiAuthenticationFailureHandler);3334//http.httpBasic() //httpBasic登录 BasicAuthenticationFilter35 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) //加载⽤户名密码过滤器的前⾯36 .formLogin() //表单登录 UsernamePasswordAuthenticationFilter37 .loginPage("/coreqi-signIn.html") //指定登录页⾯38//.loginPage("/authentication/require")39 .loginProcessingUrl("/authentication/form") //指定表单提交的地址⽤于替换UsernamePasswordAuthenticationFilter默认的提交地址40 .successHandler(coreqiAuthenticationSuccessHandler) //登录成功以后要⽤我们⾃定义的登录成功处理器,不⽤Spring默认的。

SpringSecurity添加验证码的两种方式小结

SpringSecurity添加验证码的两种方式小结

SpringSecurity添加验证码的两种⽅式⼩结⽬录⼀、⾃定义认证逻辑⼆、⾃定义过滤器总结⼀、⾃定义认证逻辑⽣成验证码⼯具<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>添加Kaptcha配置@Configurationpublic class KaptchaConfig {@BeanProducer kaptcha() {Properties properties = new Properties();properties.setProperty("kaptcha.image.width", "150");properties.setProperty("kaptcha.image.height", "50");properties.setProperty("kaptcha.textproducer.char.string", "0123456789");properties.setProperty("kaptcha.textproducer.char.length", "4");Config config = new Config(properties);DefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}}⽣成验证码⽂本,放⼊HttpSession中根据验证码⽂本⽣成图⽚通过IO流写出到前端。

springboot+kaptcha生成数学运算验证码和字符验证码

springboot+kaptcha生成数学运算验证码和字符验证码

springboot+kaptcha⽣成数学运算验证码和字符验证码使⽤以下代码只需要复制粘贴,修改⼀处⽂本⽣成器路径即可,⽂中有交代。

1.添加kaptcha依赖<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>2.编写验证码⽂本⽣成器,为了给数据运算验证码⽤的/*** 验证码⽂本⽣成器** @author shw*/public class KaptchaTextCreator extends DefaultTextCreator{private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");@Overridepublic String getText(){Integer result = 0;Random random = new SecureRandom();int x = random.nextInt(10);int y = random.nextInt(10);StringBuilder suChinese = new StringBuilder();int randomoperands = (int) Math.round(Math.random() * 2);if (randomoperands == 0){result = x * y;suChinese.append(CNUMBERS[x]);suChinese.append("*");suChinese.append(CNUMBERS[y]);}else if (randomoperands == 1){if (!(x == 0) && y % x == 0){result = y / x;suChinese.append(CNUMBERS[y]);suChinese.append("/");suChinese.append(CNUMBERS[x]);}else{result = x + y;suChinese.append(CNUMBERS[x]);suChinese.append("+");suChinese.append(CNUMBERS[y]);}}else if (randomoperands == 2){if (x >= y){result = x - y;suChinese.append(CNUMBERS[x]);suChinese.append("-");suChinese.append(CNUMBERS[y]);}else{result = y - x;suChinese.append(CNUMBERS[y]);suChinese.append("-");suChinese.append(CNUMBERS[x]);}}else{result = x + y;suChinese.append(CNUMBERS[x]);suChinese.append("+");suChinese.append(CNUMBERS[y]);}suChinese.append("=?@" + result);return suChinese.toString();}}3.编写配置⽂件@Configurationpublic class CaptchaConfig{@Bean(name = "captchaProducer")public DefaultKaptcha getKaptchaBean(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();// 是否有边框默认为true 我们可以⾃⼰设置yes,noproperties.setProperty(KAPTCHA_BORDER, "yes");// 验证码⽂本字符颜⾊默认为Color.BLACKproperties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");// 验证码图⽚宽度默认为200properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");// 验证码图⽚⾼度默认为50properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");// 验证码⽂本字符⼤⼩默认为40properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");// KAPTCHA_SESSION_KEYproperties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");// 验证码⽂本字符长度默认为5properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");// 验证码⽂本字体样式默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");// 图⽚样式⽔纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}@Bean(name = "captchaProducerMath")public DefaultKaptcha getKaptchaBeanMath(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();// 是否有边框默认为true 我们可以⾃⼰设置yes,noproperties.setProperty(KAPTCHA_BORDER, "yes");// 边框颜⾊默认为Color.BLACKproperties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");// 验证码⽂本字符颜⾊默认为Color.BLACKproperties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");// 验证码图⽚宽度默认为200properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");// 验证码图⽚⾼度默认为50properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");// 验证码⽂本字符⼤⼩默认为40properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");// KAPTCHA_SESSION_KEYproperties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");// 验证码⽂本⽣成器properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.qjwl.rabbitmq.config.KaptchaTextCreator");// 验证码⽂本字符间距默认为2properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");// 验证码⽂本字符长度默认为5properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");// 验证码⽂本字体样式默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");// 验证码噪点颜⾊默认为Color.BLACKproperties.setProperty(KAPTCHA_NOISE_COLOR, "white");// ⼲扰实现类properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");// 图⽚样式⽔纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}}注意:这个位置的com.qjwl.rabbitmq.config.KaptchaTextCreator路径要改成你⾃⼰的验证码⽂本⽣成器的路径。

Spring框架生成图片验证码实例课案

Spring框架生成图片验证码实例课案

Spring框架生成图片验证码实例
验证码在很多地方都会遇到,实现的方法和形式也有很多,主要的目的就是为了安全,防止一些恶意的攻击等。

今天在之前搭建好的一个spring框架上写了一个验证码的生成demo,我会贴出细节代码,但是spring的配置就不在介绍了,有需要的可以参考借鉴。

这篇文章会从前台页面到后台实现完整的讲解,下面跟着小编一起来看看。

1、前台的代码,image.jsp
2、后台代码ImageGenController.java
3、生成验证码的工具类RandomValidateCode.java
4、总结
本文的内容到这就结束了,如果对里面的地方有不懂的地方,可以留言讨论。

希望本文的内容对大家的学习工作能有所帮助。

SpringMvc使用GoogleKaptcha生成验证码

SpringMvc使用GoogleKaptcha生成验证码

SpringMvc使⽤GoogleKaptcha⽣成验证码前⾔:google captcha 是google⽣成验证码的⼀个⼯具类,其原理是将随机⽣成字符串保存到session中,同时以图⽚的形式返回给页⾯,之后前台页⾯提交到后台进⾏对⽐。

1、jar包准备官⽅提供的pom应该是<dependency><groupId>com.google.code.kaptcha</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>但是下载不下来,我在阿⾥的maven仓库找到的pom如下:<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>2、spring bean的配置<!-- google kaptcha的相关配置--><bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha"><property name="config"><bean class="com.google.code.kaptcha.util.Config"><constructor-arg><props><!-- 是否有边框可选yes 或者 no --><prop key="kaptcha.border">yes</prop><!-- 边框颜⾊ --><prop key="kaptcha.border.color">105,179,90</prop><!-- 验证码⽂本字符颜⾊ --><prop key="kaptcha.textproducer.font.color">blue</prop><!-- 验证码⽂本字符⼤⼩ --><prop key="kaptcha.textproducer.font.size">45</prop><!-- 验证码图⽚的宽度默认200 --><prop key="kaptcha.image.width">125</prop><!-- 验证码图⽚的⾼度默认50 --><prop key="kaptcha.image.height">45</prop><!-- 验证码⽂本字符长度默认为5 --><prop key="kaptcha.textproducer.char.length">4</prop><!-- 验证码⽂本字体样式默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) --><prop key="s">宋体,楷体,微软雅⿊</prop></props></constructor-arg></bean></property></bean>3、Controller的两个⽅法package g.controller;import java.awt.image.BufferedImage;import java.io.IOException;import java.io.PrintWriter;import javax.annotation.Resource;import javax.imageio.ImageIO;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.servlet.ModelAndView;import com.google.code.kaptcha.Constants;import com.google.code.kaptcha.Producer;@Controller@RequestMapping("captcha")public class CaptchaController {@Resourceprivate Producer captchaProducer;/**** 获取验证码图⽚* @author ccg* @param request* @param response* @return* @throws IOException* Created 2017年1⽉17⽇下午5:07:28*/@RequestMapping("getCaptchaCode")public ModelAndView getCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException{HttpSession session = request.getSession();response.setDateHeader("Expires", 0);response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");response.addHeader("Cache-Control", "post-check=0, pre-check=0");response.setHeader("Pragma", "no-cache");response.setContentType("image/jpeg");//⽣成验证码⽂本String capText = captchaProducer.createText();session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);System.out.println("⽣成验证码⽂本===="+capText);//利⽤⽣成的字符串构建图⽚BufferedImage bi = captchaProducer.createImage(capText);ServletOutputStream out = response.getOutputStream();ImageIO.write(bi, "jpg", out);try {out.flush();} finally {out.close();}return null;}/**** 前端输⼊的验证码与⽣成的对⽐* @author ccg* @param request* @param response* @param captchaCode* Created 2017年1⽉17⽇下午5:34:23*/@RequestMapping("checkCaptchaCode")public void checkCaptchaCode(HttpServletRequest request, HttpServletResponse response,@RequestParam("captchaCode") String captchaCode){System.out.println("页⾯输⼊验证码===="+captchaCode);response.setCharacterEncoding("UTF-8");response.setHeader("Pragma", "No-cache");response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expires", 0);String generateCode =(String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);String result = "";if(generateCode.equals(captchaCode)){result = "验证成功";}else{result = "输⼊错误";}PrintWriter out = null;try {out = response.getWriter();} catch (IOException e) {e.printStackTrace();}out.print(result);out.flush();}}4、前台页⾯代码<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "/TR/html4/loose.dtd"><html><head><script src="${pageContext.request.contextPath}/js/jquery.min.js" type="text/javascript"></script><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>⽣成的验证码:<img id="changeCaptcha" src="http://127.0.0.1/captcha/getCaptchaCode.htm"> <a href="javascript:changeCaptcha()" rel="external nofollow" >看不清,换⼀张</a> <br><br>请输⼊验证码:<input id="captchaCode" type="text"> <input type="button" value="提交验证" onclick="checkCaptcha()"></body><script type="text/javascript">//获取验证码图⽚function changeCaptcha(){$("#changeCaptcha").attr("src","http://127.0.0.1/captcha/getCaptchaCode.htm");}//验证输⼊的验证码function checkCaptcha(){var captchaCode = $("#captchaCode").val();$.ajax({type:'post',async : false,url:'http://127.0.0.1/captcha/checkCaptchaCode.htm',data:{"captchaCode" : captchaCode},success:function(res){alert(res);}});}</script></html>需要注意到引⽤了jquery.min.js5、运⾏效果附Google Captcha 可配置项kaptcha.border 是否有边框默认为true 我们可以⾃⼰设置yes,nokaptcha.border.color 边框颜⾊默认为Color.BLACKkaptcha.border.thickness 边框粗细度默认为1kaptcha.producer.impl 验证码⽣成器默认为DefaultKaptchakaptcha.textproducer.impl 验证码⽂本⽣成器默认为DefaultTextCreatorkaptcha.textproducer.char.string 验证码⽂本字符内容范围默认为abcde2345678gfynmnpwxkaptcha.textproducer.char.length 验证码⽂本字符长度默认为5s 验证码⽂本字体样式默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) kaptcha.textproducer.font.size 验证码⽂本字符⼤⼩默认为40kaptcha.textproducer.font.color 验证码⽂本字符颜⾊默认为Color.BLACKkaptcha.textproducer.char.space 验证码⽂本字符间距默认为2kaptcha.noise.impl 验证码噪点⽣成对象默认为DefaultNoisekaptcha.noise.color 验证码噪点颜⾊默认为Color.BLACKkaptcha.obscurificator.impl 验证码样式引擎默认为WaterRipplekaptcha.word.impl 验证码⽂本字符渲染默认为DefaultWordRendererkaptcha.background.impl 验证码背景⽣成器默认为DefaultBackgroundkaptcha.background.clear.from 验证码背景颜⾊渐进默认为Color.LIGHT_GRAYkaptcha.background.clear.to 验证码背景颜⾊渐进默认为Color.WHITEkaptcha.image.width 验证码图⽚宽度默认为200kaptcha.image.height 验证码图⽚⾼度默认为50以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

Spring+JCaptcha验证码使用示例

Spring+JCaptcha验证码使用示例

Spring+JCaptcha验证码使⽤⽰例1,导⼊jcaptcha.jar包,这⾥⽤的是1.0版本2,编写captcha-context.xml配置⽂件(⾮必须,可在spring配置⽂件中直接添加):<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/schema/beans/schema/beans/spring-beans.xsd"><bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService"><constructor-arg index="0" ref="imageEngine"/><constructor-arg type="int" index="1" value="180"/><constructor-arg type="int" index="2" value="100000"/><constructor-arg type="int" index="3" value="75000"/></bean><bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine"><constructor-arg index="0"><list><ref bean="captchaFactory"/></list></constructor-arg></bean><bean id="captchaFactory" class="com.octo.captcha.image.gimpy.GimpyFactory"><constructor-arg><ref bean="wordgen"/></constructor-arg><constructor-arg><ref bean="wordtoimage"/></constructor-arg></bean><bean id="wordgen" class= "ponent.word.wordgenerator.RandomWordGenerator"><!--可选字符--><constructor-arg><value>0123456789</value><!-- <value>abcdefghijklmnopquvwxyz</value> --></constructor-arg></bean><bean id="wordtoimage" class="posedWordToImage"><constructor-arg index="0"><ref bean="fontGenRandom"/></constructor-arg><constructor-arg index="1"><ref bean="backGenUni"/></constructor-arg><constructor-arg index="2"><ref bean="decoratedPaster"/></constructor-arg></bean><bean id="fontGenRandom" class="ponent.image.fontgenerator.RandomFontGenerator"><!--最⼩字体--><constructor-arg index="0"><value>16</value></constructor-arg><!--最⼤字体--><constructor-arg index="1"><value>20</value></constructor-arg><constructor-arg index="2"><list><bean class="java.awt.Font"><constructor-arg index="0"><value>Arial</value></constructor-arg><constructor-arg index="1"><value>0</value></constructor-arg><constructor-arg index="2"><value>12</value></constructor-arg></bean></list></constructor-arg></bean><bean id="backGenUni" class="ponent.image.backgroundgenerator.FunkyBackgroundGenerator"><!--背景宽度--><constructor-arg index="0"><value>90</value></constructor-arg><!--背景⾼度--><constructor-arg index="1"><value>30</value></constructor-arg></bean><bean id="decoratedPaster" class="ponent.image.textpaster.DecoratedRandomTextPaster"> <!--最⼤字符长度--><constructor-arg type="ng.Integer" index="0"><value>4</value></constructor-arg><!--最⼩字符长度--><constructor-arg type="ng.Integer" index="1"><value>4</value></constructor-arg><!--⽂本颜⾊--><constructor-arg index="2"><ref bean="colorGen"/></constructor-arg><!--⽂本混淆--><constructor-arg index="3"><list><ref bean="baffleDecorator"/></list></constructor-arg></bean><bean id="baffleDecorator" class="ponent.image.textpaster.textdecorator.BaffleTextDecorator"> <constructor-arg type="ng.Integer" index="0"><value>1</value></constructor-arg><constructor-arg type="java.awt.Color" index="1"><ref bean="colorWrite"/></constructor-arg></bean><bean id="colorGen" class="ponent.image.color.SingleColorGenerator"><constructor-arg type="java.awt.Color" index="0"><ref bean="colorBlack"/></constructor-arg></bean><bean id="colorWrite" class="java.awt.Color"><constructor-arg type="int" index="0"><value>255</value></constructor-arg><constructor-arg type="int" index="1"><value>255</value></constructor-arg><constructor-arg type="int" index="2"><value>255</value></constructor-arg></bean><bean id="colorBlack" class="java.awt.Color"><constructor-arg type="int" index="0"><value>50</value></constructor-arg><constructor-arg type="int" index="1"><value>50</value></constructor-arg><constructor-arg type="int" index="2"><value>50</value></constructor-arg></bean></beans>View Code3,编写JcaptchaServlet响应请求提供验证码输出:protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {byte[] captchaChallengeAsJpeg = null;// 输出jpg的字节流ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();try {// get the session id that will identify the generated captcha.// the same id must be used to validate the response, the session id// is a good candidate!String captchaId = session.getSessionId(request);BufferedImage challenge = captchaService.getImageChallengeForID(captchaId, request.getLocale());// a jpeg encoderJPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);jpegEncoder.encode(challenge);} catch (IllegalArgumentException e) {response.sendError(HttpServletResponse.SC_NOT_FOUND);return;} catch (CaptchaServiceException e) {response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);return;}captchaChallengeAsJpeg = jpegOutputStream.toByteArray();// flush it in the responseresponse.setHeader("Cache-Control", "no-store");response.setHeader("Pragma", "no-cache");response.setDateHeader("Expires", 0);response.setContentType("image/" + CAPTCHA_IMAGE_FORMAT);ServletOutputStream responseOutputStream = response.getOutputStream();responseOutputStream.write(captchaChallengeAsJpeg);responseOutputStream.flush();responseOutputStream.close();}4,在web.xml中添加该servlet:<!-- Jcaptcha --><servlet><servlet-name>Jcaptcha</servlet-name><servlet-class>cn.xxx.JcaptchaServlet</servlet-class></servlet><servlet-mapping><servlet-name>Jcaptcha</servlet-name><url-pattern>/captcha.jpg</url-pattern></servlet-mapping>5,在html中显⽰验证码只需要以下代码:<div><img src="${base }/captcha.jpg" onclick="this.src='${base}/captcha.jpg?'+Math.random()"/></div> <div class="input_capt">验证码: <input type="text"id="captcha" name="captcha" />6,后台验证的话,只需要调⽤ImageCaptchaService的⼀个⽅法即可:imageCaptchaService.validateResponseForID(session.getSessionId(getRequest()), captcha);。

Springboot+SpringSecurity实现图片验证码登录的示例

Springboot+SpringSecurity实现图片验证码登录的示例

Springboot+SpringSecurity实现图⽚验证码登录的⽰例这个问题,⽹上找了好多,结果代码都不全,找了好多,要不是就⾃动注⼊的类注⼊不了,编译报错,要不异常捕获不了浪费好多时间,就觉得,框架不熟就不能随便⽤,全是坑,⽓死我了,最后改了两天.终于弄好啦;问题主要是:返回的验证码不知道在SpringSecurity的什么地⽅和存在内存⾥的⽐较?我⽤的⽅法是前置⼀个过滤器,插⼊到表单验证之前。

⽐较之后应该怎么处理,:⽐较之后要抛出⼀个继承了AuthenticationException的异常其次是捕获验证码错误异常的处理?捕获到的异常交给⾃定义验证失败处理器AuthenticationFailureHandler处理,这⾥,⽤的处理器要和表单验证失败的处理器是同⼀个处理器,不然会报异常,所以在需要写⼀个全局的AuthenticationFailureHandler的实现类,专门⽤来处理异常。

表单验证有成功和失败两个处理器,我们⼀般直接以匿名内部类形似写。

要是加了验证码,就必须使⽤统⼀的失败处理器。

效果图⽹上⼤都是直接注⼊⼀个AuthenticationFailureHandler,我当时就不明⽩这个咋注进去的,我这个⼀写就报错,注⼊不进去,后来就想⾃⼰new⼀个哇,可以是可以了,但是还报异常,ng.IllegalStateException: Cannot call sendError() after the response has been committed,后来想表单验证的时候,失败⽤的也是这个处理器,就想定义⼀个全局的处理器,package com.liruilong.hros.config;import com.fasterxml.jackson.databind.ObjectMapper;import com.liruilong.hros.Exception.ValidateCodeException;import com.liruilong.hros.model.RespBean;import org.springframework.context.annotation.Bean;import org.springframework.security.authentication.*;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import ponent;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;/*** @Description :* @Author: Liruilong* @Date: 2020/2/11 23:08*/@Componentpublic class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();RespBean respBean = RespBean.error(e.getMessage());// 验证码⾃定义异常的处理if (e instanceof ValidateCodeException){respBean.setMsg(e.getMessage());//Security内置的异常处理}else if (e instanceof LockedException) {respBean.setMsg("账户被锁定请联系管理员!");} else if (e instanceof CredentialsExpiredException) {respBean.setMsg("密码过期请联系管理员!");} else if (e instanceof AccountExpiredException) {respBean.setMsg("账户过期请联系管理员!");} else if (e instanceof DisabledException) {respBean.setMsg("账户被禁⽤请联系管理员!");} else if (e instanceof BadCredentialsException) {respBean.setMsg("⽤户名密码输⼊错误,请重新输⼊!");}//将hr转化为Stingout.write(new ObjectMapper().writeValueAsString(respBean));out.flush();out.close();}@Beanpublic MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){return new MyAuthenticationFailureHandler();}}流程1. 请求登录页,将验证码结果存到基于Servlet的session⾥,以JSON格式返回验证码,2. 之后前端发送登录请求,SpringSecurity中处理,⾃定义⼀个filter让它继承⾃OncePerRequestFilter,然后重写doFilterInternal⽅法,在这个⽅法中实现验证码验证的功能,如果验证码错误就抛出⼀个继承⾃AuthenticationException的验证吗错误的异常消息写⼊到响应消息中.3. 之后返回异常信息交给⾃定义验证失败处理器处理。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Spring框架生成图片验证码实例
验证码在很多地方都会遇到,实现的方法和形式也有很多,主要的目的就是为了安全,防止一些恶意的攻击等。

今天在之前搭建好的一个spring框架上写了一个验证码的生成demo,我会贴出细节代码,但是spring的配置就不在介绍了,有需要的可以参考借鉴。

这篇文章会从前台页面到后台实现完整的讲解,下面跟着小编一起来看看。

1、前台的代码,image.jsp
2、后台代码ImageGenController.java
3、生成验证码的工具类RandomValidateCode.java
4、总结
本文的内容到这就结束了,如果对里面的地方有不懂的地方,可以留言讨论。

希望本文的内容对大家的学习工作能有所帮助。

相关文档
最新文档