package com.iplatform.security.controller; import com.iplatform.base.SecuritySpi; import com.iplatform.base.SystemController; import com.iplatform.base.cache.MenuCacheProvider; import com.iplatform.base.callback.SecurityCallback; import com.iplatform.base.config.SecurityUserProperties; import com.iplatform.base.config.TcpProperties; import com.iplatform.base.exception.LoginException; import com.iplatform.base.pojo.RequestLogin; import com.iplatform.base.service.MenuServiceImpl; import com.iplatform.base.util.MenuUtils; import com.iplatform.base.util.menu.MenuTree; import com.iplatform.base.util.menu.SystemMenu; import com.iplatform.model.po.S_user_core; import com.iplatform.model.vo.MenuVo; import com.iplatform.model.vo.RouterVo; import com.walker.infrastructure.utils.JsonUtils; import com.walker.infrastructure.utils.StringUtils; import com.walker.web.ResponseValue; import com.walker.web.UserPrincipal; import com.walker.web.UserType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @RestController public class SecurityController extends SystemController { // private CacheProvider captchaCacheProvider; // private AuthenticationManager authenticationManager; // private TokenGenerator tokenGenerator; // private UserOnlineProvider userOnlineProvider; private MenuServiceImpl menuService; private MenuCacheProvider menuCacheProvider; // private WebAgentService webAgentService; // private LogServiceImpl logService; // private LogProperties logProperties; // // 2023-03-22 // private LoginServiceImpl loginService; // // 2023-03-28 // private SecurityProperties securityProperties; private TcpProperties tcpProperties; private SecuritySpi securitySpi; private SecurityUserProperties securityUserProperties; @Autowired public SecurityController( // CacheProvider captchaCacheProvider // , AuthenticationManager authenticationManager, TokenGenerator tokenGenerator // , UserOnlineProvider userOnlineProvider MenuServiceImpl menuService, MenuCacheProvider menuCacheProvider // , WebAgentService webAgentService, LogServiceImpl logService, LogProperties logProperties // , LoginServiceImpl loginService // , SecurityProperties securityProperties , TcpProperties tcpProperties, SecuritySpi securitySpi, SecurityUserProperties securityUserProperties){ // this.captchaCacheProvider = captchaCacheProvider; // this.authenticationManager = authenticationManager; // this.tokenGenerator = tokenGenerator; // this.userOnlineProvider = userOnlineProvider; this.menuService = menuService; this.menuCacheProvider = menuCacheProvider; // this.webAgentService = webAgentService; // this.logService = logService; // this.logProperties = logProperties; // this.loginService = loginService; // this.securityProperties = securityProperties; this.tcpProperties = tcpProperties; this.securitySpi = securitySpi; this.securityUserProperties = securityUserProperties; } @PostMapping("/login") // public ResponseValue login(@RequestBody RequestLogin requestLogin){ public ResponseValue login(@RequestBody String raw){ logger.debug("login = " + raw); RequestLogin requestLogin = null; try { requestLogin = JsonUtils.jsonStringToObject(raw, RequestLogin.class); } catch (Exception e) { throw new RuntimeException(e); } String username = requestLogin.getUsername(); String password = requestLogin.getPassword(); if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){ return ResponseValue.error("请输入用户名或密码"); } /*// 2023-01-26 为了支持多种登录方式,使用登录回调处理具体登录细节。 PlatformLoginCallback loginCallback = LoginCallbackUtils.getLoginCallbackBean(LoginType.getType(requestLogin.getLoginType()), true); 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()); } if(StringUtils.isEmpty(requestLogin.getVerifyType())){ return ResponseValue.error("请求错误:验证码类型为空"); } if(!requestLogin.getVerifyType().equals(captchaProvider.getCaptchaType().getIndex())){ throw new IllegalArgumentException("前端配置的验证码类型与后台不一致! verifyType = " + captchaProvider.getCaptchaType()); } if(loginCallback.isValidateCaptcha()){ logger.debug("需要验证码,getCaptchaType={}", loginCallback.getCaptchaProvider().getCaptchaType()); String error = this.validateCaptcha(username, requestLogin.getCode(), requestLogin.getUuid(), captchaProvider); if(error != null){ return ResponseValue.error(error); } } } // 用户验证 Authentication authentication = null; try{ // UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); DefaultAuthenticationToken authenticationToken = new DefaultAuthenticationToken(username, password, requestLogin); // 这里放入线程,后面UserDetailsService会使用 SecurityContextHolder.getContext().setAuthentication(authenticationToken); authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e){ this.recordLoginInfo(username, String.valueOf(ResponseCode.ERROR.getCode()), "登录未成功认证", 0, null, null); if (e instanceof BadCredentialsException){ return ResponseValue.error(ResponseCode.USER_CREDENTIALS_ERROR.getCode(), ResponseCode.USER_CREDENTIALS_ERROR.getMessage()); } else { if(e instanceof UsernameNotFoundException){ logger.debug(".............用户不存在:" + username); } // 2023-03-20 // 登录验证抛出异常,会在这里统一处理,如:PcUserStopAppException。 // DefaultAuthenticationProvider return ResponseValue.error(ResponseCode.USER_CREDENTIALS_ERROR.getCode(), e.getMessage()); } } DefaultUserDetails userDetails = (DefaultUserDetails)authentication.getPrincipal(); if(this.logger.isDebugEnabled()){ logger.debug(userDetails.getUserPrincipal().toString()); } // 2023-03-20,对于空验证码类型(登录方式),需要后台自动生成uuid,因为前端没有机会请求验证码获得uuid if(loginCallback.getCaptchaProvider().getCaptchaType() == CaptchaType.None){ requestLogin.setUuid(IdUtils.simpleUUID()); } // 添加token失效时间(从配置获取),2023-03-28 long expiredMinutes = SecurityConfigUtils.getTokenExpireMinutes(requestLogin.getClientType(), securityProperties); String token = TokenUtils.generateToken(userDetails.getUserPrincipal().getId() , userDetails.getUsername(), requestLogin.getUuid(), this.tokenGenerator, expiredMinutes); logger.debug("token失效分钟:{}", expiredMinutes); // String token = this.tokenGenerator.createToken(requestLogin.getUuid(), userDetails.getUserPrincipal().getId() // , VariableConstants.DEFAULT_TOKEN_EXPIRED_MINUTES, VariableConstants.TOKEN_SECRET); // 缓存登录信息 this.userOnlineProvider.cacheUserPrincipal(requestLogin.getUuid(), userDetails.getUserPrincipal()); // 把成功登录日志,与uuid关联保存放一起,在异步中调用。2023-03-23 this.recordLoginInfo(username, String.valueOf(ResponseCode.SUCCESS.getCode()), "登录成功" , userDetails.getUserPrincipal().getUserInfo().getId(), requestLogin.getUuid(), requestLogin.getClientType()); // 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)); // */ Map param = null; try { param = this.securitySpi.login(requestLogin); return ResponseValue.success(param); } catch (LoginException e) { return ResponseValue.error(e.getMessage()); } } @RequestMapping("/getInfo") public ResponseValue getUserInfo(){ UserPrincipal userPrincipal = this.getCurrentUserPrincipal(); Map data = new HashMap<>(4); // 2023-10-13,增加权限自定义加载回调,为业务预留钩子 SecurityCallback securityCallback = this.getPlatformCallback(SecurityCallback.class); // permissions, // 该集合用于适配若依前端,后续前端将不需要自己保留权限路由,统一后台处理返回错误信息。2022-11-12 Set perms = new HashSet(); if(userPrincipal.getUserInfo().getUser_type() == UserType.TYPE_SUPER){ perms.add("*:*:*"); } else { // 获得当前用户所属的角色ID集合 List menuIdList = this.menuService.queryRoleMenuIdList(userPrincipal.getRoleIdList()); // perms.addAll(this.menuCacheProvider.getPermissionSet(menuIdList)); if(securityCallback == null){ // 2023-06-01 增加顶级单位可使用的菜单范围。 int menuScope = this.getDeptCacheProvider().getDept(userPrincipal.getUserInfo().getOrg_id()).getMenu_type(); if(menuScope == MenuUtils.MENU_SCOPE_PLATFORM){ // 平台菜单,需要根据角色查询 perms.addAll(this.menuCacheProvider.getPermissionSet(menuIdList, false, menuScope)); } else { // 商户(顶级机构)独立菜单,不需要角色 perms.addAll(this.menuCacheProvider.getPermissionSet(null, true, menuScope)); } } else { // 2023-10-13 logger.debug("存在业务自定义权限回调:{}", securityCallback.getClass().getName()); Set userPermission = securityCallback.loadUserPermission(userPrincipal.getUserInfo(), menuIdList); if(userPermission != null){ perms.addAll(userPermission); } } } logger.debug("用户 permissions = " + perms); // data.put("user", userPrincipal.getUserInfo()); data.put("roles", userPrincipal.getRoleIdList()); data.put("permissions", perms); data.put("isPerfectInfo", true); // 是否已完善信息,暂时没用,仅配合前端展示。2023-03-20 // 2023-08-05 是否强制修改密码 if(this.securityUserProperties.isPassDefaultModify()){ data.put("force_change_pass", userPrincipal.getUserInfo().getModify_pwd().intValue() == 1? false : true); } else { data.put("force_change_pass", false); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~ 适配电商模块,增加其他用户属性。2023-05-12 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ S_user_core user = userPrincipal.getUserInfo(); data.put("id", user.getId()); data.put("account", user.getUser_name()); data.put("realName", user.getNick_name()); data.put("roleNames", null); data.put("roleIds", StringUtils.collectionToCommaDelimitedString(userPrincipal.getRoleIdList())); // data.put("Token", null); data.put("phone", user.getPhonenumber()); data.put("isSms", true); data.put("merStarLevel", 0); // 2023-04-17,websocket连接信息 if(!this.tcpProperties.isEnabled()){ logger.warn("未开启'WebSocket'"); data.put("uri", "-1"); } else { data.put("uri", this.tcpProperties.getWebsocketUri()); data.put("uid", userPrincipal.getId()); } if(securityCallback != null){ securityCallback.acquireUserInfo(data, user); } return ResponseValue.success(data); } /** * 若依前端方法,获取路由权限。暂时废弃 * @return */ @Deprecated @GetMapping("/getRouters") public ResponseValue getRouters(){ List menuList = this.acquireUserMenuList(); List routerList = this.menuCacheProvider.buildMenus(menuList); return ResponseValue.success(routerList); } /** * 新界面返回菜单树,组装数据。 * @return * @date 2023-05-12 */ @GetMapping("/getMenus") public ResponseValue getMenus(){ List menuList = this.acquireUserMenuList(); // logger.debug("acquireUserMenuList = {}", menuList); List menuVoList = new ArrayList<>(32); if(menuList != null){ MenuVo menuVo = null; for(SystemMenu menu: menuList){ if(menu.getMenu_type().equals(MenuUtils.MENU_TYPE_BUTTON)){ // 这里注意,这次前端不要功能点,只要菜单本身。2023-05-13 continue; } menuVo = new MenuVo(); menuVo.setId(Long.parseLong(menu.getMenu_id())); menuVo.setPid(Long.parseLong(menu.getParent_id())); menuVo.setName(menu.getMenu_name()); menuVo.setPerms(menu.getPerms()); menuVo.setComponent(menu.getComponent()); menuVo.setSort(menu.getOrder_num()); menuVo.setIcon(menu.getIcon()); menuVo.setMenuType(menu.getMenu_type()); // if(menu.getMenu_type().equals(MenuUtils.MENU_TYPE_FOLDER)){ // menuVo.setMenuType(MenuUtils.MENU_TYPE_FOLDER); // } else if(menu.getMenu_type().equals(MenuUtils.MENU_TYPE_ITEM)){ // menuVo.setMenuType(MenuUtils.MENU_TYPE_ITEM); // } else { // menuVo.setMenuType(MenuUtils.MENU_TYPE_POINT); // } if(StringUtils.isNotEmpty(menu.getIcon_info())){ String[] customIcons = StringUtils.commaDelimitedListToStringArray(menu.getIcon_info()); if(customIcons.length != 2){ throw new IllegalArgumentException("自定义菜单图标(相对路径)必须配置两个,并用英文逗号分隔,name=" + menu.getMenu_name()); } menuVo.setIconNormal(customIcons[0]); menuVo.setIconActive(customIcons[1]); } menuVoList.add(menuVo); } } // logger.debug("menuVoList = {}", menuVoList); MenuTree menuTree = new MenuTree(menuVoList); menuVoList = menuTree.buildTree(); return ResponseValue.success(menuVoList); } private List acquireUserMenuList(){ List menuList = null; UserPrincipal userPrincipal = this.getCurrentUserPrincipal(); // 2023-10-13 当业务配置有自定义菜单加载回调对象时,需要按照业务规则加载。 SecurityCallback securityCallback = this.getPlatformCallback(SecurityCallback.class); if(securityCallback == null){ if(userPrincipal.getUserInfo().getUser_type() == UserType.TYPE_SUPER){ // menuList = this.menuCacheProvider.getMenuTree(null, false, true); menuList = this.menuCacheProvider.getMenuList(null, MenuUtils.MENU_SCOPE_PLATFORM); } else { // 获得当前用户所属的角色ID集合 // List menuIdList = this.menuService.queryRoleMenuIdList(userPrincipal.getRoleIdList()); // menuList = this.menuCacheProvider.getMenuTree(menuIdList, false, false); // 2023-06-01 增加顶级单位可使用的菜单范围。 int menuScope = this.getDeptCacheProvider().getDept(userPrincipal.getUserInfo().getOrg_id()).getMenu_type(); if(menuScope == MenuUtils.MENU_SCOPE_PLATFORM){ // 平台菜单,需要根据角色查询 menuList = this.menuCacheProvider.getMenuList(userPrincipal.getRoleIdList(), menuScope); } else { // 商户(顶级机构)独立菜单,不需要角色 menuList = this.menuCacheProvider.getMenuList(null, menuScope); } // menuList = this.menuCacheProvider.getMenuList(userPrincipal.getRoleIdList(), menuScope); } } else { // 2023-10-13 menuList = securityCallback.loadUserMenu(userPrincipal.getUserInfo(), userPrincipal.getRoleIdList()); if(this.logger.isDebugEnabled()){ logger.debug("存在业务自定义菜单回调:{}", menuList); } } if(menuList != null){ // 过滤掉按钮权限,前端只需要菜单,按钮权限在permisstion中控制 SystemMenu menu = null; for(Iterator it = menuList.iterator(); it.hasNext();){ menu = it.next(); // 1.去掉按钮菜单,同时,停用的菜单也要去掉。2023-05-14 // 2.不能展示的也要去掉,is_show = 0 if(menu.getMenu_type().equals(MenuUtils.MENU_TYPE_BUTTON) || menu.getStatus().equals(MenuUtils.MENU_STATUS_DISABLED) || menu.getVisible().equals(MenuUtils.MENU_INVISIBLE)){ it.remove(); } } } return menuList; } // private String validateCaptcha(String username, String code, String uuid, CaptchaProvider captchaProvider){ // if(StringUtils.isEmpty(uuid) || StringUtils.isEmpty(code)){ // return "请输入验证码"; // } // // CaptchaResult captchaResult = new CaptchaResult(); // captchaResult.setUuid(uuid); // captchaResult.setCode(code); // boolean success = captchaProvider.validateCaptcha(captchaResult); // // // 2023-04-07 调整,使用提供者判断验证码是否正确。 // this.captchaCacheProvider.removeCacheData(Constants.CAPTCHA_CODE_PREFIX + uuid);// 删除缓存的验证码 // // if(!success){ // logger.error("验证码校验失败: code = " + code); // return "验证码错误"; // } // return null; // } // private void recordLoginInfo(String loginId, String status, String message, long userId, String uuid, String clientType){ // if(this.logProperties.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 = this.getRequest(); // final WebUserAgent webUserAgent = this.webAgentService.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()))){ // // 登录成功才记录uuid关联缓存 // loginService.execUpdateUserLogin(userId, loginId, uuid, clientType, login_info); // } else { // // 登录失败,仅记录登录日志 // logService.execInsertLoginLog(login_info, userId); // } // } // }; // } }