init
This commit is contained in:
@@ -15,9 +15,11 @@ import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.model.ForgotLoginBody;
|
||||
import org.dromara.common.core.domain.model.LoginBody;
|
||||
import org.dromara.common.core.domain.model.RegisterBody;
|
||||
import org.dromara.common.core.domain.model.SocialLoginBody;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.*;
|
||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
@@ -40,6 +42,7 @@ import org.dromara.system.service.ISysTenantService;
|
||||
import org.dromara.web.domain.vo.LoginTenantVo;
|
||||
import org.dromara.web.domain.vo.LoginVo;
|
||||
import org.dromara.web.domain.vo.TenantListVo;
|
||||
import org.dromara.web.service.ForgotPasswordService;
|
||||
import org.dromara.web.service.IAuthStrategy;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.dromara.web.service.SysRegisterService;
|
||||
@@ -75,7 +78,7 @@ public class AuthController {
|
||||
private final ISysSocialService socialUserService;
|
||||
private final ISysClientService clientService;
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
private final ForgotPasswordService forgotPasswordService;
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
@@ -83,12 +86,13 @@ public class AuthController {
|
||||
* @param body 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
@ApiEncrypt
|
||||
@PostMapping("/login")
|
||||
public R<LoginVo> login(@RequestBody String body) {
|
||||
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
// 授权类型和客户端id
|
||||
// 授权类型和客户端id▐
|
||||
String clientId = loginBody.getClientId();
|
||||
String grantType = loginBody.getGrantType();
|
||||
SysClientVo client = clientService.queryByClientId(clientId);
|
||||
@@ -114,6 +118,7 @@ public class AuthController {
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取跳转URL
|
||||
*
|
||||
@@ -136,6 +141,7 @@ public class AuthController {
|
||||
return R.ok("操作成功", authorizeUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 前端回调绑定授权(需要token)
|
||||
*
|
||||
@@ -196,6 +202,7 @@ public class AuthController {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登录页面租户下拉框
|
||||
*
|
||||
@@ -240,4 +247,19 @@ public class AuthController {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* app用户忘记密码
|
||||
*/
|
||||
@ApiEncrypt
|
||||
@PutMapping("/forgot")
|
||||
public R<Void> forgot(@RequestBody String body) {
|
||||
ForgotLoginBody loginBody = JsonUtils.parseObject(body, ForgotLoginBody.class);
|
||||
|
||||
boolean forgotFlag = forgotPasswordService.forgotPasswordService(loginBody);
|
||||
if (!forgotFlag){
|
||||
throw new UserException("忘记密码修改失败");
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -52,6 +53,40 @@ public class CaptchaController {
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final MailProperties mailProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*
|
||||
* @param username 用户手机号
|
||||
*/
|
||||
@RateLimiter(key = "#username", time = 60, count = 1)
|
||||
@GetMapping("/resource/code")
|
||||
public R<Void> code(@NotBlank(message = "{user.username.not.blank}") String username) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + username;
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
|
||||
if (Validator.isMobile(username)){
|
||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||
String templateId = "";
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
map.put("code", code);
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(username, templateId, map);
|
||||
if (!smsResponse.isSuccess()) {
|
||||
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||
return R.fail(smsResponse.getData().toString());
|
||||
}
|
||||
}else {
|
||||
emailCodeImpl(username);
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 短信验证码
|
||||
*
|
||||
@@ -97,7 +132,7 @@ public class CaptchaController {
|
||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||
public void emailCodeImpl(String email) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
try {
|
||||
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.dromara.web.service;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
import org.dromara.common.core.domain.model.ForgotLoginBody;
|
||||
import org.dromara.common.core.exception.user.CaptchaExpireException;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.system.mapper.SysUserMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ForgotPasswordService {
|
||||
|
||||
private final SysUserMapper userMapper;
|
||||
|
||||
|
||||
public boolean forgotPasswordService(ForgotLoginBody loginBody) {
|
||||
SysUserVo appUserVo =new SysUserVo();
|
||||
if(Validator.isEmail(loginBody.getUsername())){
|
||||
userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, loginBody.getUsername()));
|
||||
}else if (Validator.isMobile(loginBody.getUsername())){
|
||||
appUserVo = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, loginBody.getUsername()));
|
||||
}else {
|
||||
appUserVo = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, loginBody.getUsername()));
|
||||
}
|
||||
if (ObjectUtil.isNull(appUserVo)){
|
||||
throw new UserException("账号未注册");
|
||||
}
|
||||
|
||||
boolean validateFlag = validateSmsCode(loginBody.getUsername(), loginBody.getSmsCode());
|
||||
if (!validateFlag){
|
||||
throw new UserException("验证码无效");
|
||||
}
|
||||
|
||||
appUserVo.setPassword(BCrypt.hashpw(loginBody.getPassword()));
|
||||
SysUser update = MapstructUtils.convert(appUserVo, SysUser.class);
|
||||
userMapper.updateById(update);
|
||||
return userMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验短信验证码
|
||||
*/
|
||||
private boolean validateSmsCode(String username, String smsCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + username);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(smsCode);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.dromara.web.service;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
@@ -24,6 +26,8 @@ import org.dromara.system.mapper.SysUserMapper;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 注册校验方法
|
||||
*
|
||||
@@ -44,24 +48,52 @@ public class SysRegisterService {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
String code = registerBody.getCode();
|
||||
// 校验用户类型是否存在
|
||||
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
|
||||
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// 验证码开关
|
||||
if (captchaEnabled) {
|
||||
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
|
||||
// boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// // 验证码开关
|
||||
// if (captchaEnabled) {
|
||||
// validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
|
||||
// }
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + username;
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
if (captcha==null){
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!StringUtils.equalsIgnoreCase(code, captcha)){
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
sysUser.setUserName(username);
|
||||
sysUser.setNickName(username);
|
||||
// sysUser.setUserName(username);
|
||||
|
||||
|
||||
sysUser.setNickName("用户"+new Date().getTime());
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
sysUser.setUserType(userType);
|
||||
boolean exist;
|
||||
if(Validator.isEmail(username)){
|
||||
sysUser.setEmail(username);
|
||||
exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getEmail, sysUser.getEmail()));
|
||||
});
|
||||
}else if(Validator.isMobile(username)) {
|
||||
sysUser.setPhonenumber(username);
|
||||
exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getPhonenumber, sysUser.getPhonenumber()));
|
||||
});
|
||||
}else {
|
||||
sysUser.setUserName(username);
|
||||
exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUserName, sysUser.getUserName()));
|
||||
});
|
||||
}
|
||||
|
||||
boolean exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUserName, sysUser.getUserName()));
|
||||
});
|
||||
if (exist) {
|
||||
throw new UserException("user.register.save.error", username);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.dromara.web.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -57,13 +58,25 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
String code = loginBody.getCode();
|
||||
String uuid = loginBody.getUuid();
|
||||
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// 验证码开关
|
||||
if (captchaEnabled) {
|
||||
validateCaptcha(tenantId, username, code, uuid);
|
||||
}
|
||||
// boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// // 验证码开关
|
||||
// if (captchaEnabled) {
|
||||
// validateCaptcha(tenantId, username, code, uuid);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||
SysUserVo user = loadUserByUsername(username);
|
||||
SysUserVo user ;
|
||||
if(Validator.isEmail(username)){
|
||||
user = loadUserByEmail(username);
|
||||
}else if (Validator.isMobile(username)){
|
||||
user = loadUserByPhone(username);
|
||||
}else {
|
||||
user = loadUserByUsername(username);
|
||||
}
|
||||
|
||||
|
||||
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
return loginService.buildLoginUser(user);
|
||||
@@ -120,4 +133,28 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
return user;
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByEmail(String username) {
|
||||
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, username));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
throw new UserException("user.not.exists", username);
|
||||
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", username);
|
||||
throw new UserException("user.blocked", username);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByPhone(String username) {
|
||||
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, username));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
throw new UserException("user.not.exists", username);
|
||||
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", username);
|
||||
throw new UserException("user.blocked", username);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -133,17 +133,17 @@ redisson:
|
||||
|
||||
--- # mail 邮件发送
|
||||
mail:
|
||||
enabled: false
|
||||
enabled: true
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
# 是否需要用户名密码验证
|
||||
auth: true
|
||||
# 发送方,遵循RFC-822标准
|
||||
from: xxx@163.com
|
||||
from: watexdevice@163.com
|
||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||
user: xxx@163.com
|
||||
user: watexdevice@163.com
|
||||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
|
||||
pass: xxxxxxxxxx
|
||||
pass: XZVDXjRdVrjiX8N3
|
||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||
starttlsEnable: true
|
||||
# 使用SSL安全连接
|
||||
|
||||
@@ -19,7 +19,6 @@ server:
|
||||
io: 8
|
||||
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
|
||||
worker: 256
|
||||
|
||||
captcha:
|
||||
# 是否启用验证码校验
|
||||
enable: true
|
||||
@@ -120,7 +119,7 @@ security:
|
||||
# 多租户配置
|
||||
tenant:
|
||||
# 是否开启
|
||||
enable: true
|
||||
enable: false
|
||||
# 排除表
|
||||
excludes:
|
||||
- sys_menu
|
||||
@@ -169,7 +168,7 @@ mybatis-encryptor:
|
||||
# api接口加密
|
||||
api-decrypt:
|
||||
# 是否开启全局接口加密
|
||||
enabled: true
|
||||
enabled: false
|
||||
# AES 加密头标识
|
||||
headerFlag: encrypt-key
|
||||
# 响应加密公钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
|
||||
|
||||
@@ -1,62 +1 @@
|
||||
#错误消息
|
||||
not.null=* 必须填写
|
||||
user.jcaptcha.error=验证码错误
|
||||
user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=对不起, 您的账号:{0} 不存在.
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,账户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号:{0} 已被删除
|
||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
user.logout.success=退出成功
|
||||
length.not.valid=长度必须在{min}到{max}个字符之间
|
||||
user.username.not.blank=用户名不能为空
|
||||
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
|
||||
user.username.length.valid=账户长度必须在{min}到{max}个字符之间
|
||||
user.password.not.blank=用户密码不能为空
|
||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
||||
user.password.not.valid=* 5-50个字符
|
||||
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
|
||||
user.email.not.valid=邮箱格式错误
|
||||
user.email.not.blank=邮箱不能为空
|
||||
user.phonenumber.not.blank=用户手机号不能为空
|
||||
user.mobile.phone.number.not.valid=手机号格式错误
|
||||
user.login.success=登录成功
|
||||
user.register.success=注册成功
|
||||
user.register.save.error=保存用户 {0} 失败,注册账号已存在
|
||||
user.register.error=注册失败,请联系系统管理人员
|
||||
user.notfound=请重新登录
|
||||
user.forcelogout=管理员强制退出,请重新登录
|
||||
user.unknown.error=未知错误,请重新登录
|
||||
auth.grant.type.error=认证权限类型错误
|
||||
auth.grant.type.blocked=认证权限类型已禁用
|
||||
auth.grant.type.not.blank=认证权限类型不能为空
|
||||
auth.clientid.not.blank=认证客户端id不能为空
|
||||
##文件上传消息
|
||||
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
|
||||
upload.filename.exceed.length=上传的文件名最长{0}个字符
|
||||
##权限
|
||||
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
|
||||
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
|
||||
repeat.submit.message=不允许重复提交,请稍候再试
|
||||
rate.limiter.message=访问过于频繁,请稍候再试
|
||||
sms.code.not.blank=短信验证码不能为空
|
||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,账户锁定{1}分钟
|
||||
email.code.not.blank=邮箱验证码不能为空
|
||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,账户锁定{1}分钟
|
||||
xcx.code.not.blank=小程序[code]不能为空
|
||||
social.source.not.blank=第三方登录平台[source]不能为空
|
||||
social.code.not.blank=第三方登录平台[code]不能为空
|
||||
social.state.not.blank=第三方登录平台[state]不能为空
|
||||
##租户
|
||||
tenant.number.not.blank=租户编号不能为空
|
||||
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
|
||||
tenant.blocked=对不起,您的租户已禁用,请联系管理员
|
||||
tenant.expired=对不起,您的租户已过期,请联系管理员
|
||||
#错误消息
|
||||
Reference in New Issue
Block a user