SpringBoot整合Captcha验证码(含代码) 1

  1. 基本结构

使用Captcha生成验证码, 利用Redis存储验证码

Redis中的结构为, Key是32位的UUID, Value为Captcha的4位随机字母以及数字的集合

设定Redis过期时间为1min, 即可实现过期验证码的自动失效

  1. Kaptcha的依赖

基本的依赖这里不再叙述, 主要说一下要导入Captcha的依赖

1
2
3
4
5
6
xml复制代码<!--Kaptcha-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>

所有的依赖如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wang</groupId>
<artifactId>spring_security_framework</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring_security_framework</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--Validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--SpringBoot Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--SpringSecurity with thymeleaf-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--MySQL connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.2</version>
</dependency>
<!--FastJSON-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--Swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--HuTool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.7</version>
</dependency>
<!--Kaptcha-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
  1. 配置SpringBoot

配置SpringBoot的配置文件, 这里主要关注一个session的过期时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
yaml复制代码#Port
server:
port: 80
servlet:
session:
timeout: 1
spring:
application:
name: SpringSecurityFramework
#dataBase Setting
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Druid Setting
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
#Setting For Druid StatView and Filter
filters: stat,wall,log4j
max-pool-prepared-statement-per-connection-size: 20
use-global-data-source-stat: true
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSql
#Redis Setting
redis:
host: 127.0.0.1
port: 6379
#Thymeleaf
thymeleaf:
cache: false
#Mybatis
mybatis:
type-aliases-package: com.wang.entity
mapper-locations: classpath:Mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true

其余的配置, 如log4j, druid, SpringSecurity, RedisTemplate,这里就不再赘述

  1. 配置Captcha

我们可以通过JAVA的配置类来配置Captcha生成验证码的一些规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
java复制代码package com.wang.spring_security_framework.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
//Kaptcha配置
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
//Properties类
Properties properties = new Properties();
// 图片边框
properties.setProperty("kaptcha.border", "yes");
// 边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
// 字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "blue");
// 图片宽
properties.setProperty("kaptcha.image.width", "110");
// 图片高
properties.setProperty("kaptcha.image.height", "40");
// 字体大小
properties.setProperty("kaptcha.textproducer.font.size", "30");
// session key
properties.setProperty("kaptcha.session.key", "code");
// 验证码长度
properties.setProperty("kaptcha.textproducer.char.length", "4");
// 字体
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
//图片干扰
properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.DefaultNoise");
//Kaptcha 使用上述配置
Config config = new Config(properties);
//DefaultKaptcha对象使用上述配置, 并返回这个Bean
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
  1. 工具类

使用UUID作为key, 同时考虑到对验证码的输出结果可能有不同的要求, 这里写两个工具类来处理它们

  • UUIDUtil

package com.wang.spring_security_framework.util;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class UUIDUtil {
/**
* 生成32位的随机UUID
* @return 字符形式的小写UUID
*/
@Bean
public String getUUID32() {
return UUID.randomUUID().toString()
.replace(“-“, “”).toLowerCase();
}
}

CaptchaUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
java复制代码package com.wang.spring_security_framework.util;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.wang.spring_security_framework.service.CaptchaService;
import io.netty.handler.codec.base64.Base64Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
@Component
//Captcha 生成工具
public class CaptchaUtil {
@Autowired
private DefaultKaptcha producer;
@Autowired
private CaptchaService captchaService;
//生成catchCreator的map
public Map<String, Object> catchaImgCreator() throws IOException {
//生成文字验证码
String text = producer.createText();
//生成文字对应的图片验证码
BufferedImage image = producer.createImage(text);
//将图片写出
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
//对写出的字节数组进行Base64编码 ==> 用于传递8比特字节码
BASE64Encoder encoder = new BASE64Encoder();
//生成token
Map<String, Object> token = captchaService.createToken(text);
token.put("img", encoder.encode(outputStream.toByteArray()));
return token;
}
}
  1. 接口以及实现类

  1. 接口

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码package com.wang.spring_security_framework.service;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
public interface CaptchaService {
//生成token
Map<String, Object> createToken(String captcha);
//生成captcha验证码
Map<String, Object> captchaCreator() throws IOException;
//验证输入的验证码是否正确
String versifyCaptcha (String token, String inputCode);
}

