mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-08 06:05:33 +08:00
feat(social): 应用内自动登录多选并拆分微信公众号与开放平台
按端配置 sources[] 并由 UA 匹配静默授权;weChat 走平台级公众号凭据,weChatOpen 保留扫码登录配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import cn.daxpay.open.platform.capability.social.justauth.request.QqRequest;
|
||||
import cn.daxpay.open.platform.capability.social.justauth.request.SocialAuthRequest;
|
||||
import cn.daxpay.open.platform.capability.social.justauth.request.WeComRequest;
|
||||
import cn.daxpay.open.platform.capability.social.justauth.request.WechatMpRequest;
|
||||
import cn.daxpay.open.platform.capability.social.justauth.request.WechatOpenRequest;
|
||||
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;
|
||||
@@ -31,6 +32,7 @@ public class SocialAuthRequestFactory {
|
||||
case GOOGLE -> new GoogleRequest(config);
|
||||
case QQ -> new QqRequest(config);
|
||||
case WECHAT_MP -> new WechatMpRequest(config);
|
||||
case WECHAT_OPEN -> new WechatOpenRequest(config);
|
||||
case WE_COM -> new WeComRequest(config);
|
||||
case FEISHU -> new FeishuRequest(config);
|
||||
case DINGTALK -> new DingTalkRequest(config);
|
||||
|
||||
@@ -25,4 +25,9 @@ public class SocialAuthConfig {
|
||||
|
||||
/// QQ: 是否申请 unionId
|
||||
private boolean unionId;
|
||||
|
||||
/// 静默/应用内授权模式
|
||||
/// 企业微信: true 走网页授权(oauth2/authorize), false 走扫码(qrConnect)
|
||||
/// 微信公众号: true 使用 snsapi_base, false 使用 snsapi_userinfo
|
||||
private boolean silent;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum SocialSourceEnum implements I18nSupport {
|
||||
|
||||
/// 微信开放平台
|
||||
/// 微信公众号(网页授权, 凭据在平台级 wechat_mp_auth, 本枚举仅作登录启停占位)
|
||||
WECHAT_MP(
|
||||
"weChat",
|
||||
"https://open.weixin.qq.com/connect/oauth2/authorize",
|
||||
@@ -23,7 +23,15 @@ public enum SocialSourceEnum implements I18nSupport {
|
||||
"https://api.weixin.qq.com/sns/userinfo"
|
||||
),
|
||||
|
||||
/// 企业微信(企业自建应用扫码)
|
||||
/// 微信开放平台(网站应用扫码登录, 凭据在 iam_social_login_config)
|
||||
WECHAT_OPEN(
|
||||
"weChatOpen",
|
||||
"https://open.weixin.qq.com/connect/qrconnect",
|
||||
"https://api.weixin.qq.com/sns/oauth2/access_token",
|
||||
"https://api.weixin.qq.com/sns/userinfo"
|
||||
),
|
||||
|
||||
/// 企业微信(企业自建应用: 扫码 + 网页授权)
|
||||
WE_COM(
|
||||
"weCom",
|
||||
"https://open.work.weixin.qq.com/wwopen/sso/qrConnect",
|
||||
@@ -142,13 +150,13 @@ public enum SocialSourceEnum implements I18nSupport {
|
||||
|
||||
/// 是否"平台级跳转型"配置
|
||||
///
|
||||
/// 此类平台(支付宝)非标准 OAuth2 或配置较重,
|
||||
/// 此类平台非标准 OAuth2 或配置较重,
|
||||
/// 不在 iam_social_login_config 表内存 clientId/clientSecret,
|
||||
/// 而是使用独立的平台级配置(EncryptPlatformConfigTypeEnum / 独立表)。
|
||||
/// 前端登录配置抽屉据此隐藏 clientId/clientSecret, 仅保留启用开关与「前往配置凭据」入口。
|
||||
///
|
||||
/// 新增此类平台时在此方法追加判断。
|
||||
public boolean isPlatformRedirect() {
|
||||
return this == ALIPAY;
|
||||
return this == ALIPAY || this == WECHAT_MP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,36 @@ import cn.daxpay.open.platform.capability.social.justauth.model.AuthUser;
|
||||
import cn.daxpay.open.platform.capability.social.justauth.util.SocialUrlBuilder;
|
||||
import cn.hutool.json.JSONObject;
|
||||
|
||||
/// # 企业微信(企业自建应用扫码)授权登录
|
||||
/// # 企业微信(企业自建应用)授权登录
|
||||
///
|
||||
/// 默认扫码登录(qrConnect); 当 [SocialAuthConfig#isSilent] 为 true 时改走网页授权,
|
||||
/// 供企业微信内置浏览器应用内自动登录使用. 两种模式换票与用户标识(UserId)一致.
|
||||
///
|
||||
public class WeComRequest extends AbstractSocialAuthRequest {
|
||||
|
||||
/// 企业微信网页授权地址(应用内)
|
||||
private static final String WEB_OAUTH_AUTHORIZE =
|
||||
"https://open.weixin.qq.com/connect/oauth2/authorize";
|
||||
|
||||
public WeComRequest(SocialAuthConfig config) {
|
||||
super(config, SocialSourceEnum.WE_COM);
|
||||
}
|
||||
|
||||
/// 企业微信授权地址使用 appid(corpid) 和 agentid
|
||||
/// 企业微信授权地址: silent=网页授权, 否则扫码
|
||||
@Override
|
||||
public String authorize(String state) {
|
||||
if (this.getConfig().isSilent()) {
|
||||
// 应用内网页授权(snsapi_base 静默拿 UserId)
|
||||
return SocialUrlBuilder.ofBaseUrl(WEB_OAUTH_AUTHORIZE)
|
||||
.queryParam("appid", this.getConfig().getClientId())
|
||||
.queryParam("redirect_uri", this.encode(this.buildRedirectUri()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", "snsapi_base")
|
||||
.queryParam("agentid", this.getConfig().getAgentId())
|
||||
.queryParam("state", state.concat("#wechat_redirect"))
|
||||
.build();
|
||||
}
|
||||
// PC 扫码登录
|
||||
return SocialUrlBuilder.ofBaseUrl(this.getSource().authorize())
|
||||
.queryParam("appid", this.getConfig().getClientId())
|
||||
.queryParam("agentid", this.getConfig().getAgentId())
|
||||
|
||||
@@ -19,13 +19,15 @@ public class WechatMpRequest extends AbstractSocialAuthRequest {
|
||||
}
|
||||
|
||||
/// 微信授权地址使用 appid 参数, 且 state 末尾需追加 #wechat_redirect
|
||||
/// silent 时使用 snsapi_base(静默仅 openId), 否则 snsapi_userinfo
|
||||
@Override
|
||||
public String authorize(String state) {
|
||||
String scope = this.getConfig().isSilent() ? "snsapi_base" : "snsapi_userinfo";
|
||||
return SocialUrlBuilder.ofBaseUrl(this.getSource().authorize())
|
||||
.queryParam("appid", this.getConfig().getClientId())
|
||||
.queryParam("redirect_uri", this.encode(this.buildRedirectUri()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", "snsapi_userinfo")
|
||||
.queryParam("scope", scope)
|
||||
.queryParam("state", state.concat("#wechat_redirect"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.daxpay.open.platform.capability.social.justauth.request;
|
||||
|
||||
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.util.SocialUrlBuilder;
|
||||
import cn.hutool.json.JSONObject;
|
||||
|
||||
/// # 微信开放平台(网站应用)扫码登录
|
||||
///
|
||||
/// 使用 qrconnect + snsapi_login, 与公众号网页授权([WechatMpRequest])分离。
|
||||
///
|
||||
public class WechatOpenRequest extends AbstractSocialAuthRequest {
|
||||
|
||||
public WechatOpenRequest(SocialAuthConfig config) {
|
||||
super(config, SocialSourceEnum.WECHAT_OPEN);
|
||||
}
|
||||
|
||||
/// 开放平台网站应用扫码授权
|
||||
@Override
|
||||
public String authorize(String state) {
|
||||
return SocialUrlBuilder.ofBaseUrl(this.getSource().authorize())
|
||||
.queryParam("appid", this.getConfig().getClientId())
|
||||
.queryParam("redirect_uri", this.encode(this.buildRedirectUri()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", "snsapi_login")
|
||||
.queryParam("state", state)
|
||||
.build() + "#wechat_redirect";
|
||||
}
|
||||
|
||||
/// 用 code 换 accessToken, 同时拿到 openid
|
||||
@Override
|
||||
public AuthToken getAccessToken(AuthCallback callback) {
|
||||
String url = SocialUrlBuilder.ofBaseUrl(this.getSource().accessToken())
|
||||
.queryParam("appid", this.getConfig().getClientId())
|
||||
.queryParam("secret", this.getConfig().getClientSecret())
|
||||
.queryParam("code", callback.getCode())
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.build();
|
||||
JSONObject object = this.parseObj(this.doGet(url));
|
||||
this.checkResponse(object);
|
||||
return new AuthToken()
|
||||
.setAccessToken(object.getStr("access_token"))
|
||||
.setRefreshToken(object.getStr("refresh_token"))
|
||||
.setExpireIn(object.getInt("expires_in", 0))
|
||||
.setOpenId(object.getStr("openid"))
|
||||
.setUnionId(object.getStr("unionid"))
|
||||
.setScope(object.getStr("scope"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthUser getUserInfo(AuthToken token) {
|
||||
String url = SocialUrlBuilder.ofBaseUrl(this.getSource().userInfo())
|
||||
.queryParam("access_token", token.getAccessToken())
|
||||
.queryParam("openid", token.getOpenId())
|
||||
.queryParam("lang", "zh_CN")
|
||||
.build();
|
||||
JSONObject object = this.parseObj(this.doGet(url));
|
||||
this.checkResponse(object);
|
||||
if (object.containsKey("unionid")) {
|
||||
token.setUnionId(object.getStr("unionid"));
|
||||
}
|
||||
return new AuthUser()
|
||||
.setUuid(token.getOpenId())
|
||||
.setUsername(object.getStr("nickname"))
|
||||
.setNickname(object.getStr("nickname"))
|
||||
.setAvatar(object.getStr("headimgurl"))
|
||||
.setSource(this.getSourceName())
|
||||
.setToken(token);
|
||||
}
|
||||
|
||||
/// 校验微信响应(errcode 不为 0 表示错误)
|
||||
private void checkResponse(JSONObject object) {
|
||||
if (object.containsKey("errcode") && object.getInt("errcode", 0) != 0) {
|
||||
throw new SocialException(object.getStr("errmsg"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "You have already bound this platform, please unbind first",
|
||||
"clientMismatch": "Current user does not belong to this client, binding is not allowed"
|
||||
},
|
||||
"oauthFailed": "Third-party authorization failed"
|
||||
"oauthFailed": "Third-party authorization failed",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "Select at least one platform when auto-login is enabled",
|
||||
"sourceNotEnabled": "Auto-login platform is not configured or disabled: {0}",
|
||||
"sourceNotAllowed": "Platform does not support in-app auto-login: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "Anda sudah mengikat platform ini, silakan unbind terlebih dahulu",
|
||||
"clientMismatch": "Pengguna saat ini bukan milik klien ini, pengikatan tidak diperbolehkan"
|
||||
},
|
||||
"oauthFailed": "Otorisasi pihak ketiga gagal"
|
||||
"oauthFailed": "Otorisasi pihak ketiga gagal",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "Pilih setidaknya satu platform saat auto-login diaktifkan",
|
||||
"sourceNotEnabled": "Platform auto-login belum dikonfigurasi atau dinonaktifkan: {0}",
|
||||
"sourceNotAllowed": "Platform tidak mendukung auto-login dalam aplikasi: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"configNotExist": "プラットフォーム設定が存在しないか無効です",
|
||||
"unsupportedSource": "未対応のプラットフォームです",
|
||||
"unsupportedClient": "ソーシャルログインは管理端(admin)と加盟店端(merchant)のみ対応しています",
|
||||
"configDuplicated": "このプラットフォーム設定は既に存在します",
|
||||
"notConfigured": "プラットフォーム未設定のため有効/無効を切り替えできません",
|
||||
"authCodeBlank": "認可コードは空にできません",
|
||||
"endpointNotConfigured": "エンドポイント設定にフロントのアクセスURLがなく、コールバックURLを生成できません",
|
||||
"adminEndpointNotConfigured": "先にエンドポイント設定で「管理端アクセスURL」(adminBaseUrl)を設定してください",
|
||||
"merchantEndpointNotConfigured": "先にエンドポイント設定で「加盟店端アクセスURL」(merchantBaseUrl)を設定してください",
|
||||
"alipayNotConfigured": "プラットフォーム級のAlipay設定が不完全です。「三方平台管理」で設定してください",
|
||||
"wechatMpNotConfigured": "プラットフォーム級のWeChat公式アカウント設定が不完全です。「三方平台管理」で設定してください",
|
||||
"douyinH5NotConfigured": "プラットフォーム級のDouyin H5アプリ設定が不完全です。「三方平台管理」で設定してください",
|
||||
"weCom": {
|
||||
"notCorpMember": "本企業のメンバーではないためログインできません"
|
||||
},
|
||||
"bind": {
|
||||
"alreadyBoundByOther": "この外部アカウントは他のユーザーに紐付け済みです",
|
||||
"alreadyBoundPlatform": "既にこのプラットフォームを紐付けています。先に解除してください",
|
||||
"clientMismatch": "現在のユーザーはこの端末に属していないため紐付けできません"
|
||||
},
|
||||
"oauthFailed": "外部認可に失敗しました",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "自動ログインを有効にする場合はプラットフォームを1つ以上選択してください",
|
||||
"sourceNotEnabled": "選択した自動ログインプラットフォームが未設定または無効です: {0}",
|
||||
"sourceNotAllowed": "このプラットフォームはアプリ内自動ログインに対応していません: {0}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"configNotExist": "플랫폼 구성이 없거나 비활성화되어 있습니다",
|
||||
"unsupportedSource": "지원하지 않는 플랫폼입니다",
|
||||
"unsupportedClient": "소셜 로그인은 운영단(admin)과 가맹점단(merchant)만 지원합니다",
|
||||
"configDuplicated": "해당 플랫폼 구성이 이미 존재합니다",
|
||||
"notConfigured": "플랫폼이 구성되지 않아 활성/비활성할 수 없습니다",
|
||||
"authCodeBlank": "인가 코드는 비울 수 없습니다",
|
||||
"endpointNotConfigured": "엔드포인트 구성에 프론트 접속 URL이 없어 콜백 URL을 생성할 수 없습니다",
|
||||
"adminEndpointNotConfigured": "먼저 엔드포인트 구성에서 「운영단 접속 주소」(adminBaseUrl)를 설정하세요",
|
||||
"merchantEndpointNotConfigured": "먼저 엔드포인트 구성에서 「가맹점단 접속 주소」(merchantBaseUrl)를 설정하세요",
|
||||
"alipayNotConfigured": "플랫폼급 Alipay 구성이 불완전합니다. 「삼자 플랫폼 관리」에서 설정하세요",
|
||||
"wechatMpNotConfigured": "플랫폼급 WeChat 공식계정 구성이 불완전합니다. 「삼자 플랫폼 관리」에서 설정하세요",
|
||||
"douyinH5NotConfigured": "플랫폼급 Douyin H5 앱 구성이 불완전합니다. 「삼자 플랫폼 관리」에서 설정하세요",
|
||||
"weCom": {
|
||||
"notCorpMember": "본 기업 구성원이 아니어서 로그인할 수 없습니다"
|
||||
},
|
||||
"bind": {
|
||||
"alreadyBoundByOther": "해당 소셜 계정이 다른 사용자에게 연결되어 있습니다",
|
||||
"alreadyBoundPlatform": "이미 이 플랫폼을 연결했습니다. 먼저 연결을 해제하세요",
|
||||
"clientMismatch": "현재 사용자는 해당 단말에 속하지 않아 연결할 수 없습니다"
|
||||
},
|
||||
"oauthFailed": "소셜 인가에 실패했습니다",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "자동 로그인 사용 시 플랫폼을 하나 이상 선택하세요",
|
||||
"sourceNotEnabled": "선택한 자동 로그인 플랫폼이 미구성이거나 비활성입니다: {0}",
|
||||
"sourceNotAllowed": "해당 플랫폼은 인앱 자동 로그인을 지원하지 않습니다: {0}"
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "Anda telah mengikat platform ini, sila buka ikatan dahulu",
|
||||
"clientMismatch": "Pengguna semasa bukan milik klien ini, pengikatan tidak dibenarkan"
|
||||
},
|
||||
"oauthFailed": "Kebenaran pihak ketiga gagal"
|
||||
"oauthFailed": "Kebenaran pihak ketiga gagal",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "Pilih sekurang-kurangnya satu platform apabila log masuk auto diaktifkan",
|
||||
"sourceNotEnabled": "Platform log masuk auto belum dikonfigurasi atau dilumpuhkan: {0}",
|
||||
"sourceNotAllowed": "Platform tidak menyokong log masuk auto dalam aplikasi: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "คุณได้ผูกมัดแพลตฟอร์มนี้แล้ว โปรดยกเลิกการผูกก่อน",
|
||||
"clientMismatch": "ผู้ใช้ปัจจุบันไม่ได้เป็นของลูกค้ารายนี้ ไม่อนุญาตให้มีการผูกข้อมูล"
|
||||
},
|
||||
"oauthFailed": "การอนุญาตของบุคคลที่สามล้มเหลว"
|
||||
"oauthFailed": "การอนุญาตของบุคคลที่สามล้มเหลว",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "เมื่อเปิดใช้งานเข้าสู่ระบบอัตโนมัติต้องเลือกอย่างน้อยหนึ่งแพลตฟอร์ม",
|
||||
"sourceNotEnabled": "แพลตฟอร์มเข้าสู่ระบบอัตโนมัติยังไม่ได้ตั้งค่าหรือปิดอยู่: {0}",
|
||||
"sourceNotAllowed": "แพลตฟอร์มนี้ไม่รองรับการเข้าสู่ระบบอัตโนมัติในแอป: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "Bạn đã ràng buộc nền tảng này, vui lòng hủy liên kết trước",
|
||||
"clientMismatch": "Người dùng hiện tại không thuộc về khách hàng này, không được phép ràng buộc"
|
||||
},
|
||||
"oauthFailed": "Ủy quyền bên thứ ba thất bại"
|
||||
"oauthFailed": "Ủy quyền bên thứ ba thất bại",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "Khi bật tự động đăng nhập phải chọn ít nhất một nền tảng",
|
||||
"sourceNotEnabled": "Nền tảng tự động đăng nhập chưa cấu hình hoặc đã tắt: {0}",
|
||||
"sourceNotAllowed": "Nền tảng không hỗ trợ tự động đăng nhập trong ứng dụng: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "您已绑定该平台,请先解绑后再绑定新账号",
|
||||
"clientMismatch": "当前登录用户不属于该终端,无法完成绑定"
|
||||
},
|
||||
"oauthFailed": "三方授权失败"
|
||||
"oauthFailed": "三方授权失败",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "开启自动登录时必须至少选择一个平台",
|
||||
"sourceNotEnabled": "所选自动登录平台未配置或未启用: {0}",
|
||||
"sourceNotAllowed": "该平台不支持应用内自动登录: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "您已綁定該平台,請先解綁後再綁定新賬號",
|
||||
"clientMismatch": "當前登錄用户不屬於該終端,無法完成綁定"
|
||||
},
|
||||
"oauthFailed": "三方授權失敗"
|
||||
"oauthFailed": "三方授權失敗",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "開啟自動登入時必須至少選擇一個平台",
|
||||
"sourceNotEnabled": "所選自動登入平台未配置或未啟用: {0}",
|
||||
"sourceNotAllowed": "該平台不支援應用內自動登入: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,10 @@
|
||||
"alreadyBoundPlatform": "您已繫結該平臺,請先解綁後再繫結新賬號",
|
||||
"clientMismatch": "當前登入使用者不屬於該終端,無法完成繫結"
|
||||
},
|
||||
"oauthFailed": "三方授權失敗"
|
||||
"oauthFailed": "三方授權失敗",
|
||||
"autoLogin": {
|
||||
"sourceRequired": "開啟自動登入時必須至少選擇一個平台",
|
||||
"sourceNotEnabled": "所選自動登入平台未配置或未啟用: {0}",
|
||||
"sourceNotAllowed": "該平台不支援應用內自動登入: {0}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "Sensitive word policy",
|
||||
"api_security": "API Security Config",
|
||||
"pay_security": "Pay Security Config",
|
||||
"iam_replay_protect": "IAM Anti-Replay Config"
|
||||
"iam_replay_protect": "IAM Anti-Replay Config",
|
||||
"security_social_auto": "In-app social auto login"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "Feishu",
|
||||
"dingTalk": "DingTalk",
|
||||
"douyin": "Douyin",
|
||||
"alipay": "Alipay"
|
||||
"alipay": "Alipay",
|
||||
"weChatOpen": "WeChat Open Platform"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"api_security": "Konfigurasi Keamanan API",
|
||||
"iam_replay_protect": "Konfigurasi Anti-Replay IAM",
|
||||
"sensitive_word": "Kebijakan kata sensitif",
|
||||
"pay_security": "Konfigurasi keamanan pembayaran"
|
||||
"pay_security": "Konfigurasi keamanan pembayaran",
|
||||
"security_social_auto": "Login otomatis sosial in-app"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "Feishu",
|
||||
"dingTalk": "DingTalk",
|
||||
"douyin": "Douyin",
|
||||
"alipay": "Alipay"
|
||||
"alipay": "Alipay",
|
||||
"weChatOpen": "Platform Terbuka WeChat"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "センシティブワード設定",
|
||||
"api_security": "APIセキュリティ設定",
|
||||
"pay_security": "決済セキュリティ設定",
|
||||
"iam_replay_protect": "IAMドメインリプレイ防止設定"
|
||||
"iam_replay_protect": "IAMドメインリプレイ防止設定",
|
||||
"security_social_auto": "アプリ内ソーシャル自動ログイン設定"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "フェイシュ",
|
||||
"dingTalk": "ディントーク",
|
||||
"douyin": "抖音",
|
||||
"alipay": "アリペイ"
|
||||
"alipay": "アリペイ",
|
||||
"weChatOpen": "WeChatオープンプラットフォーム"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "민감어 정책 설정",
|
||||
"api_security": "API 보안 설정",
|
||||
"pay_security": "결제 보안 설정",
|
||||
"iam_replay_protect": "IAM 도메인 재생 방지 설정"
|
||||
"iam_replay_protect": "IAM 도메인 재생 방지 설정",
|
||||
"security_social_auto": "인앱 소셜 자동 로그인 설정"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "페이슈",
|
||||
"dingTalk": "딩톡",
|
||||
"douyin": "두음",
|
||||
"alipay": "알리페이"
|
||||
"alipay": "알리페이",
|
||||
"weChatOpen": "위챗 오픈 플랫폼"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"api_security": "Konfigurasi Keselamatan API",
|
||||
"iam_replay_protect": "Konfigurasi Anti-Main Semula IAM",
|
||||
"sensitive_word": "Konfigurasi perkataan sensitif",
|
||||
"pay_security": "Konfigurasi keselamatan pembayaran"
|
||||
"pay_security": "Konfigurasi keselamatan pembayaran",
|
||||
"security_social_auto": "Konfigurasi log masuk sosial auto dalam app"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "Feishu",
|
||||
"dingTalk": "DingTalk",
|
||||
"douyin": "Douyin",
|
||||
"alipay": "Alipay"
|
||||
"alipay": "Alipay",
|
||||
"weChatOpen": "Platform Terbuka WeChat"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"api_security": "การกำหนดค่าความปลอดภัย API",
|
||||
"iam_replay_protect": "การกำหนดค่าการป้องกันการเล่นซ้ำ IAM",
|
||||
"sensitive_word": "การกำหนดค่าคำที่ละเอียดอ่อน",
|
||||
"pay_security": "การกำหนดค่าความปลอดภัยการชำระเงิน"
|
||||
"pay_security": "การกำหนดค่าความปลอดภัยการชำระเงิน",
|
||||
"security_social_auto": "ตั้งค่าเข้าสู่ระบบโซเชียลอัตโนมัติในแอป"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"weChat": "บัญชีอย่างเป็นทางการของ WeChat",
|
||||
"weChat": "บัญชีทางการ WeChat",
|
||||
"weCom": "วีคอม",
|
||||
"qq": "คิวคิว",
|
||||
"github": "GitHub",
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "เฟยซู",
|
||||
"dingTalk": "ติงทอล์ค",
|
||||
"douyin": "โต่วอิน",
|
||||
"alipay": "อาลีเพย์"
|
||||
"alipay": "อาลีเพย์",
|
||||
"weChatOpen": "แพลตฟอร์มเปิด WeChat"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"api_security": "Cấu hình bảo mật API",
|
||||
"iam_replay_protect": "Cấu hình chống phát lại IAM",
|
||||
"sensitive_word": "Cấu hình từ nhạy cảm",
|
||||
"pay_security": "Cấu hình bảo mật thanh toán"
|
||||
"pay_security": "Cấu hình bảo mật thanh toán",
|
||||
"security_social_auto": "Cấu hình đăng nhập xã hội tự động trong app"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"weChat": "Tài khoản chính thức của WeChat",
|
||||
"weChat": "Tài khoản chính thức WeChat",
|
||||
"weCom": "WeCom",
|
||||
"qq": "QQ",
|
||||
"github": "GitHub",
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "Feishu",
|
||||
"dingTalk": "DingTalk",
|
||||
"douyin": "Douyin",
|
||||
"alipay": "Alipay"
|
||||
"alipay": "Alipay",
|
||||
"weChatOpen": "Nền tảng mở WeChat"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "敏感词策略配置",
|
||||
"api_security": "API安全配置",
|
||||
"pay_security": "支付安全配置",
|
||||
"iam_replay_protect": "IAM域防重放配置"
|
||||
"iam_replay_protect": "IAM域防重放配置",
|
||||
"security_social_auto": "应用内社交自动登录配置"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"weChat": "微信开放平台",
|
||||
"weChat": "微信公众号",
|
||||
"weCom": "企业微信",
|
||||
"qq": "QQ",
|
||||
"github": "GitHub",
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "飞书",
|
||||
"dingTalk": "钉钉",
|
||||
"douyin": "抖音",
|
||||
"alipay": "支付宝"
|
||||
"alipay": "支付宝",
|
||||
"weChatOpen": "微信开放平台"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "敏感詞策略配置",
|
||||
"api_security": "API安全配置",
|
||||
"pay_security": "支付安全配置",
|
||||
"iam_replay_protect": "IAM域防重放配置"
|
||||
"iam_replay_protect": "IAM域防重放配置",
|
||||
"security_social_auto": "應用內社交自動登入配置"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"weChat": "微信開放平台",
|
||||
"weChat": "微信公眾號",
|
||||
"weCom": "企業微信",
|
||||
"qq": "QQ",
|
||||
"github": "GitHub",
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "飛書",
|
||||
"dingTalk": "釘釘",
|
||||
"douyin": "抖音",
|
||||
"alipay": "支付寶"
|
||||
"alipay": "支付寶",
|
||||
"weChatOpen": "微信開放平台"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"sensitive_word": "敏感詞策略配置",
|
||||
"api_security": "API安全配置",
|
||||
"pay_security": "支付安全配置",
|
||||
"iam_replay_protect": "IAM域防重放配置"
|
||||
"iam_replay_protect": "IAM域防重放配置",
|
||||
"security_social_auto": "應用內社交自動登入配置"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"weChat": "微信開放平臺",
|
||||
"weChat": "微信公眾號",
|
||||
"weCom": "企業微信",
|
||||
"qq": "QQ",
|
||||
"github": "GitHub",
|
||||
@@ -8,5 +8,6 @@
|
||||
"feishu": "飛書",
|
||||
"dingTalk": "釘釘",
|
||||
"douyin": "抖音",
|
||||
"alipay": "支付寶"
|
||||
"alipay": "支付寶",
|
||||
"weChatOpen": "微信開放平臺"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ import cn.daxpay.open.platform.core.enums.client.ClientEnum;
|
||||
import cn.daxpay.open.platform.iam.exception.auth.ApplicationNotFoundException;
|
||||
import cn.daxpay.open.platform.iam.param.auth.LoginContentParam;
|
||||
import cn.daxpay.open.platform.iam.result.auth.LoginContentResult;
|
||||
import cn.daxpay.open.platform.iam.service.social.SocialAutoLoginConfigService;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformLoginSecurityConfig;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformSocialAutoLoginConfig;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -21,14 +24,17 @@ public class LoginContentService {
|
||||
|
||||
private final IamSecurityConfigService iamSecurityConfigService;
|
||||
|
||||
private final SocialAutoLoginConfigService socialAutoLoginConfigService;
|
||||
|
||||
/// 获取登录页上下文信息
|
||||
public LoginContentResult getLoginContent(LoginContentParam param) {
|
||||
if (param != null && param.getClientId() != null && !param.getClientId().isBlank()) {
|
||||
ClientEnum.findByCode(param.getClientId())
|
||||
String clientCode = param != null ? param.getClientId() : null;
|
||||
if (StrUtil.isNotBlank(clientCode)) {
|
||||
ClientEnum.findByCode(clientCode)
|
||||
.orElseThrow(ApplicationNotFoundException::new);
|
||||
}
|
||||
PlatformLoginSecurityConfig loginSecurity = iamSecurityConfigService.getLoginSecurity();
|
||||
return new LoginContentResult()
|
||||
LoginContentResult result = new LoginContentResult()
|
||||
.setLoginTypes(authenticators.stream()
|
||||
.map(Authenticator::getLoginType)
|
||||
.distinct()
|
||||
@@ -36,6 +42,17 @@ public class LoginContentService {
|
||||
// 是否启用验证码触发(登录失败达阈值后要求输入验证码)
|
||||
.setEnableCaptcha(Boolean.TRUE.equals(loginSecurity.getCaptchaEnabled()))
|
||||
.setPasswordEncrypted(false);
|
||||
// 按本端下发自动登录片段(无 client 时不下发, 保持关闭)
|
||||
if (StrUtil.isNotBlank(clientCode)) {
|
||||
PlatformSocialAutoLoginConfig.ClientAutoLogin auto =
|
||||
socialAutoLoginConfigService.resolveForClient(clientCode);
|
||||
result.setAutoSocialLogin(new LoginContentResult.AutoSocialLogin()
|
||||
.setEnabled(Boolean.TRUE.equals(auto.getEnabled()))
|
||||
.setSources(auto.resolveSources()));
|
||||
} else {
|
||||
result.setAutoSocialLogin(new LoginContentResult.AutoSocialLogin().setEnabled(false));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package cn.daxpay.open.platform.iam.controller.social;
|
||||
import cn.daxpay.open.platform.iam.param.social.SocialLoginConfigParam;
|
||||
import cn.daxpay.open.platform.iam.result.social.SocialCallbackUrlResult;
|
||||
import cn.daxpay.open.platform.iam.result.social.SocialLoginConfigResult;
|
||||
import cn.daxpay.open.platform.iam.service.social.SocialAutoLoginConfigService;
|
||||
import cn.daxpay.open.platform.iam.service.social.SocialLoginConfigService;
|
||||
import cn.daxpay.open.platform.iam.service.social.SocialLoginService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.code.PermCodes;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import cn.daxpay.open.platform.system.param.config.security.PlatformSocialAutoLoginConfigParam;
|
||||
import cn.daxpay.open.platform.system.result.config.security.PlatformSocialAutoLoginConfigResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -37,6 +40,8 @@ public class SocialLoginConfigController {
|
||||
|
||||
private final SocialLoginService socialLoginService;
|
||||
|
||||
private final SocialAutoLoginConfigService socialAutoLoginConfigService;
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "全量查询平台配置(枚举驱动, 读时初始化缺失平台)")
|
||||
@GetMapping("/find-all")
|
||||
@@ -76,4 +81,20 @@ public class SocialLoginConfigController {
|
||||
socialLoginConfigService.updateEnabled(source, enabled);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
/// 应用内自动登录策略(按 admin/merchant 分端)
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "获取应用内社交自动登录配置")
|
||||
@GetMapping("/auto-login/get")
|
||||
public Result<PlatformSocialAutoLoginConfigResult> getAutoLoginConfig() {
|
||||
return Res.ok(socialAutoLoginConfigService.findConfig());
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "更新应用内社交自动登录配置")
|
||||
@PostMapping("/auto-login/update")
|
||||
public Result<Void> updateAutoLoginConfig(@RequestBody @Validated PlatformSocialAutoLoginConfigParam param) {
|
||||
socialAutoLoginConfigService.updateConfig(param);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ public class SocialEndpoint {
|
||||
@GetMapping("/render/{source}")
|
||||
public Result<String> render(@PathVariable String source,
|
||||
@RequestParam String client,
|
||||
@RequestParam(required = false) String mode) {
|
||||
return Res.ok(socialLoginService.generateAuthorizeUrl(source, client, mode));
|
||||
@RequestParam(required = false) String mode,
|
||||
@RequestParam(required = false) Boolean silent) {
|
||||
return Res.ok(socialLoginService.generateAuthorizeUrl(source, client, mode, silent));
|
||||
}
|
||||
|
||||
/// OAuth 授权码兑换 - 登录(公开, 无需认证)
|
||||
|
||||
@@ -23,4 +23,20 @@ public class LoginContentResult {
|
||||
@Schema(description = "密码是否加密传输")
|
||||
private boolean passwordEncrypted;
|
||||
|
||||
/// 本端应用内社交自动登录策略(已按 client 裁剪且交叉校验社交平台启用态)
|
||||
@Schema(description = "应用内社交自动登录")
|
||||
private AutoSocialLogin autoSocialLogin;
|
||||
|
||||
/// 单端自动登录下发片段
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "应用内社交自动登录")
|
||||
public static class AutoSocialLogin {
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private boolean enabled;
|
||||
|
||||
@Schema(description = "可自动跳转的社交平台编码列表(前端按 UA 匹配其一)")
|
||||
private List<String> sources;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package cn.daxpay.open.platform.iam.service.social;
|
||||
|
||||
import cn.daxpay.open.platform.capability.social.justauth.SocialSourceEnum;
|
||||
import cn.daxpay.open.platform.core.enums.client.ClientEnum;
|
||||
import cn.daxpay.open.platform.core.exception.operation.OperationFailException;
|
||||
import cn.daxpay.open.platform.iam.entity.social.SocialLoginConfig;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformSocialAutoLoginConfig;
|
||||
import cn.daxpay.open.platform.system.param.config.security.PlatformSocialAutoLoginConfigParam;
|
||||
import cn.daxpay.open.platform.system.result.config.security.PlatformSocialAutoLoginConfigResult;
|
||||
import cn.daxpay.open.platform.system.service.config.security.PlatformSocialAutoLoginConfigService;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/// # 应用内社交自动登录编排服务
|
||||
///
|
||||
/// 在平台配置读写之上叠加社交平台启用校验, 供运营配置页与登录上下文使用.
|
||||
///
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SocialAutoLoginConfigService {
|
||||
|
||||
/// 支持应用内 UA 静默授权的平台(不含开放平台扫码 weChatOpen)
|
||||
private static final Set<String> AUTO_LOGIN_ALLOWED_SOURCES = Set.of(
|
||||
SocialSourceEnum.FEISHU.getCode(),
|
||||
SocialSourceEnum.WECHAT_MP.getCode(),
|
||||
SocialSourceEnum.WE_COM.getCode()
|
||||
);
|
||||
|
||||
private final PlatformSocialAutoLoginConfigService platformSocialAutoLoginConfigService;
|
||||
|
||||
private final SocialLoginConfigService socialLoginConfigService;
|
||||
|
||||
/// 查询完整配置(运营端管理)
|
||||
public PlatformSocialAutoLoginConfigResult findConfig() {
|
||||
return platformSocialAutoLoginConfigService.findConfig();
|
||||
}
|
||||
|
||||
/// 更新配置: 开启时校验 sources 合法且社交登录平台已启用
|
||||
public void updateConfig(PlatformSocialAutoLoginConfigParam param) {
|
||||
this.validateClientItem(ClientEnum.ADMIN.getCode(), param.getAdmin());
|
||||
this.validateClientItem(ClientEnum.MERCHANT.getCode(), param.getMerchant());
|
||||
platformSocialAutoLoginConfigService.updateConfig(param);
|
||||
}
|
||||
|
||||
/// 按终端解析对外下发的自动登录片段(登录页用)
|
||||
/// 过滤未启用或不可应用内自动登录的平台; 过滤后为空则视为未开启.
|
||||
public PlatformSocialAutoLoginConfig.ClientAutoLogin resolveForClient(String clientCode) {
|
||||
PlatformSocialAutoLoginConfig config = platformSocialAutoLoginConfigService.getConfig();
|
||||
PlatformSocialAutoLoginConfig.ClientAutoLogin item = this.pickClient(config, clientCode);
|
||||
if (item == null || !Boolean.TRUE.equals(item.getEnabled())) {
|
||||
return new PlatformSocialAutoLoginConfig.ClientAutoLogin().setEnabled(false);
|
||||
}
|
||||
List<String> configured = item.resolveSources();
|
||||
if (CollUtil.isEmpty(configured)) {
|
||||
return new PlatformSocialAutoLoginConfig.ClientAutoLogin().setEnabled(false);
|
||||
}
|
||||
List<String> effective = new ArrayList<>();
|
||||
for (String source : configured) {
|
||||
if (!AUTO_LOGIN_ALLOWED_SOURCES.contains(source)) {
|
||||
continue;
|
||||
}
|
||||
SocialLoginConfig enabled = socialLoginConfigService.findEnabledBySource(source);
|
||||
if (enabled != null) {
|
||||
effective.add(source);
|
||||
}
|
||||
}
|
||||
if (effective.isEmpty()) {
|
||||
return new PlatformSocialAutoLoginConfig.ClientAutoLogin().setEnabled(false);
|
||||
}
|
||||
return new PlatformSocialAutoLoginConfig.ClientAutoLogin()
|
||||
.setEnabled(true)
|
||||
.setSources(effective);
|
||||
}
|
||||
|
||||
/// 校验单端配置项
|
||||
private void validateClientItem(String clientCode, PlatformSocialAutoLoginConfigParam.ClientAutoLoginParam item) {
|
||||
if (item == null || !Boolean.TRUE.equals(item.getEnabled())) {
|
||||
return;
|
||||
}
|
||||
List<String> sources = this.resolveParamSources(item);
|
||||
if (CollUtil.isEmpty(sources)) {
|
||||
// 开启自动登录时必须至少选择一个平台
|
||||
throw new OperationFailException("error.social.autoLogin.sourceRequired", clientCode);
|
||||
}
|
||||
for (String code : sources) {
|
||||
if (!AUTO_LOGIN_ALLOWED_SOURCES.contains(code)) {
|
||||
// 所选平台不支持应用内自动登录(如微信开放平台扫码)
|
||||
throw new OperationFailException("error.social.autoLogin.sourceNotAllowed", code);
|
||||
}
|
||||
SocialSourceEnum source = SocialSourceEnum.of(code);
|
||||
if (source == null) {
|
||||
throw new OperationFailException("error.social.unsupportedSource");
|
||||
}
|
||||
SocialLoginConfig enabled = socialLoginConfigService.findEnabledBySource(code);
|
||||
if (enabled == null) {
|
||||
// 所选平台未配置或未启用
|
||||
throw new OperationFailException("error.social.autoLogin.sourceNotEnabled", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> resolveParamSources(PlatformSocialAutoLoginConfigParam.ClientAutoLoginParam item) {
|
||||
List<String> resolved = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(item.getSources())) {
|
||||
for (String code : item.getSources()) {
|
||||
if (StrUtil.isNotBlank(code) && !resolved.contains(code)) {
|
||||
resolved.add(code);
|
||||
}
|
||||
}
|
||||
} else if (StrUtil.isNotBlank(item.getSource())) {
|
||||
resolved.add(item.getSource());
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private PlatformSocialAutoLoginConfig.ClientAutoLogin pickClient(PlatformSocialAutoLoginConfig config,
|
||||
String clientCode) {
|
||||
if (ClientEnum.ADMIN.getCode().equals(clientCode)) {
|
||||
return config.getAdmin();
|
||||
}
|
||||
if (ClientEnum.MERCHANT.getCode().equals(clientCode)) {
|
||||
return config.getMerchant();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@ import cn.daxpay.open.platform.iam.result.user.UserInfoResult;
|
||||
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.iam.service.user.UserQueryService;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.auth.PlatformWechatMpAuthConfig;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.infra.PlatformUrlConfig;
|
||||
import cn.daxpay.open.platform.system.service.config.auth.PlatformWechatMpAuthConfigService;
|
||||
import cn.daxpay.open.platform.system.service.config.infra.PlatformUrlConfigService;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -71,6 +73,8 @@ public class SocialLoginService {
|
||||
|
||||
private final PlatformUrlConfigService platformUrlConfigService;
|
||||
|
||||
private final PlatformWechatMpAuthConfigService platformWechatMpAuthConfigService;
|
||||
|
||||
private final UserQueryService userQueryService;
|
||||
|
||||
private final PlatformStarterProperties platformStarterProperties;
|
||||
@@ -119,6 +123,12 @@ public class SocialLoginService {
|
||||
/// @param client 终端编码(admin/merchant), 用于解析端点配置中的 baseUrl
|
||||
/// @param mode 授权场景(不传则按登录态判断: 已登录=绑定, 未登录=登录)
|
||||
public String generateAuthorizeUrl(String source, String client, String mode) {
|
||||
return this.generateAuthorizeUrl(source, client, mode, null);
|
||||
}
|
||||
|
||||
/// 生成授权地址
|
||||
/// @param silent true=应用内静默/网页授权(企微 oauth / 微信 snsapi_base)
|
||||
public String generateAuthorizeUrl(String source, String client, String mode, Boolean silent) {
|
||||
// 仅 admin/merchant
|
||||
SocialClientEnum socialClient = this.requireSocialClient(client);
|
||||
// 加载平台配置(全局唯一)
|
||||
@@ -138,6 +148,12 @@ public class SocialLoginService {
|
||||
String redirectUri = this.buildRedirectUri(baseUrl, authMode);
|
||||
// 构建授权请求
|
||||
SocialAuthConfig authConfig = socialLoginConfigService.buildAuthConfig(config, redirectUri);
|
||||
// 公众号: 凭据来自平台级配置, 不写在 iam_social_login_config
|
||||
this.fillPlatformRedirectCredentials(socialSource, authConfig);
|
||||
// 应用内自动登录: 企微走网页授权、公众号走 snsapi_base
|
||||
if (Boolean.TRUE.equals(silent)) {
|
||||
authConfig.setSilent(true);
|
||||
}
|
||||
SocialAuthRequest request = this.createAuthRequest(socialSource, authConfig);
|
||||
// state 仅用于 OAuth2 合规, 不缓存业务上下文
|
||||
String state = IdUtil.fastSimpleUUID();
|
||||
@@ -256,10 +272,25 @@ public class SocialLoginService {
|
||||
if (socialSource == null) {
|
||||
throw new OperationFailException("error.social.unsupportedSource");
|
||||
}
|
||||
// 公众号: 凭据来自平台级配置
|
||||
this.fillPlatformRedirectCredentials(socialSource, authConfig);
|
||||
SocialAuthRequest authRequest = this.createAuthRequest(socialSource, authConfig);
|
||||
return authRequest.login(AuthCallback.of(code, state));
|
||||
}
|
||||
|
||||
/// 平台级跳转型凭据回填(公众号 AppId/Secret 等)
|
||||
private void fillPlatformRedirectCredentials(SocialSourceEnum socialSource, SocialAuthConfig authConfig) {
|
||||
if (socialSource != SocialSourceEnum.WECHAT_MP) {
|
||||
return;
|
||||
}
|
||||
PlatformWechatMpAuthConfig mp = platformWechatMpAuthConfigService.getWechatMpAuthConfig();
|
||||
if (mp == null || StrUtil.isBlank(mp.getAppId()) || StrUtil.isBlank(mp.getAppSecret())) {
|
||||
throw new OperationFailException("error.social.wechatMpNotConfigured");
|
||||
}
|
||||
authConfig.setClientId(mp.getAppId());
|
||||
authConfig.setClientSecret(mp.getAppSecret());
|
||||
}
|
||||
|
||||
/// 按平台创建授权请求: 支付宝走 iam 侧 [AlipaySocialAuthRequest], 其余走 JustAuth 工厂
|
||||
private SocialAuthRequest createAuthRequest(SocialSourceEnum socialSource, SocialAuthConfig authConfig) {
|
||||
if (socialSource == SocialSourceEnum.ALIPAY) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.daxpay.open.platform.system.convert.config.security;
|
||||
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformSocialAutoLoginConfig;
|
||||
import cn.daxpay.open.platform.system.param.config.security.PlatformSocialAutoLoginConfigParam;
|
||||
import cn.daxpay.open.platform.system.result.config.security.PlatformSocialAutoLoginConfigResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 应用内社交自动登录配置转换
|
||||
///
|
||||
@Mapper
|
||||
public interface PlatformSocialAutoLoginConfigConvert {
|
||||
|
||||
PlatformSocialAutoLoginConfigConvert CONVERT = Mappers.getMapper(PlatformSocialAutoLoginConfigConvert.class);
|
||||
|
||||
PlatformSocialAutoLoginConfigResult toResult(PlatformSocialAutoLoginConfig data);
|
||||
|
||||
void copy(PlatformSocialAutoLoginConfigParam param, @MappingTarget PlatformSocialAutoLoginConfig data);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.daxpay.open.platform.system.entity.config.platform.security;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// # 应用内社交自动登录配置
|
||||
///
|
||||
/// 按终端(admin/merchant)分别配置是否在飞书/微信/企微等内置浏览器中自动发起 OAuth 登录。
|
||||
/// 凭据仍存放于 iam_social_login_config / 平台级公众号配置, 本配置仅存策略开关与可选平台列表.
|
||||
///
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PlatformSocialAutoLoginConfig {
|
||||
|
||||
/// 运营端自动登录策略
|
||||
private ClientAutoLogin admin = new ClientAutoLogin();
|
||||
|
||||
/// 商户端自动登录策略
|
||||
private ClientAutoLogin merchant = new ClientAutoLogin();
|
||||
|
||||
/// 单端自动登录项
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class ClientAutoLogin {
|
||||
|
||||
/// 是否启用应用内自动登录
|
||||
private Boolean enabled = Boolean.FALSE;
|
||||
|
||||
/// 可自动跳转的社交平台编码列表(如 feishu / weChat / weCom), 登录时按 UA 匹配其一
|
||||
private List<String> sources = new ArrayList<>();
|
||||
|
||||
/// 兼容旧版单字段 source; 反序列化后由 [normalize] 合并进 sources, 新写入不再持久化
|
||||
@Deprecated
|
||||
private String source;
|
||||
|
||||
/// 归一化: 旧 source 迁移为 sources[], 去空并去重
|
||||
public ClientAutoLogin normalize() {
|
||||
List<String> resolved = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(sources)) {
|
||||
for (String item : sources) {
|
||||
if (StrUtil.isNotBlank(item) && !resolved.contains(item)) {
|
||||
resolved.add(item);
|
||||
}
|
||||
}
|
||||
} else if (StrUtil.isNotBlank(source)) {
|
||||
resolved.add(source);
|
||||
}
|
||||
this.sources = resolved;
|
||||
this.source = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// 对外读取有效平台列表(已 normalize 后可直接取 sources)
|
||||
@JsonIgnore
|
||||
public List<String> resolveSources() {
|
||||
this.normalize();
|
||||
return this.sources == null ? List.of() : this.sources;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ public enum PlatformConfigTypeEnum implements I18nSupport {
|
||||
SECURITY_PASSWORD_POLICY("security_password_policy"),
|
||||
/// 登录安全配置
|
||||
SECURITY_LOGIN("security_login"),
|
||||
/// 应用内社交自动登录配置(按 admin/merchant 分端)
|
||||
SECURITY_SOCIAL_AUTO("security_social_auto"),
|
||||
/// 会话管理配置
|
||||
SECURITY_SESSION("security_session"),
|
||||
/// 双因素认证配置
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.daxpay.open.platform.system.param.config.security;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 应用内社交自动登录配置参数
|
||||
///
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "应用内社交自动登录配置参数")
|
||||
public class PlatformSocialAutoLoginConfigParam {
|
||||
|
||||
@Schema(description = "运营端自动登录策略")
|
||||
private ClientAutoLoginParam admin;
|
||||
|
||||
@Schema(description = "商户端自动登录策略")
|
||||
private ClientAutoLoginParam merchant;
|
||||
|
||||
/// 单端自动登录参数
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "单端自动登录参数")
|
||||
public static class ClientAutoLoginParam {
|
||||
|
||||
@Schema(description = "是否启用应用内自动登录")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "可自动跳转的社交平台编码列表")
|
||||
private List<String> sources;
|
||||
|
||||
/// 兼容旧客户端单字段; 服务端优先用 sources, 空时回退
|
||||
@Schema(description = "兼容旧版单平台编码", deprecated = true)
|
||||
@Deprecated
|
||||
private String source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.daxpay.open.platform.system.result.config.security;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 应用内社交自动登录配置结果
|
||||
///
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "应用内社交自动登录配置结果")
|
||||
public class PlatformSocialAutoLoginConfigResult {
|
||||
|
||||
@Schema(description = "运营端自动登录策略")
|
||||
private ClientAutoLoginResult admin;
|
||||
|
||||
@Schema(description = "商户端自动登录策略")
|
||||
private ClientAutoLoginResult merchant;
|
||||
|
||||
/// 单端自动登录结果
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "单端自动登录结果")
|
||||
public static class ClientAutoLoginResult {
|
||||
|
||||
@Schema(description = "是否启用应用内自动登录")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "可自动跳转的社交平台编码列表")
|
||||
private List<String> sources;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.daxpay.open.platform.system.service.config.security;
|
||||
|
||||
import cn.daxpay.open.platform.system.convert.config.security.PlatformSocialAutoLoginConfigConvert;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformSocialAutoLoginConfig;
|
||||
import cn.daxpay.open.platform.system.enums.PlatformConfigTypeEnum;
|
||||
import cn.daxpay.open.platform.system.param.config.security.PlatformSocialAutoLoginConfigParam;
|
||||
import cn.daxpay.open.platform.system.result.config.security.PlatformSocialAutoLoginConfigResult;
|
||||
import cn.daxpay.open.platform.system.service.config.SystemPlatformConfigService;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// # 应用内社交自动登录配置服务
|
||||
///
|
||||
/// 仅负责读写 [PlatformConfigTypeEnum#SECURITY_SOCIAL_AUTO];
|
||||
/// 社交平台是否已启用等业务校验由 IAM 层在更新前完成.
|
||||
///
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PlatformSocialAutoLoginConfigService {
|
||||
|
||||
private final SystemPlatformConfigService systemConfigService;
|
||||
|
||||
/// 获取配置实体(不存在则创建默认关闭态), 并归一化旧 source 字段
|
||||
public PlatformSocialAutoLoginConfig getConfig() {
|
||||
PlatformSocialAutoLoginConfig config = systemConfigService.getOrCreateConfig(
|
||||
PlatformConfigTypeEnum.SECURITY_SOCIAL_AUTO,
|
||||
PlatformSocialAutoLoginConfig.class,
|
||||
defaultConfig());
|
||||
this.normalize(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/// 查询配置结果
|
||||
public PlatformSocialAutoLoginConfigResult findConfig() {
|
||||
return PlatformSocialAutoLoginConfigConvert.CONVERT.toResult(this.getConfig());
|
||||
}
|
||||
|
||||
/// 更新配置(调用方已完成业务校验)
|
||||
public void updateConfig(PlatformSocialAutoLoginConfigParam param) {
|
||||
PlatformSocialAutoLoginConfig data = this.getConfig();
|
||||
PlatformSocialAutoLoginConfigConvert.CONVERT.copy(param, data);
|
||||
// 嵌套对象 MapStruct 可能留下 null, 兜底为空对象避免 NPE
|
||||
if (data.getAdmin() == null) {
|
||||
data.setAdmin(new PlatformSocialAutoLoginConfig.ClientAutoLogin());
|
||||
}
|
||||
if (data.getMerchant() == null) {
|
||||
data.setMerchant(new PlatformSocialAutoLoginConfig.ClientAutoLogin());
|
||||
}
|
||||
// 参数可能仍带旧 source, 统一合并后落盘只保留 sources
|
||||
this.applyParamSources(data.getAdmin(), param.getAdmin());
|
||||
this.applyParamSources(data.getMerchant(), param.getMerchant());
|
||||
this.normalize(data);
|
||||
systemConfigService.updateConfig(PlatformConfigTypeEnum.SECURITY_SOCIAL_AUTO, data);
|
||||
}
|
||||
|
||||
/// 默认: 两端均关闭
|
||||
private PlatformSocialAutoLoginConfig defaultConfig() {
|
||||
return new PlatformSocialAutoLoginConfig()
|
||||
.setAdmin(new PlatformSocialAutoLoginConfig.ClientAutoLogin().setEnabled(false))
|
||||
.setMerchant(new PlatformSocialAutoLoginConfig.ClientAutoLogin().setEnabled(false));
|
||||
}
|
||||
|
||||
private void normalize(PlatformSocialAutoLoginConfig config) {
|
||||
if (config.getAdmin() != null) {
|
||||
config.getAdmin().normalize();
|
||||
}
|
||||
if (config.getMerchant() != null) {
|
||||
config.getMerchant().normalize();
|
||||
}
|
||||
}
|
||||
|
||||
/// 将 Param 的 sources/source 写回实体, 避免 MapStruct 只拷同名后遗留旧 source
|
||||
private void applyParamSources(PlatformSocialAutoLoginConfig.ClientAutoLogin target,
|
||||
PlatformSocialAutoLoginConfigParam.ClientAutoLoginParam param) {
|
||||
if (target == null || param == null) {
|
||||
return;
|
||||
}
|
||||
List<String> resolved = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(param.getSources())) {
|
||||
for (String item : param.getSources()) {
|
||||
if (StrUtil.isNotBlank(item) && !resolved.contains(item)) {
|
||||
resolved.add(item);
|
||||
}
|
||||
}
|
||||
} else if (StrUtil.isNotBlank(param.getSource())) {
|
||||
resolved.add(param.getSource());
|
||||
}
|
||||
target.setSources(resolved);
|
||||
target.setSource(null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user