2
build / build-push (push) Successful in 2m15s

This commit is contained in:
wanghu
2026-09-03 21:33:33 +08:00
parent f43a0450ac
commit 12a3f240eb
@@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Reflection.Metadata;
using System.Threading.Tasks;
using Yh.Platform.Abstractions;
using Yh.Platform.Auth;
@@ -89,31 +88,37 @@ namespace YhCore.WebApi.Middleware
"<p><a href=\"/\">重新登录</a></p></body></html>");
return;
}
// API 渠道(/api/*、/mobiapi/*)的 AJAX/XHR 请求:未认证时返回 401 JSON,而非 302 重定向到 CAS。
// API 渠道(/api/*、/mobiapi/*)的 AJAX/XHR 请求:未认证时不直接 302,而是复用框架
// CasAuthenticationHandler 生成 CAS 登录 URL 后改写为 200 + JSON(code:302 + urlToRedirectTo)。
// 原因:浏览器 fetch/XHR 会跟随 302 跨域跳转到 sso.yunhaixingtong.com,而 CAS 登录页无
// Access-Control-Allow-Origin 头,导致 CORS 拦截(Response to preflight request ... blocked)。
// 前端据此 401 做整页跳转登录(location.href = "/")。文档导航(浏览器地址栏 / Accept:text/html)仍走 302。
// 前端据此 urlToRedirectTo 做整页跳转登录。文档导航(浏览器地址栏 / Accept:text/html)仍走 302。
if (IsProtectedPath(context.Request.Path.Value, sso) && IsAjaxRequest(context.Request))
{
// 复用框架 Challenge 统一构造 CAS 登录 URL:与 CasAuthenticationHandler.HandleChallengeAsync
// 使用同一 BuildServiceUrl 逻辑(AppBaseUrl + SignInCallbackPath + returnUrl 回跳),
// 自动获得 yh:sso:cas-server-url-prefix 未配置时返回 500、returnUrl 回跳、URL 编码等一致性保障,
// 避免本中间件重复手写 URL 拼接。RedirectUri 传入当前请求路径,登录成功后回跳原地址。
await context.ChallengeAsync(YhAuthDefaults.CasScheme, new AuthenticationProperties
{
RedirectUri = BuildReturnUrl(context.Request)
});
// 拦截框架写入的 302 + Location,改写为 200 + JSON,避免 XHR 跟随跨域 302 被 CORS 拦截
var location = context.Response.Headers["Location"].ToString();
context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.Headers.Remove("Location");
context.Response.ContentType = "application/json; charset=utf-8";
// 返回与文档导航 Challenge 一致的 CAS 登录地址(200 + JSON 而非 302,避免跨域跳转被 CORS 拦截)。
// service 指向 signin-cas 回调,与 CasAuthenticationHandler.HandleChallengeAsync 的 BuildServiceUrl 一致
// (AppBaseUrl + SignInCallbackPath),保证回调重建 service 时与登录时完全一致,ticket 校验才能通过。
var casBase = (sso["CasServerUrlPrefix"] ?? string.Empty).TrimEnd('/');
var root = string.IsNullOrEmpty(sso["AppBaseUrl"])
? (context.Request.Scheme + "://" + context.Request.Host.Value)
: sso["AppBaseUrl"].TrimEnd('/');
var callbackPath = sso["SignInCallbackPath"] ?? "/signin-cas";
var loginUrl = casBase + "/login?service=" + Uri.EscapeDataString(root + callbackPath);
await context.Response.WriteAsync("{\"code\":302,\"urlToRedirectTo\":\"" + loginUrl + "\"}");
await context.Response.WriteAsync("{\"code\":302,\"urlToRedirectTo\":\"" + location + "\"}");
return;
}
// 未认证 → Challenge → 302 重定向 CAS 登录页(ticket 校验与回跳由新框架 CasAuthenticationHandler 处理)
await context.ChallengeAsync(YhAuthDefaults.CasScheme);
// 未认证 → Challenge → 302 重定向 CAS 登录页(ticket 校验与回跳由新框架 CasAuthenticationHandler 处理)。
// RedirectUri 传入当前请求地址,实现登录成功后回跳原页面(对齐框架 returnUrl 语义)。
await context.ChallengeAsync(YhAuthDefaults.CasScheme, new AuthenticationProperties
{
RedirectUri = BuildReturnUrl(context.Request)
});
return;
}
@@ -173,5 +178,17 @@ namespace YhCore.WebApi.Middleware
return path.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// 构造登录后回跳原地址(本地相对路径,含 query,不含 ticket)。
/// 与框架 CasAuthenticationHandler 的 returnUrl 语义一致:必须是 / 开头的本地路径,
/// 否则框架 BuildServiceUrl 的 IsLocalUrl 校验会拒绝并回退到纯回调地址(防开放重定向)。
/// </summary>
private static string BuildReturnUrl(HttpRequest request)
{
var path = request.Path.Value ?? "/";
var query = request.QueryString.Value;
return string.IsNullOrEmpty(query) ? path : path + query;
}
}
}