[feat] Safer client authentication

This commit is contained in:
acite
2025-09-11 13:55:24 +08:00
parent f9e4510553
commit d2e11817db
7 changed files with 102 additions and 60 deletions

View File

@@ -10,13 +10,13 @@
</component>
<component name="ChangeListManager">
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/LiveController.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Static/ControllerExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Abyss.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Abyss.csproj" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/ImageController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/ImageController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/LiveController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/LiveController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/VideoController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/VideoController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/ResourceService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/ResourceService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Static/Helpers.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Static/Helpers.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Program.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -40,15 +40,12 @@
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Media/LiveController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Task/TaskController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///home/acite/embd/WebProjects/Abyss/Abyss/Components/Services/ConfigureService.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/ConfigureService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///home/acite/embd/WebProjects/Abyss/Abyss/Components/Services/ConfigureService.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/ResourceService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///home/acite/embd/WebProjects/Abyss/Abyss/Components/Services/ResourceService.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///home/acite/embd/WebProjects/Abyss/Abyss/Components/Services/ResourceService.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/TaskService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Static/Helpers.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Static/HttpContextExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Bookmark.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/ChallengeResponse.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Chip.cs" root0="FORCE_HIGHLIGHTING" />
@@ -75,30 +72,30 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Launch Settings Profile.Abyss: http.executor": "Run",
".NET Launch Settings Profile.Abyss: https.executor": "Debug",
".NET Project.AbyssCli.executor": "Run",
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Publish to folder.Publish Abyss to folder x86.executor": "Run",
"Publish to folder.Publish Abyss to folder.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"XThreadsFramesViewSplitterKey": "0.30266345",
"git-widget-placeholder": "dev-live",
"last_opened_file_path": "/storage/Images/31/summary.json",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;.NET Launch Settings Profile.Abyss: http.executor&quot;: &quot;Run&quot;,
&quot;.NET Launch Settings Profile.Abyss: https.executor&quot;: &quot;Debug&quot;,
&quot;.NET Project.AbyssCli.executor&quot;: &quot;Run&quot;,
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;Publish to folder.Publish Abyss to folder x86.executor&quot;: &quot;Run&quot;,
&quot;Publish to folder.Publish Abyss to folder.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;XThreadsFramesViewSplitterKey&quot;: &quot;0.30266345&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;/storage/Images/31/summary.json&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}]]></component>
}</component>
<component name="RunManager" selected="Publish to folder.Publish Abyss to folder">
<configuration name="Publish Abyss to folder x86" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" platform="Any CPU" produce_single_file="true" ready_to_run="true" self_contained="true" target_folder="/opt/security/https/server" target_framework="net9.0" uuid_high="3690631506471504162" uuid_low="-4858628519588143325">
@@ -207,6 +204,11 @@
<workItem from="1757076719875" duration="601000" />
<workItem from="1757219779961" duration="112000" />
<workItem from="1757386288260" duration="3634000" />
<workItem from="1757428682321" duration="171000" />
<workItem from="1757429030386" duration="20000" />
<workItem from="1757508119360" duration="1704000" />
<workItem from="1757519520290" duration="14000" />
<workItem from="1757567561745" duration="2019000" />
</task>
<servers />
</component>

View File

@@ -11,7 +11,7 @@ using System.IO;
[ApiController]
[Route("api/[controller]")]
public class ImageController(ILogger<ImageController> logger, ResourceService rs, ConfigureService config) : Controller
public class ImageController(ILogger<ImageController> logger, ResourceService rs, ConfigureService config) : BaseController
{
public readonly string ImageFolder = Path.Combine(config.MediaRoot, "Images");
@@ -78,6 +78,4 @@ public class ImageController(ILogger<ImageController> logger, ResourceService rs
return PhysicalFile(d, "image/jpeg", enableRangeProcessing: true);
}
private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
}

View File

