1. 前言
接着这篇,来记录下图片验证码如何做
自用SpringBoot完整初始化配置
其实前后端验证码还是有很多思路的,不同思路有不同结果。
本篇文章记录的是我目前正在用的一种思路,不一定合所有人胃口,但我觉得是很不错的一种思路;
2. 思路
很简单,写一个接口返回验证码的base64编码
和一个代表验证码真实值在redis中的key
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VerifyCodeDto {
private String code;
private String image;
}
很简单了,把这两样东西传递给前端,前端用户输入验证码后,把同样的code传递给后端,后端依据code把真实的验证码值和用户传来的值对比,不就可以了吗?
思路很简单,需要两步:
1、生成base64验证码
2、使用redis
3. 步骤
3.1. 修改login的过滤器
代码语言:javascript复制@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtils jwtUtils;
private final VerifyCodeCheck verifyCodeCheck;
private final AuthenticationManager authenticationManager;
public JwtLoginFilter(AuthenticationManager authenticationManager, JwtUtils jwtUtils, VerifyCodeCheck verifyCodeCheck) {
this.authenticationManager = authenticationManager;
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/login", "POST"));
this.jwtUtils = jwtUtils;
this.verifyCodeCheck = verifyCodeCheck;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
log.info("Authentication-->>attemptAuthentication");
// 从输入流中获取到登录的信息
try {
final UserDto userDto = new ObjectMapper().readValue(request.getInputStream(), UserDto.class);
// 验证 验证码
final String verifyCode = userDto.getVerifyCode();
final String code = userDto.getCode();
log.info("verifyCode ==>>{}", verifyCode);
log.info("code ==>>{}", code);
try {
verifyCodeCheck.checkVerifyCode(verifyCode, code);
} catch (MyException e) {
try {
request.setAttribute("filter.error", e);
request.getRequestDispatcher("/error/throw").forward(request, response);
} catch (ServletException servletException) {
// servletException.printStackTrace();
}
// e.printStackTrace();
return null;
}
return this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
UserEntity user = (UserEntity) authResult.getPrincipal();
System.out.println("user:" user.toString());
String role = "";
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
String token = jwtUtils.generateToken(user);
//String token = JwtTokenUtils.createToken(user.getUsername(), false);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
// 获取用户信息
final SecurityContext context = SecurityContextHolder.getContext();
Map<String, Object> res = new HashMap<>();
user.setPassword(null);
res.put("userInfo", user);
res.put("token", token);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
AppResult<Object> appResult = AppResultBuilder.success(res, ResultCode.USER_LOGIN_SUCCESS);
String s = new ObjectMapper().writeValueAsString(appResult);
PrintWriter writer = response.getWriter();
writer.print(s);//输出
writer.close();
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
final String message = failed.getMessage();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
AppResult<String> appResult;
if (failed instanceof InternalAuthenticationServiceException) {
appResult = AppResultBuilder.fail(ResultCode.USER_LOGIN_ERROR);
} else if (failed instanceof DisabledException) {
appResult = AppResultBuilder.fail(ResultCode.USER_ACCOUNT_FORBIDDEN);
} else if (StringUtils.isNoneBlank(message)) {
appResult = AppResultBuilder.fail(message);
} else {
appResult = AppResultBuilder.fail(ResultCode.USER_LOGIN_FAIL);
}
String s = new ObjectMapper().writeValueAsString(appResult);
PrintWriter writer = response.getWriter();
writer.print(s);//输出
writer.close();
}
}
这里有几点要说的:
1、filter抛出的异常,并不能用全局异常处理器处理,所以还要另做处理
很简单,就是这两行代码,把异常传递给了Controller层
代码语言:javascript复制request.setAttribute("filter.error", e);
request.getRequestDispatcher("/error/throw").forward(request, response);
然后在controller层处理即可:
代码语言:javascript复制@RestController
public class ErrorController {
/**
* 重新抛出异常
*/
@RequestMapping("/error/throw")
public void rethrow(HttpServletRequest request) {
throw ((MyException) request.getAttribute("filter.error"));
}
}
3.2. Controller
生成验证码的控制器代码:
代码语言:javascript复制@GetMapping("/verifyCode")
public AppResult<Object> getVerifyCode() {
final String stringRandom = VerifyCodeUtils.getStringRandom(4);
try {
final String s = VerifyCodeUtils.imageToBase64(120, 40, stringRandom);
String l = RandomStringUtils.random(10, true, true);
redisUtils.set(Constant.Common.KEY_VERIFY_CODE l, stringRandom, 1000);//存储
final VerifyCodeDto verifyCodeDto = new VerifyCodeDto(l, s);
log.info("Key: {}, code: {}", l, stringRandom);
return AppResultBuilder.success(verifyCodeDto, ResultCode.SUCCESS);
} catch (Exception e) {
e.printStackTrace();
return AppResultBuilder.fail(ResultCode.GENERATE_VERIFY_CODE_FAIL);
}
}
3.3. 验证工具类
由于验证验证码并不是只有一处要用到,所以这里抽象出了一个工具类
代码语言:javascript复制@Component
@Slf4j
public class VerifyCodeCheck {
@Resource
RedisUtils redisUtils;
//检查验证码
public void checkVerifyCode(String verifyCode, String code) throws MyException {
if (StringUtils.isBlank(verifyCode) || StringUtils.isBlank(code)) {
throw new MyException(ResultCode.PLEASE_VERIFY_CODE);
}
log.info("redisUtils ==>>{} ", redisUtils);
final String originCode = (String) redisUtils.get(Constant.Common.KEY_VERIFY_CODE code);
log.info("originCode ==>>{} ", originCode);
if (originCode == null) {
throw new MyException(ResultCode.VERIFY_CODE_EXPIRED);
}
if (!originCode.toLowerCase().equals(verifyCode.toLowerCase())) {
throw new MyException(ResultCode.VERIFY_CODE_ERROR);
}
// 走到这一步说明验证码已经验证成功了,所以这一步 就是清除redis中的数据,同一个验证码不能一直用
redisUtils.del(Constant.Common.KEY_VERIFY_CODE code);
}
}
3.4. UserDto
传输登录、注册数据来用的:
代码语言:javascript复制@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "验证码不能为空")
private String verifyCode;
@NotBlank(message = "验证码数据错误")
private String code;
private String email;
}
4. 附录
4.1. RedisUtils
代码语言:javascript复制@Component
public class RedisUtils {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
// private RedisTemplate<String, Object> redisTemplate;
public RedisUtils() {
}
@Resource
RedisTemplate<String, Object> redisTemplate;
// public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
// this.redisTemplate = redisTemplate;
// }
public boolean expire(String key, long time) {
try {
if (time > 0L) {
this.redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public Long getExpire(String key) {
return this.redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
public Boolean hasKey(String key) {
try {
return this.redisTemplate.hasKey(key);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
this.redisTemplate.delete(key[0]);
} else {
this.redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
public Object get(String key) {
return key == null ? null : this.redisTemplate.opsForValue().get(key);
}
public boolean set(String key, Object value) {
try {
this.redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean set(String key, Object value, long time) {
try {
if (time > 0L)
this.redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
else
this.set(key, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public Long incr(String key, long delta) {
if (delta < 0L) {
throw new RuntimeException("递增因子必须大于0");
} else
return this.redisTemplate.opsForValue().increment(key, delta);
}
public Long decr(String key, long delta) {
if (delta < 0L) {
throw new RuntimeException("递增因子必须大于0");
} else
return this.redisTemplate.opsForValue().increment(key, -delta);
}
public Object hget(String key, String item) {
return this.redisTemplate.opsForHash().get(key, item);
}
public Map<Object, Object> hmget(String key) {
return this.redisTemplate.opsForHash().entries(key);
}
public boolean hmset(String key, Map<String, Object> map) {
try {
this.redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
this.redisTemplate.opsForHash().putAll(key, map);
if (time > 0L) {
this.expire(key, time);
}
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean hset(String key, String item, Object value) {
try {
this.redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean hset(String key, String item, Object value, long time) {
try {
this.redisTemplate.opsForHash().put(key, item, value);
if (time > 0L)
this.expire(key, time);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public void hdel(String key, Object... item) {
this.redisTemplate.opsForHash().delete(key, item);
}
public boolean hHasKey(String key, String item) {
return this.redisTemplate.opsForHash().hasKey(key, item);
}
public double hincr(String key, String item, double by) {
return this.redisTemplate.opsForHash().increment(key, item, by);
}
public double hdecr(String key, String item, double by) {
return this.redisTemplate.opsForHash().increment(key, item, -by);
}
public Set<Object> sGet(String key) {
try {
return this.redisTemplate.opsForSet().members(key);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return null;
}
}
public Boolean sHasKey(String key, Object value) {
try {
return this.redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public Long sSet(String key, Object... values) {
try {
return this.redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
public Long sSetAndTime(String key, long time, Object... values) {
try {
Long count = this.redisTemplate.opsForSet().add(key, values);
if (time > 0L) {
this.expire(key, time);
}
return count;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
public Long sGetSetSize(String key) {
try {
return this.redisTemplate.opsForSet().size(key);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
public List<Object> lGet(String key, long start, long end) {
try {
return this.redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return null;
}
}
public Long lGetListSize(String key) {
try {
return this.redisTemplate.opsForList().size(key);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
public Object lGetIndex(String key, long index) {
try {
return this.redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
public boolean lSet(String key, Object value) {
try {
this.redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean lSet(String key, Object value, long time) {
try {
this.redisTemplate.opsForList().rightPush(key, value);
if (time > 0L) {
this.expire(key, time);
}
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean lSet(String key, List<Object> value) {
try {
this.redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean lSet(String key, List<Object> value, long time) {
try {
this.redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0L) {
this.expire(key, time);
}
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public boolean lUpdateIndex(String key, long index, Object value) {
try {
this.redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return false;
}
}
public Long lRemove(String key, long count, Object value) {
try {
Long remove = this.redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
this.logger.error(ExceptionUtils.getStackTrace(e));
return 0L;
}
}
}
4.2. VerifyCodeUtils
base64验证码生成工具类
代码语言:javascript复制public class VerifyCodeUtils {
private static final Random random = new Random();
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);
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);
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;
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);
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d h1, i, h1);
}
}
public static String getStringRandom(int length) {
StringBuilder val = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i ) {
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
switch (charOrNum) {
case "char":
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
val.append((char) (random.nextInt(26) temp));
break;
case "num":
val.append(random.nextInt(10));
break;
}
}
return val.toString();
}
public static String imageToBase64(int w, int h, String code) throws Exception {
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);
@SuppressWarnings("MismatchedReadAndWriteOfArray") 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};
@SuppressWarnings("MismatchedReadAndWriteOfArray") 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("Arial", 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 * 1.0 / verifySize) * i fontSize / 2.0, h / 2.0);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i 5, h / 2 fontSize / 2 - 10);
}
g2.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", baos);
return new BASE64Encoder().encode(baos.toByteArray());
}
public static void base64ToImage(String base64) {
BASE64Decoder decoder = new BASE64Decoder();
try {
File file = new File("./test.jpg");
FileOutputStream write = new FileOutputStream(file);
byte[] decoderBytes = decoder.decodeBuffer(base64);
write.write(decoderBytes);
write.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// base64ToImage();
imageToBase64(120, 40, getStringRandom(4));
}
}