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 都不同)。"); } } }