diff --git a/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java b/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java index 0729dfe8..8bbd2755 100644 --- a/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java +++ b/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.springblade.auth.constant.BladeAuthConstant; +import org.springblade.auth.support.MpLoginRequestSupport; import org.springblade.core.oauth2.props.OAuth2Properties; import org.springblade.core.oauth2.provider.OAuth2Request; import org.springblade.core.oauth2.service.OAuth2User; @@ -39,7 +40,6 @@ import org.springblade.core.tool.utils.SM2Util; import org.springblade.core.tool.utils.StringUtil; import org.springblade.resource.feign.ISmsClient; import org.springblade.resource.utils.SmsUtil; -import org.springblade.system.pojo.enums.MpUserIdentity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -91,11 +91,9 @@ public class Oauth2SmsEndpoint { // buildArgs:带上 User-Type 等请求头,小程序传 User-Type=mp 时查 blade_mp_user OAuth2Request request = OAuth2Request.create().buildArgs(); request.setTenantId(tenantId); - // 「手机号是否已注册」只对 Web 保留。小程序端必须允许未注册手机号收码, - // 否则新号走不到「验证码校验通过后自动建号」那一步(票 13)。 - // 该处是全链路唯一的防短信轰炸点,放开小程序端不等于放开 Web; - // 这里只做查询,发码路径不建号,账号仍只在登录时创建。 - if (!isMpLoginRequest(request)) { + // 「手机号是否已注册」只对 Web 校验;小程序端未注册手机号也发码,账号在登录时创建。 + // 发码路径只做查询,不建号。 + if (!MpLoginRequestSupport.isMp(request)) { OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request); if (oAuth2User == null) { return R.fail(USER_PHONE_NOT_FOUND); @@ -107,11 +105,4 @@ public class Oauth2SmsEndpoint { return result.isSuccess() ? R.data(result.getData(), SmsUtil.SEND_SUCCESS) : R.fail(SmsUtil.SEND_FAIL); } - /** - * 是否小程序请求:请求头 User-Type=mp,与登录链路同一判据 - */ - private boolean isMpLoginRequest(OAuth2Request request) { - return request != null && MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(request.getUserType())); - } - } diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java index b79f8497..cd01f6ef 100644 --- a/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java +++ b/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java @@ -27,7 +27,8 @@ package org.springblade.auth.granter; import jakarta.servlet.http.HttpServletRequest; import org.springblade.auth.constant.BladeAuthConstant; -import org.springblade.auth.service.MpLoginSupport; +import org.springblade.auth.support.MpLoginRequestSupport; +import org.springblade.auth.support.MpLoginSupport; import org.springblade.core.oauth2.constant.OAuth2TokenConstant; import org.springblade.core.oauth2.exception.UserInvalidException; import org.springblade.core.oauth2.granter.AbstractTokenGranter; @@ -44,15 +45,13 @@ import org.springblade.core.tool.utils.SM2Util; import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.WebUtil; import org.springblade.resource.feign.ISmsClient; -import org.springblade.system.pojo.enums.MpUserIdentity; import org.springframework.stereotype.Component; /** * SmsTokenGranter *
- * 除验证码校验外,还要按请求来源分流:小程序(User-Type: mp)登录时账号不存在要自动建,
- * Web 短信登录保持原样不建号。建号逻辑不能下沉到 {@code BladeUserDetailService.loadByPhone},
- * 那条方法还被「发送短信验证码」复用,会在发码时就建出账号(刷号)。
+ * 按请求来源分流:小程序(User-Type: mp)登录时账号不存在要自动建,Web 短信登录不建号。
+ * {@code BladeUserDetailService.loadByPhone} 同时被「发送短信验证码」复用,其中不得建号。
*
* @author BladeX
*/
@@ -94,8 +93,8 @@ public class SmsTokenGranter extends AbstractTokenGranter {
if (!result.isSuccess()) {
throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
}
- // 获取用户信息:只有小程序登录才允许自动建号,Web 短信登录仍走原查询
- OAuth2User user = isMpLoginRequest(request)
+ // 获取用户信息:小程序登录走自动建号编排,Web 短信登录只做查询
+ OAuth2User user = MpLoginRequestSupport.isMp(request)
? mpLoginSupport.ensureLoginAccount(tenantId, decryptedPhone, null)
: userService.loadByPhone(decryptedPhone, request);
// 校验用户信息
@@ -107,13 +106,6 @@ public class SmsTokenGranter extends AbstractTokenGranter {
return user;
}
- /**
- * 是否小程序登录:请求头 User-Type=mp(Web 与小程序共用客户端,不能按 clientId 分流)
- */
- private boolean isMpLoginRequest(OAuth2Request request) {
- return request != null && MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(request.getUserType()));
- }
-
private SmsCode buildSmsCode() {
HttpServletRequest request = WebUtil.getRequest();
return new SmsCode().setId(request.getParameter("id"))
diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java
index 101387c8..9d2bb75c 100644
--- a/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java
+++ b/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java
@@ -2,7 +2,7 @@ package org.springblade.auth.granter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
-import org.springblade.auth.service.MpLoginSupport;
+import org.springblade.auth.support.MpLoginSupport;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
@@ -79,7 +79,7 @@ public class WechatMiniTokenGranter extends AbstractTokenGranter {
throw new UserInvalidException("未获取到微信手机号");
}
- // 账号不存在时自动建号,建档与绑 openid 由编排层尽力而为,失败不阻断登录
+ // 账号不存在时自动建号;建档与绑 openid 失败不阻断登录
OAuth2User user = mpLoginSupport.ensureLoginAccount(request.getTenantId(), phone, session.getOpenid());
if (!userService.validateUser(user)) {
throw new UserInvalidException("用户身份无效,无法登录");
diff --git a/blade-auth/src/main/java/org/springblade/auth/service/BladeUserDetailService.java b/blade-auth/src/main/java/org/springblade/auth/service/BladeUserDetailService.java
index 22904efe..10f5d6b7 100644
--- a/blade-auth/src/main/java/org/springblade/auth/service/BladeUserDetailService.java
+++ b/blade-auth/src/main/java/org/springblade/auth/service/BladeUserDetailService.java
@@ -23,6 +23,7 @@
package org.springblade.auth.service;
import lombok.RequiredArgsConstructor;
+import org.springblade.auth.support.MpLoginRequestSupport;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.provider.OAuth2Request;
@@ -36,7 +37,6 @@ import org.springblade.system.feign.IMpUserClient;
import org.springblade.system.feign.IUserClient;
import org.springblade.system.pojo.entity.MpUserInfo;
import org.springblade.system.pojo.entity.UserInfo;
-import org.springblade.system.pojo.enums.MpUserIdentity;
import org.springblade.system.pojo.enums.UserType;
import java.util.Optional;
@@ -54,7 +54,7 @@ public class BladeUserDetailService implements OAuth2UserService {
@Override
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
// 小程序刷新/鉴权:User-Type=mp 时只查 blade_mp_user
- if (isMpLoginRequest(request)) {
+ if (MpLoginRequestSupport.isMp(request)) {
OAuth2User mpUser = loadMpUserById(userId);
if (mpUser == null) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
@@ -83,7 +83,7 @@ public class BladeUserDetailService implements OAuth2UserService {
String tenantId = request.getTenantId();
// 小程序账号密码 / 图形验证码登录:仅查 blade_mp_user
- if (isMpLoginRequest(request)) {
+ if (MpLoginRequestSupport.isMp(request)) {
R
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.auth.support;
+
+import org.springblade.core.oauth2.provider.OAuth2Request;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.pojo.enums.MpUserIdentity;
+
+/**
+ * 登录请求来源判定
+ *
+ * @author Chill
+ */
+public final class MpLoginRequestSupport {
+
+ private MpLoginRequestSupport() {
+ }
+
+ /**
+ * 是否小程序请求:请求头 User-Type 为 mp
+ *
+ * Web 与小程序共用同一个客户端,来源只认请求头 User-Type。
+ *
+ * @param request 登录请求,可为 null
+ * @return 请求头 User-Type 为 mp 时返回 true
+ */
+ public static boolean isMp(OAuth2Request request) {
+ return request != null && MpUserIdentity.DETAIL_TYPE.equalsIgnoreCase(Func.toStr(request.getUserType()));
+ }
+
+}
diff --git a/blade-auth/src/main/java/org/springblade/auth/service/MpLoginSupport.java b/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java
similarity index 87%
rename from blade-auth/src/main/java/org/springblade/auth/service/MpLoginSupport.java
rename to blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java
index 6032ebeb..1a48b334 100644
--- a/blade-auth/src/main/java/org/springblade/auth/service/MpLoginSupport.java
+++ b/blade-auth/src/main/java/org/springblade/auth/support/MpLoginSupport.java
@@ -23,7 +23,7 @@
*
* Author: Chill Zhuang (bladejava@qq.com)
*/
-package org.springblade.auth.service;
+package org.springblade.auth.support;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -47,10 +47,10 @@ import org.springframework.stereotype.Component;
import java.util.Objects;
/**
- * 小程序登录编排:按 openid / 手机号找到或创建账号,并尽力补齐司机档案与微信绑定。
+ * 小程序登录编排:按 openid / 手机号找到或创建账号,并补齐司机档案与微信绑定。
*
- * 只有「建号失败」会让登录失败;建档与绑 openid 都是尽力而为,失败只记 WARN,
- * 下次登录自愈。复制这段判断到别处前先确认调用方不是登录路径。
+ * 只有「建号失败」会让登录失败;建档与绑 openid 失败只记 WARN,不影响本次登录。
+ * 本类只供登录链路调用。
*
* @author Chill
*/
@@ -65,7 +65,7 @@ public class MpLoginSupport {
public static final String DETAIL_LOGIN_NOTICE = "loginNotice";
/**
- * 换号撞键:openid 属于旧账号、新手机号已属于另一账号(票 05 Q2-A)
+ * 换号撞键提示:openid 属于旧账号、新手机号已属于另一账号
*/
private static final String NOTICE_PHONE_OCCUPIED = "该微信已绑定其他账号,请联系管理员";
@@ -73,11 +73,10 @@ public class MpLoginSupport {
private final IDriverClient driverClient;
/**
- * 找到或创建本次登录使用的账号,并尽力补齐档案与 openid 绑定。
+ * 找到或创建本次登录使用的账号,并补齐档案与 openid 绑定。
*
- * 返回 {@link OAuth2User} 而不是 {@link MpUserInfo}:两条登录链路都需要 OAuth2User,
- * 且 MpUserInfo 的唯一组装点(identities / permissions / detail)在 system 侧,
- * 由本类统一转换可避免 auth 侧复制一份 detail 口径。
+ * 返回 {@link OAuth2User}:两条登录链路都消费 OAuth2User,而 MpUserInfo
+ * (identities / permissions / detail)只在 system 侧组装。
*
* @param tenantId 请求租户,为空时由 system 侧兜到默认租户
* @param phone 本次登录的手机号
@@ -102,8 +101,7 @@ public class MpLoginSupport {
if (!samePhone(openidAccount.getPhone(), phone)) {
MpUser phoneAccount = findByPhone(tenantId, phone);
if (phoneAccount != null && !Objects.equals(phoneAccount.getId(), openidAccount.getId())) {
- // ①-撞键:落到手机号账号,不迁移 openid。以 openid 账号为准会把用户
- // 正在用的手机号账号顶掉,自动合并又要动档案归属(票 05)
+ // ①-撞键:落到手机号账号,不迁移 openid
account = phoneAccount;
loginNotice = NOTICE_PHONE_OCCUPIED;
openidOccupied = true;
@@ -128,12 +126,12 @@ public class MpLoginSupport {
}
}
}
- // ③ 身份含 driver 才建档;纯 admin 不建,避免污染司机列表与派单池(票 03)
+ // ③ 身份含 driver 才建档;纯 admin 不建
String profileNotice = ensureProfile(account, phone, tenantId);
if (StringUtil.isBlank(loginNotice)) {
loginNotice = profileNotice;
}
- // ④ 微信登录补绑 openid;失败只 WARN(票 05)
+ // ④ 微信登录补绑 openid;失败只 WARN
bindOpenid(account, openid, openidOccupied);
MpUserInfo userInfo = loadUserInfo(account);
attachNotice(userInfo, loginNotice);
@@ -148,8 +146,7 @@ public class MpLoginSupport {
R
* 只 patch phone:account 是登录名,不随手机号变;status 必须带原值,
- * 否则 system 侧 updateUser 的 prepare 会把空 status 重置成启用,等于解开停用账号。
+ * system 侧 updateUser 的 prepare 会把空 status 重置成启用。
*
* @return 更新成功后的账号;失败返回 null,不抛异常
*/
@@ -230,8 +227,8 @@ public class MpLoginSupport {
if (account == null || !MpUserIdentity.has(account.getIdentities(), MpUserIdentity.DRIVER)) {
return null;
}
- // 租户与手机号优先取账号上的值:档案必须与账号落同一租户(唯一键对 NULL 不去重),
- // 手机号更新失败时也不能拿新号去建,否则档案和账号对不上
+ // 租户与手机号取账号上的值:档案必须与账号落同一租户(唯一键对 NULL 不去重);
+ // 账号手机号未能更新时,不使用入参手机号建档
String tenantId = Func.toStr(account.getTenantId(), requestedTenantId);
String phone = Func.toStr(account.getPhone(), requestedPhone);
DriverEnsureProfileDTO param = new DriverEnsureProfileDTO();
@@ -313,7 +310,7 @@ public class MpLoginSupport {
if (userInfo == null || StringUtil.isBlank(loginNotice)) {
return;
}
- // detail 是否会下发到前端取决于令牌序列化,故冲突点同时都记了 WARN
+ // detail 是否下发到前端取决于令牌序列化,冲突点同时另记 WARN
Kv detail = userInfo.getDetail() == null ? Kv.create() : userInfo.getDetail();
detail.set(DETAIL_LOGIN_NOTICE, loginNotice);
userInfo.setDetail(detail);
diff --git a/blade-auth/src/test/java/org/springblade/auth/service/MpLoginSupportTest.java b/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java
similarity index 98%
rename from blade-auth/src/test/java/org/springblade/auth/service/MpLoginSupportTest.java
rename to blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java
index 98617200..4518e344 100644
--- a/blade-auth/src/test/java/org/springblade/auth/service/MpLoginSupportTest.java
+++ b/blade-auth/src/test/java/org/springblade/auth/support/MpLoginSupportTest.java
@@ -1,4 +1,4 @@
-package org.springblade.auth.service;
+package org.springblade.auth.support;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -35,7 +35,7 @@ import static org.mockito.Mockito.when;
/**
* 登录编排测试。
*
- * 守住票 02 / 03 / 05 的三条口径:建号失败才阻断登录;建档失败放行;
+ * 保住三条不变式:建号失败才阻断登录;建档失败放行;
* openid 换号撞键时落到手机号账号且不迁移 openid。
*/
class MpLoginSupportTest {