package com.ruoyi.framework.security.service; import javax.annotation.Resource; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.project.system.mapper.SysUserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.user.BlackListException; import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.CaptchaExpireException; import com.ruoyi.common.exception.user.UserNotExistsException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.redis.RedisCache; import com.ruoyi.framework.security.LoginUser; import com.ruoyi.framework.security.context.AuthenticationContextHolder; import com.ruoyi.project.system.domain.SysUser; import com.ruoyi.project.system.service.ISysConfigService; import com.ruoyi.project.system.service.ISysUserService; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 登录校验方法 * * @author ruoyi */ @Component public class SysLoginService { private static final Logger log = LoggerFactory.getLogger(SysLoginService.class); @Autowired private TokenService tokenService; @Autowired private UserDetailsServiceImpl userDetailsService; @Resource private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Autowired private ISysUserService userService; @Autowired private ISysConfigService configService; /** * 登录验证 * * @param username 用户名 * @param password 密码 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public String login(String username, String password, String code, String uuid) { // 验证码校验 // validateCaptcha(username, code, uuid); // 登录前置校验 loginPreCheck(username, password); // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); } /** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public void validateCaptcha(String username, String code, String uuid) { boolean captchaEnabled = configService.selectCaptchaEnabled(); if (captchaEnabled) { String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); String captcha = redisCache.getCacheObject(verifyKey); if (captcha == null) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } redisCache.deleteObject(verifyKey); if (!code.equalsIgnoreCase(captcha)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } } } /** * 登录前置校验 * @param username 用户名 * @param password 用户密码 */ public void loginPreCheck(String username, String password) { // 用户名或密码为空 错误 if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); throw new UserNotExistsException(); } // 密码如果不在指定范围内 错误 if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // 用户名不在指定范围内 错误 if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // IP黑名单校验 String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); throw new BlackListException(); } } /** * 记录登录信息 * * @param userId 用户ID */ public void recordLoginInfo(Long userId) { SysUser sysUser = new SysUser(); sysUser.setUserId(userId); sysUser.setLoginIp(IpUtils.getIpAddr()); sysUser.setLoginDate(DateUtils.getNowDate()); userService.updateUserProfile(sysUser); } public Long loginCheck(String username, String password){ loginPreCheck(username, password); // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } LoginUser loginUser = (LoginUser) authentication.getPrincipal(); return loginUser.getUserId(); } @Autowired private SysUserMapper sysUserMapper; /** * 登录验证 * * @param username 用户名 * @param password 密码 * @param factoryId 公司ID * @return 结果 */ public String loginCheckFactory(String username, String password, Long factoryId) { // 登录前置校验 loginPreCheck(username, password); // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); // 扩展细节:将 companyId 放入 details(可选方式) // 使用 Map 存储额外信息 Map details = new HashMap<>(); details.put("factoryId", factoryId); authenticationToken.setDetails(details); SecurityContextHolder.getContext().setAuthentication(authenticationToken); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); // 更新用户信息,修改当前选中公司 SysUser user = loginUser.getUser(); if(factoryId != null){ user.setTenantId(factoryId); }else{ user.setTenantId(tokenService.getDeptIdsByUserId(user.getUserId())[0]); } sysUserMapper.updateUser(user); // 生成token return tokenService.createToken(loginUser); } /** * 浪潮门户 SSO 免密登录 *

* 背景:浪潮 OSS 通过 /tide/tideLogin 把 code 换成 token,再取到 tenant 信息后, * 原本调用 login(username, password, code, uuid) 用 tenantAccount + tenantPassword * 做本地登录。但 tenantPassword 是浪潮系统详情里单独配置的值,与本地 sys_user.password * 一旦不一致(浪潮侧改密码、本地重置密码、账号由 applicationOrdering 创建后改过密码等), * 就会报「租户账号[xxx]在系统中登录失败(用户不存在/密码错误)」,导致门户整条登录链路断掉。 *

* 鉴权实际上在浪潮侧已经完成(能拿到有效 token 和 userInfo 就说明身份可信), * 本地无需再次校验密码。这里直接按用户名加载用户并签发本系统 token。 *

* 仍然保留的校验:用户不存在、已删除、已停用 —— 这三种情况拒绝登录。 * * @param username 浪潮返回的租户账号(tenantAccount) * @return 本系统 token */ public String ssoLogin(String username) { SysUser user = userService.selectUserByUserName(username); if (StringUtils.isNull(user)) { log.info("SSO 登录:用户 {} 不存在.", username); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"))); throw new UserNotExistsException(); } if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { log.info("SSO 登录:用户 {} 已被删除.", username); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete"))); throw new ServiceException(MessageUtils.message("user.password.delete")); } if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { log.info("SSO 登录:用户 {} 已被停用.", username); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked"))); throw new ServiceException(MessageUtils.message("user.blocked")); } // 免密:直接构造 LoginUser(不调用 passwordService.validate) LoginUser loginUser = (LoginUser) userDetailsService.createLoginUser(user); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, "浪潮门户 SSO 登录")); recordLoginInfo(user.getUserId()); return tokenService.createToken(loginUser); } }