shikeying
2024-05-08 8924870a053f0b882ada86421c062cbdb9cff093
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
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<String, Object> 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<CaptchaResult> 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){
//                throw new LoginException("用户账号不存在,或已停用:" + requestLogin.getUsername(), e, true);
                throw new LoginException("用户账号不存在,或已停用:" + requestLogin.getUsername(), null, 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<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));
        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<CaptchaResult> 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<String> getCaptchaCacheProvider(){
        return (CacheProvider<String>)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<String> getCurrentUserRoleIdList(){
        DefaultUserDetails userDetails = this.getCurrentUserDetails();
        if(userDetails == null){
            logger.error("获取当前登录用户错误:getCurrentUserDetails() == null");
            return null;
        }
        return userDetails.getRoleIdList();
    }
 
    @Override
    public UserPrincipal<S_user_core> getCurrentUserPrincipal() {
        DefaultUserDetails userDetails = this.getCurrentUserDetails();
        if(userDetails == null){
            logger.error("获取当前登录用户错误:getCurrentUserDetails() == null");
            return null;
        }
        return userDetails.getUserPrincipal();
    }
 
    @Override
    public S_user_core getCurrentUser() {
        UserPrincipal<S_user_core> 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;
    }
}