驗證碼功能實作
最近公司對公司小程式進行安全漏洞檢測,發現我們的登入接口存在安全隐患,需要添加圖形驗證碼來進行過濾,要實作圖形驗證碼要考慮兩個問題
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);
}