package com.cloudroam.common.util;
|
|
import com.cloudroam.bo.LoginCaptchaBO;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import org.springframework.core.io.ClassPathResource;
|
|
import javax.crypto.Cipher;
|
import javax.crypto.SecretKey;
|
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
import javax.imageio.ImageIO;
|
import java.awt.*;
|
import java.awt.image.BufferedImage;
|
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
import java.nio.charset.StandardCharsets;
|
import java.security.GeneralSecurityException;
|
import java.time.LocalDateTime;
|
import java.time.ZoneId;
|
import java.util.Base64;
|
import java.util.Random;
|
|
/**
|
* @author
|
* 验证码工具类
|
*/
|
@SuppressWarnings("SpellCheckingInspection")
|
public class CaptchaUtil {
|
|
private CaptchaUtil () {
|
throw new IllegalStateException("Utility class");
|
}
|
|
/**
|
* 验证码字符个数
|
*/
|
public static final int RANDOM_STR_NUM = 4;
|
private static final Random RANDOM = new Random();
|
/**
|
* 验证码的宽
|
*/
|
private static final int WIDTH = 80;
|
/**
|
* 验证码的高
|
*/
|
private static final int HEIGHT = 40;
|
private static final String RANDOM_STRING = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWSYZ";
|
private static final String AES = "AES";
|
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
static {
|
java.security.Security.setProperty("crypto.policy", "unlimited");
|
}
|
|
/**
|
* 颜色的设置
|
*/
|
private static Color getRandomColor(int fc, int bc) {
|
|
fc = Math.min(fc, 255);
|
bc = Math.min(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 - 12);
|
|
return new Color(r, g, b);
|
}
|
|
/**
|
* 字体的设置
|
*/
|
private static Font getFont() throws IOException, FontFormatException {
|
ClassPathResource dejavuSerifBold = new ClassPathResource("DejaVuSerif-Bold.ttf");
|
return Font.createFont(Font.TRUETYPE_FONT, dejavuSerifBold.getInputStream()).deriveFont(Font.BOLD, 24);
|
}
|
|
/**
|
* 随机字符的获取
|
*/
|
public static String getRandomString(int num) {
|
num = num > 0 ? num : RANDOM_STRING.length();
|
StringBuilder sb = new StringBuilder();
|
for (int i = 0; i < num; i++) {
|
int number = RANDOM.nextInt(RANDOM_STRING.length());
|
sb.append(RANDOM_STRING.charAt(number));
|
}
|
return sb.toString();
|
}
|
|
/**
|
* 干扰线的绘制
|
*/
|
private static void drawLine(Graphics2D g) {
|
int x = RANDOM.nextInt(WIDTH);
|
int y = RANDOM.nextInt(HEIGHT);
|
int xl = WIDTH;
|
int yl = HEIGHT;
|
g.setStroke(new BasicStroke(2.0f));
|
g.setColor(getRandomColor(98, 200));
|
g.drawLine(x, y, x + xl, y + yl);
|
}
|
|
/**
|
* 字符串的绘制
|
*/
|
private static void drawString(Graphics2D g, String randomStr, int i) throws IOException, FontFormatException {
|
g.setFont(getFont());
|
g.setColor(getRandomColor(28, 130));
|
// 设置每个字符的随机旋转
|
double radianPercent = (RANDOM.nextBoolean() ? -1 : 1) * Math.PI * (RANDOM.nextInt(60) / 320D);
|
g.rotate(radianPercent, WIDTH * 0.8 / RANDOM_STR_NUM * i, HEIGHT / 2.0);
|
int y = (RANDOM.nextBoolean() ? -1 : 1) * RANDOM.nextInt(4) + 4;
|
g.translate(RANDOM.nextInt(3), y);
|
g.drawString(randomStr, WIDTH / RANDOM_STR_NUM * i, HEIGHT / 2);
|
g.rotate(-radianPercent, WIDTH * 0.8 / RANDOM_STR_NUM * i, HEIGHT / 2.0);
|
g.translate(0, -y);
|
}
|
|
private static BufferedImage getBufferedImage(String code) throws IOException, FontFormatException {
|
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
|
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_BGR);
|
Graphics2D g = (Graphics2D) image.getGraphics();
|
g.fillRect(0, 0, WIDTH, HEIGHT);
|
g.setColor(getRandomColor(105, 189));
|
g.setFont(getFont());
|
int lineSize = RANDOM.nextInt(5);
|
// 干扰线
|
for (int i = 0; i < lineSize; i++) {
|
drawLine(g);
|
}
|
// 随机字符
|
for (int i = 0; i < code.length(); i++) {
|
drawString(g, String.valueOf(code.charAt(i)), i);
|
}
|
g.dispose();
|
return image;
|
}
|
|
/**
|
* 生成随机图片的base64编码字符串
|
*
|
* @param code 验证码
|
* @return base64
|
*/
|
public static String getRandomCodeBase64(String code) throws IOException, FontFormatException {
|
BufferedImage image = getBufferedImage(code);
|
// 返回 base64
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ImageIO.write(image, "PNG", bos);
|
|
byte[] bytes = bos.toByteArray();
|
Base64.Encoder encoder = Base64.getEncoder();
|
|
return encoder.encodeToString(bytes);
|
}
|
|
public static String getTag(String captcha, String secret, String iv) throws JsonProcessingException, GeneralSecurityException {
|
LocalDateTime time = LocalDateTime.now().plusMinutes(5);
|
LoginCaptchaBO captchaBO = new LoginCaptchaBO(captcha, time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
|
String json = MAPPER.writeValueAsString(captchaBO);
|
return aesEncode(secret, iv, json);
|
}
|
|
public static LoginCaptchaBO decodeTag(String secret, String iv, String tag) throws JsonProcessingException, GeneralSecurityException {
|
String decrypted = aesDecode(secret, iv, tag);
|
return MAPPER.readValue(decrypted, LoginCaptchaBO.class);
|
}
|
|
/**
|
* AES加密
|
*/
|
public static String aesEncode(String secret, String iv, String content) throws GeneralSecurityException {
|
SecretKey secretKey = new SecretKeySpec(secret.getBytes(), AES);
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes(StandardCharsets.US_ASCII)));
|
byte[] byteEncode = content.getBytes(StandardCharsets.UTF_8);
|
// 根据密码器的初始化方式加密
|
byte[] byteAES = cipher.doFinal(byteEncode);
|
|
// 将加密后的数据转换为字符串
|
return Base64.getEncoder().encodeToString(byteAES);
|
}
|
|
/**
|
* AES解密
|
*/
|
public static String aesDecode(String secret, String iv, String content) throws GeneralSecurityException {
|
SecretKey secretKey = new SecretKeySpec(secret.getBytes(), AES);
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes(StandardCharsets.US_ASCII)));
|
// 将加密并编码后的内容解码成字节数组
|
byte[] byteContent = Base64.getDecoder().decode(content);
|
// 解密
|
byte[] byteDecode = cipher.doFinal(byteContent);
|
return new String(byteDecode, StandardCharsets.UTF_8);
|
}
|
}
|