在开发Web应用程序时,用户登录功能是一个常见的需求。为了增加安全性,我们可以引入验证码来进行登录校验。本文将介绍如何使用Spring Boot框架实现登录校验验证码的功能。
首先引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数验证包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 验证码依赖包 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
<!-- SpringBoot Boot Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.34</version>
</dependency>
</dependencies>
编写登录接口DTO,这里用了DTO进行数据验证,具体参考:使用 Spring Boot 和 DTO 进行数据验证
/**
* @Author xiaoke
* @Description 登录接口请求参数
* @Date 2023/7/24 13:13
*/
public class LoginDto {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "验证码key不能为空")
private String key;
@NotBlank(message = "验证码结果不能为空")
private String code;
public LoginDto() {
}
public LoginDto(String username, String password, String key, String code) {
this.username = username;
this.password = password;
this.key = key;
this.code = code;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
编写控制器,这里有两个方法,login
方法是登录,code
方法是获取验证码
@RestController
@RequestMapping("/auth")
public class LoginController {
@Autowired
LoginService loginService;
/**
* 登录
* @param dto 请求参数
*/
@PostMapping("/login")
public R<String> login(@RequestBody @Validated LoginDto dto) {
return loginService.login(dto);
}
/**
*
* @return 验证码vo
*/
@GetMapping("/code")
public R<LoginCodeVo> code(){
return loginService.code();
}
}
服务层,首先根据用户名获取用户信息,然后判断用户名和密码是否匹配。如果匹配失败,则返回错误信息。接下来,调用 verifyCode
方法验证验证码是否正确。如果验证码不存在或者验证失败,则返回相应的错误信息。最后,删除已经验证成功的验证码,并返回登录成功的消息。
获取验证码,我们使用 ArithmeticCaptcha
生成了一个算术类型的验证码图片,并将验证码结果存储在Redis中,设置过期时间为60秒。同时,生成一个UUID作为验证码的key,并将验证码图片和key封装到 LoginCodeVo
对象中,返回给前端。
@Service
public class LogniServiceImpl implements LoginService {
@Autowired
UserService userService;
@Autowired
RedisService redisService;
@Override
public R<String> login(LoginDto dto) {
User user = userService.getByUserName(dto.getUsername());
if(user == null || !user.getPassword().equals(dto.getPassword())) {
return R.error("用户名或密码错误");
}
if(!verifyCode(dto.getKey(), dto.getCode())) {
return R.error("验证码不存在或验证码错误");
}
//删除已经验证成功的验证码
removeCode(dto.getKey());
return R.ok("登录成功");
}
@Override
public R<LoginCodeVo> code() {
// 算术类型
ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
// 几位数运算,默认是两位
captcha.setLen(2);
// 获取运算的结果
String result = "";
try {
result = new Double(Double.parseDouble(captcha.text())).intValue() + "";
} catch (Exception e) {
result = captcha.text();
}
//生成uuid做为验证码的key
UUID key = UUID.randomUUID();
//存储验证码信息到redis中,60秒过期
redisService.setCacheObject(RedisConstants.LOGIN_CODE+key,result,RedisConstants.LOGIN_CODE_TIME, TimeUnit.SECONDS);
return R.ok(new LoginCodeVo(captcha.toBase64(),key.toString()));
}
/**
* 验证验证码是否正确
* @param key 验证码的key
* @param code 验证码的结果
*/
private boolean verifyCode(String key,String code) {
String result = redisService.getCacheObject(RedisConstants.LOGIN_CODE + key);
//验证码不存在直接返回null
if(result == null) {
return false;
}
return result.equals(code);
}
/**
* 删除验证码
* @param key 验证码Key
*/
private void removeCode(String key){
redisService.deleteObject(RedisConstants.LOGIN_CODE + key);
}
}
LoginVo
public class LoginCodeVo {
private String img;
private String key;
}
Redis工具类
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
Redis配置类
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
Redis序列化配置类
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}
视频教程:哔哩哔哩
评论区