验证码功能实现
最近公司对公司小程序进行安全漏洞检测,发现我们的登录接口存在安全隐患,需要添加图形验证码来进行过滤,要实现图形验证码要考虑两个问题
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);
}