|
|
@@ -1,15 +1,12 @@
|
|
|
package com.jtzx.crm.module.api.web.auth;
|
|
|
|
|
|
|
|
|
-import cc.uncarbon.framework.core.constant.HelioConstant;
|
|
|
import cc.uncarbon.framework.core.context.UserContext;
|
|
|
import cc.uncarbon.framework.core.context.UserContextHolder;
|
|
|
-import cc.uncarbon.framework.core.exception.BusinessException;
|
|
|
import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
|
|
|
import cc.uncarbon.framework.web.model.response.ApiResult;
|
|
|
import cn.dev33.satoken.annotation.SaCheckLogin;
|
|
|
import cn.dev33.satoken.spring.SpringMVCUtil;
|
|
|
-import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.date.DateTime;
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.lang.Dict;
|
|
|
@@ -21,7 +18,6 @@ import com.jtzx.crm.aspect.extension.SysLogAspectExtensionForSysUserSmsCodeLogin
|
|
|
import com.jtzx.crm.helper.CaptchaHelper;
|
|
|
import com.jtzx.crm.helper.RolePermissionCacheHelper;
|
|
|
import com.jtzx.crm.module.api.constant.ApiConstant;
|
|
|
-import com.jtzx.crm.module.api.constant.CommitConstant;
|
|
|
import com.jtzx.crm.module.api.constant.LimitConstant;
|
|
|
import com.jtzx.crm.module.api.constant.RedisConstant;
|
|
|
import com.jtzx.crm.module.api.model.interior.AdminCaptchaContainer;
|
|
|
@@ -34,14 +30,15 @@ import com.jtzx.crm.module.api.util.CrmAdminStpUtil;
|
|
|
import com.jtzx.crm.module.api.util.ReqIpUtil;
|
|
|
import com.jtzx.crm.module.sms.service.SmsService;
|
|
|
import com.jtzx.crm.module.sys.annotation.SysLog;
|
|
|
-import com.jtzx.crm.module.sys.model.request.SysUserLoginDTO;
|
|
|
+import com.jtzx.crm.module.sys.model.request.SysUserAccountLoginDTO;
|
|
|
+import com.jtzx.crm.module.sys.model.request.SysUserSmsCodeDTO;
|
|
|
+import com.jtzx.crm.module.sys.model.request.SysUserSmsCodeLoginDTO;
|
|
|
import com.jtzx.crm.module.sys.model.response.SysUserLoginBO;
|
|
|
import com.jtzx.crm.module.sys.model.response.SysUserLoginVO;
|
|
|
import com.jtzx.crm.module.sys.service.SysUserService;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import jakarta.validation.Valid;
|
|
|
-import jakarta.validation.constraints.Pattern;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.dromara.sms4j.api.entity.SmsResponse;
|
|
|
@@ -51,7 +48,6 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
|
@Tag(name = "后台管理-鉴权接口")
|
|
|
@@ -81,7 +77,7 @@ public class AdminAuthController {
|
|
|
@Operation(summary = "第一步账号密码登录")
|
|
|
@UseRateLimit(duration = 300, max = 5, strategy = AccountRateLimitStrategy.class, mark = "AccountLogin")
|
|
|
@PostMapping(value = "/auth/accountlogin")
|
|
|
- public ApiResult<LoginRespDataVO> login(@RequestBody @Valid SysUserLoginDTO dto) {
|
|
|
+ public ApiResult<LoginRespDataVO> accountlogin(@RequestBody @Valid SysUserAccountLoginDTO dto) {
|
|
|
//限流时redis中的key
|
|
|
String key = RedisConstant.ACCOUNT_LOGIN_REDIS_KEY_PREFIX + "account:" + dto.getUsername() + ":AccountLogin";
|
|
|
LoginRespDataVO resultData = this.getResultData(null, key);
|
|
|
@@ -89,152 +85,172 @@ public class AdminAuthController {
|
|
|
//计算封禁结束时间
|
|
|
DateTime dateTime = DateUtil.offsetSecond(DateUtil.date(), LimitConstant.login_duration);
|
|
|
resultData.setEndDateTime(DateUtil.formatDateTime(dateTime));
|
|
|
- //封禁结束时间存入redis中
|
|
|
- stringSetRedisTemplate.opsForValue()
|
|
|
- .set(RedisConstant.ACCOUNT_LOGIN_FAILED_END_DATETIME_PREFIX + dto.getUsername(),
|
|
|
- resultData.getEndDateTime(),
|
|
|
- LimitConstant.login_duration,
|
|
|
- TimeUnit.SECONDS);
|
|
|
-
|
|
|
- // RPC调用, 失败抛异常, 成功返回用户信息
|
|
|
- SysUserLoginBO userInfo = sysUserService.adminLogin(dto);
|
|
|
- if (userInfo == null) {
|
|
|
- resultData.setStatus(false);
|
|
|
- //前两次不返回图形验证码,如果第三次还失败,就返回图形验证码
|
|
|
- if (LimitConstant.login_fail_count > 0 && resultData.getCommitCount() >= LimitConstant.login_fail_count) {
|
|
|
- resultData.setShowPicCode(true);
|
|
|
- //返回图形验证码
|
|
|
- resultData.setCaptchaVO(new AdminCaptchaVO(captchaHelper.generate()));
|
|
|
- if (resultData.getCommitCount() > LimitConstant.login_fail_count) {
|
|
|
- if (StrUtil.isEmpty(dto.getCaptchaId()) || StrUtil.isEmpty(dto.getCaptchaAnswer())) {
|
|
|
- return ApiResult.data("图形验证码未填写", resultData).setCode(406);
|
|
|
- }
|
|
|
- // 登录验证码核验
|
|
|
- if (!captchaHelper.validate(dto.getCaptchaId(), dto.getCaptchaAnswer())) {
|
|
|
- return ApiResult.data("图形验证码错误", resultData).setCode(406);
|
|
|
- }
|
|
|
+ //前两次不返回图形验证码,如果第三次还失败,就返回图形验证码
|
|
|
+ if (LimitConstant.login_fail_count > 0 && resultData.getCommitCount() >= LimitConstant.login_fail_count) {
|
|
|
+ resultData.setShowPicCode(true);
|
|
|
+ //返回图形验证码
|
|
|
+ resultData.setCaptchaVO(new AdminCaptchaVO(captchaHelper.generate()));
|
|
|
+ if (resultData.getCommitCount() > LimitConstant.login_fail_count) {
|
|
|
+ if (StrUtil.isEmpty(dto.getCaptchaId()) || StrUtil.isEmpty(dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码未填写", resultData).setCode(401);
|
|
|
+ }
|
|
|
+ // 登录验证码核验
|
|
|
+ if (!captchaHelper.validate(dto.getCaptchaId(), dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码错误", resultData).setCode(401);
|
|
|
}
|
|
|
}
|
|
|
- return ApiResult.data("账号或密码错误", resultData).setCode(406);
|
|
|
}
|
|
|
|
|
|
- if (StrUtil.isEmpty(userInfo.getPhoneNo())) {
|
|
|
+ // RPC调用, 失败抛异常, 成功返回用户信息
|
|
|
+ SysUserLoginBO userInfo = sysUserService.adminLogin(dto.getUsername(), dto.getPassword());
|
|
|
+ if (userInfo == null || StrUtil.isEmpty(userInfo.getPhoneNo())) {
|
|
|
resultData.setStatus(false);
|
|
|
- return ApiResult.data("账号未设置手机号", resultData).setCode(406);
|
|
|
+ if(userInfo == null){
|
|
|
+ return ApiResult.data("账号或密码错误", resultData).setCode(401);
|
|
|
+ }
|
|
|
+ if (StrUtil.isEmpty(userInfo.getPhoneNo())) {
|
|
|
+ return ApiResult.data("账号未设置手机号", resultData).setCode(401);
|
|
|
+ }
|
|
|
}
|
|
|
//设置手机号
|
|
|
resultData.setMobile(userInfo.getPhoneNo());
|
|
|
- //记住我
|
|
|
- userInfo.setRememberMe(dto.getRememberMe());
|
|
|
|
|
|
- //用户数据存入redis,有效期为 5 分钟,超时未进行 发送手机验证码,将无效
|
|
|
- stringSetRedisTemplate.opsForValue()
|
|
|
- .set(RedisConstant.LOGIN_USER_INFO_PREFIX + userInfo.getPhoneNo(), userInfo, ApiConstant.LOGIN_USER_INFO, TimeUnit.SECONDS);
|
|
|
return ApiResult.data("登录成功", resultData);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 发送登陆验证码,5分钟最多发3次
|
|
|
- * @param mobile
|
|
|
* @return
|
|
|
*/
|
|
|
@SysLog(value = "第二步后台用户登录发送登录短信验证码", extension = SysLogAspectExtensionForSmsCodeLogin.class, queryIPLocation = true)
|
|
|
@Operation(summary = "第二步发送登录短信验证码")
|
|
|
- @GetMapping(value = "/auth/sms/smsCode")
|
|
|
- @UseRateLimit(duration = 300, max = 3, strategy = SmsCodeRateLimitStrategy.class,mark = "smsCode")
|
|
|
- public ApiResult<String> smsCode(@RequestParam String username,
|
|
|
- @Pattern(message = "手机号格式有误", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO)
|
|
|
- @RequestParam(name = "mobile") String mobile) {
|
|
|
-
|
|
|
- //限流时redis中的key,这是没啥用
|
|
|
- String key = RedisConstant.SMS_CODE_REDIS_KEY_PREFIX + "smsCode:" + mobile + ":smsCode";
|
|
|
+ @PostMapping(value = "/auth/sms/smsCode")
|
|
|
+ @UseRateLimit(duration = 300, max = 3, strategy = SmsCodeRateLimitStrategy.class, mark = "smsCode")
|
|
|
+ public ApiResult<LoginRespDataVO> smsCode(@RequestBody SysUserSmsCodeDTO dto) {
|
|
|
+
|
|
|
+ //限流时redis中的key
|
|
|
+ String key = RedisConstant.SMS_CODE_REDIS_KEY_PREFIX + "smsCode:" + dto.getMobile() + ":smsCode";
|
|
|
+ //获取返回对象
|
|
|
+ LoginRespDataVO resultData = this.getResultData(dto.getMobile(), key);
|
|
|
+ resultData.setCommitTopCount(LimitConstant.smsCode_login_max);
|
|
|
//计算封禁结束时间
|
|
|
DateTime dateTime = DateUtil.offsetSecond(DateUtil.date(), LimitConstant.smsCode_duration);
|
|
|
String formatDateTime = DateUtil.formatDateTime(dateTime);
|
|
|
- //封禁结束时间存入redis中
|
|
|
- stringSetRedisTemplate.opsForValue()
|
|
|
- .set(RedisConstant.SMS_CODE_FAILED_END_DATETIME_PREFIX + mobile,
|
|
|
- formatDateTime,
|
|
|
- LimitConstant.smsCode_duration,
|
|
|
- TimeUnit.SECONDS);
|
|
|
-
|
|
|
- //账号密码redis中存储的用户信息
|
|
|
- SysUserLoginBO userInfo = this.getLoginUserInfo(mobile);
|
|
|
- if (!userInfo.getPhoneNo().equals(mobile)) {
|
|
|
- throw new BusinessException(406, "手机号错误");
|
|
|
+ resultData.setEndDateTime(formatDateTime);
|
|
|
+ if (LimitConstant.sms_code_fail_count > 0 && resultData.getCommitCount() >= LimitConstant.sms_code_fail_count) {
|
|
|
+ resultData.setShowPicCode(true);
|
|
|
+ //返回图形验证码
|
|
|
+ resultData.setCaptchaVO(new AdminCaptchaVO(captchaHelper.generate()));
|
|
|
+ if (resultData.getCommitCount() > LimitConstant.sms_code_fail_count) {
|
|
|
+ if (StrUtil.isEmpty(dto.getCaptchaId()) || StrUtil.isEmpty(dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码未填写", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ // 登录验证码核验
|
|
|
+ if (!captchaHelper.validate(dto.getCaptchaId(), dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码错误", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ //账号密码查询用户信息
|
|
|
+// SysUserLoginBO userInfo = this.getLoginUserInfo(dto.getMobile());
|
|
|
+ SysUserLoginBO userInfo = sysUserService.adminLogin(dto.getUsername(), dto.getPassword());
|
|
|
+ if (userInfo == null || StrUtil.isEmpty(userInfo.getPhoneNo()) || !userInfo.getPhoneNo().equals(dto.getMobile())) {
|
|
|
+ resultData.setStatus(false);
|
|
|
+
|
|
|
+ if (userInfo == null) {
|
|
|
+ //跳到账号密码登陆页面
|
|
|
+ return ApiResult.data("账号不存在", resultData).setCode(401);
|
|
|
+ }
|
|
|
+ if (StrUtil.isEmpty(userInfo.getPhoneNo())) {
|
|
|
+ //跳到账号密码登陆页面
|
|
|
+ return ApiResult.data("手机号不存在", resultData).setCode(401);
|
|
|
+ }
|
|
|
+ if (!userInfo.getPhoneNo().equals(dto.getMobile())) {
|
|
|
+ return ApiResult.data("手机号错误", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ }
|
|
|
String smsCode = SmsUtils.getRandomInt(RedisConstant.SMS_CODE_LENGTH);
|
|
|
log.info("smsCode--------->{}", smsCode);
|
|
|
LinkedHashMap<String, String> messages = Maps.newLinkedHashMap();
|
|
|
messages.put("code", smsCode);
|
|
|
- SmsResponse smsResponse = SmsFactory.getSmsBlend().sendMessage(mobile, messages);
|
|
|
+ SmsResponse smsResponse = SmsFactory.getSmsBlend().sendMessage(dto.getMobile(), messages);
|
|
|
if (smsResponse.isSuccess()) {
|
|
|
- service.set(RedisConstant.SMS_CODE_PREFIX + mobile, smsCode, RedisConstant.SMS_CODE_DURATION);
|
|
|
- return ApiResult.data("发送成功");
|
|
|
+ service.set(RedisConstant.SMS_CODE_PREFIX + dto.getMobile(), smsCode, RedisConstant.SMS_CODE_DURATION);
|
|
|
+ return ApiResult.data("发送成功", resultData);
|
|
|
}
|
|
|
- return ApiResult.fail(406, "发送失败");
|
|
|
+ return ApiResult.data("发送失败", resultData).setCode(406);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
@SysLog(value = "第三步后台用户手机验证码登录", extension = SysLogAspectExtensionForSysUserSmsCodeLogin.class, queryIPLocation = true)
|
|
|
@Operation(summary = "第三步验证码登录")
|
|
|
- @UseRateLimit(duration = 600, max = 3, strategy = SmsCodeLoginRateLimitStrategy.class, mark = "smsCodeLogin")
|
|
|
- @GetMapping(value = "/auth/smsCodeLogin")
|
|
|
- public ApiResult<LoginRespDataVO> smsCodeLogin(@RequestParam String username,
|
|
|
- @Pattern(message = "手机号格式有误", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO) @RequestParam String mobile,
|
|
|
- @Pattern(message = "验证码格式有误", regexp = CommitConstant.SMS_CODE) @RequestParam String smsCode) {
|
|
|
-
|
|
|
+ @UseRateLimit(duration = 600, max = 5, strategy = SmsCodeLoginRateLimitStrategy.class, mark = "smsCodeLogin")
|
|
|
+ @PostMapping(value = "/auth/smsCodeLogin")
|
|
|
+ public ApiResult<LoginRespDataVO> smsCodeLogin(@RequestBody SysUserSmsCodeLoginDTO dto) {
|
|
|
|
|
|
//限流时redis中的key
|
|
|
- String key = RedisConstant.SMS_CODE_LOGIN_REDIS_KEY_PREFIX + "smsCodeLogin:" + mobile + ":smsCodeLogin";
|
|
|
+ String key = RedisConstant.SMS_CODE_LOGIN_REDIS_KEY_PREFIX + "smsCodeLogin:" + dto.getMobile() + ":smsCodeLogin";
|
|
|
LoginRespDataVO resultData = this.getResultData(null, key);
|
|
|
resultData.setCommitTopCount(LimitConstant.smsCode_login_max);
|
|
|
- //计算封禁限流结束时间,存入redis中,在限流拦截器中,获取到这个时间,是为了结束时间保持不变,动态修改 duration 的时间,
|
|
|
- // 修改时间在 CustomRedisBasedRateLimitStrategy 的 performRateLimitCheck 方法中,然后由每一个限流拦截器,拦截之后
|
|
|
- // 获取这个的结束时间之后,重新计算 duration 时间,重新设定 redis 中的时间
|
|
|
DateTime dateTime = DateUtil.offsetSecond(DateUtil.date(), LimitConstant.smsCode_login_duration);
|
|
|
resultData.setEndDateTime(DateUtil.formatDateTime(dateTime));
|
|
|
|
|
|
- //账号密码redis中存储的用户信息
|
|
|
- SysUserLoginBO userInfo = this.getLoginUserInfo(mobile);
|
|
|
- //封禁结束时间存入redis中
|
|
|
- stringSetRedisTemplate.opsForValue()
|
|
|
- .set(RedisConstant.SMS_CODE_LOGIN_FAILED_END_DATETIME_PREFIX + mobile,
|
|
|
- resultData.getEndDateTime(),
|
|
|
- LimitConstant.smsCode_login_duration,
|
|
|
- TimeUnit.SECONDS);
|
|
|
-
|
|
|
- if (!userInfo.getPhoneNo().equals(mobile)) {
|
|
|
- throw new BusinessException(406, "手机号错误");
|
|
|
+ if (LimitConstant.sms_code_login_fail_count > 0 && resultData.getCommitCount() >= LimitConstant.sms_code_login_fail_count) {
|
|
|
+ resultData.setShowPicCode(true);
|
|
|
+ //返回图形验证码
|
|
|
+ resultData.setCaptchaVO(new AdminCaptchaVO(captchaHelper.generate()));
|
|
|
+ if (resultData.getCommitCount() > LimitConstant.sms_code_login_fail_count) {
|
|
|
+ if (StrUtil.isEmpty(dto.getCaptchaId()) || StrUtil.isEmpty(dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码未填写", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ // 登录验证码核验
|
|
|
+ if (!captchaHelper.validate(dto.getCaptchaId(), dto.getCaptchaAnswer())) {
|
|
|
+ return ApiResult.data("图形验证码错误", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ SysUserLoginBO userInfo = sysUserService.adminLogin(dto.getUsername(), dto.getPassword());
|
|
|
|
|
|
- Object cacheData = service.get(RedisConstant.SMS_CODE_PREFIX + userInfo.getPhoneNo());
|
|
|
- if (cacheData == null) {
|
|
|
+ Object cacheData = service.get(RedisConstant.SMS_CODE_PREFIX + dto.getMobile());
|
|
|
+
|
|
|
+ if (userInfo == null || !userInfo.getPhoneNo().equals(dto.getMobile()) || cacheData == null) {
|
|
|
resultData.setStatus(false);
|
|
|
- return ApiResult.data("验证码错误", resultData).setCode(406);
|
|
|
+ if (userInfo == null) {
|
|
|
+ //跳到账号密码登陆页面
|
|
|
+ return ApiResult.data("账号不存在", resultData).setCode(401);
|
|
|
+ }
|
|
|
+ if (!userInfo.getPhoneNo().equals(dto.getMobile())) {
|
|
|
+ return ApiResult.data("手机号错误", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ if (cacheData == null) {
|
|
|
+ return ApiResult.data("验证码已过期", resultData).setCode(406);
|
|
|
+ }
|
|
|
+ String code = (String) cacheData;
|
|
|
+ if (!code.equals(dto.getSmsCode())) {
|
|
|
+ return ApiResult.data("验证码错误", resultData).setCode(406);
|
|
|
+ }
|
|
|
}
|
|
|
- String code = (String) cacheData;
|
|
|
- if (!code.equals(smsCode)) {
|
|
|
- resultData.setStatus(false);
|
|
|
- return ApiResult.data("验证码错误", resultData).setCode(406);
|
|
|
+
|
|
|
+ if(dto.getRememberMe()==null){
|
|
|
+ dto.setRememberMe(Boolean.FALSE);
|
|
|
}
|
|
|
+
|
|
|
// 构造用户上下文
|
|
|
UserContext userContext = UserContext.builder()
|
|
|
.userId(userInfo.getId())
|
|
|
.userName(userInfo.getUsername())
|
|
|
.userPhoneNo(userInfo.getPhoneNo())
|
|
|
- .userTypeStr("GroupManager")//分组组长
|
|
|
+ .userTypeStr(dto.getUserType())
|
|
|
.extraData(Dict.parse(userInfo))//用户信息保存在扩展数据中
|
|
|
.rolesIds(userInfo.getRoleIds())
|
|
|
.roles(userInfo.getRoles())
|
|
|
.clientIP(ReqIpUtil.getIpAddr(SpringMVCUtil.getRequest()))//获取用户登陆id
|
|
|
.build();
|
|
|
- if (CollUtil.isNotEmpty(userInfo.getRoles()) && userInfo.getRoles().contains("Admin")) {
|
|
|
- userContext.setUserTypeStr("Admin");//管理员
|
|
|
- }
|
|
|
+
|
|
|
// 将用户ID注册到 SA-Token ,并附加一些业务字段
|
|
|
- CrmAdminStpUtil.login(userInfo.getId(), userInfo.getRememberMe());
|
|
|
+ CrmAdminStpUtil.login(userInfo.getId(), dto.getRememberMe());
|
|
|
CrmAdminStpUtil.getSession().set(UserContext.CAMEL_NAME, userContext);
|
|
|
// 返回登录token
|
|
|
SysUserLoginVO tokenInfo = SysUserLoginVO.builder()
|
|
|
@@ -258,15 +274,6 @@ public class AdminAuthController {
|
|
|
}
|
|
|
|
|
|
|
|
|
- public SysUserLoginBO getLoginUserInfo(String mobile) throws BusinessException {
|
|
|
- //获取redis中的用户信息
|
|
|
- Object object = stringSetRedisTemplate.opsForValue().get(RedisConstant.LOGIN_USER_INFO_PREFIX + mobile);
|
|
|
- if (object == null) {
|
|
|
- throw new BusinessException(407, "登录已超时,需重新登陆");
|
|
|
- }
|
|
|
- return (SysUserLoginBO) object;
|
|
|
- }
|
|
|
-
|
|
|
public LoginRespDataVO getResultData(String mobile, String key) {
|
|
|
LoginRespDataVO resultData = LoginRespDataVO.builder().build();
|
|
|
resultData.setMobile(mobile);
|