package com.hotent.auth.server.service.impl; import cn.hutool.json.JSONUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.hotent.auth.server.service.AuthenticationService; import com.hotent.base.conf.JwtConfig; import com.hotent.base.conf.SaaSConfig; import com.hotent.base.conf.SsoConfig; import com.hotent.base.constants.ScanLoginConsts; import com.hotent.base.constants.SystemConstants; import com.hotent.base.enums.ResponseErrorEnums; import com.hotent.base.exception.BaseException; import com.hotent.base.exception.CertificateException; import com.hotent.base.exception.ServerRejectException; import com.hotent.base.feign.LPGService; import com.hotent.base.feign.PortalFeignService; import com.hotent.base.feign.UCFeignService; import com.hotent.base.jwt.JwtAuthenticationRequest; import com.hotent.base.jwt.JwtAuthenticationResponse; import com.hotent.base.jwt.JwtTokenHandler; import com.hotent.base.manager.CaptchaManager; import com.hotent.base.model.CommonResult; import com.hotent.base.service.TwoVerifyService; import com.hotent.base.util.*; import com.hotent.base.vo.TwoVerifyInfoVo; import com.hotent.i18n.util.I18nUtil; import com.hotent.uc.api.model.IUser; import org.apache.http.client.ClientProtocolException; import org.apache.http.entity.ContentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.sql.SQLSyntaxErrorException; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; /** * 用户认证 服务实现类 * * @author 欧阳高龙 * @company 广州宏天软件股份有限公司 * @since 2022/9/28 14:27 */ @Service public class AuthenticationServiceImpl implements AuthenticationService { private static final Logger logger = LoggerFactory.getLogger(AuthenticationServiceImpl.class); @Resource AuthenticationManager authenticationManager; @Resource JwtTokenHandler jwtTokenHandler; @Resource UserDetailsService userDetailsService; @Resource SsoConfig ssoConfig; @Value("${system.mode.demo:false}") protected boolean demoMode; @Resource UCFeignService uCFeignService; @Resource PortalFeignService portalFeignService; @Resource UCFeignService ucFeignService; @Resource SaaSConfig saasConfig; @Resource JwtConfig jwtConfig; @Resource private CaptchaManager captchaManager; @Resource private TwoVerifyService twoVerifyService; @Resource private LPGService lpgService; @Override public ResponseEntity createAuthenticationToken(JwtAuthenticationRequest authenticationRequest) throws AuthenticationException, CertificateException, InvalidKeySpecException, NoSuchAlgorithmException { if(!captchaManager.verifyCode(authenticationRequest)){ return ResponseEntity.ok(CommonResult.result(ResponseErrorEnums.BAD_CAPTCHA)); } String reqAccount = authenticationRequest.getUsername(); String reqPassword = ""; //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(reqAccount); try { //密码rsa解密 reqPassword = RSAUtil.Decrypt(authenticationRequest.getPassword()); } catch (Exception e) { throw new RuntimeException("解密密码异常,请检查RSA公钥和私钥配置"); } String errorMsg = ""; try { authenticate(reqAccount, reqPassword); } catch (Exception e) { logger.error(String.format("Login failed account[%s].", reqAccount), e); errorMsg = I18nUtil.handleI18nMessage("incorrect.account.password.exception", "账号或密码错误"); Throwable cause = ExceptionUtil.getRootCauseOrSelf(e.getCause()); if(cause instanceof CertificateException){ CertificateException ce = (CertificateException) cause; errorMsg = ce.getMessage(); }else if(cause instanceof SQLSyntaxErrorException){ SQLSyntaxErrorException sqlSyntaxErrorException = (SQLSyntaxErrorException) cause; logger.error(sqlSyntaxErrorException.getMessage()); }else if(cause instanceof BaseException){ BaseException baseException = (BaseException) cause; logger.error(baseException.getMessage()); } else if(e instanceof CertificateException) { logger.error(e.getMessage()); } else if(e instanceof LockedException) { logger.error("账号{}被禁用或离职", reqAccount); // errorMsg = I18nUtil.handleI18nMessage("account.disabled.exception", "账号被禁用或离职"); }else if (e instanceof InternalAuthenticationServiceException){ logger.error("账户{}错误或该租户未启用", reqAccount); // errorMsg = I18nUtil.handleI18nMessage("account.error.exception", "账户错误或该租户未启用"); } if(captchaManager.addFailedCount(authenticationRequest.getUsername())){ return ResponseEntity.ok(CommonResult.result(ResponseErrorEnums.BAD_CAPTCHA, errorMsg)); } throw new RuntimeException(errorMsg); } // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String account = ""; String userId = ""; String twoVerifySecret=""; boolean loginStatus = true; Map userAttrs = new HashMap(); if(userDetails instanceof IUser) { IUser user = ((IUser)userDetails); userName = user.getFullname(); account = user.getAccount(); userId = user.getUserId(); request.setAttribute("loginUser", String.format("%s[%s]",userName,account)); //校验密码策略 loginStatus = checkUser(user, reqPassword); userAttrs.put("tenantId", user.getTenantId()); Map userMap= JSONUtil.toBean(JSONUtil.toJsonStr(user),Map.class); twoVerifySecret= StringUtil.isNotEmpty(String.valueOf(userMap.get("twoVerifySecret")))?userMap.get("twoVerifySecret").toString():null; } //处理单用户登录 handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token); JwtAuthenticationResponse jwtAuthenticationResponse=new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs); CommonResult commonResult=this.checkTwoStepVerify(isMobile,twoVerifySecret,jwtAuthenticationResponse); //登录成功重置触发验证码校验失败次数 captchaManager.resetLimit(reqAccount); if(commonResult!=null){ return ResponseEntity.ok(commonResult); } return ResponseEntity.ok(jwtAuthenticationResponse); } @Override public ResponseEntity createLpgAuthenticationToken(JwtAuthenticationRequest authenticationRequest) throws AuthenticationException, CertificateException, InvalidKeySpecException, NoSuchAlgorithmException { if(!captchaManager.verifyCode(authenticationRequest)){ return ResponseEntity.ok(CommonResult.result(ResponseErrorEnums.BAD_CAPTCHA)); } String reqAccount = authenticationRequest.getUsername(); String reqPassword = ""; //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(reqAccount); try { //密码rsa解密 reqPassword = RSAUtil.Decrypt(authenticationRequest.getPassword()); } catch (Exception e) { throw new RuntimeException("解密密码异常,请检查RSA公钥和私钥配置"); } String errorMsg = ""; try { authenticate(reqAccount, reqPassword); } catch (Exception e) { logger.error(String.format("Login failed account[%s].", reqAccount), e); errorMsg = I18nUtil.handleI18nMessage("incorrect.account.password.exception", "账号或密码错误"); Throwable cause = ExceptionUtil.getRootCauseOrSelf(e.getCause()); if(cause instanceof CertificateException){ CertificateException ce = (CertificateException) cause; errorMsg = ce.getMessage(); }else if(cause instanceof SQLSyntaxErrorException){ SQLSyntaxErrorException sqlSyntaxErrorException = (SQLSyntaxErrorException) cause; logger.error(sqlSyntaxErrorException.getMessage()); }else if(cause instanceof BaseException){ BaseException baseException = (BaseException) cause; logger.error(baseException.getMessage()); } else if(e instanceof CertificateException) { logger.error(e.getMessage()); } else if(e instanceof LockedException) { logger.error("账号{}被禁用或离职", reqAccount); // errorMsg = I18nUtil.handleI18nMessage("account.disabled.exception", "账号被禁用或离职"); }else if (e instanceof InternalAuthenticationServiceException){ logger.error("账户{}错误或该租户未启用", reqAccount); // errorMsg = I18nUtil.handleI18nMessage("account.error.exception", "账户错误或该租户未启用"); } if(captchaManager.addFailedCount(authenticationRequest.getUsername())){ return ResponseEntity.ok(CommonResult.result(ResponseErrorEnums.BAD_CAPTCHA, errorMsg)); } throw new RuntimeException(errorMsg); } // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String account = ""; String userId = ""; String twoVerifySecret=""; boolean loginStatus = true; Map userAttrs = new HashMap(); if(userDetails instanceof IUser) { IUser user = ((IUser)userDetails); userName = user.getFullname(); account = user.getAccount(); userId = user.getUserId(); request.setAttribute("loginUser", String.format("%s[%s]",userName,account)); //校验密码策略 loginStatus = checkUser(user, reqPassword); userAttrs.put("tenantId", user.getTenantId()); Map userMap= JSONUtil.toBean(JSONUtil.toJsonStr(user),Map.class); twoVerifySecret= StringUtil.isNotEmpty(String.valueOf(userMap.get("twoVerifySecret")))?userMap.get("twoVerifySecret").toString():null; } //验证员工、用户信息 CommonResult result = lpgService.getUser(userId, authenticationRequest.getYhlx()); if (!result.getState()) { throw new RuntimeException(result.getMessage()); } //处理单用户登录 handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token); JwtAuthenticationResponse jwtAuthenticationResponse=new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs); CommonResult commonResult=this.checkTwoStepVerify(isMobile,twoVerifySecret,jwtAuthenticationResponse); //登录成功重置触发验证码校验失败次数 captchaManager.resetLimit(reqAccount); if(commonResult!=null){ return ResponseEntity.ok(commonResult); } return ResponseEntity.ok(jwtAuthenticationResponse); } @Override public ResponseEntity ssoAuth(Optional ticket, Optional code, Optional ssoMode, String service) throws AuthenticationException, ClientProtocolException, IOException { Assert.isTrue(ssoConfig.isEnable(), "当前服务未开启单点登录"); String username = null; String mode = ssoConfig.getMode(); if(ssoMode.isPresent()) { mode = ssoMode.get(); } // 使用cas认证 if(ticket.isPresent() && SsoConfig.MODE_CAS.equals(mode)) { username = getUserNameWithCas(ticket.get(), service); } // 使用oauth认证 else if(code.isPresent() && SsoConfig.MODE_OAUTH.equals(mode)) { username = getUserNameWithOauth(code.get(), service); } // 使用jwt认证 else if(ticket.isPresent() && code.isPresent() && SsoConfig.MODE_JWT.equals(mode)){ username = jwtTokenHandler.getUsernameFromToken(ticket.get()); } else { throw new ServerRejectException("单点登录模式匹配异常"); } //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(username); // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(username); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String account = ""; String userId = ""; Map userAttrs = new HashMap(); if(userDetails instanceof IUser) { IUser user = ((IUser)userDetails); userName = user.getFullname(); account = user.getAccount(); userId = user.getUserId(); request.setAttribute("loginUser", String.format("%s[%s]",userName,account)); userAttrs.put("tenantId", user.getTenantId()); } //获取超时时间 logger.debug("通过单点认证登录成功。"); //处理单用户登录 if(!(code.isPresent() && SsoConfig.MODE_JWT.equals(mode))){ handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token); } // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), userAttrs)); } @Override public ResponseEntity ssoWeixin(Optional code) throws AuthenticationException, ClientProtocolException, IOException { String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("weChatWork",code.orElse("")), "", "POST"); logger.error("企业微信登录返回结果:"+resultJson); ObjectNode result=null; try{ result = (ObjectNode) JsonUtil.toJsonNode(resultJson); }catch (Exception e){ logger.error(e.getMessage()); } Objects.requireNonNull(result); String errcode = result.get("errcode").asText(); if("0".equals(errcode)) { String wxWorkId = result.get("UserId").asText(); JsonNode simpleUser = uCFeignService.getUserByWxWorkId(wxWorkId); if(BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()){ throw new RuntimeException("查无与您企微账号[userid:"+wxWorkId+"]绑定的eip账号"); } String account = simpleUser.get("account").asText(); try { //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); Objects.requireNonNull(request); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(account); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } logger.debug("通过单点认证登录成功。"); //处理单用户登录 handleSingleLogin(isMobile, tenantId, account, token); // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId)); } catch (Exception e) { throw new RuntimeException("企业微信登录失败 ,eip用户账号:"+ account); } } throw new RuntimeException("企业微信登录失败 : " + result.get("errmsg").asText()); } @Override public ResponseEntity ssoWeixinPublic(Optional code) throws AuthenticationException, ClientProtocolException, IOException { String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("weChatOffAcc",code.get()), "", "POST"); ObjectNode result=null; try{ result = (ObjectNode) JsonUtil.toJsonNode(resultJson); }catch (Exception e){ logger.error(e.getMessage()); } if(result.has("openid")) { String openid = result.get("openid").asText(); CommonResult r = uCFeignService.getUserByOpenId(openid); if(r.getState()) { JsonNode node = r.getValue(); if(StringUtil.isNotEmpty(openid) && BeanUtils.isEmpty(node)) { return ResponseEntity.ok(new JwtAuthenticationResponse(openid)); } String account = node.get("account").asText(); //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(account); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } //处理单用户登录 handleSingleLogin(isMobile, tenantId, account, token); // Return the token JwtAuthenticationResponse jwtAuthenticationResponse = new JwtAuthenticationResponse(token, userName, account, userId,openid); return ResponseEntity.ok(jwtAuthenticationResponse); }else { if(StringUtil.isNotEmpty(openid)) { return ResponseEntity.ok(new JwtAuthenticationResponse(openid)); } } } throw new RuntimeException("微信登录失败 : " + result.get("errmsg").asText()); } @Override public ResponseEntity ssoDingTalk(Optional code) throws AuthenticationException, ClientProtocolException, IOException { String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("dingtalk",code.get()), "", "GET"); ObjectNode result=null; try{ result = (ObjectNode) JsonUtil.toJsonNode(resultJson); }catch (Exception e){ logger.error(e.getMessage()); } if(result.has("userid")) { String dingtalkId = result.get("userid").asText(); JsonNode simpleUser = uCFeignService.getUserByDingtalkId(dingtalkId); if(BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()){ throw new RuntimeException("查无与您钉钉账号[userid:"+dingtalkId+"]绑定的eip账号"); } String account = simpleUser.get("account").asText(); final UserDetails userDetails = userDetailsService.loadUserByUsername(account); if(BeanUtils.isNotEmpty(userDetails)) { //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // Reload password post-security so we can generate the token // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } //处理单用户登录 handleSingleLogin(isMobile, tenantId, account, token); // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId)); }else { throw new RuntimeException("钉钉登录失败!eip账号:"+account+"不存在"); } } throw new RuntimeException("钉钉登录失败 : " + result.get("errmsg").asText()); } @Override public CommonResult ssoMiniprogram(Optional code) throws AuthenticationException, ClientProtocolException, IOException { String url = portalFeignService.getUserInfoUrl("miniprogram",code.get()); String resultJson = HttpUtil.sendHttpsRequest(url, "", "POST"); ObjectNode result=null; try{ result = (ObjectNode) JsonUtil.toJsonNode(resultJson); }catch (Exception e){ logger.error(e.getMessage()); } if(result.has("openid")) { String openid = result.get("openid").asText(); JsonNode simpleUser = uCFeignService.getUserByMpOpenId(openid); if(BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()){ return new CommonResult(false, "系统内无openid对应的用户", openid); } String account = simpleUser.get("account").asText(); //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(account); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } //处理单用户登录 handleSingleLogin(HttpUtil.isMobile(request), tenantId, account, token); // Return the token return new CommonResult(true, "登陆成功", new JwtAuthenticationResponse(token, userName, account, userId)); } throw new RuntimeException("小程序登陆失败 : " + (result==null?"": JsonUtil.getString(result, "errmsg"))); } @Override public ResponseEntity ssoFlyBook(Optional code) throws AuthenticationException, ClientProtocolException, IOException { if(!code.isPresent()){ throw new RuntimeException("参数错误!"); } //获取登录用户身份:根据code获取飞书用户ID CommonResult resultFlyBookId = portalFeignService.getFlyBookIdByCode(code.get()); if(resultFlyBookId==null || !resultFlyBookId.getState()) { logger.error("飞书获取登录用户身份失败!" + resultFlyBookId.getMessage()); throw new RuntimeException("飞书获取登录用户身份失败!"); } String flyBookUserId = resultFlyBookId.getValue(); JsonNode simpleUser = uCFeignService.getUserByFlyBookId(flyBookUserId); if(simpleUser == null || simpleUser.isNull() || !simpleUser.has("account")){ throw new RuntimeException("查无与您飞书账号[userid:"+flyBookUserId+"]绑定的eip账号"); } String account = simpleUser.get("account").asText(); try { //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); Objects.requireNonNull(request); boolean isMobile = HttpUtil.isMobile(request); // Reload password post-security so we can generate the token final UserDetails userDetails = userDetailsService.loadUserByUsername(account); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } logger.debug("通过飞书认证登录成功。"); //处理单用户登录 handleSingleLogin(isMobile, tenantId, account, token); // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId)); } catch (Exception e) { throw new RuntimeException("飞书认证登录失败 ,eip用户账号:"+ account); } } @Override public CommonResult scanLogin(Optional code, Optional type, String redirectUri) { if(!code.isPresent() || !type.isPresent()){ return new CommonResult(false,"参数错误!"); } CommonResult result = null; if(type.get().equals(ScanLoginConsts.SCAN_TYPE_DING)){ result = portalFeignService.getDingtalkIdFromScanCode(code.get()); }else if(type.get().equals(ScanLoginConsts.SCAN_TYPE_WE_CHAT)){ result = portalFeignService.getWxWorkIdFromScanCode(code.get()); } else if(type.get().equals(ScanLoginConsts.SCAN_TYPE_FLY_BOOK)){ result = portalFeignService.getFlyBookIdFromScanCode(code.get(), redirectUri); } if(result!=null && result.getState()) { String userid = result.getValue(); String typeName = "钉钉"; JsonNode simpleUser = null; if(type.get().equals(ScanLoginConsts.SCAN_TYPE_DING)){ simpleUser = uCFeignService.getUserByDingtalkId(userid); } else if(type.get().equals(ScanLoginConsts.SCAN_TYPE_WE_CHAT)){ typeName = "企业微信"; simpleUser = uCFeignService.getUserByWxWorkId(userid); } else if(type.get().equals(ScanLoginConsts.SCAN_TYPE_FLY_BOOK)){ typeName = "飞书"; simpleUser = uCFeignService.getUserByFlyBookId(userid); } if(BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()){ String msg = "扫码登录失败,查无与您"+ typeName +"账号[userid:"+userid+"]绑定的eip账号"; return new CommonResult(false,msg); } String account = simpleUser.get("account").asText(); final UserDetails userDetails = userDetailsService.loadUserByUsername(account); if(BeanUtils.isNotEmpty(userDetails)) { //清除用户缓存 CacheEvictUtil.deleteUserDetailsCache(account); // Reload password post-security so we can generate the token // 当前切中的方法 HttpServletRequest request = HttpUtil.getRequest(); boolean isMobile = HttpUtil.isMobile(request); final String token = jwtTokenHandler.generateToken(userDetails); String userName = userDetails.getUsername(); String userId = ""; String tenantId = ""; if (userDetails instanceof IUser) { IUser user = ((IUser) userDetails); userName = user.getFullname(); userId = user.getUserId(); tenantId = user.getTenantId(); request.setAttribute("loginUser", String.format("%s[%s]", userName, account)); } //处理单用户登录 handleSingleLogin(isMobile, tenantId, account, token); // Return the token return new CommonResult(true,"扫码登录成功", new JwtAuthenticationResponse(token, userName, account, userId)); }else { return new CommonResult(false,"扫码登录失败!eip账号:"+account+"不存在"); } } return new CommonResult(false,"扫码登录失败 : " + (result!=null?result.getMessage():"")); } @Override public Map getSsoInfo() { Map map = new HashMap<>(); map.put("enable", ssoConfig.isEnable()); map.put("ssoUrl", ssoConfig.getSsoUrl()); map.put("ssoLogoutUrl", ssoConfig.getSsoLogoutUrl()); return map; } @Override public ResponseEntity refreshAndGetAuthenticationToken(HttpServletRequest request) { String authToken = request.getHeader(jwtConfig.getHeader()); final String token = authToken.substring(7); String tenantId = jwtTokenHandler.getTenantIdFromToken(token); String account = jwtTokenHandler.getUsernameFromToken(token); String refreshedToken = jwtTokenHandler.refreshToken(token); boolean isMobile = HttpUtil.isMobile(request); // 处理单用户登录 更新单用户登录的token handleSingleLogin(isMobile, tenantId, account, refreshedToken); long expiration = jwtConfig.getExpirationLong(); return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken, account, account, "", expiration, null)); } @Override public void signOut(HttpServletRequest request) { String authToken = request.getHeader(jwtConfig.getHeader()); final String token = authToken.substring(7); String tenantId = jwtTokenHandler.getTenantIdFromToken(token); String account = jwtTokenHandler.getUsernameFromToken(token); boolean isMobile = HttpUtil.isMobile(request); handleLogout(isMobile, tenantId, account); } // cas验证ticket并获取当前登录用户账号 private String getUserNameWithCas(String ticket, String service) throws IOException { String casUserDetail = ""; String username = null, errorCode=""; try{ casUserDetail = FluentUtil.get(String.format("%s/p3/serviceValidate?ticket=%s&service=%s", ssoConfig.getCasUrl(), ticket, service), ""); String json = XmlUtil.toJson(casUserDetail); JsonNode jsonNode = JsonUtil.toJsonNode(json); if (jsonNode.has("authenticationSuccess")) { username = jsonNode.get("authenticationSuccess").get("user").asText(); } else if (jsonNode.has("authenticationFailure")) { errorCode = jsonNode.get("authenticationFailure").get("code").asText(); throw new RuntimeException(errorCode); } }catch(Exception e){ e.printStackTrace(); logger.info("获取cas认证信息失败:" + casUserDetail); throw new RuntimeException("获取cas认证信息失败: " + e.getMessage()); } return username; } // oauth验证code并获取当前登录用户账号 private String getUserNameWithOauth(String code, String service) { String userName = null; String oauthTokenUrl = ssoConfig.getOauthTokenUrl(); String stufix = String.format("&code=%s&redirect_uri=%s", code, service); try { String header = ssoConfig.getOauthBasicHeader(); String tokenResult = FluentUtil.post(oauthTokenUrl + stufix, header, null, ContentType.APPLICATION_FORM_URLENCODED); JsonNode jsonNode = JsonUtil.toJsonNode(tokenResult); if(jsonNode!=null && jsonNode.isObject()) { String token = jsonNode.get(ssoConfig.getOauthAccesstokenKey()).asText(); String oauthCheckUrl = ssoConfig.getOauthCheckUrl(); String checkResult = FluentUtil.post(oauthCheckUrl + token, null, null, ContentType.APPLICATION_FORM_URLENCODED); JsonNode checkJNode = JsonUtil.toJsonNode(checkResult); if(checkJNode!=null && checkJNode.isObject()) { userName = checkJNode.get(ssoConfig.getOauthUsernameKey()).asText(); } } } catch(Exception e) { e.printStackTrace(); throw new RuntimeException("获取oauth认证信息失败",e); } return userName; } /** * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown */ private void authenticate(String username, String password) throws AuthenticationException, CertificateException { Objects.requireNonNull(username); Objects.requireNonNull(password); authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } private boolean checkUser(IUser user,String password) { if(!user.isAdmin()) { //非系统管理员 JsonNode json = ucFeignService.getPwdStrategyDefault(); if(BeanUtils.isNotEmpty(json)) { // 初始化密码 String initPwd = json.get("initPwd").asText(); // 密码策略 int pwdRule = json.get("pwdRule").asInt(); // 密码长度 int pwdLength = json.get("pwdLength").asInt(); // 密码可用时长 int duration = json.get("duration").asInt(); // 启用策略 0:停用,1:启用 int enable = json.get("enable").asInt(); Objects.requireNonNull(password); if(enable == 1) { if(password.equals(initPwd) && pwdRule!=1) { return false; } if(password.length() < pwdLength) { return false; } if(pwdRule!=1) { if(pwdRule == 2) {//必须包含数字、字母 String regex ="^(?![a-zA-z]+$)(?!\\d+$)(?![!@#$%^&*]+$)[a-zA-Z\\d!@#$%^&*]+$"; boolean result=password.matches(regex); if(!result) { return false; } }else if(pwdRule == 3) {//必须包含数字、字母、特殊字符 String regex = "^(?=.*?[A-Za-z])(?=.*?\\d)(?=.*?[~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/])[a-zA-Z\\d~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/]*$"; boolean result=password.matches(regex); if(!result) { return false; } }else if(pwdRule == 4) {//必须包含数字、大小字母、特殊字符 String regex = "^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)(?=.*?[~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/])[a-zA-Z\\d~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/]*$"; boolean result=password.matches(regex); if(!result) { return false; } } } //密码策略时间 LocalDateTime pwdCreateTime = user.getPwdCreateTime(); if(BeanUtils.isNotEmpty(pwdCreateTime)) { return LocalDateTime.now().isBefore(pwdCreateTime.plusDays(duration));//当前时间是否在过期时间之前 } } } } return true; } /** * 处理单用户登录 * @param isMobile * @param username * @param token */ private void handleSingleLogin(boolean isMobile,String tenantId, String username, String token){ //如果是单用户登录 if(jwtConfig.isSingle()){ String userAgent = isMobile ? "mobile" : "pc"; // 非SaaS模式 if(StringUtil.isEmpty(tenantId) && !saasConfig.isEnable()) { tenantId = "-1"; } // 以当前登录设备、租户ID、用户账号为key将token存放到缓存中 jwtTokenHandler.putTokenInCache(userAgent, tenantId, username, jwtConfig.getExpiration(), token); } //处理用户登录日志 ucFeignService.loginLog(username, isMobile ? "mobile" : "pc"); } /** * 处理用户登出 * @param tenantId * @param account */ private void handleLogout(boolean isMobile, String tenantId, String account) { //如果是单用户登录 if(jwtConfig.isSingle()){ String userAgent = isMobile ? "mobile" : "pc"; // 非SaaS模式 if(StringUtil.isEmpty(tenantId) && !saasConfig.isEnable()) { tenantId = "-1"; } jwtTokenHandler.removeFromCache(userAgent, tenantId, account); } } /** * 检查是否需要双因素校验 * @Author xiejun * @Date 2022/7/29 17:48 * @param isMobile:是否手机端 * @param userSecret:用户绑定的密钥 * @param jwtAuthenticationResponse:用户认证对象 * @return com.hotent.base.model.CommonResult **/ private CommonResult checkTwoStepVerify(boolean isMobile,String userSecret,JwtAuthenticationResponse jwtAuthenticationResponse) throws InvalidKeySpecException, NoSuchAlgorithmException { if(jwtAuthenticationResponse.getAccount().equals(SystemConstants.SYSTEM_ACCOUNT)){ return null; } boolean openForceVerify= twoVerifyService.getTwoVerifyConfig().getForceTwoVerify(); String key=isMobile ? "mobile" : "pc"+"_"+MapUtil.getString(jwtAuthenticationResponse.getUserAttrs(), "tenantId")+"_"+jwtAuthenticationResponse.getAccount(); if(StringUtil.isNotEmpty(userSecret)&&!isMobile){ //如果用户开启了二次验证 key=key+"_"+userSecret; twoVerifyService.putTempTokenInCache(key, jwtAuthenticationResponse); twoVerifyService.putTwoVerifyErrorCountInCache(key,0); key=RSAUtil.publicEncrypt(key, RSAUtil.getPublicKey(RSAUtil.publicKey)); return CommonResult.result(ResponseErrorEnums.NEED_TWO_VERIFY).value(key); }else if(openForceVerify&&!isMobile){ //如果开启了强制二次验证 TwoVerifyInfoVo twoVerifyInfoVo= twoVerifyService.initTwoVerifyInfo(jwtAuthenticationResponse.getAccount(),null); key=key+"_"+twoVerifyInfoVo.getSecret(); twoVerifyService.putTempTokenInCache(key, jwtAuthenticationResponse); key=RSAUtil.publicEncrypt(key, RSAUtil.getPublicKey(RSAUtil.publicKey)); twoVerifyInfoVo.setTempToken(key); return CommonResult.result(ResponseErrorEnums.NEED_TWO_VERIFY_BIND).value(twoVerifyInfoVo); } return null; } }