This commit is contained in:
dzc committed 2026-06-25 18:27:07 +08:00
commit a8b788c8e8
7 files changed
+76 -22

No files matched your search

@@ -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);
@@ -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);
}
/**
* 记录登录失败此处,如果操作阈值 调用接口锁定用户
*
@@ -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<String, PjcloudUserDetailsService> userDetailsServiceMap = SpringContextHolder
.getBeansOfType(PjcloudUserDetailsService.class);
Optional<PjcloudUserDetailsService> optional = userDetailsServiceMap.values()
Optional<PjcloudUserDetailsService> 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()
@@ -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<java.util.Map<String, Object>> loginPcByWxTicket(String ticket) {
return R.ok(pcWechatSocialService.loginByWxPcTicket(ticket));
}
/**
* 导出
*/
@@ -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;
}
}
@@ -22,4 +22,9 @@ public interface PcWechatSocialService {
*/
com.bsd.pjcloud.admin.api.vo.WxSocialResolveVO resolveWxPcCode(String code);
/**
* PC 微信已绑定:凭 resolve 下发的 ticket 直接登录(与扫码确认同一套 token 签发)
*/
Map<String, Object> loginByWxPcTicket(String ticket);
}
@@ -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<String, Object> 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.<SysUser>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<String, Object> 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) {