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<String> 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<String> 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<CaptchaResult> 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<String, Object> 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<String, Object> 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<S_user_core> userPrincipal = this.getCurrentUserPrincipal();
|
Map<String, Object> data = new HashMap<>(4);
|
|
// 2023-10-13,增加权限自定义加载回调,为业务预留钩子
|
SecurityCallback securityCallback = this.getPlatformCallback(SecurityCallback.class);
|
|
// permissions,
|
// 该集合用于适配若依前端,后续前端将不需要自己保留权限路由,统一后台处理返回错误信息。2022-11-12
|
Set<String> perms = new HashSet<String>();
|
if(userPrincipal.getUserInfo().getUser_type() == UserType.TYPE_SUPER){
|
perms.add("*:*:*");
|
} else {
|
// 获得当前用户所属的角色ID集合
|
List<String> 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<String> 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<SystemMenu> menuList = this.acquireUserMenuList();
|
List<RouterVo> routerList = this.menuCacheProvider.buildMenus(menuList);
|
return ResponseValue.success(routerList);
|
}
|
|
/**
|
* 新界面返回菜单树,组装数据。
|
* @return
|
* @date 2023-05-12
|
*/
|
@GetMapping("/getMenus")
|
public ResponseValue getMenus(){
|
List<SystemMenu> menuList = this.acquireUserMenuList();
|
// logger.debug("acquireUserMenuList = {}", menuList);
|
List<MenuVo> 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<SystemMenu> acquireUserMenuList(){
|
List<SystemMenu> menuList = null;
|
UserPrincipal<S_user_core> 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<String> 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<SystemMenu> 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<CaptchaResult> 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);
|
// }
|
// }
|
// };
|
// }
|
}
|