Files
tiku-backend.net/Tiku.Api/Middleware/BrowserCsrfMiddleware.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

67 lines
2.6 KiB
C#

using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using Tiku.Api.Options;
namespace Tiku.Api.Middleware;
public sealed class BrowserCsrfMiddleware(
RequestDelegate next,
IOptions<BrowserAuthOptions> options)
{
public async Task InvokeAsync(HttpContext context)
{
if (!IsUnsafe(context.Request.Method) || !IsBrowserCookieRequest(context.Request))
{
await next(context);
return;
}
if (!IsTrustedOrigin(context.Request) || !HasValidCsrfToken(context.Request))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsJsonAsync(new
{
title = "Browser request origin or CSRF token is invalid.",
status = StatusCodes.Status403Forbidden,
code = "browser_csrf_rejected",
traceId = context.TraceIdentifier
}, context.RequestAborted);
return;
}
await next(context);
}
private static bool IsUnsafe(string method)
{
return !HttpMethods.IsGet(method) && !HttpMethods.IsHead(method) && !HttpMethods.IsOptions(method);
}
private static bool IsBrowserCookieRequest(HttpRequest request)
{
return request.Cookies.ContainsKey(BrowserAuthOptions.AccessCookie) ||
request.Cookies.ContainsKey(BrowserAuthOptions.RefreshCookie);
}
private bool IsTrustedOrigin(HttpRequest request)
{
var origin = request.Headers.Origin.ToString().Trim().TrimEnd('/');
if (string.IsNullOrWhiteSpace(origin) || !Uri.TryCreate(origin, UriKind.Absolute, out var uri)) return false;
var sameOrigin = string.Equals(uri.Scheme, request.Scheme, StringComparison.OrdinalIgnoreCase) &&
string.Equals(uri.Authority, request.Host.Value, StringComparison.OrdinalIgnoreCase);
return sameOrigin || options.Value.AllowedOrigins.Any(allowed =>
string.Equals(allowed.Trim().TrimEnd('/'), origin, StringComparison.OrdinalIgnoreCase));
}
private static bool HasValidCsrfToken(HttpRequest request)
{
var cookie = request.Cookies[BrowserAuthOptions.CsrfCookie];
var header = request.Headers[BrowserAuthOptions.CsrfHeader].ToString();
if (string.IsNullOrWhiteSpace(cookie) || string.IsNullOrWhiteSpace(header)) return false;
var left = Encoding.UTF8.GetBytes(cookie);
var right = Encoding.UTF8.GetBytes(header);
return left.Length == right.Length && CryptographicOperations.FixedTimeEquals(left, right);
}
}