package com.iplatform.security; import com.iplatform.base.ArgumentsConstants; import com.iplatform.base.AsyncManager; import com.iplatform.base.PlatformLoginCallback; import com.iplatform.base.SecurityConstants; import com.iplatform.base.SecuritySpi; import com.iplatform.base.VariableConstants; import com.iplatform.base.callback.AfterLoginCallback; import com.iplatform.base.callback.PlatformCallbackPostProcessor; import com.iplatform.base.config.LogProperties; import com.iplatform.base.exception.LoginException; import com.iplatform.base.pojo.RequestLogin; import com.iplatform.base.service.LogServiceImpl; import com.iplatform.base.service.LoginServiceImpl; import com.iplatform.base.support.strategy.LoginStrategyManager; import com.iplatform.base.util.TokenUtils; import com.iplatform.base.util.UserUtils; import com.iplatform.core.BeanContextAware; import com.iplatform.model.po.S_login_info; import com.iplatform.model.po.S_user_core; import com.iplatform.model.po.S_user_login; import com.iplatform.security.config.SecurityProperties; import com.iplatform.security.util.LoginCallbackUtils; import com.iplatform.security.util.SecurityConfigUtils; import com.walker.cache.CacheProvider; import com.walker.infrastructure.arguments.ArgumentsManager; import com.walker.infrastructure.arguments.Variable; import com.walker.infrastructure.utils.DateUtils; import com.walker.infrastructure.utils.NumberGenerator; import com.walker.infrastructure.utils.StringUtils; import com.walker.web.CaptchaProvider; import com.walker.web.CaptchaResult; import com.walker.web.CaptchaType; import com.walker.web.Constants; import com.walker.web.LoginType; import com.walker.web.ResponseCode; import com.walker.web.TokenGenerator; import com.walker.web.UserOnlineProvider; import com.walker.web.UserPrincipal; import com.walker.web.WebAgentService; import com.walker.web.WebRuntimeException; import com.walker.web.WebUserAgent; import com.walker.web.util.IdUtils; import com.walker.web.util.ServletUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimerTask; public class DefaultSecuritySpi implements SecuritySpi { protected final transient Logger logger = LoggerFactory.getLogger(this.getClass()); public DefaultSecuritySpi(){} @Override public boolean isAllowMobileLoginRegister(){ return this.getSecurityProperties().isAllowMobileLoginReg(); } @Override public Map login(RequestLogin requestLogin) throws LoginException { String username = requestLogin.getUsername(); // 2023-01-26 为了支持多种登录方式,使用登录回调处理具体登录细节。 // 2023-12-28 移动端登录验证 CaptchaType captchaType = CaptchaType.getType(requestLogin.getVerifyType()); PlatformLoginCallback loginCallback = LoginCallbackUtils.getLoginCallbackBean(LoginType.getType(requestLogin.getLoginType()), true, captchaType); boolean captchaEnabled = this.getArgumentVariable(ArgumentsConstants.KEY_SECURITY_CAPTCHA_ENABLED).getBooleanValue(); // 2023-01-26 这里APP端是不需要验证码的 if(captchaEnabled){ CaptchaProvider captchaProvider = loginCallback.getCaptchaProvider(); if(captchaProvider == null){ // return ResponseValue.error("系统需要验证码,但登录未配置:" + loginCallback.getClass().getName()); throw new LoginException("系统需要验证码,但登录未配置:" + loginCallback.getClass().getName()); } if(StringUtils.isEmpty(requestLogin.getVerifyType())){ // return ResponseValue.error("请求错误:验证码类型为空"); throw new LoginException("请求错误:验证码类型为空"); } // 第三方对接的认证登录,不需要检测验证码类型是否与后台一致。2023-07-03 if(captchaProvider.getCaptchaType() != CaptchaType.ThirdParty && !requestLogin.getVerifyType().equals(captchaProvider.getCaptchaType().getIndex())){ throw new LoginException("前端配置的验证码类型与后台不一致! verifyType = " + captchaProvider.getCaptchaType()); } if(loginCallback.isValidateCaptcha()){ logger.debug("需要验证码,getCaptchaType={}", loginCallback.getCaptchaProvider().getCaptchaType()); String error = this.validateCaptcha(requestLogin.getCode(), requestLogin.getUuid(), captchaProvider); if(error != null){ // return ResponseValue.error(error); throw new LoginException(error); } } } // 2023-07-11 检查登录策略是否满足 if(this.getLogProperties().isLoginEnabled()){ String error = this.getLoginStrategyManager().execute(requestLogin); if(error != null){ throw new LoginException(error); } } // 用户验证 Authentication authentication = null; try{ // UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); DefaultAuthenticationToken authenticationToken = new DefaultAuthenticationToken(username, requestLogin.getPassword(), requestLogin); // 这里放入线程,后面UserDetailsService会使用 SecurityContextHolder.getContext().setAuthentication(authenticationToken); authentication = this.getAuthenticationManager().authenticate(authenticationToken); } catch (Exception e){ this.recordLoginInfo(requestLogin.getUsername(), String.valueOf(ResponseCode.ERROR.getCode()), "登录未成功认证", 0, null, null); if(e instanceof UsernameNotFoundException){ // logger.debug(".............用户不存在:" + requestLogin.getUsername()); throw new LoginException("用户账号不存在,或已停用:" + requestLogin.getUsername(), e, true); } if (e instanceof BadCredentialsException){ // return ResponseValue.error(ResponseCode.USER_CREDENTIALS_ERROR.getCode(), ResponseCode.USER_CREDENTIALS_ERROR.getMessage()); logger.debug("++++++++++++++++++ 密码错误"); throw new LoginException(ResponseCode.USER_CREDENTIALS_ERROR.getMessage()); } else { // 2023-03-20 // 登录验证抛出异常,会在这里统一处理,如:PcUserStopAppException。 // DefaultAuthenticationProvider // return ResponseValue.error(ResponseCode.USER_CREDENTIALS_ERROR.getCode(), e.getMessage()); throw new LoginException(e.getMessage()); } } DefaultUserDetails userDetails = (DefaultUserDetails)authentication.getPrincipal(); if(this.logger.isDebugEnabled()){ logger.debug(userDetails.getUserPrincipal().toString()); } // 2023-03-20,对于空验证码类型(登录方式),需要后台自动生成uuid,因为前端没有机会请求验证码获得uuid // 2023-07-03, 添加第三方对接的验证类型,也需要自动生成UUID,因为前端直接请求后台(目前预算一体化使用) CaptchaType currentCaptchaType = loginCallback.getCaptchaProvider().getCaptchaType(); if(currentCaptchaType == CaptchaType.None || currentCaptchaType == CaptchaType.ThirdParty){ requestLogin.setUuid(IdUtils.simpleUUID()); } // 添加token失效时间(从配置获取),2023-03-28 long expiredMinutes = SecurityConfigUtils.getTokenExpireMinutes(requestLogin.getClientType(), this.getSecurityProperties()); String token = TokenUtils.generateToken(userDetails.getUserPrincipal().getId() , userDetails.getUsername(), requestLogin.getUuid(), this.getTokenGenerator(), expiredMinutes); logger.debug("token失效分钟:{}", expiredMinutes); // String token = this.tokenGenerator.createToken(requestLogin.getUuid(), userDetails.getUserPrincipal().getId() // , VariableConstants.DEFAULT_TOKEN_EXPIRED_MINUTES, VariableConstants.TOKEN_SECRET); // 缓存登录信息 // 2024-01-05 登录缓存,失效时间应当长一些,系统默认30天。这和token long loginCacheExpiredTime = VariableConstants.DEFAULT_TOKEN_EXPIRED_MINUTES; if(expiredMinutes > loginCacheExpiredTime){ loginCacheExpiredTime = expiredMinutes; } this.getUserOnlineProvider().cacheUserPrincipal(requestLogin.getUuid(), userDetails.getUserPrincipal(), loginCacheExpiredTime); // 把成功登录日志,与uuid关联保存放一起,在异步中调用。2023-03-23 this.recordLoginInfo(username, String.valueOf(ResponseCode.SUCCESS.getCode()), "登录成功" , userDetails.getUserPrincipal().getUserInfo().getId(), requestLogin.getUuid(), requestLogin.getClientType()); // 2023-08-18,登录成功回调 AfterLoginCallback afterLoginCallback = PlatformCallbackPostProcessor.getCallbackObject(AfterLoginCallback.class); if(afterLoginCallback != null){ afterLoginCallback.onSuccess(requestLogin, userDetails.getUserPrincipal()); } // 2023-05-12,加上用户信息(商户系统用) Map param = new HashMap<>(2); param.put(com.walker.web.Constants.TOKEN_NAME, token); param.put(SecurityConstants.KEY_USER_INFO, UserUtils.acquireClientUserInfo(userDetails.getUserPrincipal().getUserInfo(), token)); param.put(SecurityConstants.KEY_USER_INFO_APP, userDetails.getUserPrincipal()); // return ResponseValue.success(ResponseValueUtils.acquireOneParam(com.walker.web.Constants.TOKEN_NAME, token)); return param; } private String validateCaptcha(String code, String uuid, CaptchaProvider captchaProvider){ if(StringUtils.isEmpty(uuid) || StringUtils.isEmpty(code)){ // throw new CaptchaException(); return "请输入验证码"; } CaptchaResult captchaResult = new CaptchaResult(); captchaResult.setUuid(uuid); captchaResult.setCode(code); boolean success = captchaProvider.validateCaptcha(captchaResult); // 2023-04-07 调整,使用提供者判断验证码是否正确。 // 2023-06-30 只有短信验证码时,不能删除缓存的验证码,因为存在手机端登录(并同时注册)情况,会调用两次login操作。 if(captchaProvider.getCaptchaType() != CaptchaType.SmsCode){ this.getCaptchaCacheProvider().removeCacheData(com.iplatform.base.Constants.CAPTCHA_CODE_PREFIX + uuid);// 删除缓存的验证码 } if(!success){ logger.error("验证码校验失败: code = " + code); return "验证码错误"; } /*String verifyKey = Constants.CAPTCHA_CODE_PREFIX + uuid; String captchaCode = this.captchaCacheProvider.getCacheData(verifyKey); if(StringUtils.isEmpty(captchaCode)){ return "验证码不存在,可能已经失效"; } this.captchaCacheProvider.removeCacheData(verifyKey); // 删除缓存的验证码 if(!code.equalsIgnoreCase(captchaCode)){ logger.error("验证码校验失败:" + captchaCode + ", code = " + code); return "验证码错误"; }*/ return null; } private void recordLoginInfo(String loginId, String status, String message, long userId, String uuid, String clientType){ if(this.getLogProperties().isLoginEnabled()){ logger.debug("异步记录登录日志,后续要补充:" + status + ", " + message); AsyncManager.me().execute(this.acquireLoginInfoTask(loginId, status, message, userId, uuid, clientType)); } } private TimerTask acquireLoginInfoTask(String loginId, String status, String message, Long userId , String uuid, String clientType){ HttpServletRequest request = ServletUtils.getRequest(); final WebUserAgent webUserAgent = this.getWebAgentService().getWebUserAgent(request.getHeader("User-Agent"), request); return new TimerTask() { @Override public void run() { S_login_info login_info = new S_login_info(); login_info.setLogin_time(Long.parseLong(DateUtils.getDateTimeSecondForShow())); login_info.setUser_name(loginId); login_info.setMsg(message); login_info.setStatus(status); login_info.setInfo_id(NumberGenerator.getLongSequenceNumber()); if(webUserAgent != null){ login_info.setLogin_location(webUserAgent.getLocation()); login_info.setBrowser(webUserAgent.getBrowserName()); login_info.setIpaddr(webUserAgent.getIp()); login_info.setOs(webUserAgent.getOsName()); } // 2023-03-23 if(status.equals(String.valueOf(ResponseCode.SUCCESS.getCode()))){ S_user_login user_login = null; if(getLoginStrategyManager().hasUserLogin(loginId)){ user_login = getLoginService().execUpdateUserLogin(userId, loginId, uuid, clientType, login_info, true); getLoginStrategyManager().updateUserLoginCache(user_login); } else { // 登录成功才记录uuid关联缓存 user_login = getLoginService().execUpdateUserLogin(userId, loginId, uuid, clientType, login_info, false); getLoginStrategyManager().putUserLoginCache(user_login); } } else { // 登录失败,仅记录登录日志 getLogService().execInsertLoginLog(login_info, userId); } } }; } private AuthenticationManager getAuthenticationManager(){ return BeanContextAware.getBeanByType(AuthenticationManager.class); } private TokenGenerator getTokenGenerator(){ return BeanContextAware.getBeanByType(TokenGenerator.class); } private CacheProvider getCaptchaCacheProvider(){ return (CacheProvider)BeanContextAware.getBeanByName(BEAN_NAME_CAPTCHA_CACHE); } private WebAgentService getWebAgentService(){ return BeanContextAware.getBeanByType(WebAgentService.class); } private UserOnlineProvider getUserOnlineProvider(){ return BeanContextAware.getBeanByType(UserOnlineProvider.class); } private SecurityProperties getSecurityProperties(){ return BeanContextAware.getBeanByType(SecurityProperties.class); } private LogProperties getLogProperties(){ return BeanContextAware.getBeanByType(LogProperties.class); } private LoginServiceImpl getLoginService(){ return BeanContextAware.getBeanByType(LoginServiceImpl.class); } private LogServiceImpl getLogService(){ return BeanContextAware.getBeanByType(LogServiceImpl.class); } private LoginStrategyManager getLoginStrategyManager(){ return BeanContextAware.getBeanByType(LoginStrategyManager.class); } private static final String BEAN_NAME_CAPTCHA_CACHE = "captchaCacheProvider"; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~` //~ 以上是登录方法抽取代码。2023-06-28 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~` @Deprecated public void loginAsWorkflowRole(){ DefaultUserDetails userDetails = this.getCurrentUserDetails(); userDetails.addGrantedAuthority(Constants.ROLE_ACTIVITI_USER); logger.debug("......loginAsWorkflowRole(), {}", userDetails.getRoleIdList()); } @Override public List getCurrentUserRoleIdList(){ DefaultUserDetails userDetails = this.getCurrentUserDetails(); if(userDetails == null){ logger.error("获取当前登录用户错误:getCurrentUserDetails() == null"); return null; } return userDetails.getRoleIdList(); } @Override public UserPrincipal getCurrentUserPrincipal() { DefaultUserDetails userDetails = this.getCurrentUserDetails(); if(userDetails == null){ logger.error("获取当前登录用户错误:getCurrentUserDetails() == null"); return null; } return userDetails.getUserPrincipal(); } @Override public S_user_core getCurrentUser() { UserPrincipal userPrincipal = this.getCurrentUserPrincipal(); if(userPrincipal == null){ throw new WebRuntimeException("当前操作未找到登录人员: userPrincipal not found in thread!"); } return userPrincipal.getUserInfo(); } @Override public long getCurrentUserId() { S_user_core user = this.getCurrentUser(); return user.getId(); } @Override public String encryptPassword(String password) { PasswordEncoder passwordEncoder = BeanContextAware.getBeanByType(PasswordEncoder.class); return passwordEncoder.encode(password); } @Override public boolean matchesPassword(String rawPassword, String encodedPassword) { PasswordEncoder passwordEncoder = BeanContextAware.getBeanByType(PasswordEncoder.class); return passwordEncoder.matches(rawPassword, encodedPassword); } private DefaultUserDetails getCurrentUserDetails(){ Authentication authentication = this.getAuthentication(); if(authentication == null){ throw new WebRuntimeException("当前操作未找到登录认证对象: authentication not found in thread!"); } Object userDetails = authentication.getPrincipal(); if(userDetails instanceof DefaultUserDetails){ return (DefaultUserDetails) userDetails; } return null; } /** * 获取Authentication * @author 时克英 * @date 2022-11-10 */ private Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } /** * 返回系统配置可变参数对象。 * @param key 参数 key * @return * @date 2022-11-29 */ private Variable getArgumentVariable(String key){ Variable v = BeanContextAware.getBeanByType(ArgumentsManager.class).getVariable(key); if(v == null){ throw new IllegalArgumentException("可变配置参数不存在: " + key); } return v; } }