From 428b0b9ce4cf62dd0d80ec212cd68e94702a160e Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Thu, 9 Jul 2026 17:08:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(social):=20=E6=94=AF=E4=BB=98=E5=AE=9D?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=B9=B6=E5=85=A5=E7=BB=9F=E4=B8=80=20Social?= =?UTF-8?q?=20=E8=AE=A4=E8=AF=81=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用 AlipaySocialAuthRequest 收口非标准 OAuth,删除独立 /social/alipay 端点, 登录与绑定与标准平台共用 SocialEndpoint 编排。 Co-authored-by: Cursor --- .../social/auth/SocialAuthRequestFactory.java | 4 +- .../social/justauth/SocialSourceEnum.java | 6 +- .../resources/i18n/en-US/error/social.json | 1 - .../resources/i18n/zh-CN/error/social.json | 1 - .../service-iam/pom.xml | 2 +- .../iam/endpoint/AlipayAuthEndpoint.java | 177 ------------------ .../social/SocialLoginConfigService.java | 7 +- .../service/social/SocialLoginService.java | 17 +- .../social/other/AlipaySocialAuthRequest.java | 105 +++++++++++ .../other/AlipaySocialAuthRequestFactory.java | 28 +++ 10 files changed, 157 insertions(+), 191 deletions(-) delete mode 100644 daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/endpoint/AlipayAuthEndpoint.java create mode 100644 daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequest.java create mode 100644 daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequestFactory.java diff --git a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/auth/SocialAuthRequestFactory.java b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/auth/SocialAuthRequestFactory.java index 3c97d307a..e7fff1f69 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/auth/SocialAuthRequestFactory.java +++ b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/auth/SocialAuthRequestFactory.java @@ -35,8 +35,8 @@ public class SocialAuthRequestFactory { case FEISHU -> new FeishuRequest(config); case DINGTALK -> new DingTalkRequest(config); case DOUYIN -> new DouyinRequest(config); - // 支付宝非标准 OAuth2, 不走 JustAuth 工厂, 由 iam 模块 AlipayAuthEndpoint 独立处理 - case ALIPAY -> throw new OperationFailException("error.social.alipayUseDedicatedEndpoint"); + // 支付宝由 iam 模块 AlipaySocialAuthRequestFactory 创建, 不走本工厂 + case ALIPAY -> throw new OperationFailException("error.social.unsupportedSource"); }; } diff --git a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/justauth/SocialSourceEnum.java b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/justauth/SocialSourceEnum.java index 99012cf86..93200e4c2 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/justauth/SocialSourceEnum.java +++ b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/java/cn/daxpay/open/platform/capability/social/justauth/SocialSourceEnum.java @@ -48,10 +48,8 @@ public enum SocialSourceEnum implements I18nSupport { "https://api.dingtalk.com/v1.0/contact/users/me" ), - /// 支付宝(非标准 OAuth2, 不走 JustAuth 流程) - /// 端点地址留空, 实际授权由 iam 模块的 AlipayAuthEndpoint 独立处理(调用 alipay.system.oauth.token)。 - /// 在 [SocialAuthRequestFactory] 中会对该 source 抛出"请使用支付宝专用端点"异常, - /// 避免误走标准 OAuth2 工厂分支。 + /// 支付宝(非标准 OAuth2: alipay.system.oauth.token) + /// 端点地址留空; 授权由 iam 模块 [AlipaySocialAuthRequest] 实现, 经统一 SocialEndpoint 入口编排。 /// 排序位于钉钉之后、抖音之前。 ALIPAY("alipay", "", "", ""), diff --git a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/en-US/error/social.json b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/en-US/error/social.json index a1d437dbc..2129de026 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/en-US/error/social.json +++ b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/en-US/error/social.json @@ -4,7 +4,6 @@ "configDuplicated": "The platform configuration already exists", "notConfigured": "The platform is not configured and cannot be toggled", "authCodeBlank": "Authorization code cannot be empty", - "alipayUseDedicatedEndpoint": "Alipay uses non-standard OAuth2, please use the dedicated endpoint /social/alipay/render", "alipayNotConfigured": "Platform-level Alipay configuration is incomplete, please configure it in 'Third-party Platform Management' first", "weCom": { "notCorpMember": "Not a member of this enterprise, login is not supported" diff --git a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/zh-CN/error/social.json b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/zh-CN/error/social.json index 4e65bcd1e..51ef64667 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/zh-CN/error/social.json +++ b/daxpay-platform/daxpay-platform-capability/capability-social/src/main/resources/i18n/zh-CN/error/social.json @@ -4,7 +4,6 @@ "configDuplicated": "该平台配置已存在", "notConfigured": "平台尚未配置,无法启停", "authCodeBlank": "授权码不能为空", - "alipayUseDedicatedEndpoint": "支付宝非标准 OAuth2,请使用专用授权端点 /social/alipay/render", "alipayNotConfigured": "平台级支付宝配置不完整,请先在「三方平台管理」中配置", "weCom": { "notCorpMember": "非本企业成员,不支持登录" diff --git a/daxpay-platform/daxpay-platform-service/service-iam/pom.xml b/daxpay-platform/daxpay-platform-service/service-iam/pom.xml index 94a54c7fe..fb5fb7a71 100644 --- a/daxpay-platform/daxpay-platform-service/service-iam/pom.xml +++ b/daxpay-platform/daxpay-platform-service/service-iam/pom.xml @@ -26,7 +26,7 @@ capability-social ${project.version} - + cn.daxpay.open capability-alipay diff --git a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/endpoint/AlipayAuthEndpoint.java b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/endpoint/AlipayAuthEndpoint.java deleted file mode 100644 index af2d7515f..000000000 --- a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/endpoint/AlipayAuthEndpoint.java +++ /dev/null @@ -1,177 +0,0 @@ -package cn.daxpay.open.platform.iam.endpoint; - -import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthConfig; -import cn.daxpay.open.platform.capability.alipay.auth.result.AlipayAuthResult; -import cn.daxpay.open.platform.capability.alipay.auth.service.AlipayAuthCapability; -import cn.daxpay.open.platform.capability.auth.util.SecurityUtil; -import cn.daxpay.open.platform.capability.social.justauth.SocialSourceEnum; -import cn.daxpay.open.platform.capability.social.justauth.model.AuthUser; -import cn.daxpay.open.platform.core.annotation.IgnoreAuth; -import cn.daxpay.open.platform.core.rest.Res; -import cn.daxpay.open.platform.core.rest.result.Result; -import cn.daxpay.open.platform.core.exception.operation.OperationFailException; -import cn.daxpay.open.platform.iam.enums.SocialAuthMode; -import cn.daxpay.open.platform.iam.enums.SocialClientEnum; -import cn.daxpay.open.platform.iam.result.social.SocialExchangeResult; -import cn.daxpay.open.platform.iam.entity.social.SocialLoginConfig; -import cn.daxpay.open.platform.iam.service.social.IamSocialLoginHandler; -import cn.daxpay.open.platform.iam.service.social.IamUserSocialBindStore; -import cn.daxpay.open.platform.iam.service.social.SocialLoginConfigService; -import cn.daxpay.open.platform.system.entity.config.platform.PlatformUrlConfig; -import cn.daxpay.open.platform.system.service.config.PlatformAlipayAuthConfigService; -import cn.daxpay.open.platform.system.service.config.PlatformUrlConfigService; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/// # 支付宝授权登录端点 -/// -/// 支付宝非标准 OAuth2(走 `alipay.system.oauth.token`), 不复用 JustAuth 的 [SocialEndpoint], -/// 独立实现 render/exchange。但登录态签发、绑定关系存储复用 iam 现有体系 -/// ([IamSocialLoginHandler] / [IamUserSocialBindStore]), 绑定写入 `iam_user_social` 表(source=`alipay`)。 -/// -/// ## 流程 -/// 1. 前端调 `render` 获取授权链接(用平台级支付宝配置拼 `publicAppAuthorize.htm`) -/// 2. 前端 `location.href` 跳转, 用户在支付宝授权 -/// 3. 支付宝回调到前端 `/auth/oauth-callback/alipay?auth_code=xxx&state=xxx` -/// 4. 前端调 `exchange` 用 auth_code 换 userId, 完成登录或绑定 -/// -@Slf4j -@IgnoreAuth -@Tag(name = "支付宝授权登录") -@RestController -@RequestMapping("/social/alipay") -@RequiredArgsConstructor -public class AlipayAuthEndpoint { - - /// 支付宝在三方登录体系中的 source 编码 - private static final String SOURCE = SocialSourceEnum.ALIPAY.getCode(); - - /// 授权范围: auth_user(需用户确认, 可获取用户信息) - private static final String SCOPE = "auth_user"; - - /// 登录回调路径(前端路由, 与标准 OAuth2 平台同构: /auth/oauth-callback/{source}) - private static final String LOGIN_CALLBACK_PATH = "/auth/oauth-callback/" + SOURCE; - - /// 绑定回调路径(前端路由: /auth/social-bind-callback/{source}) - private static final String BIND_CALLBACK_PATH = "/auth/social-bind-callback/" + SOURCE; - - private final PlatformAlipayAuthConfigService platformAlipayAuthConfigService; - private final AlipayAuthCapability alipayAuthCapability; - private final PlatformUrlConfigService platformUrlConfigService; - private final SocialLoginConfigService socialLoginConfigService; - private final IamUserSocialBindStore socialBindStore; - private final IamSocialLoginHandler socialLoginHandler; - - /// 生成支付宝授权地址 - /// @param client 终端编码(admin/merchant), 用于解析端点配置中的 baseUrl - /// @param mode 授权场景(不传则按登录态判断: 已登录=绑定, 未登录=登录) - @GetMapping("/render") - public Result render(@RequestParam String client, - @RequestParam(required = false) String mode) { - // 0. 校验登录配置已启用(与标准 OAuth 平台一致) - this.requireLoginEnabled(); - // 1. 读平台级配置 - AlipayAuthConfig capabilityConfig = platformAlipayAuthConfigService.toCapabilityConfig(); - if (!alipayAuthCapability.isConfigured(capabilityConfig)) { - // 支付宝: 平台级支付宝配置不完整, 请先在「三方平台管理」中配置 - throw new OperationFailException("error.social.alipayNotConfigured"); - } - // 2. 按 client 解析前端 baseUrl - PlatformUrlConfig urlConfig = platformUrlConfigService.getUrlConfig(); - String baseUrl = SocialClientEnum.of(client).resolveBaseUrl(urlConfig); - if (StrUtil.isBlank(baseUrl)) { - // 社交登录: 端点配置缺失 - throw new OperationFailException("error.social.endpointNotConfigured"); - } - // 3. 拼回调地址(LOGIN/BIND 不同路径) - SocialAuthMode authMode = this.resolveMode(mode); - String callbackPath = authMode == SocialAuthMode.BIND ? BIND_CALLBACK_PATH : LOGIN_CALLBACK_PATH; - String redirectUri = baseUrl + callbackPath; - // 4. 生成 state(OAuth2 合规, CSRF 防护) - String state = IdUtil.fastSimpleUUID(); - // 5. 拼授权链接(平台级配置固定生产环境, 不再读取 sandbox) - String authUrl = alipayAuthCapability.generateAuthUrl(capabilityConfig, redirectUri, SCOPE, state, false); - return Res.ok(authUrl); - } - - /// 支付宝授权码兑换(LOGIN/BIND 由 mode 决定, 未传按登录态判断) - /// @param authCode 支付宝回调回传的 auth_code - /// @param state render 阶段生成的 state(原样回传, 目前仅做日志, 不做强校验) - /// @param client 终端编码 - /// @param mode 授权场景 - @PostMapping("/exchange") - public Result exchange(@RequestParam("authCode") String authCode, - @RequestParam("state") String state, - @RequestParam("client") String client, - @RequestParam(name = "mode", required = false) String mode, - HttpServletRequest request, - HttpServletResponse response) { - try { - // 0. 校验登录配置已启用 - this.requireLoginEnabled(); - // 1. 换 userId - AlipayAuthConfig capabilityConfig = platformAlipayAuthConfigService.toCapabilityConfig(); - AlipayAuthResult authResult = alipayAuthCapability.getUserId(capabilityConfig, authCode); - String alipayUserId = authResult.getUserId(); - if (StrUtil.isBlank(alipayUserId)) { - return Res.ok(new SocialExchangeResult().setError("oauth_failed")); - } - // 2. 构造 AuthUser(复用现有绑定存储, source=alipay, uuid=支付宝userId) - AuthUser authUser = new AuthUser() - .setSource(SOURCE) - .setUuid(alipayUserId) - .setNickname("支付宝用户"); - // 3. 按场景分流 - SocialAuthMode authMode = this.resolveMode(mode); - if (authMode == SocialAuthMode.BIND) { - // 绑定场景: 必须已登录 - Long userId = SecurityUtil.getUserId(); - socialBindStore.saveBind(userId, client, authUser); - return Res.ok(new SocialExchangeResult().setResult("bind_success")); - } - // 登录场景: 查绑定关系 - Long userId = socialBindStore.findUserIdBySourceAndOpenId(SOURCE, alipayUserId).orElse(null); - if (userId == null) { - // 未绑定 - return Res.ok(new SocialExchangeResult().setError("unbind")); - } - String token = socialLoginHandler.login(userId, client, SOURCE, request, response); - return Res.ok(new SocialExchangeResult().setToken(token)); - } catch (Exception e) { - log.error("支付宝授权登录兑换失败: authCode={}, msg={}", authCode, e.getMessage(), e); - return Res.ok(new SocialExchangeResult().setError("oauth_failed")); - } - } - - /// 解析授权场景(未传 mode 时按登录态判断) - private SocialAuthMode resolveMode(String mode) { - if (StrUtil.isNotBlank(mode)) { - try { - return SocialAuthMode.valueOf(mode.toUpperCase()); - } catch (IllegalArgumentException ignored) { - } - } - // 默认: 已登录走绑定, 未登录走登录 - boolean login = SecurityUtil.isLogin(); - return login ? SocialAuthMode.BIND : SocialAuthMode.LOGIN; - } - - /// 校验支付宝登录已在「三方平台登录配置」中启用 - private void requireLoginEnabled() { - SocialLoginConfig config = socialLoginConfigService.findEnabledBySource(SOURCE); - if (config == null) { - // 社交登录: 平台配置不存在或未启用 - throw new OperationFailException("error.social.configNotExist"); - } - } -} diff --git a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginConfigService.java b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginConfigService.java index 8d5257056..f2222299c 100644 --- a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginConfigService.java +++ b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginConfigService.java @@ -15,6 +15,7 @@ import cn.daxpay.open.platform.iam.result.social.SocialEnabledPlatformResult; import cn.daxpay.open.platform.capability.social.justauth.SocialAuthConfig; import cn.daxpay.open.platform.capability.social.justauth.SocialSourceEnum; import cn.daxpay.open.platform.core.exception.operation.OperationFailException; +import cn.daxpay.open.platform.iam.service.social.other.AlipaySocialAuthRequest; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; @@ -30,7 +31,7 @@ import org.springframework.stereotype.Service; /// 配置表 source 全局唯一, 占位记录仅插入一次. /// /// **平台级跳转型**(如支付宝): 凭据在独立平台配置中维护, 本表仅存 enabled 占位; -/// 「是否启用」与平台凭据解耦——可先启用登录入口, 凭据是否齐全在发起授权时由专用端点校验。 +/// 「是否启用」与平台凭据解耦——可先启用登录入口, 凭据是否齐全在发起授权时由 [AlipaySocialAuthRequest] 校验。 /// @Slf4j @Service @@ -135,8 +136,8 @@ public class SocialLoginConfigService { socialLoginConfigManager.updateById(entity); } - /// 根据平台来源查询已配置且启用的配置(供 SocialAuthRequestFactory / 支付宝端点使用) - /// 仅看本表 enabled+configured; 平台凭据完整性由专用端点在发起授权时校验。 + /// 根据平台来源查询已配置且启用的配置(供 SocialLoginService 授权编排使用) + /// 仅看本表 enabled+configured; 平台凭据完整性由 AlipaySocialAuthRequest 在发起授权时校验。 public SocialLoginConfig findEnabledBySource(String source) { return socialLoginConfigManager.findEnabledBySource(source).orElse(null); } diff --git a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginService.java b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginService.java index 95c11ed62..68630b494 100644 --- a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginService.java +++ b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/SocialLoginService.java @@ -16,6 +16,8 @@ import cn.daxpay.open.platform.iam.result.social.SocialBindResult; import cn.daxpay.open.platform.iam.result.social.SocialEnabledPlatformResult; import cn.daxpay.open.platform.iam.result.social.SocialExchangeResult; import cn.daxpay.open.platform.iam.enums.SocialAuthMode; +import cn.daxpay.open.platform.iam.service.social.other.AlipaySocialAuthRequest; +import cn.daxpay.open.platform.iam.service.social.other.AlipaySocialAuthRequestFactory; import cn.daxpay.open.platform.system.entity.config.platform.PlatformUrlConfig; import cn.daxpay.open.platform.system.service.config.PlatformUrlConfigService; import cn.hutool.core.util.IdUtil; @@ -33,6 +35,7 @@ import org.springframework.stereotype.Service; /// 前端拿到 code+state 后调用 exchange 接口完成换 token. /// state 仅用于 OAuth2 合规(CSRF), 不携带业务上下文. /// 登录和绑定的区分由回调页路由决定(两个不同的前端页面), exchange 时显式传 source + client. +/// 支付宝非标准 OAuth 经 [AlipaySocialAuthRequest] 收口, 对外与标准平台共用本服务入口. /// @Slf4j @Service @@ -47,6 +50,8 @@ public class SocialLoginService { private final SocialAuthRequestFactory socialAuthRequestFactory; + private final AlipaySocialAuthRequestFactory alipaySocialAuthRequestFactory; + private final IamUserSocialBindStore socialBindStore; private final IamSocialLoginHandler socialLoginHandler; @@ -84,7 +89,7 @@ public class SocialLoginService { String redirectUri = this.buildRedirectUri(baseUrl, authMode); // 构建授权请求 SocialAuthConfig authConfig = socialLoginConfigService.buildAuthConfig(config, redirectUri); - SocialAuthRequest request = socialAuthRequestFactory.create(socialSource, authConfig); + SocialAuthRequest request = this.createAuthRequest(socialSource, authConfig); // state 仅用于 OAuth2 合规, 不缓存业务上下文 String state = IdUtil.fastSimpleUUID(); return request.authorize(state); @@ -157,10 +162,18 @@ public class SocialLoginService { if (socialSource == null) { throw new OperationFailException("error.social.unsupportedSource"); } - SocialAuthRequest authRequest = socialAuthRequestFactory.create(socialSource, authConfig); + SocialAuthRequest authRequest = this.createAuthRequest(socialSource, authConfig); return authRequest.login(AuthCallback.of(code, state)); } + /// 按平台创建授权请求: 支付宝走 iam 侧 [AlipaySocialAuthRequest], 其余走 JustAuth 工厂 + private SocialAuthRequest createAuthRequest(SocialSourceEnum socialSource, SocialAuthConfig authConfig) { + if (socialSource == SocialSourceEnum.ALIPAY) { + return alipaySocialAuthRequestFactory.create(authConfig); + } + return socialAuthRequestFactory.create(socialSource, authConfig); + } + /// 解析授权场景(未传 mode 时按登录态判断) private SocialAuthMode resolveMode(String mode) { if (StrUtil.isNotBlank(mode)) { diff --git a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequest.java b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequest.java new file mode 100644 index 000000000..19f6088a0 --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequest.java @@ -0,0 +1,105 @@ +package cn.daxpay.open.platform.iam.service.social.other; + +import cn.daxpay.open.platform.capability.alipay.auth.config.AlipayAuthConfig; +import cn.daxpay.open.platform.capability.alipay.auth.result.AlipayAuthResult; +import cn.daxpay.open.platform.capability.alipay.auth.service.AlipayAuthCapability; +import cn.daxpay.open.platform.capability.social.justauth.SocialAuthConfig; +import cn.daxpay.open.platform.capability.social.justauth.SocialSourceEnum; +import cn.daxpay.open.platform.capability.social.justauth.exception.SocialException; +import cn.daxpay.open.platform.capability.social.justauth.model.AuthCallback; +import cn.daxpay.open.platform.capability.social.justauth.model.AuthToken; +import cn.daxpay.open.platform.capability.social.justauth.model.AuthUser; +import cn.daxpay.open.platform.capability.social.justauth.request.SocialAuthRequest; +import cn.daxpay.open.platform.core.exception.operation.OperationFailException; +import cn.daxpay.open.platform.iam.service.social.SocialLoginService; +import cn.daxpay.open.platform.system.service.config.PlatformAlipayAuthConfigService; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; + +/// # 支付宝社交授权请求 +/// +/// 实现 [SocialAuthRequest], 将支付宝非标准 OAuth(`alipay.system.oauth.token`) +/// 收口进统一的 Social 编排([SocialLoginService]), 对外与 JustAuth 平台同入口。 +/// 凭据来自平台级 [PlatformAlipayAuthConfigService], 不使用 [SocialAuthConfig] 的 clientId/secret; +/// 公钥/证书模式差异封装在 [AlipayAuthCapability] 内, 本类不分支 authType。 +/// +@RequiredArgsConstructor +public class AlipaySocialAuthRequest implements SocialAuthRequest { + + /// 授权范围: auth_user(需用户确认, 可获取用户信息) + private static final String SCOPE = "auth_user"; + + private final SocialAuthConfig config; + + private final AlipayAuthCapability alipayAuthCapability; + + private final PlatformAlipayAuthConfigService platformAlipayAuthConfigService; + + /// 生成支付宝授权地址(仅用平台配置 appId + redirectUri + state) + @Override + public String authorize(String state) { + AlipayAuthConfig capabilityConfig = this.requireCapabilityConfig(); + // redirectUri 与标准平台一致: 基础 path + /{source} + String redirectUri = this.buildRedirectUri(); + // 平台级配置固定生产环境 + return alipayAuthCapability.generateAuthUrl(capabilityConfig, redirectUri, SCOPE, state, false); + } + + /// 用授权码换 AuthUser(前端已将 auth_code 归一为 code) + @Override + public AuthUser login(AuthCallback callback) { + if (StrUtil.isBlank(callback.getCode())) { + // 授权码不能为空 + throw new SocialException("error.social.authCodeBlank"); + } + AlipayAuthConfig capabilityConfig = this.requireCapabilityConfig(); + AlipayAuthResult authResult = alipayAuthCapability.getUserId(capabilityConfig, callback.getCode()); + String alipayUserId = authResult.getUserId(); + if (StrUtil.isBlank(alipayUserId)) { + throw new SocialException("error.social.oauthFailed"); + } + AuthToken token = new AuthToken() + .setAccessToken(authResult.getAccessToken()) + .setOpenId(authResult.getOpenId()) + .setUnionId(alipayUserId); + return new AuthUser() + .setSource(SocialSourceEnum.ALIPAY.getCode()) + .setUuid(alipayUserId) + .setNickname("支付宝用户") + .setToken(token); + } + + /// 支付宝换用户在 [login] 内一次完成, 不单独暴露 accessToken 步骤 + @Override + public AuthToken getAccessToken(AuthCallback callback) { + throw new SocialException("error.social.unsupportedSource"); + } + + /// 支付宝换用户在 [login] 内一次完成, 不单独暴露 userInfo 步骤 + @Override + public AuthUser getUserInfo(AuthToken token) { + throw new SocialException("error.social.unsupportedSource"); + } + + /// 加载并校验平台级支付宝配置完整可用 + private AlipayAuthConfig requireCapabilityConfig() { + AlipayAuthConfig capabilityConfig = platformAlipayAuthConfigService.toCapabilityConfig(); + if (!alipayAuthCapability.isConfigured(capabilityConfig)) { + // 支付宝: 平台级支付宝配置不完整, 请先在「三方平台管理」中配置 + throw new OperationFailException("error.social.alipayNotConfigured"); + } + return capabilityConfig; + } + + /// 构建回调地址(配置基础路径 + 平台编码), 与 [AbstractSocialAuthRequest#buildRedirectUri] 对齐 + private String buildRedirectUri() { + String base = config.getRedirectUri(); + if (base == null) { + base = ""; + } + if (base.endsWith("/")) { + base = base.substring(0, base.length() - 1); + } + return base + "/" + SocialSourceEnum.ALIPAY.getCode(); + } +} diff --git a/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequestFactory.java b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequestFactory.java new file mode 100644 index 000000000..4c1fce93c --- /dev/null +++ b/daxpay-platform/daxpay-platform-service/service-iam/src/main/java/cn/daxpay/open/platform/iam/service/social/other/AlipaySocialAuthRequestFactory.java @@ -0,0 +1,28 @@ +package cn.daxpay.open.platform.iam.service.social.other; + +import cn.daxpay.open.platform.capability.alipay.auth.service.AlipayAuthCapability; +import cn.daxpay.open.platform.capability.social.justauth.SocialAuthConfig; +import cn.daxpay.open.platform.capability.social.justauth.request.SocialAuthRequest; +import cn.daxpay.open.platform.iam.service.social.SocialLoginService; +import cn.daxpay.open.platform.system.service.config.PlatformAlipayAuthConfigService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/// # 支付宝社交授权请求工厂 +/// +/// 在 iam 模块创建 [AlipaySocialAuthRequest], 避免 capability-social 反向依赖 capability-alipay。 +/// 由 [SocialLoginService] 在 source=alipay 时委托本工厂, 其余平台仍走 [SocialAuthRequestFactory]。 +/// +@Component +@RequiredArgsConstructor +public class AlipaySocialAuthRequestFactory { + + private final AlipayAuthCapability alipayAuthCapability; + + private final PlatformAlipayAuthConfigService platformAlipayAuthConfigService; + + /// 创建支付宝授权请求(凭据在请求内部从平台配置读取, config 仅提供 redirectUri) + public SocialAuthRequest create(SocialAuthConfig config) { + return new AlipaySocialAuthRequest(config, alipayAuthCapability, platformAlipayAuthConfigService); + } +}