2. 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
typescript复制代码package com.wang.spring_security_framework.service.serviceImpl;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.wang.spring_security_framework.service.CaptchaService;
import com.wang.spring_security_framework.util.CaptchaUtil;
import com.wang.spring_security_framework.util.UUIDUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class CaptchaServiceImpl implements CaptchaService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UUIDUtil uuidUtil;
@Autowired
private CaptchaUtil captchaUtil;
//从SpringBoot的配置文件中取出过期时间
@Value("${server.servlet.session.timeout}")
private Integer timeout;
//UUID为key, 验证码为Value放在Redis中
@Override
public Map<String, Object> createToken(String captcha) {
//生成一个token
String key = uuidUtil.getUUID32();
//生成验证码对应的token 以token为key 验证码为value存在redis中
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, captcha);
//设置验证码过期时间
redisTemplate.expire(key, timeout, TimeUnit.MINUTES);
Map<String, Object> map = new HashMap<>();
map.put("token", key);
map.put("expire", timeout);
return map;
}
//生成captcha验证码
@Override
public Map<String, Object> captchaCreator() throws IOException {
return captchaUtil.catchaImgCreator();
}
//验证输入的验证码是否正确
@Override
public String versifyCaptcha(String token, String inputCode) {
//根据前端传回的token在redis中找对应的value
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
if (redisTemplate.hasKey(token)) {
//验证通过, 删除对应的key
if (valueOperations.get(token).equals(inputCode)) {
redisTemplate.delete(token);
return "true";
} else {
return "false";
}
} else {
return "false";
}
}
}
  • 这里的验证, 只是简单的验证了输入是否能从Redis中匹配, 返回了字符串
  • 真实的验证中, 我们还要在逻辑中添加用户名和密码的考虑
  1. Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kotlin复制代码package com.wang.spring_security_framework.controller;
import com.wang.spring_security_framework.service.CaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Map;
@RestController
public class LoginController {
@Autowired
CaptchaService captchaService;
@GetMapping("/captcha")
public Map<String, Object> captcha() throws IOException {
return captchaService.captchaCreator();
}
@GetMapping("/login1")
public String login(@RequestParam("token") String token,
@RequestParam("inputCode") String inputCode) {
return captchaService.versifyCaptcha(token, inputCode);
}
}
  • captcha 用于获取一个验证码
  • login1 用于接收到前端的请求后验证并返回结果
  • login1 这里为了测试简便实用了GET方法, 而实际中最好使用POST方法, 这样安全性更高
  1. 前端页面的实现

前端结构如图, 实现了一个简单的验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
xml复制代码<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<div>
<div>
<form th:action="@{/login1}" method="get">
<input type="text" id="userName" placeholder="请输入用户名" name="userName">
<br>
<input type="password" id="password" placeholder="请输入密码" name="password">
<br>
<input type="text" id="inputCode" placeholder="请输入验证码" maxlength="4" name="inputCode">
<!--通过隐藏域传递值, 在下面的验证码点击事件中, 将值绑定过来, 这样就可以获得最新的验证码对应的值了!-->
<input id="token" value="" type="hidden" name="token">
<input type="submit" value="登录">
</form>
</div>
<div>
<!-- 当用户链接时,void(0)计算为0,用户点击不会发生任何效果 -->
<a href="javascript:void(0);" title="点击更换验证码">
<!--this参数, 返回当前的DOM元素-->
<img src="" alt="更换验证码" id="imgVerify" onclick="getVerify(this)">
</a>
</div>
</div>
<script>
//获得img对象
let imgVerify = $("#imgVerify").get(0);
//$(function())等同于$(document).ready(function()) ==> 页面加载完毕之后, 才执行函数
$(function () {
getVerify(imgVerify);
});
//onclick时间绑定的getVerify函数
function getVerify(obj) {
$.ajax({
type: "GET",
url: "/captcha",
success: function (result) {
obj.src = "data:image/jpeg;base64," + result.img;
$("#token").val(result.token);
}
});
}
</script>
</body>
</html>
  • 用一个 a 标签包围 img 标签, 这样如果图片没有加载出来也有一个超链接, 不过点了以后没有效果
  • (function())等同于(function())等同于(function())等同于(document).ready(function()) ==> 页面加载完毕之后, 才执行函数, 这里必须要写这个函数, 否则第一次加载不会调用 onclick 方法, 也就不会生成验证码!
  • 我们利用隐藏域将验证码的key传递到表单中, 我们在 img 的点击事件对应的函数的ajax回调函数中可以利用jQuery操作DOM, 顺带取出key值放到我们的隐藏域中, 这样提交的时候就会提交 key 和用户输入的 value 了

示例

验证通过

完成!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%