From df46fd869f869ed0d6e56fbf48f966be2483308f Mon Sep 17 00:00:00 2001 From: sxu <602087911@qq.com> Date: Mon, 27 Jan 2025 14:02:25 +0800 Subject: [PATCH] custauth --- bonus-cust-auth/pom.xml | 136 ++++++ .../bonus/auth/BonusCustAuthApplication.java | 24 + .../java/com/bonus/auth/config/LoginType.java | 43 ++ .../auth/config/PasswordPolicyConfig.java | 54 +++ .../auth/config/VerificationCodeType.java | 34 ++ .../auth/controller/ConfigController.java | 37 ++ .../auth/controller/TokenController.java | 222 +++++++++ .../auth/factory/LoginStrategyFactory.java | 43 ++ .../VerificationCodeStrategyFactory.java | 38 ++ .../java/com/bonus/auth/form/LoginBody.java | 50 ++ .../java/com/bonus/auth/form/LoginType.java | 5 + .../com/bonus/auth/form/RegisterBody.java | 10 + .../auth/service/EmailOtpLoginStrategy.java | 46 ++ .../service/EmailPasswordLoginStrategy.java | 57 +++ .../com/bonus/auth/service/LoginStrategy.java | 16 + .../service/LoginVerificationCodeSender.java | 95 ++++ .../service/PasswordValidatorService.java | 446 ++++++++++++++++++ .../auth/service/PhoneOtpLoginStrategy.java | 49 ++ .../service/PhonePasswordLoginStrategy.java | 60 +++ .../RegisterVerificationCodeSender.java | 86 ++++ .../bonus/auth/service/SysLoginService.java | 298 ++++++++++++ .../auth/service/SysPasswordService.java | 123 +++++ .../auth/service/SysRecordLogService.java | 261 ++++++++++ .../UsernamePasswordLoginStrategy.java | 59 +++ .../service/VerificationCodeStrategy.java | 8 + bonus-cust-auth/src/main/resources/banner.txt | 2 + .../bootstrap-smart_canteen_local.yml | 27 ++ .../src/main/resources/bootstrap.yml | 14 + .../src/main/resources/logback.xml | 74 +++ pom.xml | 1 + 30 files changed, 2418 insertions(+) create mode 100644 bonus-cust-auth/pom.xml create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/BonusCustAuthApplication.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/config/LoginType.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/config/PasswordPolicyConfig.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/config/VerificationCodeType.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/controller/ConfigController.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/controller/TokenController.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/factory/LoginStrategyFactory.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/factory/VerificationCodeStrategyFactory.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginBody.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginType.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/form/RegisterBody.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailOtpLoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailPasswordLoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginVerificationCodeSender.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/PasswordValidatorService.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/PhoneOtpLoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/PhonePasswordLoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/RegisterVerificationCodeSender.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/SysLoginService.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/SysPasswordService.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/SysRecordLogService.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/UsernamePasswordLoginStrategy.java create mode 100644 bonus-cust-auth/src/main/java/com/bonus/auth/service/VerificationCodeStrategy.java create mode 100644 bonus-cust-auth/src/main/resources/banner.txt create mode 100644 bonus-cust-auth/src/main/resources/bootstrap-smart_canteen_local.yml create mode 100644 bonus-cust-auth/src/main/resources/bootstrap.yml create mode 100644 bonus-cust-auth/src/main/resources/logback.xml diff --git a/bonus-cust-auth/pom.xml b/bonus-cust-auth/pom.xml new file mode 100644 index 00000000..f0e887d1 --- /dev/null +++ b/bonus-cust-auth/pom.xml @@ -0,0 +1,136 @@ + + + com.bonus + bonus + 24.12.0-SNAPSHOT + + 4.0.0 + + bonus-cust-auth + + + bonus-cust-auth认证授权中心 + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.bonus + bonus-common-security + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + ${jasypt-spring-boot-starter.version} + + + + com.bonus + bonus-common-log + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + + com.bonus + bonus-common-swagger + + + + com.hankcs + hanlp + portable-1.7.8 + + + + org.springframework.boot + spring-boot-starter-mail + + + com.bonus + bonus-common-config + 24.12.0-SNAPSHOT + compile + + + javax.servlet + servlet-api + 2.5 + compile + + + com.alibaba + druid + 1.2.23 + compile + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + + bns-releases + http://192.168.0.56:8081/repository/maven-releases/ + + + bns-snapshots + http://192.168.0.56:8081/repository/maven-snapshots/ + + + + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/BonusCustAuthApplication.java b/bonus-cust-auth/src/main/java/com/bonus/auth/BonusCustAuthApplication.java new file mode 100644 index 00000000..c4baabde --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/BonusCustAuthApplication.java @@ -0,0 +1,24 @@ +package com.bonus.auth; + +import com.bonus.common.swagger.annotation.EnableCustomSwagger2; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import com.bonus.common.security.annotation.EnableRyFeignClients; + +/** + * 认证授权中心 + * + * @author bonus + */ +@EnableCustomSwagger2 +@EnableRyFeignClients +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) +public class BonusCustAuthApplication +{ + public static void main(String[] args) + { + SpringApplication.run(BonusCustAuthApplication.class, args); + System.out.println("认证授权中心启动成功"); + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/config/LoginType.java b/bonus-cust-auth/src/main/java/com/bonus/auth/config/LoginType.java new file mode 100644 index 00000000..3fe870cd --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/config/LoginType.java @@ -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; + } +} + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/config/PasswordPolicyConfig.java b/bonus-cust-auth/src/main/java/com/bonus/auth/config/PasswordPolicyConfig.java new file mode 100644 index 00000000..078ee9f6 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/config/PasswordPolicyConfig.java @@ -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 weakPasswords; + + // 密码历史记录限制 + private int passwordHistoryLimit; + + // 是否限制连续相同字符 + private boolean restrictConsecutiveChars; + + // 最大允许的连续字符数 + private int maxConsecutiveChars; + + // 密码中是否不能包含用户名 + private boolean excludeUsernameInPassword; + + // 是否在首次登录时强制修改密码 + private boolean forcePasswordChangeOnFirstLogin; +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/config/VerificationCodeType.java b/bonus-cust-auth/src/main/java/com/bonus/auth/config/VerificationCodeType.java new file mode 100644 index 00000000..db7c6824 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/config/VerificationCodeType.java @@ -0,0 +1,34 @@ +package com.bonus.auth.config; + +import com.fasterxml.jackson.annotation.JsonCreator; + +/** + * @author bonus + */ + +public enum VerificationCodeType { + /** + * 登录 + */ + LOGIN, + /** + * 注册 + */ + REGISTER; + + @JsonCreator + public static VerificationCodeType fromString(String key) { + if (key == null) { + return null; + } + + // 自定义转换逻辑,允许大小写不敏感的匹配 + for (VerificationCodeType type : VerificationCodeType.values()) { + if (type.name().equalsIgnoreCase(key)) { + return type; + } + } + // throw new ServiceException("不支持的登录方式"); + return null; + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/controller/ConfigController.java b/bonus-cust-auth/src/main/java/com/bonus/auth/controller/ConfigController.java new file mode 100644 index 00000000..fe768e73 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/controller/ConfigController.java @@ -0,0 +1,37 @@ +package com.bonus.auth.controller; + +import com.bonus.common.core.domain.R; +import com.bonus.config.SystemConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +/** + * @author bonus 系统配置层 + */ +@RestController +@Slf4j +@RefreshScope +public class ConfigController { + @Resource + private SystemConfig systemConfig; + + @GetMapping("getConfig") + public R getConfig() { + Map map = new HashMap<>(); + map.put("loginConfig", systemConfig.getLoginConfig()); + map.put("registersConfig", systemConfig.getRegistersConfig()); + map.put("isAdmin", systemConfig.isAdmin()); + map.put("webSocketurl", systemConfig.getWebsocketurl()); + map.put("isAddRootCompany", systemConfig.isAddRootCompany()); + map.put("requestConfig", systemConfig.getRequestConfig()); + map.put("passwordConfig", systemConfig.getPasswordConfig()); + map.put("addAddress", systemConfig.isAddAddress()); + return R.ok(map); + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/controller/TokenController.java b/bonus-cust-auth/src/main/java/com/bonus/auth/controller/TokenController.java new file mode 100644 index 00000000..dc2c28b5 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/controller/TokenController.java @@ -0,0 +1,222 @@ +package com.bonus.auth.controller; + +import com.alibaba.fastjson.JSONObject; +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.*; +import com.bonus.common.core.constant.CacheConstants; +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.JwtUtils; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.common.core.web.domain.AjaxResult; +import com.bonus.common.redis.service.RedisService; +import com.bonus.common.security.auth.AuthUtil; +import com.bonus.common.security.service.TokenService; +import com.bonus.common.security.utils.SecurityUtils; +import com.bonus.config.SystemConfig; +import com.bonus.system.api.RemoteConfigService; +import com.bonus.system.api.RemoteLogService; +import com.bonus.system.api.RemoteUserService; +import com.bonus.system.api.domain.SysUser; +import com.bonus.system.api.model.LoginUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +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 org.springframework.web.client.RestTemplate; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Token 控制器 + * 处理登录、获取验证码、登出、刷新令牌和注册功能 + */ +@RestController +@Slf4j +public class TokenController { + + @Resource + private SystemConfig config; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysLoginService sysLoginService; + + @Autowired + private LoginStrategyFactory loginStrategyFactory; + + @Resource + private RemoteUserService remoteUserService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private PasswordValidatorService passwordValidatorService; + + @Autowired + private SysRecordLogService logService; + + @Autowired + private RedisService redisService; + + @Resource + private RemoteConfigService configService; + + @PostMapping("isAdmin") + public R isAdmin(@RequestBody LoginBody form) { + if (!config.isAdmin()) { + return R.ok(false); + } + passwordValidatorService.validateLoginParameters(form.getUsername(), form.getPassword()); + //通过用户名获取人员信息 + R userResult = remoteUserService.getUserInfo(form.getUsername(), SecurityConstants.INNER); + // 验证用户查询结果 + passwordValidatorService.validateUserResult(form.getUsername(), userResult); + LoginUser userInfo = userResult.getData(); + SysUser user = userInfo.getSysUser(); + passwordValidatorService.validateApprovalStatus(form.getUsername(), user); + // 验证用户状态 + passwordValidatorService.validateUserStatus(form.getUsername(), user); + // 验证密码 + passwordService.validate(user, form.getPassword(), System.currentTimeMillis()); + // 处理IP校验 + passwordValidatorService.handleIpValidation(form.getUsername(), user); + if (userResult.getData() == null || R.FAIL == userResult.getCode()) { + return R.fail("登录用户不存在"); + } + Set roles = userResult.getData().getRoles(); + 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("不支持的登录方式"); + } + if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP ){ + form.setPassword(form.getVerificationCode()); + } + LoginUser login = strategy.login(form.getUsername(), form.getPassword()); + return R.ok(tokenService.isLogin(String.valueOf(login.getSysUser().getUserId()))); + } + + @PostMapping("login") + public R login(@RequestBody LoginBody form) { + // 获取相应的登录策略 + LoginStrategy strategy = loginStrategyFactory.getStrategy(form.getLoginType()); + if (strategy == null) { + return R.fail("不支持的登录方式"); + } + if (form.getLoginType()== LoginType.EMAIL_OTP || form.getLoginType()== LoginType.PHONE_OTP ){ + form.setPassword(form.getVerificationCode()); + } + + /**对系统并发数进行判断*/ + long concurrency = 100; + AjaxResult result = configService.getConfigKey("sys.backend.concurrency"); + if (result.isSuccess()) + { + concurrency = Long.parseLong(result.get("msg").toString()); + } + Collection keys = redisService.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + if (keys.size() >= concurrency){ + return R.fail("当前系统用户并发数超过系统配置,请稍后再试"); + } + + LoginUser login = strategy.login(form.getUsername(), form.getPassword()); + logService.saveLogin(form.getUsername(), "登录", "登录成功", null, "成功"); + return R.ok(tokenService.createToken(login)); + + } + + /** + * 获取手机验证码 + * + * @param form 登录表单 + * @return 验证码发送结果 + */ + @PostMapping("getPhoneCode") + public R getPhoneCode(@RequestBody LoginBody form) { + return sysLoginService.getPhoneCode(form.getUsername(), form.getVerificationCodeType()); + } + + /** + * 用户登出 + * + * @param request HTTP 请求 + * @return 登出结果 + */ + @PostMapping("logout") + public R logout(HttpServletRequest request) { + try { + String token = SecurityUtils.getToken(request); + if (StringUtils.isNotEmpty(token)) { + String key = JwtUtils.getUserKey(token); + boolean key1 = tokenService.isKey(key); + if (key1) { + String username = JwtUtils.getUserName(token); + String userId = JwtUtils.getUserId(token); + AuthUtil.logoutByToken(token); + tokenService.delExistingToken(Long.valueOf(userId)); + sysLoginService.logout(username, userId); + logService.saveLogout(username, "退出登录", "退出成功", userId, "成功"); + } + return R.ok(); + } + } catch (Exception e) { + log.error("登出失败: {}", e.getMessage(), e); + } + sysLoginService.logout("", ""); + return R.ok(); + } + + /** + * 刷新令牌 + * + * @param request HTTP 请求 + * @return 刷新结果 + */ + @PostMapping("refresh") + public R refresh(HttpServletRequest request) { + try { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) { + tokenService.refreshToken(loginUser); + return R.ok(); + } + } catch (Exception e) { + log.error("刷新令牌失败: {}", e.getMessage(), e); + } + return R.fail("刷新令牌失败"); + } + + /** + * 用户注册 + * + * @param registerBody 注册表单 + * @return 注册结果 + */ + @PostMapping("register") + public R register(@RequestBody RegisterBody registerBody) { + sysLoginService.register(registerBody); + logService.saveRegister(registerBody.getUsername(), "注册", "注册成功", null, "成功"); + return R.ok(); + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/factory/LoginStrategyFactory.java b/bonus-cust-auth/src/main/java/com/bonus/auth/factory/LoginStrategyFactory.java new file mode 100644 index 00000000..8c99f39d --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/factory/LoginStrategyFactory.java @@ -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 strategyMap; + + @Autowired + public LoginStrategyFactory(List 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); + } +} + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/factory/VerificationCodeStrategyFactory.java b/bonus-cust-auth/src/main/java/com/bonus/auth/factory/VerificationCodeStrategyFactory.java new file mode 100644 index 00000000..91801f4f --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/factory/VerificationCodeStrategyFactory.java @@ -0,0 +1,38 @@ +package com.bonus.auth.factory; + +import com.bonus.auth.config.VerificationCodeType; +import com.bonus.auth.service.LoginVerificationCodeSender; +import com.bonus.auth.service.RegisterVerificationCodeSender; +import com.bonus.auth.service.VerificationCodeStrategy; +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 VerificationCodeStrategyFactory { + private final Map strategyMap; + + @Autowired + public VerificationCodeStrategyFactory(List strategies) { + strategyMap = new HashMap<>(); + // 通过类型查找对应的策略 + strategies.forEach(strategy -> { + if (strategy instanceof LoginVerificationCodeSender) { + strategyMap.put(VerificationCodeType.LOGIN, strategy); + } else if (strategy instanceof RegisterVerificationCodeSender) { + strategyMap.put(VerificationCodeType.REGISTER, strategy); + } + // 继续添加其他策略 + }); + } + + public VerificationCodeStrategy getStrategy(VerificationCodeType verificationCodeType) { + return strategyMap.get(verificationCodeType); + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginBody.java b/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginBody.java new file mode 100644 index 00000000..5724fdbb --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginBody.java @@ -0,0 +1,50 @@ +package com.bonus.auth.form; + +import com.bonus.auth.config.LoginType; +import com.bonus.auth.config.VerificationCodeType; +import lombok.Data; + +/** + * 用户登录对象 + * + * @author bonus + */ +@Data +public class LoginBody { + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + + private String nickName; + + private String email; + + private String phone; + private String mobile; + private String verificationCode; + + private String code; + + private LoginType loginType; + + private VerificationCodeType verificationCodeType; + + private String mobileCodeType; + + /** + * i皖送使用的ticket + */ + private String ticket; + + /** + * i皖送登录方式 0:web端登录 1:H5登录 + */ + private String sysType; +} \ No newline at end of file diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginType.java b/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginType.java new file mode 100644 index 00000000..d3cfe386 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/form/LoginType.java @@ -0,0 +1,5 @@ +package com.bonus.auth.form; + +public enum LoginType { + PASSWORD, MOBILE +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/form/RegisterBody.java b/bonus-cust-auth/src/main/java/com/bonus/auth/form/RegisterBody.java new file mode 100644 index 00000000..35b45d02 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/form/RegisterBody.java @@ -0,0 +1,10 @@ +package com.bonus.auth.form; + +/** + * 用户注册对象 + * + * @author bonus + */ +public class RegisterBody extends LoginBody { + +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailOtpLoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailOtpLoginStrategy.java new file mode 100644 index 00000000..297d34f5 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailOtpLoginStrategy.java @@ -0,0 +1,46 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.config.SystemConfig; +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 SystemConfig systemConfig; + @Resource + private RemoteUserService remoteUserService; + + @Resource + private PasswordValidatorService passwordValidatorService; + + @Override + public LoginUser login(String email, String otp) { + if (!systemConfig.getLoginConfig().isPhoneCode()) { + throw new ServiceException("用户不存在/密码错误"); + } + passwordValidatorService.checkPhoneCaptcha(email, otp); + R userResult = remoteUserService.getUserInfoByEmail(email, SecurityConstants.INNER); + //验证用户是否存在 + passwordValidatorService.validateUserResult(email, userResult); + LoginUser userInfo = userResult.getData(); + SysUser user = userInfo.getSysUser(); + passwordValidatorService.validateApprovalStatus(email, user); + // 验证用户状态 + passwordValidatorService.validateUserStatus(email, user); + passwordValidatorService.processLoginBlackList(user); + //返回信息 + return userInfo; + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailPasswordLoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailPasswordLoginStrategy.java new file mode 100644 index 00000000..733ebb03 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/EmailPasswordLoginStrategy.java @@ -0,0 +1,57 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.config.SystemConfig; +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 SystemConfig systemConfig; + + @Resource + private RemoteUserService remoteUserService; + + @Resource + private PasswordValidatorService passwordValidatorService; + @Resource + private SysPasswordService passwordService; + + @Override + public LoginUser login(String email, String password) { + if (!systemConfig.getLoginConfig().isEmailPassword()) { + throw new ServiceException("用户不存在/密码错误"); + } + //通过手机号获取用户信息 + R userResult = remoteUserService.getUserInfoByEmail(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); + passwordValidatorService.processLoginBlackList(user); + //返回信息 + return userInfo; + } +} + + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginStrategy.java new file mode 100644 index 00000000..d5f77c89 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginStrategy.java @@ -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); +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginVerificationCodeSender.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginVerificationCodeSender.java new file mode 100644 index 00000000..44a0f8d0 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/LoginVerificationCodeSender.java @@ -0,0 +1,95 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.security.service.EmailService; +import com.bonus.common.security.service.SmsService; +import com.bonus.system.api.RemoteUserService; +import com.bonus.system.api.model.LoginUser; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 验证码发送服务 + * 可发送到邮箱或手机 + * + * @author bonus + */ +@Service +public class LoginVerificationCodeSender implements VerificationCodeStrategy { + + private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; + private static final String PHONE_REGEX = "^1[3-9]\\d{9}$"; + + @Resource + private EmailService emailService; + + @Resource + private SmsService smsService; + + @Resource + private RemoteUserService remoteUserService; + + /** + * 发送验证码到邮箱或手机 + * + * @param contactInfo 可以是邮箱地址或手机号码 + * @return 验证码发送的结果 + */ + @Override + public void sendVerificationCode(String contactInfo) { + if (isEmail(contactInfo)) { + emailService.sendSimpleEmail(contactInfo); + } else if (isPhone(contactInfo)) { + smsService.sendSimplePhone(contactInfo); + } else { + handleUsernameLogin(contactInfo); + } + } + + /** + * 检查是否是邮箱 + * + * @param contactInfo 输入信息 + * @return 是否为邮箱 + */ + private boolean isEmail(String contactInfo) { + return contactInfo.matches(EMAIL_REGEX); + } + + /** + * 检查是否是手机号 + * + * @param contactInfo 输入信息 + * @return 是否为手机号 + */ + private boolean isPhone(String contactInfo) { + return contactInfo.matches(PHONE_REGEX); + } + + /** + * 处理用户名登录逻辑 + * + * @param username 用户名 + * @return 验证码发送的结果 + */ + private void handleUsernameLogin(String username) { + R userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); + if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) { + throw new ServiceException("用户名/密码错误"); + } + LoginUser user = userResult.getData(); + // 如果用户是管理员,则发送手机验证码 + if (user.getRoles().contains("admin")) { + if (StringUtils.isEmpty(user.getSysUser().getPhonenumber())) { + throw new ServiceException("此账号未绑定手机号,请先绑定手机号"); + } + smsService.sendSimplePhone(user.getSysUser().getPhonenumber()); + } else { + throw new ServiceException("不支持的登录方式"); + } + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/PasswordValidatorService.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PasswordValidatorService.java new file mode 100644 index 00000000..749bca65 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PasswordValidatorService.java @@ -0,0 +1,446 @@ +package com.bonus.auth.service; + +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.config.SystemConfig; +import com.bonus.system.api.RemoteUserService; +import com.bonus.system.api.domain.SysUser; +import com.bonus.system.api.model.LoginUser; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Component +public class PasswordValidatorService { + + @Resource + private SystemConfig systemConfig; + + @Resource + private RedisService redisService; + + @Resource + private SysRecordLogService recordLogService; + + @Resource + private RemoteUserService remoteUserService; + + /** + * 对新密码进行校验,返回布尔值表示密码是否符合要求。 + * + * @param username 用户名,不能包含在密码中 + * @param newPassword 新密码 + * @return 如果密码符合策略要求,则返回 AjaxResult.success,否则返回错误提示 + */ + public AjaxResult validatePassword(String username, String newPassword) { + if (!isPasswordLengthValid(newPassword)) { + return AjaxResult.error("密码长度应为" + systemConfig.getPasswordConfig().getMinLength() + "至" + systemConfig.getPasswordConfig().getMaxLength() + "位!"); + } + + if (!containsRequiredCharacters(newPassword)) { + return AjaxResult.error(getCharacterRequirementErrorMessage()); + } + + if (containsWeakPassword(newPassword)) { + return AjaxResult.error("密码包含常见的弱密码片段!"); + } + + if (containsConsecutiveCharacters(newPassword.toLowerCase(), systemConfig.getPasswordConfig().getMaxConsecutiveChars())) { + return AjaxResult.error("密码不能包含超过" + systemConfig.getPasswordConfig().getMaxConsecutiveChars() + "个连续相同字符、连续递增/递减的数字或字母(不区分大小写)"); + } + + if (newPassword.toLowerCase().contains(username.toLowerCase())) { + return AjaxResult.error("密码不能包含账号!"); + } + + return AjaxResult.success(); + } + + /** + * 检查密码长度是否符合配置要求 + */ + private boolean isPasswordLengthValid(String password) { + return password.length() >= systemConfig.getPasswordConfig().getMinLength() && password.length() <= systemConfig.getPasswordConfig().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 (!systemConfig.getPasswordConfig().isRequireUpperCase() || hasUpperCase) && + (!systemConfig.getPasswordConfig().isRequireLowerCase() || hasLowerCase) && + (!systemConfig.getPasswordConfig().isRequireDigit() || hasDigit) && + (!systemConfig.getPasswordConfig().isRequireSpecialChar() || hasSpecialChar); + } + + /** + * 根据配置返回密码不符合要求时的错误提示信息 + */ + private String getCharacterRequirementErrorMessage() { + if (systemConfig.getPasswordConfig().isRequireUpperCase()) { + return "密码必须包含大写字母!"; + } + if (systemConfig.getPasswordConfig().isRequireLowerCase()) { + return "密码必须包含小写字母!"; + } + if (systemConfig.getPasswordConfig().isRequireDigit()) { + return "密码必须包含数字!"; + } + if (systemConfig.getPasswordConfig().isRequireSpecialChar()) { + return "密码必须包含特殊字符!"; + } + return "密码不符合字符要求!"; + } + + /** + * 检查密码是否包含常见的弱密码 + */ + private boolean containsWeakPassword(String password) { + for (String weakPwd : systemConfig.getPasswordConfig().getWeakPasswords()) { + if (password.toLowerCase().contains(weakPwd)) { + return true; + } + } + return false; + } + + /** + * 检查密码中是否包含超过 n 个连续相同字符、连续递增/递减的数字或字母(不区分大小写) + */ + private boolean containsConsecutiveCharacters(String password, int n) { + // 检查连续相同字符 + n = n + 1; + for (int i = 0; i <= password.length() - n; i++) { + boolean consecutiveSameChar = true; + for (int j = 1; j < n; j++) { + if (password.charAt(i + j) != password.charAt(i)) { + consecutiveSameChar = false; + break; + } + } + if (consecutiveSameChar) { + return true; // 包含超过 n 个连续相同字符 + } + } + + // 检查连续递增或递减的数字 + for (int i = 0; i <= password.length() - n; i++) { + boolean consecutiveIncreasing = true; + boolean consecutiveDecreasing = true; + for (int j = 1; j < n; j++) { + char currentChar = password.charAt(i); + char nextChar = password.charAt(i + j); + + // 检查数字递增或递减 + if (Character.isDigit(currentChar) && Character.isDigit(nextChar)) { + if (nextChar != currentChar + j) { + consecutiveIncreasing = false; + } + if (nextChar != currentChar - j) { + consecutiveDecreasing = false; + } + } else { + consecutiveIncreasing = false; + consecutiveDecreasing = false; + break; + } + } + if (consecutiveIncreasing || consecutiveDecreasing) { + return true; // 包含超过 n 个递增或递减的连续数字 + } + } + + // 检查连续递增或递减的字母(不区分大小写) + for (int i = 0; i <= password.length() - n; i++) { + boolean consecutiveIncreasing = true; + boolean consecutiveDecreasing = true; + for (int j = 1; j < n; j++) { + char currentChar = Character.toLowerCase(password.charAt(i)); // 转为小写 + char nextChar = Character.toLowerCase(password.charAt(i + j)); // 转为小写 + + // 检查字母递增或递减 + if (Character.isLetter(currentChar) && Character.isLetter(nextChar)) { + if (nextChar != currentChar + j) { + consecutiveIncreasing = false; + } + if (nextChar != currentChar - j) { + consecutiveDecreasing = false; + } + } else { + consecutiveIncreasing = false; + consecutiveDecreasing = false; + break; + } + } + if (consecutiveIncreasing || consecutiveDecreasing) { + // 包含超过 n 个递增或递减的连续字母 + 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)); + String ip = IpUtils.getIpAddr(); + if (IpUtils.isMatchedIp(blackStr,ip )) { + logAndThrowError(username, "访问IP已被列入系统黑名单", "访问IP已被列入系统黑名单"); + } + } catch (Exception e) { + logAndThrowError(username, "IP黑名单校验失败,请稍后重试", e.getMessage()); + } + } + + /** + * 验证用户查询结果 + */ + public void validateUserResult(String username, R userResult) { + if (userResult == null || userResult.getData() == null || R.FAIL == userResult.getCode()) { + if (Objects.nonNull(userResult)) { + logAndThrowError(username, userResult.getMsg(), userResult.getMsg()); + } + 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(),"用户连续两次在不同IP登录"); + } + redisService.setCacheObject("IP:" + user.getUserId(), nowIp, 5L, TimeUnit.MINUTES); + } catch (Exception e) { + logAndThrowError(username, "IP校验失败,请稍后重试", e.getMessage()); + } + } + + public void processLoginBlackList(SysUser user){ + // 获取黑名单列表 + List> cacheList = redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST); + + // 获取客户端的 IP 地址 + String ip = IpUtils.getIpAddr(); + + // 遍历黑名单 + for (Map map : cacheList) { + String ipAddress = (String) map.getOrDefault("ipAddress", null); + String ipRangeEnd = (String) map.getOrDefault("ipRangeEnd", null); + String ipRangeStart = (String) map.getOrDefault("ipRangeStart", null); + String accessStartTime = (String) map.getOrDefault("accessStartTime", null); + String accessEndTime = (String) map.getOrDefault("accessEndTime", null); + + // 如果 ipAddress 为空,检查是否在 ip 范围内 + if (ObjectUtils.isEmpty(ipAddress)) { + if (isIpInRange(ip, ipRangeStart, ipRangeEnd)) { + boolean result = handleAccessTimeCheck(user, accessStartTime, accessEndTime); + if (!result) { + throw new ServiceException("ip地址异常"); + }else{ + return; + } + } + } else if (ipAddress.equals(ip)) { + boolean result = handleAccessTimeCheck(user, accessStartTime, accessEndTime); + if (!result) { + throw new ServiceException("ip地址异常"); + }else{ + return; + } + + } + } + + } + + + /** + * 检查当前时间是否在有效的访问时间范围内 + * @param accessStartTime 访问开始时间 + * @param accessEndTime 访问结束时间 + */ + private boolean handleAccessTimeCheck(SysUser user, String accessStartTime, String accessEndTime) { + if (ObjectUtils.isNotEmpty(accessStartTime)) { + boolean currentTimeInRange = isCurrentTimeInRange(accessStartTime, accessEndTime); + if (!currentTimeInRange) { + recordLogService.saveErrorLogs(user.getUserName(), System.currentTimeMillis(), user.getUserId().toString(),"IP地址异常"); + return false; + } else { + return true; + } + } else { + return true; + } + } + + /** + * 处理正常情况 + */ + private void handleNormalProcess() { + // 正常处理的具体逻辑 + System.out.println("IP access is within the allowed time range."); + } + + /** + * 检查给定的IP地址是否在指定的网段区间内 + * + * @param ip 要检查的IP地址,例如 "192.168.1.10" + * @param startIp 区间开始的IP地址,例如 "192.168.1.0" + * @param endIp 区间结束的IP地址,例如 "192.168.1.255" + * @return true 如果IP在区间内;否则返回 false + */ + public static boolean isIpInRange(String ip, String startIp, String endIp) { + try { + // 将 IP 地址、起始 IP 和结束 IP 转换为整数 + long ipToCheck = ipToLong(InetAddress.getByName(ip)); + long start = ipToLong(InetAddress.getByName(startIp)); + long end = ipToLong(InetAddress.getByName(endIp)); + + // 检查 IP 是否在区间内 + return ipToCheck >= start && ipToCheck <= end; + } catch (UnknownHostException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将IP地址转换为整数 + * + * @param inetAddress IP地址对象 + * @return 转换后的长整数 + */ + private static long ipToLong(InetAddress inetAddress) { + byte[] octets = inetAddress.getAddress(); + long result = 0; + for (byte octet : octets) { + result = (result << 8) | (octet & 0xFF); + } + return result; + } + public static boolean isCurrentTimeInRange(String startDateTime, String endDateTime) { + // 定义日期时间格式 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + // 将字符串转换为 LocalDateTime + LocalDateTime start = LocalDateTime.parse(startDateTime, formatter); + LocalDateTime end = LocalDateTime.parse(endDateTime, formatter); + + // 获取当前日期和时间 + LocalDateTime currentDateTime = LocalDateTime.now(); + + // 检查当前日期和时间是否在指定的范围内 + return !currentDateTime.isBefore(start) && !currentDateTime.isAfter(end); + } + + + 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 phone, String code) throws CaptchaException { + if (StringUtils.isEmpty(code)) { + throw new CaptchaException("手机验证码不能为空"); + } + if (StringUtils.isEmpty(phone)) { + throw new CaptchaException("手机号不能为空"); + } + String verifyKey = CacheConstants.VERIFICATION_CODE + StringUtils.nvl(phone, ""); + String captcha = redisService.getCacheObject(verifyKey); + if (captcha == null) { + throw new CaptchaException("手机验证码已失效"); + } + + if (!code.equalsIgnoreCase(captcha)) { + throw new CaptchaException("手机验证码错误"); + }else { + redisService.deleteObject(verifyKey); + } + } + + +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhoneOtpLoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhoneOtpLoginStrategy.java new file mode 100644 index 00000000..3570a035 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhoneOtpLoginStrategy.java @@ -0,0 +1,49 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.config.SystemConfig; +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 SystemConfig systemConfig; + + @Resource + private RemoteUserService remoteUserService; + + @Resource + private PasswordValidatorService passwordValidatorService; + + + @Override + public LoginUser login(String phone, String otp) { + if (!systemConfig.getLoginConfig().isPhoneCode()) { + throw new ServiceException("用户不存在/验证码错误"); + } + passwordValidatorService.checkPhoneCaptcha(phone, otp); + R userResult = remoteUserService.getUserInfoByPhone(phone, SecurityConstants.INNER); + //验证用户是否存在 + passwordValidatorService.validateUserResult(phone, userResult); + LoginUser userInfo = userResult.getData(); + SysUser user = userInfo.getSysUser(); + passwordValidatorService.validateApprovalStatus(phone, user); + // 验证用户状态 + passwordValidatorService.validateUserStatus(phone, user); + + passwordValidatorService.processLoginBlackList(user); + //返回信息 + return userInfo; + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhonePasswordLoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhonePasswordLoginStrategy.java new file mode 100644 index 00000000..c580ed26 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/PhonePasswordLoginStrategy.java @@ -0,0 +1,60 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.config.SystemConfig; +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 SystemConfig systemConfig; + + @Resource + private RemoteUserService remoteUserService; + + @Resource + private PasswordValidatorService passwordValidatorService; + + @Resource + private SysPasswordService passwordService; + + @Override + public LoginUser login(String phone, String password) { + if (!systemConfig.getLoginConfig().isPhonePassword()) { + throw new ServiceException("用户不存在/密码错误"); + } + //通过手机号获取用户信息 + R userResult = remoteUserService.getUserInfoByPhone(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); + + passwordValidatorService.processLoginBlackList(user); + //返回信息 + return userInfo; + } +} + + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/RegisterVerificationCodeSender.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/RegisterVerificationCodeSender.java new file mode 100644 index 00000000..7ddf9159 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/RegisterVerificationCodeSender.java @@ -0,0 +1,86 @@ +package com.bonus.auth.service; + +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.domain.R; +import com.bonus.common.core.exception.ServiceException; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.common.security.service.EmailService; +import com.bonus.common.security.service.SmsService; +import com.bonus.config.SystemConfig; +import com.bonus.system.api.RemoteUserService; +import com.bonus.system.api.model.LoginUser; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * @author bonus + */ +@Service +public class RegisterVerificationCodeSender implements VerificationCodeStrategy { + + private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; + private static final String PHONE_REGEX = "^1[3-9]\\d{9}$"; + + @Resource + private EmailService emailService; + @Resource + private SmsService smsService; + @Resource + private SystemConfig systemConfig; + + @Resource + private RemoteUserService remoteUserService; + + /** + * 发送验证码到邮箱或手机 + * + * @param contactInfo 可以是邮箱地址或手机号码 + * @return 验证码发送的结果 + */ + @Override + public void sendVerificationCode(String contactInfo) { + if (isEmail(contactInfo)) { + if (!systemConfig.getRegistersConfig().isEmailRegisters()) { + throw new ServiceException("请输入正确的联系方式"); + } + R userResult = remoteUserService.getUserInfoByEmail(contactInfo , SecurityConstants.INNER); + if (userResult.getData() != null) { + throw new ServiceException("联系方式已经注册账号"); + } + emailService.sendSimpleEmail(contactInfo); + } else if (isPhone(contactInfo)) { + if (!systemConfig.getRegistersConfig().isPhoneRegisters()) { + throw new ServiceException("请输入正确的联系方式"); + } + R userResult = remoteUserService.getUserInfoByPhone(contactInfo, SecurityConstants.INNER); + if (userResult.getData() != null) { + throw new ServiceException("联系方式已经注册账号"); + } + smsService.sendSimplePhone(contactInfo); + } else { + throw new ServiceException("请输入正确的联系方式"); + } + } + + /** + * 检查是否是邮箱 + * + * @param contactInfo 输入信息 + * @return 是否为邮箱 + */ + private boolean isEmail(String contactInfo) { + return contactInfo.matches(EMAIL_REGEX); + } + + /** + * 检查是否是手机号 + * + * @param contactInfo 输入信息 + * @return 是否为手机号 + */ + private boolean isPhone(String contactInfo) { + return contactInfo.matches(PHONE_REGEX); + } +} + diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysLoginService.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysLoginService.java new file mode 100644 index 00000000..3e665523 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysLoginService.java @@ -0,0 +1,298 @@ +package com.bonus.auth.service; + +import com.alibaba.fastjson.JSONObject; +import com.bonus.auth.config.VerificationCodeType; +import com.bonus.auth.factory.VerificationCodeStrategyFactory; +import com.bonus.auth.form.RegisterBody; +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.exception.ServiceException; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.common.core.utils.encryption.Sm4Utils; +import com.bonus.common.core.web.domain.AjaxResult; +import com.bonus.common.security.utils.SecurityUtils; +import com.bonus.config.SystemConfig; +import com.bonus.system.api.RemoteConfigService; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +/** + * 登录校验方法 + * 提供登录、注册、验证码获取等服务 + */ +@Component +@Slf4j +public class SysLoginService { + @Resource + private RemoteUserService remoteUserService; + + + @Autowired + private SysRecordLogService recordLogService; + + + @Resource + private PasswordValidatorService passwordValidatorService; + + @Resource + private VerificationCodeStrategyFactory verificationCodeStrategyFactory; + @Autowired + private SystemConfig systemConfig; + + @Resource + private RemoteConfigService configService; + + /** + * 获取验证码 + * + * @param username 用户名或手机号 + * @param verificationCodeType 获取验证码类型(register:注册,其他:登录) + * @return 响应结果 + */ + public R getPhoneCode(String username, VerificationCodeType verificationCodeType) { + // 获取相应的登录策略 + VerificationCodeStrategy strategyFactory = verificationCodeStrategyFactory.getStrategy(verificationCodeType); + if (strategyFactory == null) { + return R.fail("不支持的方式"); + } + strategyFactory.sendVerificationCode(username); + return R.ok(); + } + + /** + * 用户登出 + * + * @param loginName 登录名 + * @param userId 用户ID + */ + public void logout(String loginName, String userId) { + try { + recordLogService.saveLogout(loginName, "成功", "成功", userId, "成功"); + } catch (Exception e) { + recordLogService.saveLogs(loginName, System.currentTimeMillis(), "登出异常", e.getMessage(), null, "失败"); + throw new ServiceException("退出失败,请稍后重试"); + } + } + + /** + * 用户注册 + * + * @param registerBody 注册信息 + */ + public void register(RegisterBody registerBody) { + long startTime = System.currentTimeMillis(); // 记录开始时间 + String result = convertAndAppend(registerBody.getNickName(), registerBody.getMobile()); + int contactType = getContactType(registerBody.getMobile()); + + if (contactType == 0) { + R userResult = remoteUserService.getUserInfoByEmail(registerBody.getMobile() , SecurityConstants.INNER); + if (userResult.getData() != null) { + throw new ServiceException("联系方式已经注册账号"); + } + } else if (contactType == 1) { + R userResult = remoteUserService.getUserInfoByPhone(registerBody.getMobile() , SecurityConstants.INNER); + if (userResult.getData() != null) { + throw new ServiceException("联系方式已经注册账号"); + } + }else { + throw new ServiceException("请输入正确的联系方式"); + } + registerBody.setUsername(result); + if (StringUtils.isAnyBlank(registerBody.getUsername(), registerBody.getPassword()) || + registerBody.getUsername().length() < UserConstants.USERNAME_MIN_LENGTH || + registerBody.getUsername().length() > UserConstants.USERNAME_MAX_LENGTH) { + recordLogService.saveLogs(registerBody.getUsername(), startTime, "注册参数无效", "账户或密码长度不符合要求", null, "失败"); + throw new ServiceException("账户或密码长度不符合要求"); + } + if (systemConfig.getRegistersConfig().isVerificationCode()){ + passwordValidatorService.checkPhoneCaptcha(registerBody.getMobile(),registerBody.getVerificationCode()); + } + 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()); + + if (systemConfig.getRegistersConfig().isApprovalStatus()){ + sysUser.setApprovalStatus("0"); + sysUser.setStatus("1"); + }else { + sysUser.setApprovalStatus("1"); + sysUser.setStatus("0"); + } + //有要求另加 + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + if (getContactType(registerBody.getMobile()) == 1) { + sysUser.setPhonenumber(registerBody.getMobile()); + } else { + sysUser.setEmail(registerBody.getMobile()); + } + + try { + R registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER); + if (R.FAIL == registerResult.getCode()) { + recordLogService.saveLogs(registerBody.getUsername(), startTime, "注册失败", registerResult.getMsg(), null, "失败"); + throw new ServiceException(registerResult.getMsg()); + } + + recordLogService.recordLogininfor(registerBody.getUsername(), Constants.REGISTER, "注册成功"); + } catch (Exception e) { + recordLogService.saveLogs(registerBody.getUsername(), startTime, "注册异常", e.getMessage(), null, "失败"); + throw new ServiceException("注册失败,请稍后重试"); + } + } + + /** + * 获取联系方式类型 + * + * @param contactInfo 联系方式 + * @return 联系方式类型(0:邮箱,1:手机号,2:无效) + */ + public static int getContactType(String contactInfo) { + String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; + String phoneRegex = "^1[3-9]\\d{9}$"; + if (contactInfo.matches(emailRegex)) { + return 0; + } else if (contactInfo.matches(phoneRegex)) { + return 1; + } else { + return 2; + } + } + + /** + * 将中文转换为拼音并拼接联系方式 + * + * @param chineseText 中文文本 + * @param contactInfo 联系方式 + * @return 拼接后的字符串 + */ + public static String convertAndAppend(String chineseText, String contactInfo) { + String pinyin = HanLP.convertToPinyinString(chineseText, "", true); + int contactType = getContactType(contactInfo); + + if (contactType == 0) { + return pinyin + "_" + contactInfo.substring(0, 4); + } else if (contactType == 1) { + return pinyin + "_" + contactInfo.substring(contactInfo.length() - 4); + } else { + return pinyin; + } + } + + /** + * i皖送Web端登录 + * @param ticket + * @param iwsWebAppId + * @param iwsWebUrl + * @return + */ + public void iwsWebLogin(String ticket, String iwsWebAppId, String iwsWebUrl,LoginUser loginUser,SysUser sysUser) { + Map paramMap = new HashMap<>(); + paramMap.put("ticket", ticket); + paramMap.put("appId", iwsWebAppId); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity authResponse = restTemplate.getForEntity(iwsWebUrl, String.class, paramMap); + log.info("authResponse:" + authResponse.toString()); + if ("200".equals(authResponse.getStatusCode())){ + // 根据ResponseEntity responseEntity对象,获取body部分,body为json格式字符串 + String content = authResponse.getBody(); + // 将json字符串转化为json对象 + JSONObject json = JSONObject.parseObject(content); + // 取出data部分对象 + JSONObject data = json.getJSONObject("data"); + sysUser.setUserName(data.get("userName").toString()); + sysUser.setNickName(data.get("name").toString()); + sysUser.setPhonenumber(data.get("mobile").toString()); + loginUser.setSysUser(sysUser); + createUser(sysUser,loginUser); + } + } + + /** + * 先查,如果没有进行创建 + * @param sysUser + * @param loginUser + */ + private void createUser(SysUser sysUser, LoginUser loginUser) { + //通过用户名获取人员信息 + R userResult = remoteUserService.getUserInfo(sysUser.getUserName(), SecurityConstants.INNER); + if (userResult.getData() == null || R.FAIL == userResult.getCode()) { + log.info("登录用户不存在,进行创建----"); + RegisterBody registerBody = new RegisterBody(); + registerBody.setUsername(sysUser.getUserName()); + registerBody.setNickName(sysUser.getNickName()); + registerBody.setMobile(sysUser.getPhonenumber()); + //获取配置中的初始密码 + AjaxResult result = configService.getConfigKey("sys.user.initPassword"); + if (result.isSuccess()) + { + sysUser.setPassword(result.get("msg").toString()); + } + //新用户注册 + try { + register(registerBody); + //查询用户信息 + userResult = remoteUserService.getUserInfo(sysUser.getUserName(), SecurityConstants.INNER); + loginUser = userResult.getData(); + sysUser = loginUser.getSysUser(); + //初始化一个角色 + Long[] roleIds = new Long[5]; + // 将数组的第一个元素赋值为 2 + roleIds[0] = 2L; + remoteUserService.insertAuthRole(sysUser.getUserId(),roleIds,SecurityConstants.INNER); + }catch (Exception e){ + throw new ServiceException("登录失败,请稍后重试"); + } + }else { + loginUser = userResult.getData(); + sysUser = loginUser.getSysUser(); + } + } + + /** + * i皖送H5端登录 + * @param ticket + * @param iwsH5AppId + * @param iwsH5Url + * @return + */ + public void iwsH5Login(String ticket, String iwsH5AppId, String iwsH5Url,LoginUser loginUser,SysUser sysUser) { + Map paramMap = new HashMap<>(); + paramMap.put("ticket", ticket); + paramMap.put("appId", iwsH5AppId); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity authResponse = restTemplate.getForEntity(iwsH5Url, String.class, paramMap); + log.info("authResponse:" + authResponse.toString()); + if ("200".equals(authResponse.getStatusCode())){ + // 根据ResponseEntity responseEntity对象,获取body部分,body为json格式字符串 + String content = authResponse.getBody(); + // 将json字符串转化为json对象 + JSONObject json = JSONObject.parseObject(content); + // 取出data部分对象 + JSONObject data = json.getJSONObject("data"); + JSONObject userInfo = data.getJSONObject("userInfo"); + sysUser.setUserName(userInfo.get("userName").toString()); + sysUser.setNickName(userInfo.get("name").toString()); + sysUser.setPhonenumber(userInfo.get("mobile").toString()); + loginUser.setSysUser(sysUser); + createUser(sysUser,loginUser); + } + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysPasswordService.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysPasswordService.java new file mode 100644 index 00000000..b2bac6cd --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysPasswordService.java @@ -0,0 +1,123 @@ +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.exception.ServiceException; +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.core.web.domain.AjaxResult; +import com.bonus.common.redis.service.RedisService; +import com.bonus.common.security.utils.SecurityUtils; +import com.bonus.system.api.RemoteConfigService; +import com.bonus.system.api.domain.SysUser; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * 登录密码方法 + * + * @author bonus + */ +@Component +public class SysPasswordService { + @Autowired + private RedisService redisService; + + private int maxRetryCount = CacheConstants.PASSWORD_MAX_RETRY_COUNT; + + private Long lockTime = CacheConstants.PASSWORD_LOCK_TIME; + + @Autowired + private SysRecordLogService recordLogService; + + @Resource + private RemoteConfigService configService; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) { + return CacheConstants.REDIS_KEY + username; + } + + public void validate(SysUser user, String password, long startTime) { + String username = user.getUserName(); + Integer retryCount = redisService.getCacheObject(getCacheKey(username)); + Integer times = 5; + Integer lockTime = 20; + AjaxResult timesAjaxResult = configService.getConfigKey("sys.login.failed.times"); + AjaxResult lockTimeAjaxResult = configService.getConfigKey("sys.login.failed.locktime"); + if (timesAjaxResult.isSuccess()){ + times = Integer.parseInt(timesAjaxResult.get("msg").toString()); + } + if (lockTimeAjaxResult.isSuccess()){ + lockTime = Integer.parseInt(lockTimeAjaxResult.get("msg").toString()); + } + + if (ObjectUtils.isEmpty(retryCount)){ + retryCount = 0; + } + if (retryCount >= times) { + long time = redisService.getExpire(getCacheKey(username)); + String errMsg = String.format("密码输入错误%s次,帐户锁定,请%s分钟后重试", maxRetryCount, time / 60 + 1); + recordLogService.saveErrorLogs( user.getUserName(), startTime,"", "连续登录失败,锁定账号" ); + throw new ServiceException(errMsg); + } + if (!matches(user, password)) { + retryCount = retryCount + 1; + recordLogService.saveLogs(username, startTime, "密码输入错误", "用户不存在/密码错误", null, null); + redisService.setCacheObject(getCacheKey(username), retryCount, (long)lockTime, TimeUnit.MINUTES); + throw new ServiceException("用户不存在/密码错误"); + } else { + clearLoginRecordCache(username); + } + } + public boolean matches(SysUser user, String rawPassword) { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) { + if (redisService.hasKey(getCacheKey(loginName))) { + redisService.deleteObject(getCacheKey(loginName)); + } + } + + /** + * 生成手机验证码 + * + * @return AjaxResult + * @throws IOException 输入输出异常 + * @throws CaptchaException 自定义captcha 异常 + */ + public R createPhoneCaptcha(String phone) { + if (StringUtils.isEmpty(phone)) { + throw new CaptchaException("手机号不能为空"); + } + String code = VerificationCodeUtils.generateVerificationCode(VerificationCodeUtils.CodeType.NUMERIC); + String str = "您的验证码为" + code + ",尊敬的客户,以上验证码3分钟有效,微服务平台提醒您:转发可能导致账号被盗,请勿将验证码泄露于他人"; + String verifyKey = CacheConstants.VERIFICATION_CODE + phone; + String s = SmsUtils.smsToken(phone, str, ""); + if (StringUtils.isNotEmpty(s)) { + if (s.contains("ok")) { + redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + } else { + throw new CaptchaException("获取短信失败"); + } + } else { + throw new CaptchaException("获取短信失败"); + } + + return R.ok(); + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysRecordLogService.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysRecordLogService.java new file mode 100644 index 00000000..38bd9f4e --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/SysRecordLogService.java @@ -0,0 +1,261 @@ +package com.bonus.auth.service; + +import com.alibaba.nacos.common.utils.UuidUtils; +import com.bonus.common.core.utils.DateUtils; +import com.bonus.common.core.utils.global.SystemGlobal; +import com.bonus.common.log.enums.OperaResult; +import com.bonus.common.log.enums.OperaType; +import com.bonus.config.SystemConfig; +import com.bonus.system.api.domain.SysLogsVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.bonus.common.core.constant.Constants; +import com.bonus.common.core.constant.SecurityConstants; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.common.core.utils.ip.IpUtils; +import com.bonus.system.api.RemoteLogService; +import com.bonus.system.api.domain.SysLogininfor; +import org.springframework.util.ObjectUtils; + +import java.util.UUID; + +/** + * 记录日志方法 + * + * @author bonus + */ +@Component +@Slf4j +public class SysRecordLogService +{ + @Autowired + private RemoteLogService remoteLogService; + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * @return + */ + public void recordLogininfor(String username, String status, String message) + { + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(IpUtils.getIpAddr()); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.LOGIN_FAIL_STATUS); + } + remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER); + } + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param + * @param message 消息内容 + * @return + */ + public void saveLogs(String username, long startTime, String message,String resultData,String userId,String result) { + long endTime = System.currentTimeMillis(); + SysLogsVo sysLogsVo = new SysLogsVo(); + String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase(); + sysLogsVo.setLogId(uuid); + sysLogsVo.setOperaUserName(username); + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setModel("系统认证模块"); + sysLogsVo.setOperTime(DateUtils.getTime()); + sysLogsVo.setMethodType(SystemGlobal.POST); + sysLogsVo.setMethod("login()"); + sysLogsVo.setParams("{\"username\":\""+username+"\"}"); + sysLogsVo.setOperateDetail("用户登录系统"); + sysLogsVo.setOperType(OperaType.LOGIN); + sysLogsVo.setOperUri("/login"); + sysLogsVo.setLogType(0); + if (StringUtils.isNotEmpty(result)){ + sysLogsVo.setResult(result); + }else{ + sysLogsVo.setResult(OperaResult.FAIL); + } + if (StringUtils.isNotEmpty(userId)){ + sysLogsVo.setUserId(userId); + } + sysLogsVo.setFailureReason(message); + sysLogsVo.setTitle("系统登录"); + sysLogsVo.setResultData(resultData); + try{ + long times=endTime-startTime; + sysLogsVo.setTimes(times+""); + remoteLogService.addLogs(sysLogsVo, SecurityConstants.INNER); + }catch (Exception e){ + log.error(e.toString(),e); + } + } + /** + * 记录IP异常信息 + * + * @param username 用户名 + * @param + * @param + * @return + */ + public void saveErrorLogs(String username, long startTime,String userId, String errMessage) { + long endTime = System.currentTimeMillis(); + SysLogsVo sysLogsVo = new SysLogsVo(); + sysLogsVo.setGrade("高"); + String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase(); + sysLogsVo.setOperType("登录"); + sysLogsVo.setOperUri("/login"); + sysLogsVo.setLogType(2); + sysLogsVo.setResult(OperaResult.SUCCESS); + if (StringUtils.isNotEmpty(userId)){ + sysLogsVo.setUserId(userId); + } + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setResultData("用户登录成功"); + sysLogsVo.setTitle("系统登录"); + sysLogsVo.setModel("系统认证模块"); + sysLogsVo.setOperTime(DateUtils.getTime()); + sysLogsVo.setMethodType(SystemGlobal.POST); + sysLogsVo.setMethod("login()"); + sysLogsVo.setLogId(uuid); + sysLogsVo.setOperaUserName(username); + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setParams("{\"username\":\""+username+"\"}"); + sysLogsVo.setOperateDetail("用户登录系统"); + sysLogsVo.setErrType(errMessage); + try{ + if(startTime != 0) { + long times = endTime - startTime; + sysLogsVo.setTimes(times + ""); + } + remoteLogService.addLogs(sysLogsVo, SecurityConstants.INNER); + }catch (Exception e){ + log.error(e.toString(),e); + } + } + /** + * 记录登出信息 + * + * @param username 用户名 + * @param + * @param message 消息内容 + * @return + */ + public void saveLogout(String username, String message,String resultData,String userId,String result) { + SysLogsVo sysLogsVo = new SysLogsVo(); + String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase(); + sysLogsVo.setLogId(uuid); + sysLogsVo.setOperaUserName(username); + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setModel("系统认证模块"); + sysLogsVo.setLogType(0); + if (StringUtils.isNotEmpty(userId)){ + sysLogsVo.setUserId(userId); + } + sysLogsVo.setOperTime(DateUtils.getTime()); + sysLogsVo.setMethodType(SystemGlobal.POST); + sysLogsVo.setMethod("logout()"); + sysLogsVo.setParams("{\"username\":\""+username+"\"}"); + sysLogsVo.setOperateDetail("用户退出登录"); + sysLogsVo.setOperType("登出"); + sysLogsVo.setOperUri("/logout"); + if (StringUtils.isNotEmpty(result)){ + sysLogsVo.setResult(result); + }else{ + sysLogsVo.setResult(OperaResult.SUCCESS); + } + sysLogsVo.setFailureReason(message); + sysLogsVo.setTitle("退出登录"); + sysLogsVo.setResultData(resultData); + try{ + remoteLogService.addLogs(sysLogsVo, SecurityConstants.INNER); + }catch (Exception e){ + log.error(e.toString(),e); + } + } + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param + * @param message 消息内容 + * @return + */ + public void saveLogin(String username, String message,String resultData,String userId,String result) { + SysLogsVo sysLogsVo = new SysLogsVo(); + String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase(); + sysLogsVo.setLogId(uuid); + sysLogsVo.setOperaUserName(username); + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setModel("系统认证模块"); + sysLogsVo.setLogType(0); + if (StringUtils.isNotEmpty(userId)){ + sysLogsVo.setUserId(userId); + } + sysLogsVo.setOperTime(DateUtils.getTime()); + sysLogsVo.setMethodType(SystemGlobal.POST); + sysLogsVo.setMethod("login()"); + sysLogsVo.setParams("{\"username\":\""+username+"\"}"); + sysLogsVo.setOperateDetail("用户登录"); + sysLogsVo.setOperType(OperaType.LOGIN); + sysLogsVo.setOperUri("/login"); + if (StringUtils.isNotEmpty(result)){ + sysLogsVo.setResult(result); + }else{ + sysLogsVo.setResult(OperaResult.SUCCESS); + } + sysLogsVo.setFailureReason(message); + sysLogsVo.setTitle("登录"); + sysLogsVo.setResultData(resultData); + try{ + remoteLogService.addLogs(sysLogsVo, SecurityConstants.INNER); + }catch (Exception e){ + log.error(e.toString(),e); + } + } + + public void saveRegister(String username, String message,String resultData,String userId,String result) { + SysLogsVo sysLogsVo = new SysLogsVo(); + String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase(); + sysLogsVo.setLogId(uuid); + sysLogsVo.setOperaUserName(username); + sysLogsVo.setIp(IpUtils.getIpAddr()); + sysLogsVo.setModel("系统认证模块"); + sysLogsVo.setLogType(0); + if (StringUtils.isNotEmpty(userId)){ + sysLogsVo.setUserId(userId); + } + sysLogsVo.setOperTime(DateUtils.getTime()); + sysLogsVo.setMethodType(SystemGlobal.POST); + sysLogsVo.setMethod("register()"); + sysLogsVo.setParams("{\"username\":\""+username+"\"}"); + sysLogsVo.setOperateDetail("用户注册"); + sysLogsVo.setOperType(OperaType.REGISTER); + sysLogsVo.setOperUri("/register"); + if (StringUtils.isNotEmpty(result)){ + sysLogsVo.setResult(result); + }else{ + sysLogsVo.setResult(OperaResult.SUCCESS); + } + sysLogsVo.setFailureReason(message); + sysLogsVo.setTitle("注册"); + sysLogsVo.setResultData(resultData); + try{ + remoteLogService.addLogs(sysLogsVo, SecurityConstants.INNER); + }catch (Exception e){ + log.error(e.toString(),e); + } + } +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/UsernamePasswordLoginStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/UsernamePasswordLoginStrategy.java new file mode 100644 index 00000000..00a66b43 --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/UsernamePasswordLoginStrategy.java @@ -0,0 +1,59 @@ +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 userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); + passwordValidatorService.validateUserResult(username, userResult); + //获取用户信息 + LoginUser userInfo = userResult.getData(); + SysUser user = userInfo.getSysUser(); + passwordValidatorService.validateApprovalStatus(username, user); + // 验证用户状态 + passwordValidatorService.validateUserStatus(username, user); + // 验证密码 + passwordService.validate(user, password, System.currentTimeMillis()); + // 处理IP校验 + passwordValidatorService.handleIpValidation(username, user); + + passwordValidatorService.processLoginBlackList(user); + //返回信息 + return userInfo; + } + +} diff --git a/bonus-cust-auth/src/main/java/com/bonus/auth/service/VerificationCodeStrategy.java b/bonus-cust-auth/src/main/java/com/bonus/auth/service/VerificationCodeStrategy.java new file mode 100644 index 00000000..5daad7cf --- /dev/null +++ b/bonus-cust-auth/src/main/java/com/bonus/auth/service/VerificationCodeStrategy.java @@ -0,0 +1,8 @@ +package com.bonus.auth.service; + +public interface VerificationCodeStrategy { + /** + * @param contactInfo 可以是邮箱地址或手机号码 + */ + void sendVerificationCode(String contactInfo); +} diff --git a/bonus-cust-auth/src/main/resources/banner.txt b/bonus-cust-auth/src/main/resources/banner.txt new file mode 100644 index 00000000..591c4518 --- /dev/null +++ b/bonus-cust-auth/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} \ No newline at end of file diff --git a/bonus-cust-auth/src/main/resources/bootstrap-smart_canteen_local.yml b/bonus-cust-auth/src/main/resources/bootstrap-smart_canteen_local.yml new file mode 100644 index 00000000..c2f45d34 --- /dev/null +++ b/bonus-cust-auth/src/main/resources/bootstrap-smart_canteen_local.yml @@ -0,0 +1,27 @@ +# Tomcat +server: + port: 58079 + +# Spring +spring: + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8848 + namespace: smart_canteen + config: + # 配置中心地址 + server-addr: 127.0.0.1:8848 + namespace: smart_canteen + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + +#加密组件 +jasypt: + encryptor: + password: Encrypt + diff --git a/bonus-cust-auth/src/main/resources/bootstrap.yml b/bonus-cust-auth/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..755a5526 --- /dev/null +++ b/bonus-cust-auth/src/main/resources/bootstrap.yml @@ -0,0 +1,14 @@ +# Spring +spring: + application: + # 应用名称 + name: bonus-cust-auth + profiles: + # 环境配置 + active: smart_canteen_local + +#加密组件 +jasypt: + encryptor: + password: Encrypt + diff --git a/bonus-cust-auth/src/main/resources/logback.xml b/bonus-cust-auth/src/main/resources/logback.xml new file mode 100644 index 00000000..c3048261 --- /dev/null +++ b/bonus-cust-auth/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index bdfa6560..6788d7a8 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,7 @@ bonus-modules bonus-common-biz + bonus-cust-auth pom