黎星凯
2024-05-17 3520e86e2b00b9c1ee3f4fffd4ab49fe3d6c259e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
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);
//                }
//            }
//        };
//    }
}