@@ -6,7 +6,7 @@ namespace Abyss.Components.Controllers.Media;
[ApiController]
[Route("api/[controller]")]
public class LiveController(ILogger<LiveController> logger, ResourceService rs, ConfigureService config): Controller
public class LiveController(ILogger<LiveController> logger, ResourceService rs, ConfigureService config): BaseController
{
public readonly string LiveFolder = Path.Combine(config.MediaRoot, "Live");
@@ -33,25 +33,17 @@ public class LiveController(ILogger<LiveController> logger, ResourceService rs,
return r ? Ok("Success") : BadRequest();
}
[HttpGet("{id}/{item}")]
public async Task<IActionResult> GetLive(string id, string? token, string item)
[HttpGet("{id}/{token}/{item}")]
public async Task<IActionResult> GetLive(string id, string token, string item)
{
var d = Helpers.SafePathCombine(LiveFolder, [id, item]);
var f = Helpers.SafePathCombine(LiveFolder, [id]);
if (d == null || f == null) return BadRequest();
// TODO: ffplay does not add the m3u8 query parameter in ts requests, so special treatment is given to ts here
// TODO: It should be pointed out that this implementation is not secure and should be modified in subsequent updates
if (d.EndsWith(".ts"))
{
if(System.IO.File.Exists(d))
return PhysicalFile(d, Helpers.GetContentType(d));
else
return NotFound();
}
// TODO: (History)ffplay does not add the m3u8 query parameter in ts requests, so special treatment is given to ts here
// TODO: (History)It should be pointed out that this implementation is not secure and should be modified in subsequent updates
if(token == null)
return StatusCode(403, new { message = "403 Denied" });
// TODO: It's still not very elegant, but it's a bit better to some extent
bool r = await rs.Valid(f, token, OperationType.Read, Ip);
if(!r) return StatusCode(403, new { message = "403 Denied" });
@@ -61,6 +53,4 @@ public class LiveController(ILogger<LiveController> logger, ResourceService rs,
else
return NotFound();
}
private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
}

View File

@@ -10,7 +10,7 @@ namespace Abyss.Components.Controllers.Media;
[ApiController]
[Route("api/[controller]")]
public class VideoController(ILogger<VideoController> logger, ResourceService rs, ConfigureService config) : Controller
public class VideoController(ILogger<VideoController> logger, ResourceService rs, ConfigureService config) : BaseController
{
private ILogger<VideoController> _logger = logger;
@@ -109,6 +109,4 @@ public class VideoController(ILogger<VideoController> logger, ResourceService rs
if (!r) return StatusCode(403, new { message = "403 Denied" });
return PhysicalFile(d, "video/mp4", enableRangeProcessing: true);
}
private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
}

View File

@@ -3,6 +3,7 @@
using System.Text.RegularExpressions;
using Abyss.Components.Services;
using Abyss.Components.Static;
using Abyss.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
@@ -12,7 +13,7 @@ namespace Abyss.Components.Controllers.Security;
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("Fixed")]
public class UserController(UserService user, ILogger<UserController> logger) : Controller
public class UserController(UserService user, ILogger<UserController> logger) : BaseController
{
private readonly ILogger<UserController> _logger = logger;
private readonly UserService _user = user;
@@ -125,6 +126,4 @@ public class UserController(UserService user, ILogger<UserController> logger) :
return false;
return Regex.IsMatch(input, @"^[a-zA-Z0-9]+$");
}
private string Ip => HttpContext.Connection.RemoteIpAddress?.ToString() ?? "127.0.0.1";
}

View File

@@ -0,0 +1,57 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace Abyss.Components.Static;
public abstract class BaseController : Controller
{
private string? _ip;
protected string Ip
{
get
{
if (_ip != null)
return _ip;
_ip = GetClientIpAddress();
if (string.IsNullOrEmpty(_ip))
throw new InvalidOperationException("invalid IP");
return _ip;
}
}
private string? GetClientIpAddress()
{
var remoteIp = HttpContext.Connection.RemoteIpAddress;
if (remoteIp != null && (IPAddress.IsLoopback(remoteIp) || remoteIp.ToString() == "::1"))
{
return remoteIp.ToString();
}
string? ip = remoteIp?.ToString();
if (HttpContext.Request.Headers.TryGetValue("X-Forwarded-For", out var forwardedFor))
{
var forwardedIps = forwardedFor.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Where(x => !string.IsNullOrEmpty(x))
.ToArray();
if (forwardedIps.Length > 0)
{
ip = forwardedIps[0];
}
}
if (string.IsNullOrEmpty(ip) && HttpContext.Request.Headers.TryGetValue("X-Real-IP", out var realIp))
{
ip = realIp.ToString();
}
return ip;
}
}

View File

@@ -25,7 +25,6 @@ public class Program
{
options.AddFixedWindowLimiter("Fixed", policyOptions =>
{
// 时间窗口长度
policyOptions.Window = TimeSpan.FromSeconds(30);
policyOptions.PermitLimit = 10;
policyOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
@@ -48,8 +47,7 @@ public class Program
app.MapOpenApi();
}
app.UseHttpsRedirection();
// app.UseHttpsRedirection();
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllers();