验证码功能实现
最近公司对公司小程序进行安全漏洞检测,发现我们的登录接口存在安全隐患,需要添加图形验证码来进行过滤,要实现图形验证码要考虑两个问题
1,图片展现的形式
1)二进制传到前端直接展示
2)存到一个图片地址,返回url前端直接获取
2,如何定位
后端生成一个key,验证码,存到缓存中,传给前端,前端传验证码,key到后端
啥也不说了,直接上代码
ImageVerificationCode.java
package org.source.dsmh.utils; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.Random; import javax.imageio.ImageIO; import org.apache.commons.lang3.ArrayUtils; import sun.misc.BASE64Encoder; public class ImageVerificationCode { private volatile static ImageVerificationCode imageCode; private ImageVerificationCode(){} public static ImageVerificationCode getInstance(){ if(imageCode==null){ synchronized (ImageVerificationCode.class){ if(imageCode==null){ imageCode=new ImageVerificationCode(); } } } return imageCode; } private static Random r = new Random(); //获取随机数对象 //private String[] fontNames = {"宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312"}; //字体数组 //验证码数组 // private static String codes = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static String codes = "1234567890"; /** * 获取随机的颜色 * * @return */ private Color randomColor() { int r = this.r.nextInt(225); //这里为什么是225,因为当r,g,b都为255时,即为白色,为了好辨认,需要颜色深一点。 int g = this.r.nextInt(225); int b = this.r.nextInt(225); return new Color(r, g, b); //返回一个随机颜色 } /** * 获取随机字符串 * * @return */ public String randomStr() { StringBuilder sb=new StringBuilder(); for(int i=0;i<4;i++) { int index = r.nextInt(codes.length()); char x=codes.charAt(index); sb.append(x); } return sb.toString(); } public String createImageWithVerifyCode(int width, int height, String word) throws IOException { String png_base64=""; //绘制内存中的图片 BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); //得到画图对象 Graphics graphics = bufferedImage.getGraphics(); //绘制图片前指定一个颜色 graphics.setColor( Color.white); graphics.fillRect(0,0,width,height); //绘制边框 graphics.setColor(Color.white); graphics.drawRect(0, 0, width - 1, height - 1); // 步骤四 四个随机数字 Graphics2D graphics2d = (Graphics2D) graphics; graphics2d.setFont(new Font("宋体", Font.BOLD, 18)); Random random = new Random(); // 定义x坐标 int x = 5; for (int i = 0; i < word.length(); i++) { // 随机颜色 //graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); graphics2d.setColor(new Color(0,29,38)); // 旋转 -30 --- 30度 int jiaodu = random.nextInt(60) - 30; // 换算弧度 double theta = jiaodu * Math.PI / 180; // 获得字母数字 char c = word.charAt(i); //将c 输出到图片 graphics2d.rotate(theta, x, 20); graphics2d.drawString(String.valueOf(c), x, 20); graphics2d.rotate(-theta, x, 20); x += 18; } //保存验证码 // 绘制干扰线 graphics.setColor(this.randomColor()); int x1; int x2; int y1; int y2; for (int i = 0; i < 30; i++) { x1 = random.nextInt(width); x2 = random.nextInt(12); y1 = random.nextInt(height); y2 = random.nextInt(12); // graphics.drawLine(x1, y1, x1 + x2, x2 + y2); } graphics.dispose();// 释放资源 ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流 ImageIO.write(bufferedImage, "png", baos);//写入流中 byte[] bytes = baos.toByteArray();//转换成字节 BASE64Encoder encoder = new BASE64Encoder(); png_base64 = encoder.encodeBuffer(bytes).trim(); png_base64= "data:image/jpeg;base64,"+png_base64; // png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n return png_base64; } public static String getRandomString(int length){ Random random=new Random(); StringBuffer sb=new StringBuffer(); for(int i=0;i<length;i++){ int number=random.nextInt(100); sb.append(number); } return sb.toString(); } public static void main(String[] args) throws IOException { ImageVerificationCode ivc = new ImageVerificationCode(); //用我们的验证码类,生成验证码类对象 /* * String str=ivc.randomStr(); System.out.println(str); * System.out.println(ivc.createImageWithVerifyCode(75, 31,str )); */ System.out.println(ivc.getRandomString(5)); } }
LocalAccountPicCodeServiceImpl.java
package org.source.dsmh.service.impl; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import org.source.dsmh.service.LocalDataService; import org.source.dsmh.utils.AccountConstant; import org.source.dsmh.utils.DataTemplate; import org.source.dsmh.utils.ImageVerificationCode; import org.source.dsmh.utils.mongodb.LogMsg; import org.source.dsmh.utils.redis.RedisOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.druid.util.StringUtils; import com.alibaba.fastjson.JSONObject; /**** * 图片验证码*/ @Service("local-account-picCode") public class LocalAccountPicCodeServiceImpl implements LocalDataService { private Logger log = Logger.getLogger(LocalAccountPicCodeServiceImpl.class); @Autowired private RedisOperator redisOperator; public static final String PICCODE_PREFIX = "account:code"; private final ReentrantLock lock=new ReentrantLock(); @Override public DataTemplate localData(String paramJson, String function, String method, String appUser) { this.log.info(LogMsg.getLogMsgForInfo(function, method, paramJson, method)); /** * @info 完成参数转换 */ JSONObject jsonObject = null; if (!StringUtils.isEmpty(paramJson)) { try { jsonObject = JSONObject.parseObject(paramJson); } catch (Exception e) { e.printStackTrace(); log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", AccountConstant.PARAM_JSON_ERROR), e); return DataTemplate.error(AccountConstant.PARAM_JSON_ERROR); } } else { jsonObject = new JSONObject(); } /** * @info 随机生成验证码--将验证码发送至用户手机 */ lock.lock(); try { long time=System.currentTimeMillis(); String key=time+ImageVerificationCode.getInstance().getRandomString(5); final int width = 75; // 图片宽度 final int height = 31; // 图片高度 String words = ImageVerificationCode.getInstance().randomStr(); // 创建验证码图片并返回图片上的字符串 String code = ImageVerificationCode.getInstance().createImageWithVerifyCode(width, height, words); log.info("验证码内容: " + words); this.redisOperator.setCache(PICCODE_PREFIX, "code:" + key, words); log.info(LogMsg.getLogMsgForInfo(function, method, PICCODE_PREFIX+":code:"+key+"验证码:"+words, AccountConstant.SUCCESS)); JSONObject result = new JSONObject(); result.put("code", code); result.put("picCodeKey", key); return DataTemplate.ok(result); } catch (Exception e) { e.printStackTrace(); log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", "获取验证码失败"), e); return DataTemplate.error("获取验证码失败"); }finally { lock.unlock(); } } }
LocalAccountLoginvalidateServiceImpl.java
String picCode=jsonObject.getString("picCode"); if (StringUtils.isEmpty(picCode)){ return DataTemplate.error("验证码不能为空"); } String picCodeKey=jsonObject.getString("picCodeKey"); try { //获取缓存中验证码值,判断验证码是否正确 String valid = this.redisOperator.getCache(PICCODE_PREFIX, "code:" + picCodeKey); if (StringUtils.isEmpty(picCode) || !picCode.equalsIgnoreCase(valid)) { log.error(LogMsg.getLogMsgForError(function, method, paramJson, "缓存验证码:"+valid, AccountConstant.VALID_ERROR_MESSAGE)); return DataTemplate.error(AccountConstant.VALID_ERROR_MESSAGE); } } catch (Exception e) { log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", AccountConstant.VALID_ERROR_MESSAGE)); return DataTemplate.error(AccountConstant.VALID_ERROR_MESSAGE); }
好记性不如烂笔头