From 7e13c6ef0339a03f555438f2643a784e21cf41c1 Mon Sep 17 00:00:00 2001 From: fengshuonan Date: Sat, 17 Jun 2023 23:50:19 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=907.6.0=E3=80=91=E3=80=90sys=E3=80=91?= =?UTF-8?q?=E3=80=90auth=E3=80=91=E6=95=B4=E7=90=86=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernel/auth/auth/AuthServiceImpl.java | 269 +--------------- .../roses/kernel/auth/auth/LoginService.java | 304 ++++++++++++++++++ 2 files changed, 310 insertions(+), 263 deletions(-) create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginService.java diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java index 57c54f0b3..7f02e47dd 100644 --- a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java @@ -25,36 +25,23 @@ package cn.stylefeng.roses.kernel.auth.auth; import cn.hutool.core.codec.Base64; -import cn.hutool.core.convert.Convert; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.AES; -import cn.hutool.extra.spring.SpringUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; import cn.stylefeng.roses.kernel.auth.api.AuthServiceApi; import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; -import cn.stylefeng.roses.kernel.auth.api.SsoServerApi; -import cn.stylefeng.roses.kernel.auth.api.TempSecretApi; -import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; -import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; import cn.stylefeng.roses.kernel.auth.api.context.AuthJwtContext; import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; -import cn.stylefeng.roses.kernel.auth.api.enums.SsoClientTypeEnum; import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; -import cn.stylefeng.roses.kernel.auth.api.password.PasswordStoredEncryptApi; -import cn.stylefeng.roses.kernel.auth.api.password.PasswordTransferEncryptApi; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginWithTokenRequest; import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; import cn.stylefeng.roses.kernel.auth.api.pojo.payload.DefaultJwtPayload; -import cn.stylefeng.roses.kernel.auth.api.pojo.sso.SsoLoginCodeRequest; -import cn.stylefeng.roses.kernel.auth.api.pojo.sso.SsoProperties; import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; import cn.stylefeng.roses.kernel.demo.expander.DemoConfigExpander; import cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants; @@ -66,18 +53,8 @@ import cn.stylefeng.roses.kernel.jwt.api.exception.enums.JwtExceptionEnum; import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi; import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; -import cn.stylefeng.roses.kernel.rule.tenant.RequestTenantCodeHolder; -import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; -import cn.stylefeng.roses.kernel.scanner.api.exception.ScannerException; -import cn.stylefeng.roses.kernel.scanner.api.exception.enums.ScannerExceptionEnum; -import cn.stylefeng.roses.kernel.scanner.api.holder.InitScanFlagHolder; -import cn.stylefeng.roses.kernel.security.api.DragCaptchaApi; -import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi; -import cn.stylefeng.roses.kernel.security.api.expander.SecurityConfigExpander; import cn.stylefeng.roses.kernel.sys.api.SysUserServiceApi; -import cn.stylefeng.roses.kernel.sys.api.enums.user.UserStatusEnum; import cn.stylefeng.roses.kernel.sys.api.pojo.user.UserValidateDTO; -import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; @@ -103,24 +80,9 @@ public class AuthServiceImpl implements AuthServiceApi { @Resource private SessionManagerApi sessionManagerApi; - @Resource - private PasswordStoredEncryptApi passwordStoredEncryptApi; - - @Resource - private PasswordTransferEncryptApi passwordTransferEncryptApi; - @Resource private LoginLogServiceApi loginLogServiceApi; - @Resource - private ImageCaptchaApi captchaApi; - - @Resource - private DragCaptchaApi dragCaptchaApi; - - @Resource - private SsoProperties ssoProperties; - @Resource(name = "loginErrorCountCacheApi") private CacheOperatorApi loginErrorCountCacheApi; @@ -130,23 +92,26 @@ public class AuthServiceImpl implements AuthServiceApi { @Resource private JwtApi jwtApi; + @Resource + private LoginService loginService; + @Override public LoginResponse login(LoginRequest loginRequest) { - return loginAction(loginRequest, true, null); + return loginService.loginAction(loginRequest, true, null); } @Override public LoginResponse loginWithUserName(String username) { LoginRequest loginRequest = new LoginRequest(); loginRequest.setAccount(username); - return loginAction(loginRequest, false, null); + return loginService.loginAction(loginRequest, false, null); } @Override public LoginResponse loginWithUserNameAndCaToken(String username, String caToken) { LoginRequest loginRequest = new LoginRequest(); loginRequest.setAccount(username); - return loginAction(loginRequest, false, caToken); + return loginService.loginAction(loginRequest, false, caToken); } @Override @@ -265,7 +230,6 @@ public class AuthServiceImpl implements AuthServiceApi { // 2. 校验用户token是否正确,校验失败会抛出异常 this.validateToken(token); - } @Override @@ -305,225 +269,4 @@ public class AuthServiceImpl implements AuthServiceApi { return loginUser; } - /** - * 登录的真正业务逻辑 - * - * @param loginRequest 登录参数 - * @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码 - * @param caToken 单点登录后服务端的token,一般为32位uuid - * @author fengshuonan - * @since 2020/10/21 16:59 - */ - private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword, String caToken) { - // 1.参数为空校验 - if (validatePassword) { - if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount(), loginRequest.getPassword())) { - throw new AuthException(AuthExceptionEnum.PARAM_EMPTY); - } - } else { - if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount())) { - throw new AuthException(AuthExceptionEnum.ACCOUNT_IS_BLANK); - } - } - - // 1.2 判断账号是否密码重试次数过多被冻结 - Integer loginErrorCount = loginErrorCountCacheApi.get(loginRequest.getAccount()); - if (loginErrorCount != null && loginErrorCount >= LoginCacheConstants.MAX_ERROR_LOGIN_COUNT) { - throw new AuthException(AuthExceptionEnum.LOGIN_LOCKED); - } - - // 1.3 暂存多租户编码(v7.3.2增加,方便缓存调用过程中获取多租户的前缀) - RequestTenantCodeHolder.setTenantCode(loginRequest.getTenantCode()); - - // 2. 如果开启了验证码校验,则验证当前请求的验证码是否正确 - if (SecurityConfigExpander.getCaptchaOpen()) { - String verKey = loginRequest.getVerKey(); - String verCode = loginRequest.getVerCode(); - - if (StrUtil.isEmpty(verKey) || StrUtil.isEmpty(verCode)) { - throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); - } - if (!captchaApi.validateCaptcha(verKey, verCode)) { - throw new AuthException(ValidatorExceptionEnum.CAPTCHA_ERROR); - } - } - - // 2.1 验证拖拽验证码 - if (SecurityConfigExpander.getDragCaptchaOpen()) { - String verKey = loginRequest.getVerKey(); - String verXLocationValue = loginRequest.getVerCode(); - - if (StrUtil.isEmpty(verKey) || StrUtil.isEmpty(verXLocationValue)) { - throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); - } - if (!dragCaptchaApi.validateCaptcha(verKey, Convert.toInt(verXLocationValue))) { - throw new AuthException(ValidatorExceptionEnum.DRAG_CAPTCHA_ERROR); - } - } - - // 2.2 校验当前系统是否初始化资源完成,如果资源还没有初始化,提示用户请等一下再登录 - if (!InitScanFlagHolder.getFlag()) { - throw new ScannerException(ScannerExceptionEnum.SYSTEM_RESOURCE_URL_NOT_INIT); - } - - // 3. 解密密码的密文,需要sys_config相关配置打开 - if (loginRequest.getPassword() != null && AuthConfigExpander.getPasswordRsaValidateFlag()) { - String decryptPassword = passwordTransferEncryptApi.decrypt(loginRequest.getPassword()); - loginRequest.setPassword(decryptPassword); - } - - // 4. 如果开启了单点登录,并且CaToken没有值,走单点登录,获取loginCode - if (ssoProperties.getOpenFlag() && StrUtil.isEmpty(caToken)) { - if (SsoClientTypeEnum.client.name().equals(ssoProperties.getSsoClientType())) { - // 调用单点的接口获取loginCode,远程接口校验用户级密码正确性。 - String remoteLoginCode = getRemoteLoginCode(loginRequest); - return new LoginResponse(remoteLoginCode); - } else { - // 如果当前系统是单点服务端 - SsoServerApi ssoServerApi = SpringUtil.getBean(SsoServerApi.class); - SsoLoginCodeRequest ssoLoginCodeRequest = new SsoLoginCodeRequest(); - ssoLoginCodeRequest.setAccount(loginRequest.getAccount()); - ssoLoginCodeRequest.setPassword(loginRequest.getPassword()); - String remoteLoginCode = ssoServerApi.createSsoLoginCode(ssoLoginCodeRequest); - return new LoginResponse(remoteLoginCode); - } - } - - // 5. 获取用户密码的加密值和用户的状态 - UserValidateDTO userValidateInfo = sysUserServiceApi.getUserLoginValidateDTO(loginRequest.getAccount()); - - // 6. 校验用户密码是否正确 - validateUserPassword(validatePassword, loginErrorCount, loginRequest, userValidateInfo); - - // 7. 校验用户是否异常(不是正常状态) - if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) { - throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus())); - } - - // 8. 生成用户的token - DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(userValidateInfo.getUserId(), loginRequest.getAccount(), - loginRequest.getRememberMe(), caToken, loginRequest.getTenantCode()); - String jwtToken = AuthJwtContext.me().generateTokenDefaultPayload(defaultJwtPayload); - - // 9. 创建loginUser对象 - LoginUser loginUser = new LoginUser(userValidateInfo.getUserId(), jwtToken); - - synchronized (loginRequest.getAccount().intern()) { - - // 10. 缓存用户信息,创建会话 - sessionManagerApi.createSession(jwtToken, loginUser); - - // 11. 如果开启了单账号单端在线,则踢掉已经上线的该用户 - if (AuthConfigExpander.getSingleAccountLoginFlag()) { - sessionManagerApi.removeSessionExcludeToken(jwtToken); - } - } - - // 演示环境,跳过记录日志 - if (!DemoConfigExpander.getDemoEnvFlag()) { - // 12. 更新用户登录时间和ip - String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest()); - sysUserServiceApi.updateUserLoginInfo(loginUser.getUserId(), ip); - - // 13.登录成功日志 - loginLogServiceApi.loginSuccess(loginUser.getUserId()); - } - - // 13.1 登录成功,清空用户的错误登录次数 - this.cancelFreeze(loginRequest); - - // 14. 组装返回结果 - return new LoginResponse(loginUser.getUserId(), jwtToken); - } - - /** - * 调用远程接口获取loginCode - * - * @author fengshuonan - * @since 2021/2/26 15:15 - */ - private String getRemoteLoginCode(LoginRequest loginRequest) { - - // 获取sso的地址 - String ssoUrl = AuthConfigExpander.getSsoUrl(); - - // 请求sso服务获取loginCode - HttpRequest httpRequest = HttpRequest.post(ssoUrl + AuthConstants.SYS_AUTH_SSO_GET_LOGIN_CODE); - httpRequest.body(JSON.toJSONString(loginRequest)); - HttpResponse httpResponse = httpRequest.execute(); - - // 获取返回结果的message - String body = httpResponse.body(); - JSONObject jsonObject = new JSONObject(); - if (StrUtil.isNotBlank(body)) { - jsonObject = JSON.parseObject(body); - } - - // 如果返回结果是失败的 - if (httpResponse.getStatus() != 200) { - String message = jsonObject.getString("message"); - throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, message); - } - - // 从body中获取loginCode - String loginCode = jsonObject.getString("data"); - - // loginCode为空 - if (loginCode == null) { - throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, "loginCode为空"); - } - - return loginCode; - } - - /** - * 用户密码校验,校验失败会报异常 - * - * @author fengshuonan - * @since 2022/3/26 14:16 - */ - private void validateUserPassword(Boolean validatePassword, Integer loginErrorCount, LoginRequest loginRequest, - UserValidateDTO userValidateInfo) { - - // 如果本次登录需要校验密码 - if (validatePassword) { - Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), - userValidateInfo.getUserPasswordHexed()); - - // 校验用户表密码是否正确,如果正确则直接返回 - if (checkResult) { - return; - } - - // 如果密码不正确则校验用户是否有临时秘钥 - TempSecretApi tempSecretApi = null; - try { - tempSecretApi = SpringUtil.getBean(TempSecretApi.class); - if (tempSecretApi != null) { - String userTempSecretKey = tempSecretApi.getUserTempSecretKey(userValidateInfo.getUserId()); - // 如果用户有临时秘钥,则校验秘钥是否正确 - if (StrUtil.isNotBlank(userTempSecretKey)) { - Boolean checkTempKeyResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userTempSecretKey); - if (checkTempKeyResult) { - return; - } - } - } - } catch (Exception ignored) { - } - - // 临时秘钥校验完成,返回前端用户密码错误 - if (loginErrorCount == null) { - loginErrorCount = 0; - } - - // 演示环境,不记录error次数 - if (!DemoConfigExpander.getDemoEnvFlag()) { - loginErrorCountCacheApi.put(loginRequest.getAccount(), loginErrorCount + 1); - } - - throw new AuthException(AuthExceptionEnum.USERNAME_PASSWORD_ERROR); - } - } - } diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginService.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginService.java new file mode 100644 index 000000000..ed83add5d --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginService.java @@ -0,0 +1,304 @@ +package cn.stylefeng.roses.kernel.auth.auth; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.SsoServerApi; +import cn.stylefeng.roses.kernel.auth.api.TempSecretApi; +import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; +import cn.stylefeng.roses.kernel.auth.api.constants.LoginCacheConstants; +import cn.stylefeng.roses.kernel.auth.api.context.AuthJwtContext; +import cn.stylefeng.roses.kernel.auth.api.enums.SsoClientTypeEnum; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.auth.api.password.PasswordStoredEncryptApi; +import cn.stylefeng.roses.kernel.auth.api.password.PasswordTransferEncryptApi; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.auth.api.pojo.payload.DefaultJwtPayload; +import cn.stylefeng.roses.kernel.auth.api.pojo.sso.SsoLoginCodeRequest; +import cn.stylefeng.roses.kernel.auth.api.pojo.sso.SsoProperties; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import cn.stylefeng.roses.kernel.demo.expander.DemoConfigExpander; +import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi; +import cn.stylefeng.roses.kernel.rule.tenant.RequestTenantCodeHolder; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; +import cn.stylefeng.roses.kernel.scanner.api.exception.ScannerException; +import cn.stylefeng.roses.kernel.scanner.api.exception.enums.ScannerExceptionEnum; +import cn.stylefeng.roses.kernel.scanner.api.holder.InitScanFlagHolder; +import cn.stylefeng.roses.kernel.security.api.DragCaptchaApi; +import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi; +import cn.stylefeng.roses.kernel.security.api.expander.SecurityConfigExpander; +import cn.stylefeng.roses.kernel.sys.api.SysUserServiceApi; +import cn.stylefeng.roses.kernel.sys.api.enums.user.UserStatusEnum; +import cn.stylefeng.roses.kernel.sys.api.pojo.user.UserValidateDTO; +import cn.stylefeng.roses.kernel.validator.api.exception.enums.ValidatorExceptionEnum; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 登录相关的逻辑封装 + * + * @author fengshuonan + * @since 2023/6/17 23:45 + */ +@Service +public class LoginService { + + @Resource + private SysUserServiceApi sysUserServiceApi; + + @Resource + private SessionManagerApi sessionManagerApi; + + @Resource + private PasswordTransferEncryptApi passwordTransferEncryptApi; + + @Resource + private ImageCaptchaApi captchaApi; + + @Resource + private DragCaptchaApi dragCaptchaApi; + + @Resource + private SsoProperties ssoProperties; + + @Resource + private LoginLogServiceApi loginLogServiceApi; + + @Resource(name = "loginErrorCountCacheApi") + private CacheOperatorApi loginErrorCountCacheApi; + + @Resource + private PasswordStoredEncryptApi passwordStoredEncryptApi; + + /** + * 登录的真正业务逻辑 + * + * @param loginRequest 登录参数 + * @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码 + * @param caToken 单点登录后服务端的token,一般为32位uuid + * @author fengshuonan + * @since 2020/10/21 16:59 + */ + public LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword, String caToken) { + // 1.参数为空校验 + if (validatePassword) { + if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount(), loginRequest.getPassword())) { + throw new AuthException(AuthExceptionEnum.PARAM_EMPTY); + } + } else { + if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount())) { + throw new AuthException(AuthExceptionEnum.ACCOUNT_IS_BLANK); + } + } + + // 1.2 判断账号是否密码重试次数过多被冻结 + Integer loginErrorCount = loginErrorCountCacheApi.get(loginRequest.getAccount()); + if (loginErrorCount != null && loginErrorCount >= LoginCacheConstants.MAX_ERROR_LOGIN_COUNT) { + throw new AuthException(AuthExceptionEnum.LOGIN_LOCKED); + } + + // 1.3 暂存多租户编码(v7.3.2增加,方便缓存调用过程中获取多租户的前缀) + RequestTenantCodeHolder.setTenantCode(loginRequest.getTenantCode()); + + // 2. 如果开启了验证码校验,则验证当前请求的验证码是否正确 + if (SecurityConfigExpander.getCaptchaOpen()) { + String verKey = loginRequest.getVerKey(); + String verCode = loginRequest.getVerCode(); + + if (StrUtil.isEmpty(verKey) || StrUtil.isEmpty(verCode)) { + throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); + } + if (!captchaApi.validateCaptcha(verKey, verCode)) { + throw new AuthException(ValidatorExceptionEnum.CAPTCHA_ERROR); + } + } + + // 2.1 验证拖拽验证码 + if (SecurityConfigExpander.getDragCaptchaOpen()) { + String verKey = loginRequest.getVerKey(); + String verXLocationValue = loginRequest.getVerCode(); + + if (StrUtil.isEmpty(verKey) || StrUtil.isEmpty(verXLocationValue)) { + throw new AuthException(ValidatorExceptionEnum.CAPTCHA_EMPTY); + } + if (!dragCaptchaApi.validateCaptcha(verKey, Convert.toInt(verXLocationValue))) { + throw new AuthException(ValidatorExceptionEnum.DRAG_CAPTCHA_ERROR); + } + } + + // 2.2 校验当前系统是否初始化资源完成,如果资源还没有初始化,提示用户请等一下再登录 + if (!InitScanFlagHolder.getFlag()) { + throw new ScannerException(ScannerExceptionEnum.SYSTEM_RESOURCE_URL_NOT_INIT); + } + + // 3. 解密密码的密文,需要sys_config相关配置打开 + if (loginRequest.getPassword() != null && AuthConfigExpander.getPasswordRsaValidateFlag()) { + String decryptPassword = passwordTransferEncryptApi.decrypt(loginRequest.getPassword()); + loginRequest.setPassword(decryptPassword); + } + + // 4. 如果开启了单点登录,并且CaToken没有值,走单点登录,获取loginCode + if (ssoProperties.getOpenFlag() && StrUtil.isEmpty(caToken)) { + if (SsoClientTypeEnum.client.name().equals(ssoProperties.getSsoClientType())) { + // 调用单点的接口获取loginCode,远程接口校验用户级密码正确性。 + String remoteLoginCode = getRemoteLoginCode(loginRequest); + return new LoginResponse(remoteLoginCode); + } else { + // 如果当前系统是单点服务端 + SsoServerApi ssoServerApi = SpringUtil.getBean(SsoServerApi.class); + SsoLoginCodeRequest ssoLoginCodeRequest = new SsoLoginCodeRequest(); + ssoLoginCodeRequest.setAccount(loginRequest.getAccount()); + ssoLoginCodeRequest.setPassword(loginRequest.getPassword()); + String remoteLoginCode = ssoServerApi.createSsoLoginCode(ssoLoginCodeRequest); + return new LoginResponse(remoteLoginCode); + } + } + + // 5. 获取用户密码的加密值和用户的状态 + UserValidateDTO userValidateInfo = sysUserServiceApi.getUserLoginValidateDTO(loginRequest.getAccount()); + + // 6. 校验用户密码是否正确 + validateUserPassword(validatePassword, loginErrorCount, loginRequest, userValidateInfo); + + // 7. 校验用户是否异常(不是正常状态) + if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) { + throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR, UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus())); + } + + // 8. 生成用户的token + DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(userValidateInfo.getUserId(), loginRequest.getAccount(), + loginRequest.getRememberMe(), caToken, loginRequest.getTenantCode()); + String jwtToken = AuthJwtContext.me().generateTokenDefaultPayload(defaultJwtPayload); + + // 9. 创建loginUser对象 + LoginUser loginUser = new LoginUser(userValidateInfo.getUserId(), jwtToken); + + synchronized (loginRequest.getAccount().intern()) { + + // 10. 缓存用户信息,创建会话 + sessionManagerApi.createSession(jwtToken, loginUser); + + // 11. 如果开启了单账号单端在线,则踢掉已经上线的该用户 + if (AuthConfigExpander.getSingleAccountLoginFlag()) { + sessionManagerApi.removeSessionExcludeToken(jwtToken); + } + } + + // 演示环境,跳过记录日志 + if (!DemoConfigExpander.getDemoEnvFlag()) { + // 12. 更新用户登录时间和ip + String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest()); + sysUserServiceApi.updateUserLoginInfo(loginUser.getUserId(), ip); + + // 13.登录成功日志 + loginLogServiceApi.loginSuccess(loginUser.getUserId()); + } + + // 13.1 登录成功,清空用户的错误登录次数 + loginErrorCountCacheApi.remove(loginRequest.getAccount()); + + // 14. 组装返回结果 + return new LoginResponse(loginUser.getUserId(), jwtToken); + } + + /** + * 调用远程接口获取loginCode + * + * @author fengshuonan + * @since 2021/2/26 15:15 + */ + private String getRemoteLoginCode(LoginRequest loginRequest) { + + // 获取sso的地址 + String ssoUrl = AuthConfigExpander.getSsoUrl(); + + // 请求sso服务获取loginCode + HttpRequest httpRequest = HttpRequest.post(ssoUrl + AuthConstants.SYS_AUTH_SSO_GET_LOGIN_CODE); + httpRequest.body(JSON.toJSONString(loginRequest)); + HttpResponse httpResponse = httpRequest.execute(); + + // 获取返回结果的message + String body = httpResponse.body(); + JSONObject jsonObject = new JSONObject(); + if (StrUtil.isNotBlank(body)) { + jsonObject = JSON.parseObject(body); + } + + // 如果返回结果是失败的 + if (httpResponse.getStatus() != 200) { + String message = jsonObject.getString("message"); + throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, message); + } + + // 从body中获取loginCode + String loginCode = jsonObject.getString("data"); + + // loginCode为空 + if (loginCode == null) { + throw new AuthException(AuthExceptionEnum.SSO_LOGIN_CODE_GET_ERROR, "loginCode为空"); + } + + return loginCode; + } + + /** + * 用户密码校验,校验失败会报异常 + * + * @author fengshuonan + * @since 2022/3/26 14:16 + */ + private void validateUserPassword(Boolean validatePassword, Integer loginErrorCount, LoginRequest loginRequest, + UserValidateDTO userValidateInfo) { + + // 如果本次登录需要校验密码 + if (validatePassword) { + Boolean checkResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), + userValidateInfo.getUserPasswordHexed()); + + // 校验用户表密码是否正确,如果正确则直接返回 + if (checkResult) { + return; + } + + // 如果密码不正确则校验用户是否有临时秘钥 + TempSecretApi tempSecretApi = null; + try { + tempSecretApi = SpringUtil.getBean(TempSecretApi.class); + if (tempSecretApi != null) { + String userTempSecretKey = tempSecretApi.getUserTempSecretKey(userValidateInfo.getUserId()); + // 如果用户有临时秘钥,则校验秘钥是否正确 + if (StrUtil.isNotBlank(userTempSecretKey)) { + Boolean checkTempKeyResult = passwordStoredEncryptApi.checkPassword(loginRequest.getPassword(), userTempSecretKey); + if (checkTempKeyResult) { + return; + } + } + } + } catch (Exception ignored) { + } + + // 临时秘钥校验完成,返回前端用户密码错误 + if (loginErrorCount == null) { + loginErrorCount = 0; + } + + // 演示环境,不记录error次数 + if (!DemoConfigExpander.getDemoEnvFlag()) { + loginErrorCountCacheApi.put(loginRequest.getAccount(), loginErrorCount + 1); + } + + throw new AuthException(AuthExceptionEnum.USERNAME_PASSWORD_ERROR); + } + } + +}