diff --git a/admin/server/service/gaia/gaia_login.go b/admin/server/service/gaia/gaia_login.go index cba2b3d4f4..fed8f8e690 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 f2e3164b04..b5a3cc7c5a 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) @@ -34,13 +53,13 @@ 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 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) @@ -48,7 +67,10 @@ 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 + 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 a03bae9d3e..b4a3d39e2b 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, 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 { global.GVA_LOG.Error("创建测试请求失败", zap.Error(err)) return errors.New(fmt.Sprintf("创建测试请求失败: %s", err.Error())) diff --git a/admin/web/src/view/systemIntegrated/oauth2/index.vue b/admin/web/src/view/systemIntegrated/oauth2/index.vue index ea8d701141..99942e7b67 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 &&