diff --git a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java index 73ce9bf39..fbb9caff9 100644 --- a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java +++ b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/endpoint/PjcloudTokenEndpoint.java @@ -31,6 +31,7 @@ import com.bsd.pjcloud.admin.api.vo.TokenVO; import com.bsd.pjcloud.common.core.constant.CacheConstants; import com.bsd.pjcloud.common.core.constant.CommonConstants; import com.bsd.pjcloud.common.core.constant.SecurityConstants; +import com.bsd.pjcloud.common.core.constant.enums.UserTypeEnum; import com.bsd.pjcloud.common.core.util.KeyStrResolver; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.core.util.RetOps; @@ -115,11 +116,13 @@ public class PjcloudTokenEndpoint { .build(); SysUser sysUser = userTokenDTO.getSysUser(); - // 构建用户信息 , 这里只拼接了 UserTokenDTO 中的 username 和 authorities ,其他字段写死,可以自行场地; - PjcloudUser pigUser = new PjcloudUser(sysUser.getUserId(),sysUser.getUsername(),sysUser.getDeptId(),sysUser.getPhone() - ,sysUser.getAvatar(),sysUser.getNickname(),sysUser.getName(), sysUser.getEmail(), sysUser.getTenantId() - , sysUser.getPassword(), true, true,"1", true,false, userTokenDTO.getAuthorities() - .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); + PjcloudUser pigUser = new PjcloudUser(sysUser.getUserId(), sysUser.getUsername(), sysUser.getDeptId(), sysUser.getPhone(), + sysUser.getAvatar(), sysUser.getNickname(), sysUser.getName(), sysUser.getEmail(), sysUser.getTenantId(), + sysUser.getPassword(), true, true, UserTypeEnum.TOB.getStatus(), + !CommonConstants.STATUS_LOCK.equals(sysUser.getPasswordExpireFlag()), + !CommonConstants.STATUS_LOCK.equals(sysUser.getLockFlag()), + userTokenDTO.getAuthorities() + .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(pigUser, StrUtil.EMPTY); diff --git a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/support/handler/PjcloudAuthenticationFailureEventHandler.java b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/support/handler/PjcloudAuthenticationFailureEventHandler.java index 4ef8f487c..fd4eacbe7 100644 --- a/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/support/handler/PjcloudAuthenticationFailureEventHandler.java +++ b/pjcloud-auth/src/main/java/com/bsd/pjcloud/auth/support/handler/PjcloudAuthenticationFailureEventHandler.java @@ -29,6 +29,7 @@ import com.bsd.pjcloud.common.data.resolver.ParamResolver; import com.bsd.pjcloud.common.log.event.SysLogEvent; import com.bsd.pjcloud.common.log.util.LogTypeEnum; import com.bsd.pjcloud.common.log.util.SysLogUtils; +import com.bsd.pjcloud.common.security.util.OAuth2ErrorCodesExpand; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -89,10 +90,11 @@ public class PjcloudAuthenticationFailureEventHandler implements AuthenticationF if (OAuth2ParameterNames.PASSWORD.equals(grantType)) { String username = request.getParameter(OAuth2ParameterNames.USERNAME); - // 密码错误记录错误次数 (TOC用户不记录) + // 密码错误记录错误次数 (TOC用户不记录;微信绑定/确认门禁不算失败) String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC); - if (exception instanceof OAuth2AuthenticationException - && !SecurityConstants.HEADER_TOC_YES.equals(header)) { + if (exception instanceof OAuth2AuthenticationException oauth2Ex + && !SecurityConstants.HEADER_TOC_YES.equals(header) + && !isWechatGateError(oauth2Ex)) { recordLoginFailureTimes(username); } @@ -131,6 +133,15 @@ public class PjcloudAuthenticationFailureEventHandler implements AuthenticationF publisher.publishEvent(new SysLogEvent(logVo)); } + /** + * PC 微信绑定/扫码确认:密码已校验通过,不应计入登录失败锁定 + */ + private boolean isWechatGateError(OAuth2AuthenticationException exception) { + String errorCode = exception.getError().getErrorCode(); + return OAuth2ErrorCodesExpand.WECHAT_BIND_REQUIRED.equals(errorCode) + || OAuth2ErrorCodesExpand.WECHAT_CONFIRM_REQUIRED.equals(errorCode); + } + /** * 记录登录失败此处,如果操作阈值 调用接口锁定用户 * diff --git a/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java b/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java index c02ada6a0..57eb4d3b7 100644 --- a/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java +++ b/pjcloud-common/pjcloud-common-security/src/main/java/com/bsd/pjcloud/common/security/component/PjcloudCustomOpaqueTokenIntrospector.java @@ -41,21 +41,16 @@ public class PjcloudCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospe if (Objects.isNull(oldAuthorization)) { throw new InvalidBearerTokenException(token); } - if (SecurityConstants.FROM.equals(oldAuthorization.getRegisteredClientId())){ - return (PjcloudUser) ((UsernamePasswordAuthenticationToken) Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName())).getPrincipal(); - } - // 客户端模式默认返回 if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) { return new PjcloudClientCredentialsOAuth2AuthenticatedPrincipal( Objects.requireNonNull(oldAuthorization.getAccessToken().getClaims()), AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName()); } - Map userDetailsServiceMap = SpringContextHolder - .getBeansOfType(PjcloudUserDetailsService.class); - - Optional optional = userDetailsServiceMap.values() + Optional optional = SpringContextHolder + .getBeansOfType(PjcloudUserDetailsService.class) + .values() .stream() .filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(), oldAuthorization.getAuthorizationGrantType().getValue())) @@ -65,8 +60,9 @@ public class PjcloudCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospe try { Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName()); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal; - Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal(); - userDetails = optional.get().loadUserByUser((PjcloudUser) tokenPrincipal); + PjcloudUser tokenPrincipal = (PjcloudUser) usernamePasswordAuthenticationToken.getPrincipal(); + // 统一从 DB/缓存重载用户(含 ROLE_*、deptId),数据权限与密码登录一致 + userDetails = optional.orElseThrow().loadUserByUser(tokenPrincipal); } catch (UsernameNotFoundException notFoundException) { log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage()); @@ -76,7 +72,6 @@ public class PjcloudCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospe log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage()); } - // 注入客户端信息,方便上下文中获取 PjcloudUser pjcloudUser = (PjcloudUser) userDetails; Objects.requireNonNull(pjcloudUser) .getAttributes() diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysSocialDetailsController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysSocialDetailsController.java index b035f2868..309adecb4 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysSocialDetailsController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysSocialDetailsController.java @@ -198,6 +198,15 @@ public class SysSocialDetailsController { return R.ok(pcWechatSocialService.confirmLogin(ticket, code)); } + /** + * PC 微信已绑定:凭 ticket 直接登录(与 confirm-login 同一 token 签发链路) + */ + @Inner(value = false) + @PostMapping("/pc/login-by-ticket") + public R> loginPcByWxTicket(String ticket) { + return R.ok(pcWechatSocialService.loginByWxPcTicket(ticket)); + } + /** * 导出 */ diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/handler/WxPcTicketLoginHandler.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/handler/WxPcTicketLoginHandler.java index 992b31884..808245643 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/handler/WxPcTicketLoginHandler.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/handler/WxPcTicketLoginHandler.java @@ -9,6 +9,7 @@ import com.bsd.pjcloud.common.core.constant.CacheConstants; import com.bsd.pjcloud.common.core.exception.CheckedException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.CacheManager; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @@ -24,6 +25,8 @@ public class WxPcTicketLoginHandler implements LoginHandler { private final SysUserService sysUserService; + private final CacheManager cacheManager; + private String ticketKey(String ticket) { return CacheConstants.WX_PC_SOCIAL_TICKET + ticket; } @@ -56,7 +59,11 @@ public class WxPcTicketLoginHandler implements LoginHandler { @Override public UserInfo handle(String loginStr) { String openid = identify(loginStr); - return info(openid); + UserInfo userInfo = info(openid); + if (userInfo != null && userInfo.getSysUser() != null) { + cacheManager.getCache(CacheConstants.USER_DETAILS).evict(userInfo.getSysUser().getUsername()); + } + return userInfo; } } diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/PcWechatSocialService.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/PcWechatSocialService.java index 646491c6f..011649ff3 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/PcWechatSocialService.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/PcWechatSocialService.java @@ -22,4 +22,9 @@ public interface PcWechatSocialService { */ com.bsd.pjcloud.admin.api.vo.WxSocialResolveVO resolveWxPcCode(String code); + /** + * PC 微信已绑定:凭 resolve 下发的 ticket 直接登录(与扫码确认同一套 token 签发) + */ + Map loginByWxPcTicket(String ticket); + } diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/PcWechatSocialServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/PcWechatSocialServiceImpl.java index 887f87d20..151d604f3 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/PcWechatSocialServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/PcWechatSocialServiceImpl.java @@ -117,14 +117,38 @@ public class PcWechatSocialServiceImpl implements PcWechatSocialService { throw new CheckedException("微信账号与绑定不一致,请使用已绑定的微信扫码"); } redisTemplate.delete(loginConfirmKey(confirmTicket)); + cacheManager.getCache(CacheConstants.USER_DETAILS).evict(user.getUsername()); + return issueToken(user); + } + + @Override + public Map loginByWxPcTicket(String ticket) { + if (StrUtil.isBlank(ticket)) { + throw new CheckedException("微信授权已过期,请重新扫码"); + } + String openid = redisTemplate.opsForValue().get(wxPcTicketKey(ticket)); + if (StrUtil.isBlank(openid)) { + throw new CheckedException("微信授权已过期,请重新扫码"); + } + redisTemplate.delete(wxPcTicketKey(ticket)); + SysUser user = sysUserService.getOne(Wrappers.lambdaQuery().eq(SysUser::getWxPcOpenid, openid)); + if (user == null) { + throw new CheckedException("该微信未绑定 PC 后台账号,请先用账号密码登录并绑定"); + } + cacheManager.getCache(CacheConstants.USER_DETAILS).evict(user.getUsername()); return issueToken(user); } private Map issueToken(SysUser user) { - UserInfo userInfo = sysUserService.findUserInfo(user); + SysUser freshUser = sysUserService.getById(user.getUserId()); + if (freshUser == null) { + throw new CheckedException("用户不存在"); + } + cacheManager.getCache(CacheConstants.USER_DETAILS).evict(freshUser.getUsername()); + UserInfo userInfo = sysUserService.findUserInfo(freshUser); UserTokenDTO userTokenDTO = new UserTokenDTO(); userTokenDTO.setSysUser(userInfo.getSysUser()); - userTokenDTO.setUsername(user.getUsername()); + userTokenDTO.setUsername(freshUser.getUsername()); userTokenDTO.setAuthorities(buildTokenAuthorities(userInfo)); R tokenResult = remoteTokenService.generateToken(userTokenDTO); if (tokenResult == null || tokenResult.getCode() != 0 || tokenResult.getData() == null) {