82 lines
3.3 KiB
C#
82 lines
3.3 KiB
C#
using System;
|
||
using System.Net.Http;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using Yh.Platform.Auth;
|
||
|
||
namespace YhCore.WebApi.Auth
|
||
{
|
||
/// <summary>
|
||
/// 轻量 CAS service ticket 校验器(对齐 Java CasServiceTicketValidator,不依赖 CAS 客户端库)。
|
||
/// 调用 CAS 的 /serviceValidate 端点,从响应 XML 中提取 <cas:user>。
|
||
/// </summary>
|
||
public class CasServiceTicketValidator
|
||
{
|
||
private readonly string _casServerUrl;
|
||
private readonly int _timeoutMs;
|
||
|
||
public CasServiceTicketValidator(string casServerUrl, int timeoutMs = 5000)
|
||
{
|
||
_casServerUrl = casServerUrl;
|
||
_timeoutMs = timeoutMs > 0 ? timeoutMs : 5000;
|
||
}
|
||
|
||
/// <summary>校验 ticket,成功返回登录账号,失败/无效返回 null。</summary>
|
||
public async Task<string> ValidateAsync(string ticket, string service, CancellationToken ct = default)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_casServerUrl))
|
||
{
|
||
throw new AuthException("未配置 CasServerUrlPrefix(yh:sso:cas-server-url-prefix),无法进行 ticket 校验");
|
||
}
|
||
var base_ = _casServerUrl.EndsWith('/') ? _casServerUrl : _casServerUrl + "/";
|
||
var endpoint = base_ + "serviceValidate"
|
||
+ "?ticket=" + Uri.EscapeDataString(ticket ?? "")
|
||
+ "&service=" + Uri.EscapeDataString(service ?? "");
|
||
try
|
||
{
|
||
using var handler = new SocketsHttpHandler { ConnectTimeout = TimeSpan.FromMilliseconds(_timeoutMs) };
|
||
using var http = new HttpClient(handler) { Timeout = TimeSpan.FromMilliseconds(_timeoutMs) };
|
||
using var resp = await http.GetAsync(endpoint, ct).ConfigureAwait(false);
|
||
var body = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||
if (!resp.IsSuccessStatusCode || body == null)
|
||
{
|
||
return null;
|
||
}
|
||
return ExtractUser(body);
|
||
}
|
||
catch (Exception e) when (e is HttpRequestException or TaskCanceledException)
|
||
{
|
||
throw new AuthException("CAS serviceValidate 请求失败: " + e.Message, e);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 CAS 响应 XML 提取用户名。
|
||
/// 成功响应形如 <cas:serviceResponse><cas:authenticationSuccess><cas:user>NAME</cas:user>...
|
||
/// </summary>
|
||
public static string ExtractUser(string xml)
|
||
{
|
||
if (string.IsNullOrEmpty(xml))
|
||
{
|
||
return null;
|
||
}
|
||
var start = xml.IndexOf("<cas:user>", StringComparison.Ordinal);
|
||
if (start < 0)
|
||
{
|
||
start = xml.IndexOf("<cas:user ", StringComparison.Ordinal); // 容错带属性
|
||
}
|
||
if (start < 0)
|
||
{
|
||
return null;
|
||
}
|
||
var tagEnd = xml.IndexOf('>', start);
|
||
var end = xml.IndexOf("</cas:user>", tagEnd, StringComparison.Ordinal);
|
||
if (tagEnd < 0 || end < 0)
|
||
{
|
||
return null;
|
||
}
|
||
return xml.Substring(tagEnd + 1, end - tagEnd - 1).Trim();
|
||
}
|
||
}
|
||
}
|