用户登录问题修改
This commit is contained in:
parent
b200f2a4b4
commit
d192646d04
|
|
@ -0,0 +1,43 @@
|
|||
package com.bonus.auth.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
||||
public enum LoginType {
|
||||
/**
|
||||
* 账号密码
|
||||
*/
|
||||
USERNAME_PASSWORD,
|
||||
/**
|
||||
* 手机号密码
|
||||
*/
|
||||
PHONE_PASSWORD,
|
||||
/**
|
||||
* 邮箱密码
|
||||
*/
|
||||
EMAIL_PASSWORD,
|
||||
/**
|
||||
* 手机号验证码
|
||||
*/
|
||||
PHONE_OTP,
|
||||
/**
|
||||
* 邮箱验证码
|
||||
*/
|
||||
EMAIL_OTP;
|
||||
|
||||
@JsonCreator
|
||||
public static LoginType fromString(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 自定义转换逻辑,允许大小写不敏感的匹配
|
||||
for (LoginType type : LoginType.values()) {
|
||||
if (type.name().equalsIgnoreCase(key)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
// throw new ServiceException("不支持的登录方式");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.bonus.auth.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 该类用于从 `application.yml` 中加载密码策略的配置项。
|
||||
* 使用 @ConfigurationProperties 注解,前缀为 password-policy。
|
||||
* @author bonus
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "password-policy")
|
||||
@Data
|
||||
public class PasswordPolicyConfig {
|
||||
|
||||
// 密码的最小长度
|
||||
private int minLength;
|
||||
|
||||
// 密码的最大长度
|
||||
private int maxLength;
|
||||
|
||||
// 是否需要包含大写字母
|
||||
private boolean requireUpperCase;
|
||||
|
||||
// 是否需要包含小写字母
|
||||
private boolean requireLowerCase;
|
||||
|
||||
// 是否需要包含数字
|
||||
private boolean requireDigit;
|
||||
|
||||
// 是否需要包含特殊字符
|
||||
private boolean requireSpecialChar;
|
||||
|
||||
// 常见的弱密码列表,禁止使用这些密码
|
||||
private List<String> weakPasswords;
|
||||
|
||||
// 密码历史记录限制
|
||||
private int passwordHistoryLimit;
|
||||
|
||||
// 是否限制连续相同字符
|
||||
private boolean restrictConsecutiveChars;
|
||||
|
||||
// 最大允许的连续字符数
|
||||
private int maxConsecutiveChars;
|
||||
|
||||
// 密码中是否不能包含用户名
|
||||
private boolean excludeUsernameInPassword;
|
||||
|
||||
// 是否在首次登录时强制修改密码
|
||||
private boolean forcePasswordChangeOnFirstLogin;
|
||||
}
|
||||
|
|
@ -1,25 +1,33 @@
|
|||
package com.bonus.auth.controller;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.bonus.auth.config.LoginType;
|
||||
import com.bonus.auth.factory.LoginStrategyFactory;
|
||||
import com.bonus.auth.form.LoginBody;
|
||||
import com.bonus.auth.form.RegisterBody;
|
||||
import com.bonus.auth.service.LoginStrategy;
|
||||
import com.bonus.auth.service.PasswordValidatorService;
|
||||
import com.bonus.auth.service.SysLoginService;
|
||||
import com.bonus.auth.service.SysPasswordService;
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.utils.JwtUtils;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.security.auth.AuthUtil;
|
||||
import com.bonus.common.security.service.TokenService;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Token 控制器
|
||||
|
|
@ -34,33 +42,62 @@ public class TokenController {
|
|||
@Autowired
|
||||
private SysLoginService sysLoginService;
|
||||
|
||||
@PostMapping("isLogin")
|
||||
public R<?> isLogin(@RequestBody LoginBody form) {
|
||||
LoginUser userInfo;
|
||||
if ("mobile".equals(form.getLoginType())) {
|
||||
userInfo = sysLoginService.login(form.getMobile(), form.getPassword(), form.getLoginType());
|
||||
} else {
|
||||
userInfo = sysLoginService.login(form.getUsername(), form.getPassword(), form.getLoginType());
|
||||
@Autowired
|
||||
private LoginStrategyFactory loginStrategyFactory;
|
||||
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Autowired
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Autowired
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
@PostMapping("isAdmin")
|
||||
public R<?> isAdmin(@RequestBody LoginBody form) {
|
||||
//通过用户名获取人员信息
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(form.getUsername(), SecurityConstants.INNER);
|
||||
if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) {
|
||||
return R.fail("登录用户不存在");
|
||||
}
|
||||
SysUser sysUser = userInfo.getSysUser();
|
||||
return R.ok(tokenService.isLogin(String.valueOf(sysUser.getUserId())));
|
||||
Set<String> roles = userResult.getData().getRoles();
|
||||
if (roles.contains("admin")) {
|
||||
if (ObjectUtils.isNotEmpty(userResult.getData().getSysUser().getPhonenumber())) {
|
||||
passwordService.createPhoneCaptcha(userResult.getData().getSysUser().getPhonenumber());
|
||||
} else {
|
||||
R.fail(roles.contains("admin"));
|
||||
}
|
||||
}
|
||||
return R.ok(roles.contains("admin"));
|
||||
}
|
||||
|
||||
@PostMapping("isLogin")
|
||||
public R<?> isLogin(@RequestBody LoginBody form) {
|
||||
LoginStrategy strategy = loginStrategyFactory.getStrategy(form.getLoginType());
|
||||
if (strategy == null) {
|
||||
return R.fail("不支持的登录方式");
|
||||
}
|
||||
LoginUser login = strategy.login(form.getUsername(), form.getPassword());
|
||||
return R.ok(tokenService.isLogin(String.valueOf(login.getSysUser().getUserId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param form 登录表单
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("login")
|
||||
public R<?> login(@RequestBody LoginBody form) {
|
||||
LoginUser userInfo;
|
||||
if ("mobile".equals(form.getLoginType())) {
|
||||
userInfo = sysLoginService.login(form.getMobile(), form.getPassword(), form.getLoginType());
|
||||
} else {
|
||||
userInfo = sysLoginService.login(form.getUsername(), form.getPassword(), form.getLoginType());
|
||||
|
||||
// 获取相应的登录策略
|
||||
LoginStrategy strategy = loginStrategyFactory.getStrategy(form.getLoginType());
|
||||
if (strategy == null) {
|
||||
return R.fail("不支持的登录方式");
|
||||
}
|
||||
return R.ok(tokenService.createToken(userInfo));
|
||||
LoginUser login = strategy.login(form.getUsername(), form.getPassword());
|
||||
if (login.getRoles().contains("admin") && form.getLoginType().equals(LoginType.USERNAME_PASSWORD)) {
|
||||
passwordValidatorService.checkPhoneCaptcha(form.getVerificationCode(), login.getSysUser().getPhonenumber());
|
||||
return R.ok(tokenService.createToken(login));
|
||||
} else {
|
||||
return R.ok(tokenService.createToken(login));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.bonus.auth.factory;
|
||||
|
||||
import com.bonus.auth.config.LoginType;
|
||||
import com.bonus.auth.service.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Component
|
||||
public class LoginStrategyFactory {
|
||||
private final Map<LoginType, LoginStrategy> strategyMap;
|
||||
|
||||
@Autowired
|
||||
public LoginStrategyFactory(List<LoginStrategy> strategies) {
|
||||
strategyMap = new HashMap<>();
|
||||
// 通过类型查找对应的策略
|
||||
strategies.forEach(strategy -> {
|
||||
if (strategy instanceof UsernamePasswordLoginStrategy) {
|
||||
strategyMap.put(LoginType.USERNAME_PASSWORD, strategy);
|
||||
} else if (strategy instanceof PhonePasswordLoginStrategy) {
|
||||
strategyMap.put(LoginType.PHONE_PASSWORD, strategy);
|
||||
} else if (strategy instanceof PhoneOtpLoginStrategy) {
|
||||
strategyMap.put(LoginType.PHONE_OTP, strategy);
|
||||
} else if (strategy instanceof EmailPasswordLoginStrategy) {
|
||||
strategyMap.put(LoginType.EMAIL_PASSWORD, strategy);
|
||||
} else if (strategy instanceof EmailOtpLoginStrategy) {
|
||||
strategyMap.put(LoginType.EMAIL_OTP, strategy);
|
||||
}
|
||||
// 继续添加其他策略
|
||||
});
|
||||
}
|
||||
|
||||
public LoginStrategy getStrategy(LoginType loginType) {
|
||||
return strategyMap.get(loginType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
package com.bonus.auth.form;
|
||||
|
||||
import com.bonus.auth.config.LoginType;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户登录对象
|
||||
*
|
||||
* @author bonus
|
||||
*/
|
||||
@Data
|
||||
public class LoginBody {
|
||||
/**
|
||||
* 用户名
|
||||
|
|
@ -27,87 +31,8 @@ public class LoginBody {
|
|||
|
||||
private String code;
|
||||
|
||||
private String loginType;
|
||||
private LoginType loginType;
|
||||
|
||||
private String mobileCodeType;
|
||||
|
||||
public String getMobileCodeType() {
|
||||
return mobileCodeType;
|
||||
}
|
||||
|
||||
public void setMobileCodeType(String mobileCodeType) {
|
||||
this.mobileCodeType = mobileCodeType;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
this.mobile = mobile;
|
||||
}
|
||||
|
||||
public String getLoginType() {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
public void setLoginType(String loginType) {
|
||||
this.loginType = loginType;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getNickName() {
|
||||
return nickName;
|
||||
}
|
||||
|
||||
public void setNickName(String nickName) {
|
||||
this.nickName = nickName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getVerificationCode() {
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public void setVerificationCode(String verificationCode) {
|
||||
this.verificationCode = verificationCode;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Service
|
||||
public class EmailOtpLoginStrategy implements LoginStrategy {
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
@Override
|
||||
public LoginUser login(String email, String otp) {
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
//验证用户是否存在
|
||||
passwordValidatorService.validateUserResult(email, userResult);
|
||||
// 验证用户查询结果
|
||||
passwordValidatorService.validateUserResult(email, userResult);
|
||||
passwordValidatorService.validateApprovalStatus(email, user);
|
||||
// 验证用户状态
|
||||
passwordValidatorService.validateUserStatus(email, user);
|
||||
//返回信息
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Service
|
||||
public class EmailPasswordLoginStrategy implements LoginStrategy {
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Override
|
||||
public LoginUser login(String email, String password) {
|
||||
//通过手机号获取用户信息
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
|
||||
//验证用户是否存在
|
||||
passwordValidatorService.validateUserResult(email, userResult);
|
||||
//获取用户信息
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
//校验用户审批状态
|
||||
passwordValidatorService.validateApprovalStatus(user.getUserName(), user);
|
||||
// 处理IP校验
|
||||
passwordValidatorService.validateIpBlacklist(user.getUserName());
|
||||
// 验证密码
|
||||
passwordService.validate(user, password, System.currentTimeMillis());
|
||||
//校验用户启用状态
|
||||
passwordValidatorService.validateUserStatus(user.getUserName(), user);
|
||||
//返回信息
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3,10 +3,7 @@ package com.bonus.auth.service;
|
|||
import com.bonus.common.core.constant.CacheConstants;
|
||||
import com.bonus.common.core.constant.Constants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.exception.CaptchaException;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.VerificationCodeUtils;
|
||||
import com.bonus.common.core.utils.sms.SmsUtils;
|
||||
import com.bonus.common.redis.service.RedisService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
|
@ -32,11 +29,16 @@ public class EmailService {
|
|||
String code = VerificationCodeUtils.generateVerificationCode(VerificationCodeUtils.CodeType.NUMERIC);
|
||||
String str = "您的验证码为" + code + ",尊敬的客户,以上验证码3分钟有效,微服务平台提醒您:转发可能导致账号被盗,请勿将验证码泄露于他人";
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom("2642480752@qq.com"); // 发件人邮箱地址
|
||||
message.setTo(to); // 收件人邮箱地址
|
||||
message.setSubject("【博诺思】"); // 邮件主题
|
||||
message.setText(str); // 邮件内容
|
||||
mailSender.send(message); // 发送邮件
|
||||
// 发件人邮箱地址
|
||||
message.setFrom("2642480752@qq.com");
|
||||
// 收件人邮箱地址
|
||||
message.setTo(to);
|
||||
// 邮件主题
|
||||
message.setSubject("【博诺思】");
|
||||
// 邮件内容
|
||||
message.setText(str);
|
||||
// 发送邮件
|
||||
mailSender.send(message);
|
||||
String verifyKey = CacheConstants.CAPTCHA_PHONE_CODE_KEY + to;
|
||||
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
|
||||
return R.ok();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
public interface LoginStrategy {
|
||||
/**
|
||||
* 登录方法
|
||||
* @param identifier 用户的标识符(用户名、手机号、邮箱)
|
||||
* @param credential 用户凭据(密码或验证码)
|
||||
* @return 登录结果
|
||||
*/
|
||||
LoginUser login(String identifier, String credential);
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.auth.config.PasswordPolicyConfig;
|
||||
import com.bonus.common.core.constant.CacheConstants;
|
||||
import com.bonus.common.core.constant.UserConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.enums.UserStatus;
|
||||
import com.bonus.common.core.exception.CaptchaException;
|
||||
import com.bonus.common.core.exception.ServiceException;
|
||||
import com.bonus.common.core.text.Convert;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.ip.IpUtils;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.common.redis.service.RedisService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class PasswordValidatorService {
|
||||
|
||||
@Resource
|
||||
private PasswordPolicyConfig config;
|
||||
|
||||
@Resource
|
||||
private RedisService redisService;
|
||||
|
||||
@Resource
|
||||
private SysRecordLogService recordLogService;
|
||||
|
||||
/**
|
||||
* 对新密码进行校验,返回布尔值表示密码是否符合要求。
|
||||
*
|
||||
* @param username 用户名,不能包含在密码中
|
||||
* @param newPassword 新密码
|
||||
* @return 如果密码符合策略要求,则返回 AjaxResult.success,否则返回错误提示
|
||||
*/
|
||||
public AjaxResult validatePassword(String username, String newPassword) {
|
||||
if (!isPasswordLengthValid(newPassword)) {
|
||||
return AjaxResult.error("密码长度应为" + config.getMinLength() + "至" + config.getMaxLength() + "位!");
|
||||
}
|
||||
|
||||
if (!containsRequiredCharacters(newPassword)) {
|
||||
return AjaxResult.error(getCharacterRequirementErrorMessage());
|
||||
}
|
||||
|
||||
if (containsWeakPassword(newPassword)) {
|
||||
return AjaxResult.error("密码包含常见的弱密码片段!");
|
||||
}
|
||||
|
||||
if (containsConsecutiveCharacters(newPassword, config.getMaxConsecutiveChars())) {
|
||||
return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "位连续字符!");
|
||||
}
|
||||
|
||||
if (newPassword.contains(username)) {
|
||||
return AjaxResult.error("密码不能包含账号!");
|
||||
}
|
||||
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密码长度是否符合配置要求
|
||||
*/
|
||||
private boolean isPasswordLengthValid(String password) {
|
||||
return password.length() >= config.getMinLength() && password.length() <= config.getMaxLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密码是否包含必需的字符类型
|
||||
*/
|
||||
private boolean containsRequiredCharacters(String password) {
|
||||
boolean hasUpperCase = false, hasLowerCase = false, hasDigit = false, hasSpecialChar = false;
|
||||
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
hasUpperCase = true;
|
||||
}
|
||||
if (Character.isLowerCase(c)) {
|
||||
hasLowerCase = true;
|
||||
}
|
||||
if (Character.isDigit(c)) {
|
||||
hasDigit = true;
|
||||
}
|
||||
if ("!@#$%^&*()-_=+[{]};:'\",<.>/?".indexOf(c) >= 0) {
|
||||
hasSpecialChar = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (!config.isRequireUpperCase() || hasUpperCase) &&
|
||||
(!config.isRequireLowerCase() || hasLowerCase) &&
|
||||
(!config.isRequireDigit() || hasDigit) &&
|
||||
(!config.isRequireSpecialChar() || hasSpecialChar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置返回密码不符合要求时的错误提示信息
|
||||
*/
|
||||
private String getCharacterRequirementErrorMessage() {
|
||||
if (config.isRequireUpperCase()) {
|
||||
return "密码必须包含大写字母!";
|
||||
}
|
||||
if (config.isRequireLowerCase()) {
|
||||
return "密码必须包含小写字母!";
|
||||
}
|
||||
if (config.isRequireDigit()) {
|
||||
return "密码必须包含数字!";
|
||||
}
|
||||
if (config.isRequireSpecialChar()) {
|
||||
return "密码必须包含特殊字符!";
|
||||
}
|
||||
return "密码不符合字符要求!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密码是否包含常见的弱密码
|
||||
*/
|
||||
private boolean containsWeakPassword(String password) {
|
||||
for (String weakPwd : config.getWeakPasswords()) {
|
||||
if (password.contains(weakPwd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密码中是否包含超过 n 个连续相同字符
|
||||
*/
|
||||
private boolean containsConsecutiveCharacters(String password, int n) {
|
||||
for (int i = 0; i <= password.length() - n; i++) {
|
||||
boolean consecutive = true;
|
||||
for (int j = 1; j < n; j++) {
|
||||
if (password.charAt(i + j) != password.charAt(i)) {
|
||||
consecutive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (consecutive) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证登录参数
|
||||
*/
|
||||
public void validateLoginParameters(String username, String password) {
|
||||
if (StringUtils.isAnyBlank(username, password)) {
|
||||
logAndThrowError(username, "用户名/密码必须填写", "用户名/密码为空");
|
||||
}
|
||||
if (!isPasswordLengthValid(password)) {
|
||||
logAndThrowError(username, "密码格式不正确", "密码格式不正确");
|
||||
}
|
||||
if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
logAndThrowError(username, "用户名格式不正确", "用户名格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IP黑名单
|
||||
*/
|
||||
public void validateIpBlacklist(String username) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
|
||||
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
|
||||
logAndThrowError(username, "访问IP已被列入系统黑名单", "访问IP已被列入系统黑名单");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logAndThrowError(username, "IP黑名单校验失败,请稍后重试", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户查询结果
|
||||
*/
|
||||
public void validateUserResult(String username, R<LoginUser> userResult) {
|
||||
if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) {
|
||||
logAndThrowError(username, "用户名/密码错误", "登录用户不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户状态
|
||||
*/
|
||||
public void validateUserStatus(String username, SysUser user) {
|
||||
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()) || UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
logAndThrowError(username, "账号已被删除或停用", "账号已被删除或停用");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理IP校验
|
||||
*/
|
||||
public void handleIpValidation(String username, SysUser user) {
|
||||
try {
|
||||
String nowIp = IpUtils.getIpAddr();
|
||||
String hisIp = redisService.getCacheObject("IP:" + user.getUserId());
|
||||
|
||||
if (!nowIp.equals(hisIp)) {
|
||||
recordLogService.saveErrorLogs(username, System.currentTimeMillis(), user.getUserId().toString());
|
||||
}
|
||||
redisService.setCacheObject("IP:" + user.getUserId(), nowIp, 5L, TimeUnit.MINUTES);
|
||||
} catch (Exception e) {
|
||||
logAndThrowError(username, "IP校验失败,请稍后重试", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateApprovalStatus(String username, SysUser user) {
|
||||
if ("0".equals(user.getApprovalStatus())) {
|
||||
logAndThrowError(username, "账号未审批", "用户不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误日志并抛出异常
|
||||
*/
|
||||
private void logAndThrowError(String username, String message, String logMessage) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
recordLogService.saveLogs(username, startTime, logMessage, message, null, "失败");
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验手机验证码
|
||||
*/
|
||||
public void checkPhoneCaptcha(String code, String phone) throws CaptchaException {
|
||||
if (StringUtils.isEmpty(code)) {
|
||||
throw new CaptchaException("手机验证码不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(phone)) {
|
||||
throw new CaptchaException("手机号不能为空");
|
||||
}
|
||||
String verifyKey = CacheConstants.CAPTCHA_PHONE_CODE_KEY + StringUtils.nvl(phone, "");
|
||||
String captcha = redisService.getCacheObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
throw new CaptchaException("手机验证码已失效");
|
||||
}
|
||||
redisService.deleteObject(verifyKey);
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
throw new CaptchaException("手机验证码错误");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Service
|
||||
public class PhoneOtpLoginStrategy implements LoginStrategy {
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Override
|
||||
public LoginUser login(String phone, String otp) {
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(phone, SecurityConstants.INNER);
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
//验证用户是否存在
|
||||
passwordValidatorService.validateUserResult(phone, userResult);
|
||||
// 验证用户查询结果
|
||||
passwordValidatorService.validateUserResult(phone, userResult);
|
||||
passwordValidatorService.validateApprovalStatus(phone, user);
|
||||
// 验证用户状态
|
||||
passwordValidatorService.validateUserStatus(phone, user);
|
||||
//返回信息
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Service
|
||||
public class PhonePasswordLoginStrategy implements LoginStrategy {
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Override
|
||||
public LoginUser login(String phone, String password) {
|
||||
//通过手机号获取用户信息
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(phone, SecurityConstants.INNER);
|
||||
//验证用户是否存在
|
||||
passwordValidatorService.validateUserResult(phone, userResult);
|
||||
//获取用户信息
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
//校验用户审批状态
|
||||
passwordValidatorService.validateApprovalStatus(user.getUserName(), user);
|
||||
// 处理IP校验
|
||||
passwordValidatorService.validateIpBlacklist(user.getUserName());
|
||||
// 验证密码
|
||||
passwordService.validate(user, password, System.currentTimeMillis());
|
||||
//校验用户启用状态
|
||||
passwordValidatorService.validateUserStatus(user.getUserName(), user);
|
||||
//返回信息
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,27 +1,25 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.auth.form.RegisterBody;
|
||||
import com.bonus.common.core.constant.*;
|
||||
import com.hankcs.hanlp.HanLP;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bonus.common.core.constant.Constants;
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.constant.UserConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.enums.UserStatus;
|
||||
import com.bonus.common.core.exception.ServiceException;
|
||||
import com.bonus.common.core.text.Convert;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.ip.IpUtils;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.common.redis.service.RedisService;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import com.hankcs.hanlp.HanLP;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
|
|
@ -50,6 +48,9 @@ public class SysLoginService {
|
|||
@Value("${system.supports.emailLogin}")
|
||||
private boolean supportsEmailLogin;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*
|
||||
|
|
@ -58,14 +59,19 @@ public class SysLoginService {
|
|||
* @return 响应结果
|
||||
*/
|
||||
public R<T> getPhoneCode(String username, String getMobileCodeType) {
|
||||
long startTime = System.currentTimeMillis(); // 记录开始时间
|
||||
// 记录开始时间
|
||||
long startTime = System.currentTimeMillis();
|
||||
int contactType = getContactType(username);
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
|
||||
boolean userExists = userResult != null && userResult.getData() != null;
|
||||
if (userResult.getData().getRoles().contains("admin")) {
|
||||
passwordService.createPhoneCaptcha(userResult.getData().getSysUser().getPhonenumber());
|
||||
return R.ok();
|
||||
}
|
||||
if (contactType == 2) {
|
||||
recordLogService.saveLogs(username, startTime, "获取验证码失败", "联系方式无效", null, "失败");
|
||||
throw new ServiceException("请输入正确的联系方式");
|
||||
}
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
|
||||
boolean userExists = userResult != null && userResult.getData() != null;
|
||||
if ("register".equals(getMobileCodeType)) {
|
||||
handleRegister(username, startTime, contactType, userExists);
|
||||
} else {
|
||||
|
|
@ -117,7 +123,7 @@ public class SysLoginService {
|
|||
private void handleLogin(String username, long startTime, int contactType, boolean userExists) {
|
||||
if (!userExists) {
|
||||
recordLogService.saveLogs(username, startTime, "登录用户不存在", "手机号未注册", null, "失败");
|
||||
throw new ServiceException("手机号登录失败");
|
||||
throw new ServiceException("登录用户不存在");
|
||||
}
|
||||
if (contactType == 0) {
|
||||
if (!supportsEmailLogin) {
|
||||
|
|
@ -135,180 +141,6 @@ public class SysLoginService {
|
|||
recordLogService.saveLogs(username, startTime, "获取验证码", "用户存在", null, "成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录方法
|
||||
*
|
||||
* @param username 用户名或手机号
|
||||
* @param password 密码或验证码
|
||||
* @param loginType 登录类型(mobile:手机验证码登录,其他:用户名密码登录)
|
||||
* @return 登录用户信息
|
||||
*/
|
||||
public LoginUser login(String username, String password, String loginType) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
int contactType = getContactType(username);
|
||||
if (contactType == 0) {
|
||||
if (!supportsEmailLogin) {
|
||||
recordLogService.saveLogs(username, startTime, "邮箱登录不支持", "邮箱登录未开启", null, "失败");
|
||||
throw new ServiceException("用户名/密码错误");
|
||||
}
|
||||
} else if (contactType == 1) {
|
||||
if (!supportsPhoneLogin) {
|
||||
recordLogService.saveLogs(username, startTime, "手机登录不支持", "手机登录未开启", null, "失败");
|
||||
throw new ServiceException("用户名/密码错误");
|
||||
}
|
||||
}// 记录开始时间
|
||||
if ("mobile".equals(loginType)) {
|
||||
return handleMobileLogin(username, startTime);
|
||||
} else {
|
||||
return handleUsernamePasswordLogin(username, password, startTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理手机号验证码登录
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param startTime 操作开始时间
|
||||
* @return 登录用户信息
|
||||
*/
|
||||
private LoginUser handleMobileLogin(String mobile, long startTime) {
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(mobile, SecurityConstants.INNER);
|
||||
validateUserResult(mobile, userResult, startTime);
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
validateApprovalStatus(user.getUserName(), user, startTime);
|
||||
validateIpBlacklist(user.getUserName(), startTime);
|
||||
validateUserStatus(user.getUserName(), user, startTime);
|
||||
recordLogService.saveLogs(user.getUserName(), startTime, "登陆成功", "手机号验证码登录成功", user.getUserId().toString(), "成功");
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户名密码登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param startTime 操作开始时间
|
||||
* @return 登录用户信息
|
||||
*/
|
||||
private LoginUser handleUsernamePasswordLogin(String username, String password, long startTime) {
|
||||
//validateLoginParameters(username, password, startTime); // 验证登录参数
|
||||
validateIpBlacklist(username, startTime); // IP黑名单校验
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
|
||||
validateUserResult(username, userResult, startTime); // 验证用户查询结果
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
validateApprovalStatus(username, user, startTime);
|
||||
validateUserStatus(username, user, startTime); // 验证用户状态
|
||||
passwordService.validate(user, password, startTime); // 验证密码
|
||||
handleIpValidation(username, user, startTime); // 处理IP校验
|
||||
recordLogService.saveLogs(username, startTime, "登陆成功", "用户名密码登录成功", user.getUserId().toString(), "成功");
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证登录参数
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param startTime 操作开始时间
|
||||
*/
|
||||
private void validateLoginParameters(String username, String password, long startTime) {
|
||||
if (StringUtils.isAnyBlank(username, password)) {
|
||||
recordLogService.saveLogs(username, startTime, "用户名/密码为空", "用户名/密码必须填写", null, "失败");
|
||||
throw new ServiceException("用户名/密码必须填写");
|
||||
}
|
||||
|
||||
if (password.length() < ValidateUtils.MIN_LENGTH || password.length() > ValidateUtils.MAX_LENGTH) {
|
||||
recordLogService.saveLogs(username, startTime, "密码格式不正确", "密码格式不正确", null, "失败");
|
||||
throw new ServiceException("用户名/密码错误");
|
||||
}
|
||||
|
||||
if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
recordLogService.saveLogs(username, startTime, "用户名格式不正确", "用户名格式不正确", null, "失败");
|
||||
throw new ServiceException("用户名/密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IP黑名单
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param startTime 操作开始时间
|
||||
*/
|
||||
private void validateIpBlacklist(String username, long startTime) {
|
||||
try {
|
||||
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
|
||||
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
|
||||
recordLogService.saveLogs(username, startTime, "访问IP已被列入系统黑名单", "很遗憾,访问IP已被列入系统黑名单", null, "失败");
|
||||
throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
recordLogService.saveLogs(username, startTime, "IP黑名单校验异常", e.getMessage(), null, "失败");
|
||||
throw new ServiceException("IP黑名单校验失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户查询结果
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param userResult 用户查询结果
|
||||
* @param startTime 操作开始时间
|
||||
*/
|
||||
private void validateUserResult(String username, R<LoginUser> userResult, long startTime) {
|
||||
if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) {
|
||||
recordLogService.saveLogs(username, startTime, "登录用户不存在", "用户名/密码错误", null, "失败");
|
||||
throw new ServiceException(userResult == null ? "用户名/密码错误" : userResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void validateApprovalStatus(String username, SysUser user, long startTime) {
|
||||
if ("0".equals(user.getApprovalStatus())) {
|
||||
recordLogService.saveLogs(username, startTime, "账号未审批", "用户不存在", null, "失败");
|
||||
throw new ServiceException("账号未审批,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户状态
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param user 用户信息
|
||||
* @param startTime 操作开始时间
|
||||
*/
|
||||
private void validateUserStatus(String username, SysUser user, long startTime) {
|
||||
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()) || UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
recordLogService.saveLogs(username, startTime, "账号已被删除或停用", "用户不存在", null, "失败");
|
||||
throw new ServiceException("账号已被删除或停用,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理IP校验
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param user 用户信息
|
||||
* @param startTime 操作开始时间
|
||||
*/
|
||||
private void handleIpValidation(String username, SysUser user, long startTime) {
|
||||
try {
|
||||
String nowIp = IpUtils.getIpAddr();
|
||||
String hisIp = redisService.getCacheObject("IP:" + user.getUserId().toString());
|
||||
|
||||
if (!nowIp.equals(hisIp)) {
|
||||
recordLogService.saveErrorLogs(username, startTime, user.getUserId().toString());
|
||||
}
|
||||
redisService.setCacheObject("IP:" + user.getUserId().toString(), nowIp, 5L, TimeUnit.MINUTES);
|
||||
} catch (Exception e) {
|
||||
recordLogService.saveLogs(username, startTime, "IP校验异常", e.getMessage(), null, "失败");
|
||||
throw new ServiceException("IP校验失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*
|
||||
|
|
@ -336,13 +168,14 @@ public class SysLoginService {
|
|||
|
||||
if (StringUtils.isAnyBlank(registerBody.getUsername(), registerBody.getPassword()) ||
|
||||
registerBody.getUsername().length() < UserConstants.USERNAME_MIN_LENGTH ||
|
||||
registerBody.getUsername().length() > UserConstants.USERNAME_MAX_LENGTH ||
|
||||
registerBody.getPassword().length() < UserConstants.PASSWORD_MIN_LENGTH ||
|
||||
registerBody.getPassword().length() > UserConstants.PASSWORD_MAX_LENGTH) {
|
||||
registerBody.getUsername().length() > UserConstants.USERNAME_MAX_LENGTH) {
|
||||
recordLogService.saveLogs(registerBody.getUsername(), startTime, "注册参数无效", "账户或密码长度不符合要求", null, "失败");
|
||||
throw new ServiceException("账户或密码长度不符合要求");
|
||||
}
|
||||
|
||||
AjaxResult ajaxResult = passwordValidatorService.validatePassword(registerBody.getUsername(), registerBody.getPassword());
|
||||
if (ajaxResult.isError()) {
|
||||
throw new ServiceException((String) ajaxResult.get("msg"));
|
||||
}
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserName(registerBody.getUsername());
|
||||
sysUser.setNickName(registerBody.getNickName());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.bonus.auth.service;
|
||||
|
||||
import com.bonus.common.core.constant.SecurityConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.system.api.RemoteUserService;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Service
|
||||
public class UsernamePasswordLoginStrategy implements LoginStrategy {
|
||||
@Resource
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
* @param username 用户的标识符(用户名、手机号、邮箱)
|
||||
* @param password 用户凭据(密码或验证码)
|
||||
* @return 登录结果
|
||||
*/
|
||||
@Override
|
||||
public LoginUser login(String username, String password) {
|
||||
//参数校验
|
||||
passwordValidatorService.validateLoginParameters(username, password);
|
||||
// IP黑名单校验;
|
||||
passwordValidatorService.validateIpBlacklist(username);
|
||||
//通过用户名获取人员信息
|
||||
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
|
||||
//获取用户信息
|
||||
LoginUser userInfo = userResult.getData();
|
||||
SysUser user = userInfo.getSysUser();
|
||||
// 验证用户查询结果
|
||||
passwordValidatorService.validateUserResult(username, userResult);
|
||||
passwordValidatorService.validateApprovalStatus(username, user);
|
||||
// 验证用户状态
|
||||
passwordValidatorService.validateUserStatus(username, user);
|
||||
// 验证密码
|
||||
passwordService.validate(user, password, System.currentTimeMillis());
|
||||
// 处理IP校验
|
||||
passwordValidatorService.handleIpValidation(username, user);
|
||||
//返回信息
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.bonus.system.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 该类用于从 `application.yml` 中加载密码策略的配置项。
|
||||
* 使用 @ConfigurationProperties 注解,前缀为 password-policy。
|
||||
* @author bonus
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "password-policy")
|
||||
@Data
|
||||
public class PasswordPolicyConfig {
|
||||
|
||||
// 密码的最小长度
|
||||
private int minLength;
|
||||
|
||||
// 密码的最大长度
|
||||
private int maxLength;
|
||||
|
||||
// 是否需要包含大写字母
|
||||
private boolean requireUpperCase;
|
||||
|
||||
// 是否需要包含小写字母
|
||||
private boolean requireLowerCase;
|
||||
|
||||
// 是否需要包含数字
|
||||
private boolean requireDigit;
|
||||
|
||||
// 是否需要包含特殊字符
|
||||
private boolean requireSpecialChar;
|
||||
|
||||
// 常见的弱密码列表,禁止使用这些密码
|
||||
private List<String> weakPasswords;
|
||||
|
||||
// 密码历史记录限制
|
||||
private int passwordHistoryLimit;
|
||||
|
||||
// 是否限制连续相同字符
|
||||
private boolean restrictConsecutiveChars;
|
||||
|
||||
// 最大允许的连续字符数
|
||||
private int maxConsecutiveChars;
|
||||
|
||||
// 密码中是否不能包含用户名
|
||||
private boolean excludeUsernameInPassword;
|
||||
|
||||
// 是否在首次登录时强制修改密码
|
||||
private boolean forcePasswordChangeOnFirstLogin;
|
||||
|
||||
// 定期修改密码
|
||||
private int regularlyChangePassword;
|
||||
}
|
||||
|
|
@ -1,101 +1,96 @@
|
|||
package com.bonus.system.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.bonus.common.core.constant.ValidateUtils;
|
||||
import com.bonus.common.log.annotation.SysLog;
|
||||
import com.bonus.common.log.enums.OperaType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.file.FileTypeUtils;
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.common.core.web.controller.BaseController;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.common.log.annotation.SysLog;
|
||||
import com.bonus.common.log.enums.OperaType;
|
||||
import com.bonus.common.security.service.TokenService;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.RemoteFileService;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import com.bonus.system.domain.UserPasswordHistory;
|
||||
import com.bonus.system.service.ISysUserService;
|
||||
import com.bonus.system.service.PasswordValidatorService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 个人信息 业务处理
|
||||
*
|
||||
*
|
||||
* @author bonus
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/user/profile")
|
||||
public class SysProfileController extends BaseController
|
||||
{
|
||||
public class SysProfileController extends BaseController {
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private RemoteFileService remoteFileService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
/**
|
||||
* 个人信息
|
||||
*/
|
||||
@GetMapping
|
||||
public AjaxResult profile() {
|
||||
try{
|
||||
String username = SecurityUtils.getUsername();
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
AjaxResult ajax = AjaxResult.success(user);
|
||||
ajax.put("roleGroup", userService.selectUserRoleGroup(username));
|
||||
ajax.put("postGroup", userService.selectUserPostGroup(username));
|
||||
return ajax;
|
||||
}catch (Exception e){
|
||||
log.error(e.toString(),e);
|
||||
}
|
||||
return error("数据查询异常");
|
||||
try {
|
||||
String username = SecurityUtils.getUsername();
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
AjaxResult ajax = AjaxResult.success(user);
|
||||
ajax.put("roleGroup", userService.selectUserRoleGroup(username));
|
||||
ajax.put("postGroup", userService.selectUserPostGroup(username));
|
||||
return ajax;
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
return error("数据查询异常");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
*/
|
||||
@PutMapping
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE,logType = 0,module = "首页->个人中心")
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE, logType = 0, module = "首页->个人中心")
|
||||
public AjaxResult updateProfile(@RequestBody SysUser user) {
|
||||
try{
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
SysUser currentUser = loginUser.getSysUser();
|
||||
currentUser.setNickName(user.getNickName());
|
||||
currentUser.setEmail(user.getEmail());
|
||||
currentUser.setPhonenumber(user.getPhonenumber());
|
||||
currentUser.setSex(user.getSex());
|
||||
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser))
|
||||
{
|
||||
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) {
|
||||
return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser))
|
||||
{
|
||||
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) {
|
||||
return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
|
||||
}
|
||||
if (userService.updateUserProfile(currentUser) > 0)
|
||||
{
|
||||
if (userService.updateUserProfile(currentUser) > 0) {
|
||||
// 更新缓存用户信息
|
||||
tokenService.setLoginUser(loginUser);
|
||||
return success();
|
||||
}
|
||||
return error("修改个人信息异常,请联系管理员");
|
||||
}catch (Exception e){
|
||||
log.error(e.toString(),e);
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
return error("修改个人信息异常,请联系管理员");
|
||||
}
|
||||
|
|
@ -104,71 +99,71 @@ public class SysProfileController extends BaseController
|
|||
* 重置密码
|
||||
*/
|
||||
@PutMapping("/updatePwd")
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE,logType = 0,module = "首页->个人中心",details = "修改密码")
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE, logType = 0, module = "首页->个人中心", details = "修改密码")
|
||||
public AjaxResult updatePwd(String oldPassword, String newPassword) {
|
||||
try{
|
||||
String username = SecurityUtils.getUsername();
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
String password = user.getPassword();
|
||||
String msg= ValidateUtils.isPwd(oldPassword);
|
||||
if (StringUtils.isNotEmpty(msg)) {
|
||||
return error(msg);
|
||||
}
|
||||
if (!SecurityUtils.matchesPassword(oldPassword, password)) {
|
||||
return error("修改密码失败,旧密码错误");
|
||||
}
|
||||
if (SecurityUtils.matchesPassword(newPassword, password)) {
|
||||
return error("新密码不能与旧密码相同");
|
||||
}
|
||||
newPassword = SecurityUtils.encryptPassword(newPassword);
|
||||
if (userService.resetUserPwd(username, newPassword) > 0)
|
||||
{
|
||||
// 更新缓存用户密码
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
loginUser.getSysUser().setPassword(newPassword);
|
||||
tokenService.setLoginUser(loginUser);
|
||||
return success();
|
||||
}
|
||||
return error("修改密码异常,请联系管理员");
|
||||
}catch (Exception e){
|
||||
return error("修改密码异常,请联系管理员");
|
||||
}
|
||||
try {
|
||||
String username = SecurityUtils.getUsername();
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
|
||||
String password = user.getPassword();
|
||||
if (!SecurityUtils.matchesPassword(oldPassword, password)) {
|
||||
return error("修改密码失败,旧密码错误");
|
||||
}
|
||||
AjaxResult ajaxResult = passwordValidatorService.validatePassword(user.getUserId(), user.getUserName(), password, newPassword);
|
||||
if (ajaxResult.isError()) {
|
||||
return ajaxResult;
|
||||
}
|
||||
newPassword = SecurityUtils.encryptPassword(newPassword);
|
||||
if (userService.resetUserPwd(username, newPassword) > 0) {
|
||||
// 更新缓存用户密码
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
loginUser.getSysUser().setPassword(newPassword);
|
||||
tokenService.setLoginUser(loginUser);
|
||||
UserPasswordHistory userPasswordHistory = new UserPasswordHistory();
|
||||
userPasswordHistory.setUserId(user.getUserId());
|
||||
userPasswordHistory.setChangeUser(SecurityUtils.getUserId());
|
||||
userPasswordHistory.setNewPassword(newPassword);
|
||||
userPasswordHistory.setOldPassword(password);
|
||||
passwordValidatorService.addPasswordExpiry(userPasswordHistory);
|
||||
return success();
|
||||
}
|
||||
return error("修改密码异常,请联系管理员");
|
||||
} catch (Exception e) {
|
||||
return error("修改密码异常,请联系管理员");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 头像上传
|
||||
*/
|
||||
@PostMapping("/avatar")
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE,logType = 0,module = "首页->个人中心",details = "头像上传")
|
||||
@SysLog(title = "个人中心", businessType = OperaType.UPDATE, logType = 0, module = "首页->个人中心", details = "头像上传")
|
||||
public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) {
|
||||
try{
|
||||
if (!file.isEmpty()) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String extension = FileTypeUtils.getExtension(file);
|
||||
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION))
|
||||
{
|
||||
return error("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
|
||||
}
|
||||
R<SysFile> fileResult = remoteFileService.upload(file);
|
||||
if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData()))
|
||||
{
|
||||
return error("文件服务异常,请联系管理员");
|
||||
}
|
||||
String url = fileResult.getData().getUrl();
|
||||
if (userService.updateUserAvatar(loginUser.getUsername(), url))
|
||||
{
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("imgUrl", url);
|
||||
// 更新缓存用户头像
|
||||
loginUser.getSysUser().setAvatar(url);
|
||||
tokenService.setLoginUser(loginUser);
|
||||
return ajax;
|
||||
}
|
||||
}
|
||||
return error("上传图片异常,请联系管理员");
|
||||
}catch (Exception e){
|
||||
log.error(e.toString(),e);
|
||||
}
|
||||
try {
|
||||
if (!file.isEmpty()) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String extension = FileTypeUtils.getExtension(file);
|
||||
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
|
||||
return error("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
|
||||
}
|
||||
R<SysFile> fileResult = remoteFileService.upload(file);
|
||||
if (StringUtils.isNull(fileResult) || StringUtils.isNull(fileResult.getData())) {
|
||||
return error("文件服务异常,请联系管理员");
|
||||
}
|
||||
String url = fileResult.getData().getUrl();
|
||||
if (userService.updateUserAvatar(loginUser.getUsername(), url)) {
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("imgUrl", url);
|
||||
// 更新缓存用户头像
|
||||
loginUser.getSysUser().setAvatar(url);
|
||||
tokenService.setLoginUser(loginUser);
|
||||
return ajax;
|
||||
}
|
||||
}
|
||||
return error("上传图片异常,请联系管理员");
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
return error("上传图片异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,13 @@
|
|||
package com.bonus.system.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.bonus.common.core.constant.ValidateUtils;
|
||||
import com.bonus.common.log.annotation.SysLog;
|
||||
import com.bonus.common.log.enums.OperaType;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.poi.ExcelUtil;
|
||||
import com.bonus.common.core.web.controller.BaseController;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.common.core.web.page.TableDataInfo;
|
||||
import com.bonus.common.log.annotation.SysLog;
|
||||
import com.bonus.common.log.enums.OperaType;
|
||||
import com.bonus.common.security.annotation.InnerAuth;
|
||||
import com.bonus.common.security.annotation.RequiresPermissions;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
|
|
@ -36,12 +15,21 @@ import com.bonus.system.api.domain.SysDept;
|
|||
import com.bonus.system.api.domain.SysRole;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import com.bonus.system.service.ISysConfigService;
|
||||
import com.bonus.system.service.ISysDeptService;
|
||||
import com.bonus.system.service.ISysPermissionService;
|
||||
import com.bonus.system.service.ISysPostService;
|
||||
import com.bonus.system.service.ISysRoleService;
|
||||
import com.bonus.system.service.ISysUserService;
|
||||
import com.bonus.system.domain.UserPasswordHistory;
|
||||
import com.bonus.system.service.*;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
|
|
@ -69,6 +57,9 @@ public class SysUserController extends BaseController {
|
|||
@Autowired
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorService passwordValidatorService;
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
|
|
@ -107,6 +98,7 @@ public class SysUserController extends BaseController {
|
|||
List<SysUser> userList = util.importExcel(file.getInputStream());
|
||||
String operName = SecurityUtils.getUsername();
|
||||
String message = userService.importUser(userList, updateSupport, operName);
|
||||
|
||||
return success(message);
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString(), e);
|
||||
|
|
@ -162,6 +154,7 @@ public class SysUserController extends BaseController {
|
|||
if (!userService.checkUserNameUnique(sysUser)) {
|
||||
return R.fail("保存用户'" + username + "'失败,注册账号已存在");
|
||||
}
|
||||
|
||||
return R.ok(userService.registerUser(sysUser));
|
||||
}
|
||||
|
||||
|
|
@ -231,12 +224,17 @@ public class SysUserController extends BaseController {
|
|||
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
|
||||
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
|
||||
}
|
||||
String pwd = ValidateUtils.isPwd(user.getPassword());
|
||||
/*String pwd = ValidateUtils.isPwd(user.getPassword());
|
||||
if (StringUtils.isNotEmpty(pwd)) {
|
||||
return error("新增用户'" + user.getUserName() + "'失败," + pwd);
|
||||
}*/
|
||||
AjaxResult ajaxResult = passwordValidatorService.validatePassword(-100L, user.getUserName(), "", user.getPassword());
|
||||
if (ajaxResult.isError()) {
|
||||
return ajaxResult;
|
||||
}
|
||||
user.setCreateBy(SecurityUtils.getUsername());
|
||||
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
|
||||
|
||||
return toAjax(userService.insertUser(user));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString(), e);
|
||||
|
|
@ -297,10 +295,21 @@ public class SysUserController extends BaseController {
|
|||
@SysLog(title = "用户管理", businessType = OperaType.UPDATE, logType = 0, module = "系统管理->用户管理", details = "重置用户密码")
|
||||
public AjaxResult resetPwd(@RequestBody SysUser user) {
|
||||
try {
|
||||
SysUser sysUser = userService.selectUserById(user.getUserId());
|
||||
AjaxResult ajaxResult = passwordValidatorService.validatePassword(sysUser.getUserId(), sysUser.getUserName(), sysUser.getPassword(), user.getPassword());
|
||||
if (ajaxResult.isError()) {
|
||||
return ajaxResult;
|
||||
}
|
||||
userService.checkUserAllowed(user);
|
||||
userService.checkUserDataScope(user.getUserId());
|
||||
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
|
||||
user.setUpdateBy(SecurityUtils.getUsername());
|
||||
UserPasswordHistory userPasswordHistory = new UserPasswordHistory();
|
||||
userPasswordHistory.setUserId(user.getUserId());
|
||||
userPasswordHistory.setChangeUser(SecurityUtils.getUserId());
|
||||
userPasswordHistory.setNewPassword(user.getPassword());
|
||||
userPasswordHistory.setOldPassword(sysUser.getPassword());
|
||||
passwordValidatorService.addPasswordExpiry(userPasswordHistory);
|
||||
return toAjax(userService.resetPwd(user));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString(), e);
|
||||
|
|
@ -390,4 +399,22 @@ public class SysUserController extends BaseController {
|
|||
}
|
||||
return error("系统异常,请联系管理员");
|
||||
}
|
||||
|
||||
@GetMapping("/checkPasswordStatus")
|
||||
public AjaxResult checkPasswordStatus() {
|
||||
// 1. 先检查是否是首次登录
|
||||
boolean firstLoginResult = passwordValidatorService.checkFirstLogin();
|
||||
if (firstLoginResult) {
|
||||
return AjaxResult.success("首次登录需要修改密码", true);
|
||||
}
|
||||
// 2. 再检查密码是否已过期
|
||||
boolean passwordExpiryResult = passwordValidatorService.checkPasswordExpiry();
|
||||
// 如果密码已过期,返回密码过期的提示
|
||||
if (passwordExpiryResult) {
|
||||
return AjaxResult.success("密码已过期,需要修改密码", true);
|
||||
}
|
||||
// 3. 如果都不需要操作,返回成功
|
||||
return AjaxResult.success(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.bonus.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Data
|
||||
public class UserPasswordHistory implements Serializable {
|
||||
/**
|
||||
* 记录的唯一标识符
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 用户的唯一标识符
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 旧密码的哈希值
|
||||
*/
|
||||
private String oldPassword;
|
||||
/**
|
||||
* 新密码的哈希值
|
||||
*/
|
||||
private String newPassword;
|
||||
/**
|
||||
* 密码变更时间
|
||||
*/
|
||||
private LocalDateTime changeTimestamp;
|
||||
/**
|
||||
* 变更人
|
||||
*/
|
||||
private Long changeUser;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.bonus.system.service;
|
||||
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.system.domain.UserPasswordHistory;
|
||||
|
||||
/**
|
||||
* 密码校验器接口类,用于根据配置对用户输入的密码进行验证。
|
||||
* 提供对新密码的校验、首次登录检测、定期密码修改功能。
|
||||
*
|
||||
* @author bonus
|
||||
*/
|
||||
public interface PasswordValidatorService {
|
||||
|
||||
/**
|
||||
* 对新密码进行校验,返回 AjaxResult 表示密码是否符合要求。
|
||||
*
|
||||
* @param username 用户名,不能包含在密码中
|
||||
* @param oldPassword 旧密码
|
||||
* @param newPassword 新密码
|
||||
* @return 如果密码符合策略要求,则返回 AjaxResult 对象
|
||||
*/
|
||||
AjaxResult validatePassword(Long userId, String username, String oldPassword, String newPassword);
|
||||
|
||||
/**
|
||||
* 检查用户是否是首次登录。
|
||||
*
|
||||
* @return 如果是首次登录,返回 AjaxResult 提示用户修改密码,否则返回错误信息
|
||||
*/
|
||||
boolean checkFirstLogin();
|
||||
|
||||
/**
|
||||
* 检查用户密码是否已过期,提示用户定期修改密码。
|
||||
*
|
||||
* @return 如果需要修改密码,返回 AjaxResult 提示用户修改,否则返回成功信息
|
||||
*/
|
||||
boolean checkPasswordExpiry();
|
||||
|
||||
/**
|
||||
* 检查新密码是否在最近五次修改的密码中
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param newPassword 新密码
|
||||
* @return 如果新密码在最近的五次修改记录中,返回 true,否则返回 false
|
||||
*/
|
||||
AjaxResult isPasswordInRecentHistory(Long userId, String newPassword);
|
||||
|
||||
int addPasswordExpiry(UserPasswordHistory userPasswordHistory);
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
package com.bonus.system.service.impl;
|
||||
|
||||
import com.bonus.common.core.utils.DateUtils;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.api.model.LoginUser;
|
||||
import com.bonus.system.config.PasswordPolicyConfig;
|
||||
import com.bonus.system.domain.UserPasswordHistory;
|
||||
import com.bonus.system.mapper.PasswordValidatorMapper;
|
||||
import com.bonus.system.service.ISysConfigService;
|
||||
import com.bonus.system.service.PasswordValidatorService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 密码校验器类,用于根据配置对用户输入的密码进行验证。
|
||||
*/
|
||||
@Service
|
||||
public class PasswordValidatorServiceImpl implements PasswordValidatorService {
|
||||
|
||||
@Resource
|
||||
private PasswordPolicyConfig config;
|
||||
|
||||
@Resource
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Resource
|
||||
private PasswordValidatorMapper passwordValidatorMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public AjaxResult validatePassword(Long userId, String username, String oldPassword, String newPassword) {
|
||||
// 1. 检查密码长度是否符合配置
|
||||
if (newPassword.length() < config.getMinLength() || newPassword.length() > config.getMaxLength()) {
|
||||
return AjaxResult.error("密码长度应为" + config.getMinLength() + "至" + config.getMaxLength() + "位!");
|
||||
}
|
||||
// 2. 检查密码是否包含大写字母、小写字母、数字和特殊字符
|
||||
boolean hasUpperCase = false, hasLowerCase = false, hasDigit = false, hasSpecialChar = false;
|
||||
for (char c : newPassword.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
hasUpperCase = true;
|
||||
}
|
||||
if (Character.isLowerCase(c)) {
|
||||
hasLowerCase = true;
|
||||
}
|
||||
if (Character.isDigit(c)) {
|
||||
hasDigit = true;
|
||||
}
|
||||
if ("!@#$%^&*()-_=+[{]};:'\",<.>/?".indexOf(c) >= 0) {
|
||||
hasSpecialChar = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据配置检查大写字母、小写字母、数字和特殊字符的要求
|
||||
if (config.isRequireUpperCase() && !hasUpperCase) {
|
||||
return AjaxResult.error("密码必须包含大写字母!");
|
||||
|
||||
}
|
||||
|
||||
if (config.isRequireLowerCase() && !hasLowerCase) {
|
||||
return AjaxResult.error("密码必须包含小写字母!");
|
||||
}
|
||||
|
||||
if (config.isRequireDigit() && !hasDigit) {
|
||||
return AjaxResult.error("密码必须包含数字!");
|
||||
}
|
||||
|
||||
if (config.isRequireSpecialChar() && !hasSpecialChar) {
|
||||
return AjaxResult.error("密码必须包含特殊字符!");
|
||||
}
|
||||
|
||||
// 3. 检查是否包含常见弱密码
|
||||
for (String weakPwd : config.getWeakPasswords()) {
|
||||
if (newPassword.contains(weakPwd)) {
|
||||
return AjaxResult.error("密码包含常见的弱密码片段: " + weakPwd);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查是否包含超过规定数量的连续字符
|
||||
if (config.isRestrictConsecutiveChars() && containsConsecutiveCharacters(newPassword, config.getMaxConsecutiveChars())) {
|
||||
return AjaxResult.error("密码不能包含超过" + config.getMaxConsecutiveChars() + "位连续字符!");
|
||||
}
|
||||
|
||||
// 5. 检查密码中是否包含用户名
|
||||
if (config.isExcludeUsernameInPassword() && newPassword.contains(username)) {
|
||||
return AjaxResult.error("密码不能包含账号!");
|
||||
}
|
||||
|
||||
// 6. 检查新密码是否与旧密码相同
|
||||
if (SecurityUtils.matchesPassword(newPassword, oldPassword)) {
|
||||
return AjaxResult.error("新密码不能与原密码相同!");
|
||||
}
|
||||
|
||||
// 7. 检查新密码是否与历史密码相同
|
||||
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
|
||||
// 如果没有找到密码历史记录,返回提示
|
||||
if (userPasswordHistories.isEmpty()) {
|
||||
return AjaxResult.success("没有找到密码历史记录。");
|
||||
}
|
||||
// 只取最近的五次修改记录
|
||||
// 使用 Math.min 以确保不会超过实际记录数
|
||||
int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit());
|
||||
for (int i = 0; i < limit; i++) {
|
||||
UserPasswordHistory history = userPasswordHistories.get(i);
|
||||
// 比较新密码与历史密码
|
||||
if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) {
|
||||
return AjaxResult.error("新密码不能与最近的" + config.getPasswordHistoryLimit() + "个旧密码相同!");
|
||||
}
|
||||
}
|
||||
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否是首次登录
|
||||
*
|
||||
* @return 如果密码符合策略要求,则返回 true,否则返回 false
|
||||
*/
|
||||
@Override
|
||||
public boolean checkFirstLogin() {
|
||||
// 如果未开启首次登录强制修改密码功能,直接返回错误
|
||||
if (!config.isForcePasswordChangeOnFirstLogin()) {
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
SysUser sysUser = loginUser.getSysUser();
|
||||
// 获取初始密码配置
|
||||
String initialPassword = configService.selectConfigByKey("sys.user.initPassword");
|
||||
// 检查当前用户密码是否是初始密码
|
||||
// 根据检查结果返回相应的响应
|
||||
return SecurityUtils.matchesPassword(initialPassword, sysUser.getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户密码是否已过期,提示用户定期修改密码。
|
||||
*
|
||||
* @return 如果需要修改密码,返回 AjaxResult 提示用户修改,否则返回成功信息
|
||||
*/
|
||||
@Override
|
||||
public boolean checkPasswordExpiry() {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
|
||||
// 如果没有找到密码历史记录,返回错误
|
||||
if (userPasswordHistories.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 获取最近的密码修改记录
|
||||
UserPasswordHistory userPasswordHistory = userPasswordHistories.get(0);
|
||||
// 获取当前日期
|
||||
Date currentDate = DateUtils.getNowDate();
|
||||
// 获取最近密码修改日期
|
||||
Date changeDate = DateUtils.toDate(userPasswordHistory.getChangeTimestamp());
|
||||
|
||||
// 计算最近密码修改到当前日期的天数
|
||||
long daysSinceChange = DateUtils.daysBetween(changeDate, currentDate);
|
||||
// 检查是否达到定期修改密码的天数要求
|
||||
return daysSinceChange > config.getRegularlyChangePassword();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查新密码是否在最近五次修改的密码中
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param newPassword 新密码
|
||||
* @return 如果新密码在最近的五次修改记录中,返回错误提示,否则返回成功提示
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult isPasswordInRecentHistory(Long userId, String newPassword) {
|
||||
List<UserPasswordHistory> userPasswordHistories = passwordValidatorMapper.checkPasswordExpiry(userId);
|
||||
|
||||
// 如果没有找到密码历史记录,返回提示
|
||||
if (userPasswordHistories.isEmpty()) {
|
||||
return AjaxResult.success("没有找到密码历史记录。");
|
||||
}
|
||||
|
||||
// 只取最近的五次修改记录
|
||||
// 使用 Math.min 以确保不会超过实际记录数
|
||||
int limit = Math.min(userPasswordHistories.size(), config.getPasswordHistoryLimit());
|
||||
for (int i = 0; i < limit; i++) {
|
||||
UserPasswordHistory history = userPasswordHistories.get(i);
|
||||
// 比较新密码与历史密码
|
||||
if (SecurityUtils.matchesPassword(newPassword, history.getNewPassword())) {
|
||||
return AjaxResult.error("新密码不能与最近的密码相同。"); // 新密码在最近五次记录中
|
||||
}
|
||||
}
|
||||
|
||||
return AjaxResult.success("新密码有效。"); // 新密码不在记录中
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userPasswordHistory
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int addPasswordExpiry(UserPasswordHistory userPasswordHistory) {
|
||||
return passwordValidatorMapper.addPasswordExpiry(userPasswordHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 帮助方法,用于检测密码是否包含超过 n 个连续相同字符。
|
||||
*
|
||||
* @param password 需要检查的密码
|
||||
* @param n 允许的最大连续相同字符数量
|
||||
* @return 如果包含连续字符,则返回 true,否则返回 false
|
||||
*/
|
||||
private boolean containsConsecutiveCharacters(String password, int n) {
|
||||
for (int i = 0; i <= password.length() - n; i++) {
|
||||
boolean consecutive = true;
|
||||
for (int j = 1; j < n; j++) {
|
||||
if (password.charAt(i + j) != password.charAt(i)) {
|
||||
consecutive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (consecutive) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,24 @@
|
|||
package com.bonus.system.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import com.bonus.common.core.constant.UserConstants;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.exception.ServiceException;
|
||||
import com.bonus.common.core.utils.SpringUtils;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.bean.BeanValidators;
|
||||
import com.bonus.common.core.utils.sms.SmsUtils;
|
||||
import com.bonus.common.core.web.domain.BaseEntity;
|
||||
import com.bonus.common.datascope.annotation.DataScope;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.domain.SysRole;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.domain.SysPost;
|
||||
import com.bonus.system.domain.SysUserPost;
|
||||
import com.bonus.system.domain.SysUserRole;
|
||||
import com.bonus.system.mapper.*;
|
||||
import com.bonus.system.service.ISysConfigService;
|
||||
import com.bonus.system.service.ISysDeptService;
|
||||
import com.bonus.system.service.ISysUserService;
|
||||
import com.bonus.system.utils.CommonDataPermissionInfo;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -20,26 +31,11 @@ import org.springframework.mail.javamail.JavaMailSender;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import com.bonus.common.core.constant.UserConstants;
|
||||
import com.bonus.common.core.exception.ServiceException;
|
||||
import com.bonus.common.core.utils.SpringUtils;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.bean.BeanValidators;
|
||||
import com.bonus.common.datascope.annotation.DataScope;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.domain.SysRole;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.domain.SysPost;
|
||||
import com.bonus.system.domain.SysUserPost;
|
||||
import com.bonus.system.domain.SysUserRole;
|
||||
import com.bonus.system.mapper.SysPostMapper;
|
||||
import com.bonus.system.mapper.SysRoleMapper;
|
||||
import com.bonus.system.mapper.SysUserMapper;
|
||||
import com.bonus.system.mapper.SysUserPostMapper;
|
||||
import com.bonus.system.mapper.SysUserRoleMapper;
|
||||
import com.bonus.system.service.ISysConfigService;
|
||||
import com.bonus.system.service.ISysDeptService;
|
||||
import com.bonus.system.service.ISysUserService;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户 业务层处理
|
||||
|
|
@ -80,6 +76,8 @@ public class SysUserServiceImpl implements ISysUserService {
|
|||
protected Validator validator;
|
||||
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender mailSender; // 自动注入JavaMailSender,用于发送邮件
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bonus.system.mapper.PasswordValidatorMapper">
|
||||
<!-- 插入密码历史记录 -->
|
||||
<insert id="addPasswordExpiry">
|
||||
INSERT INTO sys_user_password_history (user_id,
|
||||
old_password,
|
||||
new_password,
|
||||
change_timestamp,
|
||||
change_user)
|
||||
VALUES (#{userId},
|
||||
#{oldPassword},
|
||||
#{newPassword},
|
||||
NOW(),
|
||||
#{changeUser});
|
||||
</insert>
|
||||
<select id="checkPasswordExpiry" resultType="com.bonus.system.domain.UserPasswordHistory">
|
||||
SELECT id AS id,
|
||||
old_password AS oldPassword,
|
||||
new_password AS newPassword,
|
||||
change_timestamp AS changeTimestamp,
|
||||
change_user AS changeUser
|
||||
FROM sys_user_password_history
|
||||
WHERE user_id = #{userId}
|
||||
ORDER BY change_timestamp DESC
|
||||
LIMIT 5;
|
||||
</select>
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue