using System.Security.Cryptography;
namespace MiGu.Server.Auth;
///
/// MiGu.Server ↔ Simple3 8222 之间的 **内部共享 token**。
///
/// 设计动机:Simple3 8222 的 EmbedIO WebApi(ReflectionApi / PersistenceApi / MapEditApi)
/// 历史上完全无鉴权,远程访问 = 接管调度内核。为不在 Simple3 侧实现完整 JWT 验签
/// (减少 Simple3 复杂度),采用一个轻量约定:
///
/// - 本机回环(127.0.0.1 / ::1)Simple3 直接放行(开发机直连不受影响);
/// - 其它来源必须携带 X-Platform-Internal-Token header;
/// - MiGu.Server YARP 反代 /api/sl/* 到 Simple3 8222 时,YARP transform
/// 会自动追加该 header;
/// - Simple3 端通过 simple.json:platform.internalToken 或环境变量
/// SIMPLE3__PLATFORM__INTERNALTOKEN 配置同一个 token;两端不一致即拒绝。
///
/// Token 来源优先级:
/// 1) appsettings.json:Internal:Token 显式配置(生产推荐:strong, length ≥ 32)
/// 2) 环境变量 PLATFORM__INTERNAL__TOKEN
/// 3) 兜底:进程随机 64 字节 base64,并在日志告警 + 写入 data/.internal-token
/// 文件供本机 Simple3 读取(同机部署常见场景)
///
public sealed class InternalTokenStore
{
public string Token { get; }
public bool IsEphemeral { get; }
public string? PersistedFilePath { get; }
public InternalTokenStore(IConfiguration config, IWebHostEnvironment env, ILogger logger)
{
var configured = config["Internal:Token"];
if (!string.IsNullOrWhiteSpace(configured) && configured != "REPLACE_ME")
{
Token = configured;
IsEphemeral = false;
logger.LogInformation("Internal token 从配置读取(长度 {Len})。", Token.Length);
return;
}
// 兜底:进程随机 + 落地到 data/.internal-token 便于同机 Simple3 读取
var dataDir = Path.Combine(env.ContentRootPath, "data");
Directory.CreateDirectory(dataDir);
var file = Path.Combine(dataDir, ".internal-token");
if (File.Exists(file))
{
try
{
var existing = File.ReadAllText(file).Trim();
if (existing.Length >= 32)
{
Token = existing;
IsEphemeral = false;
PersistedFilePath = file;
logger.LogInformation("Internal token 复用 {File}(长度 {Len})。", file, Token.Length);
return;
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "读取 {File} 失败,将重新生成 internal token。", file);
}
}
Token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48));
IsEphemeral = true;
try
{
File.WriteAllText(file, Token);
PersistedFilePath = file;
logger.LogWarning(
"Internal token 未配置 —— 已生成进程随机值并写入 {File}(重启后保留)。" +
"若 MiGu.Server 与 Simple3 不在同一台机器,请把同一个值写入 Simple3 端 simple.json:platform.internalToken。",
file);
}
catch (Exception ex)
{
logger.LogError(ex, "internal token 落盘失败 —— 远程 Simple3 调用将无法通过鉴权(每次重启 token 都不同)。");
}
}
}