后台登录微信二次认证

This commit is contained in:
zjf committed 2026-06-22 17:32:13 +08:00
1 parent 10d6900777
commit 39878e0aa5
21 files changed
+701 -5

No files matched your search

@@ -0,0 +1,120 @@
package com.bsd.pjcloud.auth.support;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.bsd.pjcloud.admin.api.dto.UserInfo;
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.core.util.RetOps;
import com.bsd.pjcloud.common.data.resolver.ParamResolver;
import com.bsd.pjcloud.common.security.exception.WechatBindRequiredException;
import com.bsd.pjcloud.common.security.exception.WechatConfirmRequiredException;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* PC 后台(pig 客户端)密码登录:可选绑定 / 扫码确认 PC 微信
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PcWechatPasswordLoginGate {
private static final String PC_PASSWORD_CLIENT_ID = "pig";
private static final String PARAM_BIND_REQUIRED = "PC_WECHAT_BIND_REQUIRED";
private static final String PARAM_CONFIRM_REQUIRED = "PC_WECHAT_CONFIRM_REQUIRED";
private static final long TICKET_TTL_MINUTES = 15;
private final RemoteUserService remoteUserService;
private final StringRedisTemplate redisTemplate;
private final PcWechatSecurityProperties pcWechatSecurityProperties;
public void check(RegisteredClient registeredClient, Authentication authentication) {
if (registeredClient == null || !PC_PASSWORD_CLIENT_ID.equals(registeredClient.getClientId())) {
return;
}
if (!isBindRequired() && !isConfirmRequired()) {
return;
}
Object principal = authentication.getPrincipal();
if (!(principal instanceof PjcloudUser pjcloudUser)) {
return;
}
R<UserInfo> userResult = remoteUserService.getInnerByUserId(pjcloudUser.getId());
UserInfo userInfo = RetOps.of(userResult).getData().orElse(null);
if (userInfo == null || userInfo.getSysUser() == null) {
return;
}
String wxPcOpenid = userInfo.getSysUser().getWxPcOpenid();
String ticket = IdUtil.simpleUUID();
if (StrUtil.isBlank(wxPcOpenid)) {
if (!isBindRequired() && !isConfirmRequired()) {
return;
}
// 未绑定:只要开启了绑定或扫码确认,均须先扫码绑定
redisTemplate.opsForValue().set(pwdVerifiedKey(ticket), String.valueOf(pjcloudUser.getId()), TICKET_TTL_MINUTES,
TimeUnit.MINUTES);
throw new WechatBindRequiredException(ticket);
}
if (!isConfirmRequired()) {
return;
}
redisTemplate.opsForValue().set(loginConfirmKey(ticket), String.valueOf(pjcloudUser.getId()), TICKET_TTL_MINUTES,
TimeUnit.MINUTES);
throw new WechatConfirmRequiredException(ticket);
}
private boolean isBindRequired() {
return resolveFlag(PARAM_BIND_REQUIRED, pcWechatSecurityProperties.isBindRequired());
}
private boolean isConfirmRequired() {
return resolveFlag(PARAM_CONFIRM_REQUIRED, pcWechatSecurityProperties.isConfirmRequired());
}
private boolean resolveFlag(String paramKey, boolean defaultValue) {
try {
String value = ParamResolver.getStr(paramKey);
if (StrUtil.isNotBlank(value)) {
return parseBool(value);
}
}
catch (Exception e) {
log.debug("读取公共参数 {} 失败,使用配置文件默认值", paramKey);
}
return defaultValue;
}
private boolean parseBool(String value) {
if ("1".equals(value) || "Y".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value)) {
return true;
}
if ("0".equals(value) || "N".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value)) {
return false;
}
return BooleanUtil.toBoolean(value);
}
private String pwdVerifiedKey(String ticket) {
return CacheConstants.PC_PWD_VERIFIED + ticket;
}
private String loginConfirmKey(String ticket) {
return CacheConstants.PC_LOGIN_CONFIRM + ticket;
}
}
@@ -0,0 +1,36 @@
package com.bsd.pjcloud.auth.support;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* PC 后台密码登录微信校验(开发/生产可关)
* <p>
* 配置示例(pjcloud-auth application.yml 或 Nacos):
* <pre>
* security:
* pc-wechat:
* bind-required: false
* confirm-required: false
* </pre>
* 也可在「参数管理」覆盖:PC_WECHAT_BIND_REQUIRED、PC_WECHAT_CONFIRM_REQUIRED(true/false 或 1/0)
*/
@Data
@Component
@RefreshScope
@ConfigurationProperties("security.pc-wechat")
public class PcWechatSecurityProperties {
/**
* 密码登录后未绑定 PC 微信时是否拦截(须先扫码绑定)
*/
private boolean bindRequired = false;
/**
* 已绑定 PC 微信时,密码登录是否须微信扫码确认
*/
private boolean confirmRequired = false;
}
@@ -2,7 +2,10 @@ package com.bsd.pjcloud.auth.support.base;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.bsd.pjcloud.auth.support.PcWechatPasswordLoginGate;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
import com.bsd.pjcloud.common.security.exception.WechatBindRequiredException;
import com.bsd.pjcloud.common.security.exception.WechatConfirmRequiredException;
import com.bsd.pjcloud.common.security.service.PjcloudRedisOAuth2AuthorizationService;
import com.bsd.pjcloud.common.security.util.OAuth2ErrorCodesExpand;
import com.bsd.pjcloud.common.security.util.ScopeException;
@@ -148,6 +151,10 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OA
Authentication usernamePasswordAuthentication = authenticationManager
.authenticate(usernamePasswordAuthenticationToken);
if (AuthorizationGrantType.PASSWORD.equals(resouceOwnerBaseAuthentication.getAuthorizationGrantType())) {
SpringUtil.getBean(PcWechatPasswordLoginGate.class).check(registeredClient, usernamePasswordAuthentication);
}
Object onlineQuantity = registeredClient.getClientSettings()
.getSettings()
.get(CommonConstants.ONLINE_QUANTITY);
@@ -303,6 +310,14 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OA
"User credentials have expired"),
""));
}
if (authenticationException instanceof WechatBindRequiredException bindEx) {
return new OAuth2AuthenticationException(
new OAuth2Error(OAuth2ErrorCodesExpand.WECHAT_BIND_REQUIRED, bindEx.getTicket(), ""));
}
if (authenticationException instanceof WechatConfirmRequiredException confirmEx) {
return new OAuth2AuthenticationException(
new OAuth2Error(OAuth2ErrorCodesExpand.WECHAT_CONFIRM_REQUIRED, confirmEx.getTicket(), ""));
}
if (authenticationException instanceof ScopeException) {
return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "invalid_scope"), ""));