在C#项目开发中,几乎每个应用都会涉及数据库连接字符串、第三方API密钥、云服务凭证这类敏感信息。不少开发者图方便直接把这些内容写进appsettings.json,然后随手git push,结果密钥被推到远程仓库。一旦仓库公开或人员流动,这些凭证就可能被恶意利用,造成真实的经济损失。.NET团队为此提供了User Secrets(用户机密)机制,专门用于在开发环境中隔离敏感数据。本文将详细讲解它的配置方法、存储原理以及使用中的注意事项。

为什么不能把密钥写在appsettings.json里
appsettings.json属于项目文件,默认会被纳入版本控制。哪怕你后来意识到问题把它删掉,历史提交记录里依然保留着旧的密钥。GitHub的自动扫描服务会实时检测公开仓库中泄露的AWS密钥、数据库密码,攻击者同样也在扫描。现实中因为连接字符串泄露导致云数据库被勒索删库的案例并不少见。
有人会想到把appsettings.json加入.gitignore,这确实能阻止提交,但代价是团队其他成员拉取代码后缺少配置文件,项目直接跑不起来,新人入手成本变高。还有人用appsettings.Development.json区分环境,但只要这个文件被提交,问题依然存在。
正确的思路是:代码仓库里只保留配置项的键名和占位值,真实的密钥值存放在每位开发者本机的一个独立目录中,与项目完全解耦。这正是User Secrets的设计初衷。它在.NET Core 2.1之后集成进了项目模板,通过一个随机生成的GUID把项目和本机秘密存储目录关联起来。
User Secrets的配置与使用方法
启用用户机密最简单的方式是使用命令行。在项目目录(.csproj所在目录)下执行以下命令:
dotnet user-secrets init
执行后会在.csproj文件中生成一个UserSecretsId元素,值是一个随机GUID。这个GUID就是项目与本地秘密存储之间的关联凭证,它可以安全地提交到仓库,因为它本身不包含任何敏感信息。
接下来设置具体的机密项。假设要存储数据库连接字符串,可以执行:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyApp;User Id=sa;Password=your_password;"
这里的键名支持冒号分层结构,与appsettings.json中的层级配置保持一致。除了set命令,还有几个常用命令需要记住:dotnet user-secrets list列出所有机密项,dotnet user-secrets remove删除指定键,dotnet user-secrets clear清空全部机密。
如果你使用Visual Studio,还可以在解决方案资源管理器中右键项目,选择“管理用户机密”,IDE会直接打开本机的secrets.json文件供编辑,效果与命令行操作完全相同。
机密数据存储在哪里,代码如何读取
理解User Secrets的存储位置有助于建立安全直觉。在Windows上,机密文件保存在%APPDATA%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json,而在Linux和macOS上路径是~/.microsoft/usersecrets/{UserSecretsId}/secrets.json。注意两点:第一,该目录在项目文件夹之外,天然不会被git追踪;第二,文件内容是明文JSON,没有任何加密。
这一点经常被误解。User Secrets解决的是“防止密钥进入版本控制”这个问题,而不是加密问题。任何能登录你操作系统的用户,理论上都能读取这个文件。所以官方文档明确强调它仅适用于开发环境,绝不能用于生产。
读取方面几乎零成本。在Program.cs中,开发者模板默认已经调用了AddUserSecrets相关逻辑(通过WebApplication.CreateBuilder或Host.CreateDefaultBuilder自动注册,需Development环境)。配置系统会按照环境变量、appsettings.json、User Secrets的优先级顺序合并,因此代码中读取方式与普通配置完全一致:
var builder = WebApplication.CreateBuilder(args);
// 开发环境下User Secrets已自动注入配置系统
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var apiKey = builder.Configuration["ExternalApi:Key"];
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
var app = builder.Build();
app.MapGet("/", () => $"API Key: {apiKey}");
app.Run();如果你手动构建ConfigurationBuilder,则需要显式调用AddUserSecrets<Program>()扩展方法。建议在代码中通过配置占位符的方式让团队成员知道有哪些必需的机密项,例如在appsettings.json中保留"Key": "请在本地配置User Secrets"这样的占位值。
生产环境怎么办:替代方案与整体思路
User Secrets只覆盖开发阶段,部署到生产环境时需要另一套方案。常见做法有三种,可以按团队规模和基础设施选择。
第一种是环境变量。在Linux服务器上通过systemd的EnvironmentFile,或在Docker中使用--env-file传入敏感配置,ASP.NET Core的默认配置系统会优先读取环境变量。这种方式简单直接,但要确保服务器本身的访问权限受控,且不要把.env文件放进代码仓库。
第二种是密钥管理服务。Azure Key Vault、AWS Secrets Manager、阿里云KMS这类服务提供集中式密钥存储、访问审计和自动轮换能力。ASP.NET Core对Azure Key Vault有一等支持:
var builder = WebApplication.CreateBuilder(args);
if (!builder.Environment.IsDevelopment())
{
builder.Configuration.AddAzureKeyVault(
new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential());
}第三种是对称加密配置节。使用Microsoft.Extensions.Configuration.ConfigurableKeyVault或自己基于DPAPI、AES实现配置加密,把密文写在配置文件中,启动时解密。这种方式不依赖外部服务,但密钥管理责任完全在自己身上,一般只作为过渡手段。
无论选择哪种方案,核心原则始终一致:敏感数据与代码分离、按环境区分配置来源、为不同环境使用不同的密钥值。开发用User Secrets、测试用安全的配置中心、生产用密钥管理服务,这样的分层结构才能既保证开发体验,又守住安全底线。
常见问题与最佳实践总结
使用User Secrets时有几个易踩的坑值得注意。首先是必须在项目目录下执行命令,否则GUID关联不上,配置读不到值还不会报错,只是静默返回null,排查起来很费时间。其次,secrets.json不支持注释,JSON格式错误也会导致配置静默丢失,遇到配置读取异常时可以先用dotnet user-secrets list验证数据是否正常写入。
多人协作时建议在项目README中记录必需的机密键名清单,或者提供一个读取配置并在缺失时抛出详细异常的启动校验逻辑,避免新成员因为缺少本地机密而面对一堆空引用异常。
最后整理一条完整的实践路径:仓库中的appsettings.json只保留非敏感配置和占位符;开发环境的敏感值全部通过User Secrets管理;CI/CD流水线中的密钥通过流水线变量或密钥服务注入;生产环境接入Key Vault类服务并启用访问审计。做到这几点,你的C#项目在敏感数据管理上就达到了工程化的合格水准。
C#用户机密管理User Secrets敏感数据保护修改时间:2026-09-13 18:34:58