From 7b7bbf6e1a1885b8868536f2e158774b6ad64fe8 Mon Sep 17 00:00:00 2001 From: ye4241 <6803102+ye4241@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:35:51 +0800 Subject: [PATCH 1/3] fix(oauth2): support absolute URLs for authorize_url and token_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some IdP providers (e.g. Alibaba Cloud IDaaS) expose authorization and token endpoints on different domains, making it impossible to construct them from a single server_url base. The previous code unconditionally concatenated server_url + authorize_url / token_url, which breaks when either value is already a fully-qualified URL. Changes: - login_options.go: when AuthorizeURL starts with "http", use it directly instead of prepending ServerURL. Remove the ServerURL == "" validation guard so that configurations without a server_url are accepted. - system.go (TestOAuth2Connection): same treatment for TokenURL — use it as-is when it is an absolute URL. Remove the ServerURL == "" validation guard accordingly. This is fully backward-compatible: existing configs that store a relative path in authorize_url / token_url continue to work unchanged. --- admin/server/service/gaia/login_options.go | 9 +++++++-- admin/server/service/gaia/system.go | 9 ++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/admin/server/service/gaia/login_options.go b/admin/server/service/gaia/login_options.go index f2e3164b0..3c713906d 100644 --- a/admin/server/service/gaia/login_options.go +++ b/admin/server/service/gaia/login_options.go @@ -34,7 +34,7 @@ func (e *SystemIntegratedService) GetLoginOptions(frontendOrigin string) (res re if err := json.Unmarshal([]byte(integrateOAuth.Config), &configMap); err != nil { return res } - if configMap.ServerURL == "" || configMap.AuthorizeURL == "" { + if configMap.AuthorizeURL == "" { return res } res.OAuth2.Enabled = true @@ -48,7 +48,12 @@ func (e *SystemIntegratedService) GetLoginOptions(frontendOrigin string) (res re scope = "openid" } // Extend: 兼容 Casdoor 等 provider。用 net/url 解析并合并 query,保证 client_id 等参数一定被附加上去 - baseURLStr := strings.TrimSuffix(configMap.ServerURL, "/") + configMap.AuthorizeURL + var baseURLStr string + if strings.HasPrefix(configMap.AuthorizeURL, "http") { + baseURLStr = configMap.AuthorizeURL + } else { + baseURLStr = strings.TrimSuffix(configMap.ServerURL, "/") + configMap.AuthorizeURL + } u, err := url.Parse(baseURLStr) if err != nil { // 解析失败时退回字符串拼接 diff --git a/admin/server/service/gaia/system.go b/admin/server/service/gaia/system.go index a03bae9d3..4f8d46370 100644 --- a/admin/server/service/gaia/system.go +++ b/admin/server/service/gaia/system.go @@ -534,7 +534,7 @@ func (e *SystemIntegratedService) TestOAuth2Connection(integrate gaia.SystemInte return nil } // 检查必要字段 - if configMap.ServerURL == "" || configMap.TokenURL == "" || integrate.AppID == "" || integrate.AppSecret == "" { + if configMap.TokenURL == "" || integrate.AppID == "" || integrate.AppSecret == "" { return errors.New("请填写完整的 OAuth2 配置信息") } @@ -556,8 +556,11 @@ func (e *SystemIntegratedService) TestOAuth2Connection(integrate gaia.SystemInte // 发送请求 var req *http.Request client := &http.Client{} - req, err = http.NewRequest("POST", fmt.Sprintf( - "%s%s", configMap.ServerURL, configMap.TokenURL), strings.NewReader(formData.Encode())) + tokenEndpoint := configMap.TokenURL + if !strings.HasPrefix(tokenEndpoint, "http") { + tokenEndpoint = configMap.ServerURL + tokenEndpoint + } + req, err = http.NewRequest("POST", tokenEndpoint, strings.NewReader(formData.Encode())) if err != nil { global.GVA_LOG.Error("创建测试请求失败", zap.Error(err)) return errors.New(fmt.Sprintf("创建测试请求失败: %s", err.Error())) From 1cdfa50dae764e76339b26a14a29cee54f0a7701 Mon Sep 17 00:00:00 2001 From: ye4241 <6803102+ye4241@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:57:46 +0800 Subject: [PATCH 2/3] fix(oauth2): fix default redirect_uri and remove server_url requirement from frontend Two follow-up fixes to the OAuth2 absolute URL support: 1. login_options.go: fix wrong default redirect_uri The fallback when redirect_uri is not configured was using "/#/loginCallback?provider=oauth2" (a frontend hash route used by DingTalk). OAuth2 flow uses the server-side callback handler, so the correct default is "/api/base/auth2/callback". 2. admin/web oauth2/index.vue: remove server_url from isConfigValid The enable toggle was disabled whenever server_url was empty, which made it impossible to save a config that uses absolute URLs for authorize_url/token_url without a server_url base. Removing server_url from the validation guard matches the backend behavior introduced in the previous commit. --- admin/server/service/gaia/login_options.go | 2 +- admin/web/src/view/systemIntegrated/oauth2/index.vue | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/admin/server/service/gaia/login_options.go b/admin/server/service/gaia/login_options.go index 3c713906d..c3b767d5f 100644 --- a/admin/server/service/gaia/login_options.go +++ b/admin/server/service/gaia/login_options.go @@ -40,7 +40,7 @@ func (e *SystemIntegratedService) GetLoginOptions(frontendOrigin string) (res re res.OAuth2.Enabled = true redirectURI := strings.TrimSpace(configMap.RedirectUri) if redirectURI == "" { - redirectURI = frontendOrigin + "/#/loginCallback?provider=oauth2" + redirectURI = frontendOrigin + "/api/base/auth2/callback" } res.OAuth2.RedirectURI = redirectURI scope := strings.TrimSpace(configMap.Scope) diff --git a/admin/web/src/view/systemIntegrated/oauth2/index.vue b/admin/web/src/view/systemIntegrated/oauth2/index.vue index ea8d70114..99942e7b6 100644 --- a/admin/web/src/view/systemIntegrated/oauth2/index.vue +++ b/admin/web/src/view/systemIntegrated/oauth2/index.vue @@ -205,10 +205,9 @@ const config = ref({ test: false, }) -// 验证配置是否有效 +// 验证配置是否有效(server_url 为可选,支持 token_url/authorize_url 填绝对 URL 的场景) const isConfigValid = computed(() => { return !!( - config.value.server_url && config.value.token_url && config.value.userinfo_url && config.value.app_id && From 8514b8286faee6c1a44f67e88511a7c36151f0b2 Mon Sep 17 00:00:00 2001 From: ye4241 <6803102+ye4241@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:35:59 +0800 Subject: [PATCH 3/3] fix(oauth2): address Copilot review comments on URL resolution - Extract resolveEndpointURL() helper to correctly detect only http:// and https:// schemes (vs the too-broad HasPrefix("http") check) - Return error early when authorize_url/token_url is relative but server_url is empty, preventing partial/invalid URL generation - Apply absolute-URL support to OAuth2CodeLogin token + userinfo endpoints (gaia_login.go) to fix "test passes but real login fails" scenario --- admin/server/service/gaia/gaia_login.go | 10 ++++++-- admin/server/service/gaia/login_options.go | 27 ++++++++++++++++++---- admin/server/service/gaia/system.go | 6 ++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/admin/server/service/gaia/gaia_login.go b/admin/server/service/gaia/gaia_login.go index cba2b3d4f..fed8f8e69 100644 --- a/admin/server/service/gaia/gaia_login.go +++ b/admin/server/service/gaia/gaia_login.go @@ -62,7 +62,10 @@ func (e *SystemIntegratedService) OAuth2CodeLogin( formData.Set("client_id", integrate.AppID) formData.Set("client_secret", integrate.AppSecret) } - tokenURL := strings.TrimSuffix(configMap.ServerURL, "/") + configMap.TokenURL + tokenURL, err := resolveEndpointURL(configMap.ServerURL, configMap.TokenURL) + if err != nil { + return nil, fmt.Errorf("token_url 配置错误: %w", err) + } httpReq, err := http.NewRequest("POST", tokenURL, strings.NewReader(formData.Encode())) if err != nil { return nil, err @@ -97,7 +100,10 @@ func (e *SystemIntegratedService) OAuth2CodeLogin( // Extend Stop: 兼容 casdoor // 拉用户信息 - userInfoURL := strings.TrimSuffix(configMap.ServerURL, "/") + configMap.UserinfoURL + userInfoURL, err := resolveEndpointURL(configMap.ServerURL, configMap.UserinfoURL) + if err != nil { + return nil, fmt.Errorf("userinfo_url 配置错误: %w", err) + } userReq, err := http.NewRequest("GET", userInfoURL, nil) if err != nil { return nil, err diff --git a/admin/server/service/gaia/login_options.go b/admin/server/service/gaia/login_options.go index c3b767d5f..b5a3cc7c5 100644 --- a/admin/server/service/gaia/login_options.go +++ b/admin/server/service/gaia/login_options.go @@ -12,6 +12,25 @@ import ( "strings" ) +// isAbsoluteHTTP returns true only for URLs starting with "http://" or "https://". +func isAbsoluteHTTP(s string) bool { + return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") +} + +// resolveEndpointURL returns an absolute endpoint URL. +// If endpointURL is absolute (http:// or https://), it is returned as-is. +// Otherwise serverURL is prepended with trailing-slash normalization. +// Returns an error if endpointURL is relative and serverURL is empty. +func resolveEndpointURL(serverURL, endpointURL string) (string, error) { + if isAbsoluteHTTP(endpointURL) { + return endpointURL, nil + } + if strings.TrimSpace(serverURL) == "" { + return "", fmt.Errorf("server_url 未配置,且端点 %q 不是绝对 URL", endpointURL) + } + return strings.TrimSuffix(serverURL, "/") + endpointURL, nil +} + // GetLoginOptions 获取登录方式选项(供登录页展示钉钉/OAuth2 按钮,不暴露密钥) func (e *SystemIntegratedService) GetLoginOptions(frontendOrigin string) (res response.LoginOptionsResponse) { // 非本地的需要加上 admin(若 Referer 已带 /admin 则不再追加,避免 /admin/admin) @@ -48,11 +67,9 @@ func (e *SystemIntegratedService) GetLoginOptions(frontendOrigin string) (res re scope = "openid" } // Extend: 兼容 Casdoor 等 provider。用 net/url 解析并合并 query,保证 client_id 等参数一定被附加上去 - var baseURLStr string - if strings.HasPrefix(configMap.AuthorizeURL, "http") { - baseURLStr = configMap.AuthorizeURL - } else { - baseURLStr = strings.TrimSuffix(configMap.ServerURL, "/") + configMap.AuthorizeURL + baseURLStr, err := resolveEndpointURL(configMap.ServerURL, configMap.AuthorizeURL) + if err != nil { + return res } u, err := url.Parse(baseURLStr) if err != nil { diff --git a/admin/server/service/gaia/system.go b/admin/server/service/gaia/system.go index 4f8d46370..b4a3d39e2 100644 --- a/admin/server/service/gaia/system.go +++ b/admin/server/service/gaia/system.go @@ -556,9 +556,9 @@ func (e *SystemIntegratedService) TestOAuth2Connection(integrate gaia.SystemInte // 发送请求 var req *http.Request client := &http.Client{} - tokenEndpoint := configMap.TokenURL - if !strings.HasPrefix(tokenEndpoint, "http") { - tokenEndpoint = configMap.ServerURL + tokenEndpoint + tokenEndpoint, err := resolveEndpointURL(configMap.ServerURL, configMap.TokenURL) + if err != nil { + return errors.New(fmt.Sprintf("token_url 配置错误: %s", err.Error())) } req, err = http.NewRequest("POST", tokenEndpoint, strings.NewReader(formData.Encode())) if err != nil {