From 373d9f23160ffdcfa403d88dc1239b2cda7d1694 Mon Sep 17 00:00:00 2001 From: lijiaqi Date: Fri, 22 Dec 2023 17:14:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 +- .../com/ydool/common/config/LogConfig.java | 17 + .../common/constant/RedissonConstant.java | 20 + .../com/ydool/common/utils/RedisUtils.java | 498 ++++++++++++++++++ .../saToken/config/SaTokenConfig.java | 2 +- .../system/controller/AuthController.java | 16 +- .../com/ydool/system/request/AuthRequest.java | 8 + .../ydool/system/service/IAuthService.java | 4 +- .../system/service/impl/AuthServiceImpl.java | 148 +++++- src/main/resources/application.yml | 33 ++ 10 files changed, 739 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/ydool/common/constant/RedissonConstant.java create mode 100644 src/main/java/com/ydool/common/utils/RedisUtils.java diff --git a/pom.xml b/pom.xml index fd0a57a..dfec3ce 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.2.4 1.4.2.Final - + 3.20.0 ${project.artifactId}-${project.version} target/build @@ -268,6 +268,12 @@ system ${project.basedir}/lib/spire.doc.free-5.2.0.jar + + + org.redisson + redisson + ${redisson.version} + diff --git a/src/main/java/com/ydool/common/config/LogConfig.java b/src/main/java/com/ydool/common/config/LogConfig.java index 3276b27..802ee2f 100644 --- a/src/main/java/com/ydool/common/config/LogConfig.java +++ b/src/main/java/com/ydool/common/config/LogConfig.java @@ -1,6 +1,10 @@ package com.ydool.common.config; import com.ydool.common.interceptor.SqlLogInterceptor; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,4 +14,17 @@ public class LogConfig { public SqlLogInterceptor sqlLogInterceptor() { return new SqlLogInterceptor(); } + + + @Value("${redis:address}") + private String address; + @Value("${redis:password}") + private String password; + + @Bean + public RedissonClient redissonClient(){ + Config config = new Config(); + config.useSingleServer().setAddress(address).setPassword(password); + return Redisson.create(); + } } diff --git a/src/main/java/com/ydool/common/constant/RedissonConstant.java b/src/main/java/com/ydool/common/constant/RedissonConstant.java new file mode 100644 index 0000000..6931174 --- /dev/null +++ b/src/main/java/com/ydool/common/constant/RedissonConstant.java @@ -0,0 +1,20 @@ +package com.ydool.common.constant; + +public interface RedissonConstant { + /** + * 验证码 + */ + String CAPTCHA_CODE = "lc:::oa:::captcha:::code"; + /** + * ip请求验证码接口次数 + */ + String CAPTCHA_CODE_COUNT = "lc:::oa:::captcha:::code:::count"; + /** + * ip连续登录失败次数 + */ + String LOGIN_FAIL_COUNT = "lc:::oa:::login:::fail:::count"; + /** + * ip黑名单 + */ + String IP_BLACK_LIST = "lc:::oa:::ip:::black:::list"; +} diff --git a/src/main/java/com/ydool/common/utils/RedisUtils.java b/src/main/java/com/ydool/common/utils/RedisUtils.java new file mode 100644 index 0000000..2808528 --- /dev/null +++ b/src/main/java/com/ydool/common/utils/RedisUtils.java @@ -0,0 +1,498 @@ +package com.ydool.common.utils; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.*; +import org.redisson.client.codec.StringCodec; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * @author liuhaoze + * @date 2023/7/17 11:00 + */ + +@Slf4j +@Component +@ConditionalOnProperty(name = "redis.redisson.enabled", havingValue = "true") +public class RedisUtils { + + @Resource + private RedissonClient redissonClient; + + + //---------------------------------------------------------------- + + /** + * 获取 字符串 的 RSetCache + * + * @param name 缓存名称 + * @return RSetCache + */ + public RSetCache getSetCache(String name) { + RSetCache setCache = redissonClient.getSetCache(name, StringCodec.INSTANCE); + return setCache; + } + + /** + * 新增 RSetCache 数据 + * + * @param name 缓存名称 + * @param value 值 + * @param size 缓存时间 单位:s + * @param timeUnit 缓存时间单位 + * @return 是否新增成功 + */ + public boolean addSetCache(String name, String value, Integer size, TimeUnit timeUnit) { + + if (ObjectUtil.isNotNull(size) && ObjectUtil.isNotNull(timeUnit)) { + return this.getSetCache(name).add(value, size, timeUnit); + } + return this.getSetCache(name).add(value); + } + + /** + * 新增 RSetCache 数据 + * + * @param name 缓存名称 + * @param value 值 + * @return 是否新增成功 + */ + public boolean addSetCache(String name, String value) { + return this.getSetCache(name).add(value); + } + + /** + * 删除 RSetCache 数据 + * + * @param name 缓存名称 + * @param value 需要移除的值 + * @return 是否移除成功 + */ + public boolean removeSetCache(String name, String value) { + return this.getSetCache(name).remove(value); + } + + /** + * 验证RSetCache里是否包含 + * + * @param name 缓存name + * @param value 需要验证的值 + * @return 是否包含 + */ + public boolean setCacheContains(String name, String value) { + return this.getSetCache(name).contains(value); + } + + /** + * 刷新RSetCache的某个参数的缓存时间 + * + * @param name 缓存name + * @param value 需要刷新缓存时间的值 + * @param size 缓存时间 + * @param timeUnit 缓存时间单位 + * @return + */ + public boolean setCacheRefresh(String name, String value, Integer size, TimeUnit timeUnit) { + this.removeSetCache(name, value); + return this.addSetCache(name, value, size, timeUnit); + } + //---------------------------------------------------------------- + + + //---------------------------------------------------------------- + + /** + * 获取 RMapCache + * + * @param name 缓存名 + * @return RMapCache + */ + public RMapCache getMapCache(String name) { + return redissonClient.getMapCache(name, StringCodec.INSTANCE); + } + + /** + * 获取 RMapCache + * + * @param name 缓存名 + * @param key 键 + * @return 返回 返回结果 + */ + public Object getMapCache(String name, String key) { + return getMapCache(name).get(key); + } + + /** + * 新增 RMapCache 数据 + * + * @param name 缓存名称 + * @param key 键 + * @param value 值 + * @param size 缓存时间 单位:s + * @param timeUnit 缓存时间单位 + * @return 返回 返回结果 + */ + public Object putMapCache(String name, String key, Object value, Integer size, TimeUnit timeUnit) { + if (ObjectUtil.isNotNull(size) && ObjectUtil.isNotNull(timeUnit)) { + return getMapCache(name).put(key, value, size, timeUnit); + } + return getMapCache(name).put(key, value); + } + + /** + * 新增 RMapCache 数据 + * + * @param name 缓存名称 + * @param key 键 + * @param value 值 + * @return 返回 返回结果 + */ + public Object putMapCache(String name, String key, Object value) { + return getMapCache(name).put(key, value); + } + + /** + * 删除 RMapCache 数据 + * + * @param name 缓存名称 + * @param key 键 + * @return 返回 返回结果 + */ + public Object removeMapCache(String name, String key) { + return getMapCache(name).remove(key); + } + + /** + * 更新 RMapCache 数据 + * + * @param name 缓存名称 + * @param key 键 + * @param value 值 + * @param size 缓存时间 单位:s + * @param timeUnit 缓存时间单位 + * @return 返回 返回结果 + */ + public Object mapCacheRefresh(String name, String key, Object value, Integer size, TimeUnit timeUnit) { + this.removeMapCache(name, key); + return this.putMapCache(name, key, value, size, timeUnit); + } + //---------------------------------------------------------------- + + + + //---------------------------------------------------------------- + /** + * 获取 字符串 的 RSet + * + * @param name 缓存名称 + * @return RSet + */ + public RSet getSet(String name) { + RSet set = redissonClient.getSet(name, StringCodec.INSTANCE); + return set; + } + + /** + * 新增 RSet 数据 + * + * @param name 缓存名称 + * @param value 值 + * @return 是否新增成功 + */ + public boolean addSet(String name, String value) { + return this.getSet(name).add(value); + } + + /** + * 新增 RSet 数据 + * + * @param name 缓存名称 + * @param values 值 + * @return 是否新增成功 + */ + public boolean addSet(String name, List values) { + return this.getSet(name).addAll(values); + } + + /** + * 删除 RSet 数据 + * + * @param name 缓存名称 + * @param value 需要移除的值 + * @return 是否移除成功 + */ + public boolean removeSet(String name, String value) { + return this.getSet(name).remove(value); + } + + + /** + * 验证是否包含 + * + * @param name 缓存name + * @param value 需要验证的值 + * @return 是否包含 + */ + public boolean contains(String name, String value) { + return this.getSet(name).contains(value); + } + //---------------------------------------------------------------- + + + //---------------------------------------------------------------- + + + /** + * 获取 RMap + * + * @param name 缓存名 + * @return RMap + */ + public RMap getMap(String name) { + RMap map = redissonClient.getMap(name, StringCodec.INSTANCE); + return map; + } + + + /** + * 获取 RMap + * + * @param name 缓存名 + * @param key 键 + * @return 返回 返回结果 + */ + public Object getMapObject(String name, String key) { + return getMap(name).get(key); + } + + /** + * 新增 RMap 数据 + * + * @param name 名称 + * @param key 键 + * @param value 值 + * @return 返回 返回结果 + */ + public Object putMap(String name, String key, Object value) { + return this.getMap(name).put(key, value); + } + + /** + * 新增 RMap 数据 + * + * @param name 名称 + * @param map 值 + */ + public void putAllMap(String name, Map map) { + this.getMap(name).putAll(map); + } + + /** + * 删除 RMap 数据 + * + * @param name 名称 + * @param key 键 + * @return 返回 返回结果 + */ + public Object removeMap(String name, String key) { + return this.getMap(name).remove(key); + } + + //---------------------------------------------------------------- + + /** + * SET缓存 + * @param name 缓存KEY + * @param value 缓存值 + */ + public void set(String name, Object value) { + set(name, value, null); + } + + /** + * SET缓存 + * @param name 缓存KEY + * @param value 缓存值 + * @param seconds 缓存秒数 + */ + public void set(String name, Object value, Integer seconds) { + RBucket bucket = redissonClient.getBucket(name); + bucket.set(value); + if (seconds != null) { + bucket.expire(seconds, TimeUnit.SECONDS); + } + } + + + /** + * GET缓存 String + * @param name 缓存NAME + * @return 缓存对应的值, 不存在返回NULL + */ + public String getStr(String name) { + RBucket bucket = redissonClient.getBucket(name); + if(bucket.isExists()) { + return bucket.get(); + } + return null; + } + + /** + * GET缓存 Integer + * @param name 缓存KEY + * @return 缓存对应的值, 不存在返回NULL + */ + public Integer getInt(String name) { + RBucket bucket = redissonClient.getBucket(name); + if(bucket.isExists()) { + return bucket.get(); + } + return null; + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的键。 + * @param key 数据名称 + * @param f 获取数据的方法。 + * @return 数据对象。 + */ + public String getFromCacheStr(String name, String key, Function f) { + return this.getFromCacheStr(name, key, f, null); + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的名称。 + * @param key 数据key + * @param f 获取数据的方法。 + * @param seconds 过期秒数。 + * @return 数据对象。 + */ + public String getFromCacheStr(String name, String key, Function f, Integer seconds) { + String m; + RBucket bucket = redissonClient.getBucket(name); + if (!bucket.isExists()) { + m = f.apply(key); + if (m != null) { + bucket.set(m); + if (seconds != null) { + bucket.expire(seconds, TimeUnit.SECONDS); + } + } + } else { + m = bucket.get(); + } + return m; + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的键。 + * @param id 数据Id。 + * @param f 获取数据的方法。 + * @param clazz 数据对象类型。 + * @return 数据对象。 + */ + public M getFromCache(String name, Serializable id, Function f, Class clazz) { + return this.getFromCache(name, id, f, clazz, null); + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的键。 + * @param filter mybatis plus的过滤对象。 + * @param f 获取数据的方法。 + * @param clazz 数据对象类型。 + * @return 数据对象。 + */ + public N getFromCache( + String name, LambdaQueryWrapper filter, Function, N> f, Class clazz) { + N m; + RBucket bucket = redissonClient.getBucket(name); + if (!bucket.isExists()) { + m = f.apply(filter); + if (m != null) { + bucket.set(JSONUtil.toJsonStr(m)); + } + } else { + m = JSONUtil.toBean(bucket.get(), clazz); + } + return m; + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的键。 + * @param filter 过滤对象。 + * @param f 获取数据的方法。 + * @param clazz 数据对象类型。 + * @return 数据对象。 + */ + public N getFromCache(String name, M filter, Function f, Class clazz) { + N m; + RBucket bucket = redissonClient.getBucket(name); + if (!bucket.isExists()) { + m = f.apply(filter); + if (m != null) { + bucket.set(JSONUtil.toJsonStr(m)); + } + } else { + m = JSONUtil.toBean(bucket.get(), clazz); + } + return m; + } + + /** + * 从缓存中获取数据。如果缓存中不存在则从执行指定的方法获取数据,并将得到的数据同步到缓存。 + * + * @param name 缓存的键。 + * @param id 数据Id。 + * @param f 获取数据的方法。 + * @param clazz 数据对象类型。 + * @param seconds 过期秒数。 + * @return 数据对象。 + */ + public M getFromCache( + String name, Serializable id, Function f, Class clazz, Integer seconds) { + M m; + RBucket bucket = redissonClient.getBucket(name); + if (!bucket.isExists()) { + m = f.apply(id); + if (m != null) { + bucket.set(JSONUtil.toJsonStr(m)); + if (seconds != null) { + bucket.expire(seconds, TimeUnit.SECONDS); + } + } + } else { + m = JSONUtil.toBean(bucket.get(), clazz); + } + return m; + } + + /** + * 移除指定缓存。 + * + * @param name 缓存名。 + */ + public void evictFormCache(String name) { + redissonClient.getBucket(name).delete(); + } + +} diff --git a/src/main/java/com/ydool/platform/saToken/config/SaTokenConfig.java b/src/main/java/com/ydool/platform/saToken/config/SaTokenConfig.java index cda4c0f..c6cf0f3 100644 --- a/src/main/java/com/ydool/platform/saToken/config/SaTokenConfig.java +++ b/src/main/java/com/ydool/platform/saToken/config/SaTokenConfig.java @@ -14,7 +14,7 @@ public class SaTokenConfig implements WebMvcConfigurer { // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。 registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())) .addPathPatterns(UrlConstant.API + "/**") - .excludePathPatterns(UrlConstant.AUTH+"/login",UrlConstant.PERSON+"/getPersonnelRetire"); + .excludePathPatterns(UrlConstant.AUTH+"/login",UrlConstant.PERSON+"/getPersonnelRetire",UrlConstant.AUTH+"/image"); } @Override diff --git a/src/main/java/com/ydool/system/controller/AuthController.java b/src/main/java/com/ydool/system/controller/AuthController.java index d3b7f81..eecf013 100644 --- a/src/main/java/com/ydool/system/controller/AuthController.java +++ b/src/main/java/com/ydool/system/controller/AuthController.java @@ -1,5 +1,7 @@ package com.ydool.system.controller; +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.hutool.core.lang.Dict; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiSupport; import com.ydool.common.constant.UrlConstant; @@ -34,7 +36,7 @@ public class AuthController { @ApiOperation("登录") @ApiOperationSupport(order = 1) public AjaxResult login(@RequestBody @Validated AuthRequest authRequest) { - return authService.login(authRequest.getUserName(), authRequest.getPassword()); + return authService.login(authRequest.getUserName(), authRequest.getPassword(),authRequest.getCaptchaId(),authRequest.getCaptcha()); } /** @@ -88,5 +90,17 @@ public class AuthController { return authService.editSign(newSignRequest.getSign()); } + /** + * 获取图片验证码接口 + * + * @return + */ + @GetMapping("/image") + @ApiOperation("获取图片验证码接口") + public AjaxResult captcha() { + return authService.captcha(); + } + + } diff --git a/src/main/java/com/ydool/system/request/AuthRequest.java b/src/main/java/com/ydool/system/request/AuthRequest.java index 558b3e1..6b34255 100644 --- a/src/main/java/com/ydool/system/request/AuthRequest.java +++ b/src/main/java/com/ydool/system/request/AuthRequest.java @@ -16,4 +16,12 @@ public class AuthRequest { @ApiModelProperty(value = "密码", required = true) private String password; + @NotBlank(message = "请输入验证码") + @ApiModelProperty(value = "验证码", required = true) + private String captcha; + + @NotBlank(message = "请输入验证码ID") + @ApiModelProperty(value = "验证码ID", required = true) + private String captchaId; + } \ No newline at end of file diff --git a/src/main/java/com/ydool/system/service/IAuthService.java b/src/main/java/com/ydool/system/service/IAuthService.java index 042e025..2991d9a 100644 --- a/src/main/java/com/ydool/system/service/IAuthService.java +++ b/src/main/java/com/ydool/system/service/IAuthService.java @@ -9,7 +9,7 @@ public interface IAuthService { * @param password 密码 * @return */ - AjaxResult login(String userName, String password); + AjaxResult login(String userName, String password, String captchaId, String captcha); /** * 登出 @@ -38,4 +38,6 @@ public interface IAuthService { * @return */ AjaxResult editSign(String sign); + + AjaxResult captcha(); } diff --git a/src/main/java/com/ydool/system/service/impl/AuthServiceImpl.java b/src/main/java/com/ydool/system/service/impl/AuthServiceImpl.java index 50d3826..1fbbebe 100644 --- a/src/main/java/com/ydool/system/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/ydool/system/service/impl/AuthServiceImpl.java @@ -2,20 +2,22 @@ package com.ydool.system.service.impl; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.generator.RandomGenerator; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ydool.common.base.BaseService; import com.ydool.common.cache.ConfigCache; +import com.ydool.common.constant.RedissonConstant; import com.ydool.common.constant.UrlConstant; import com.ydool.common.data.dto.AjaxResult; -import com.ydool.common.utils.CacheUtil; -import com.ydool.common.utils.HttpServletUtil; -import com.ydool.common.utils.PasswordUtil; -import com.ydool.common.utils.RsaUtil; +import com.ydool.common.utils.*; import com.ydool.system.entity.Dept; import com.ydool.system.entity.Menu; import com.ydool.system.entity.Role; @@ -33,6 +35,7 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service @@ -46,6 +49,9 @@ public class AuthServiceImpl extends BaseService implements IA @Autowired private DeptServiceImpl deptService; + @Autowired + private RedisUtils redisUtils; + /** * 登录接口 @@ -56,7 +62,35 @@ public class AuthServiceImpl extends BaseService implements IA */ @SneakyThrows @Override - public AjaxResult login(String userName, String password) { + public AjaxResult login(String userName, String password, String captchaId, String captcha) { + //获取IP + String ip = HttpServletUtil.getRemoteAddress(); + //判断是否在黑名单中 + boolean ipBlack = redisUtils.setCacheContains(RedissonConstant.IP_BLACK_LIST, ip); + if (ipBlack) { + //如果在黑名单中,则20分钟后再尝试 + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + return AjaxResult.fail("您的IP地址在黑名单中,请20分钟后再尝试"); + } + //判断验证码是否正确 + String captchaCache = (String) redisUtils.getMapCache(RedissonConstant.CAPTCHA_CODE, captchaId); + if (!captcha.equalsIgnoreCase(captchaCache)) { + //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("验证码错误"); + } + //清空验证码缓存,以及验证码请求次数缓存 + redisUtils.removeMapCache(RedissonConstant.CAPTCHA_CODE, captchaId); + redisUtils.removeMapCache(RedissonConstant.CAPTCHA_CODE_COUNT, ip); //用户名密码按照UTF-8编码解密 userName = URLDecoder.decode(userName, StandardCharsets.UTF_8.name()); password = URLDecoder.decode(password, StandardCharsets.UTF_8.name()); @@ -65,19 +99,83 @@ public class AuthServiceImpl extends BaseService implements IA password = RsaUtil.decrypt(password, UrlConstant.APP_PRIVATE_KEY); //根据用户名查询账号是否存在 User loginUser = getOne(new QueryWrapper().lambda().eq(User::getLoginName, userName)); - if (ObjectUtil.isNull(loginUser)) return AjaxResult.fail("账号或者密码错误"); + if (ObjectUtil.isNull(loginUser)) { + //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("账号或者密码错误"); + } //加密密码 String loginPassword = PasswordUtil.password(loginUser.getSalt(), password); //与数据库的密码进行匹配 - if (!loginPassword.equals(loginUser.getPassword())) return AjaxResult.fail("账号或者密码错误"); + if (!loginPassword.equals(loginUser.getPassword())) { + //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("账号或者密码错误"); + } //判断账号是否停用 - if (!loginUser.getStatus()) return AjaxResult.fail("当前账号已被停用,请联系管理员"); + if (!loginUser.getStatus()) { + //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("当前账号已被停用,请联系管理员"); + } //判断账号角色 - if (StrUtil.isBlank(loginUser.getRoles())) return AjaxResult.fail("该用户没有对应的角色,无法登陆系统"); + if (StrUtil.isBlank(loginUser.getRoles())) { + //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("该用户没有对应的角色,无法登陆系统"); + } List roleList = userService.roleListByUser(loginUser.getId()); - if (CollUtil.isEmpty(roleList)) return AjaxResult.fail("该用户没有对应的角色或角色已被删除或禁用,无法登陆系统"); + if (CollUtil.isEmpty(roleList)) { //获取IP失败次数 + Integer failCount = (Integer) redisUtils.getMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + if (ObjectUtil.isNull(failCount)) { + failCount = 1; + } else { + failCount++; + } + if (failCount >= 5) { + redisUtils.putMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip, failCount, 20, TimeUnit.MINUTES); + redisUtils.setCacheRefresh(RedissonConstant.IP_BLACK_LIST, ip, 20, TimeUnit.MINUTES); + } + return AjaxResult.fail("该用户没有对应的角色或角色已被删除或禁用,无法登陆系统"); + } loginUser.setLoginDate(LocalDateTime.now()); @@ -87,8 +185,10 @@ public class AuthServiceImpl extends BaseService implements IA StpUtil.login(loginUser.getId()); CacheUtil.put(ConfigCache.SCHEDULED_CODE_PERSONNEL_RETIRE, ConfigCache.SCHEDULED_CODE_PERSONNEL_RETIRE, "scheduled", false); boolean flag = updateById(loginUser); - if (flag) { + //清空ip黑名单和登录失败次数缓存 + redisUtils.removeMapCache(RedissonConstant.LOGIN_FAIL_COUNT, ip); + redisUtils.removeSetCache(RedissonConstant.IP_BLACK_LIST, ip); // 第2步,获取 Token 相关参数 SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); Dict result = Dict.create(); @@ -190,5 +290,31 @@ public class AuthServiceImpl extends BaseService implements IA return flag ? AjaxResult.ok().msg("修改电子签名成功") : AjaxResult.fail("修改电子签名失败"); } + @Override + public AjaxResult captcha() { + //获取请求IP + String ip = HttpServletUtil.getRemoteAddress(); + Integer count = (Integer) redisUtils.getMapCache(RedissonConstant.CAPTCHA_CODE_COUNT, ip); + if (count != null && count > 5) { + redisUtils.putMapCache(RedissonConstant.CAPTCHA_CODE_COUNT, ip, count + 1, 1, TimeUnit.MINUTES); + return AjaxResult.fail("验证码获取频繁,请稍后再试"); + } + // 自定义纯数字的验证码(随机4位数字,可重复) + RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4); + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 30); + lineCaptcha.setGenerator(randomGenerator); + // 重新生成code + lineCaptcha.createCode(); + String uuid = IdUtil.randomUUID(); + String code = lineCaptcha.getCode(); + String imageBase64 = lineCaptcha.getImageBase64(); + Dict dict = new Dict(); + dict.set("key", uuid); + dict.set("value", "data:image/jpeg;base64," + imageBase64); + redisUtils.putMapCache(RedissonConstant.CAPTCHA_CODE, uuid, code, 1, TimeUnit.MINUTES); + redisUtils.putMapCache(RedissonConstant.CAPTCHA_CODE_COUNT, ip, count == null ? 1 : count + 1, 1, TimeUnit.MINUTES); + return AjaxResult.ok().data(dict); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 045ea88..4b24fad 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -119,3 +119,36 @@ sa-token: # token前缀 token-prefix: Bearer + +redis: + # redisson的配置。每个服务可以自己的配置文件中覆盖此选项。 + redisson: + # 如果该值为false,系统将不会创建RedissionClient的bean。 + enabled: true + # mode的可用值为,single/cluster/sentinel/master-slave + mode: single + # single: 单机模式 + # address: redis://localhost:6379 + # cluster: 集群模式 + # 每个节点逗号分隔,同时每个节点前必须以redis://开头。 + # address: redis://localhost:6379,redis://localhost:6378,... + # sentinel: + # 每个节点逗号分隔,同时每个节点前必须以redis://开头。 + # address: redis://localhost:6379,redis://localhost:6378,... + # master-slave: + # 每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。 + # address: redis://localhost:6379,redis://localhost:6378,... + address: redis://localhost:6379 + # 链接超时,单位毫秒。 + timeout: 6000 + # 单位毫秒。分布式锁的超时检测时长。 + # 如果一次锁内操作超该毫秒数,或在释放锁之前异常退出,Redis会在该时长之后主动删除该锁使用的key。 + lockWatchdogTimeout: 60000 + # redis 密码,空可以不填。 + password: + pool: + # 连接池数量。 + poolSize: 20 + # 连接池中最小空闲数量。 + minIdle: 5 +