通过Rick Anderson, Daniel Roth,和Scott Addie
本文档介绍在开发计算机上开发 ASP.NET Core 应用过程中存储和检索敏感数据的方法。 永远不要将密码或其他敏感数据存储在源代码中。 不应使用生产机密进行开发或测试。 机密不应与应用一起部署。 相反,机密应通过受控方式(如环境变量、Azure Key Vault 等)在生产环境中可用。可以通过Azure Key Vault 配置提供程序存储和保护 Azure 测试和生产机密。
使用环境变量以避免在代码中或在本地配置文件中的应用机密的存储。 环境变量重写所有以前指定的配置源的配置的值。
通过在 Startup
构造函数中调用 AddEnvironmentVariables 来配置读取环境变量值:
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); }
请考虑在其中一个 ASP.NET Core web 应用单个用户帐户已启用安全性。 默认数据库连接字符串包含在项目的appsettings.json文件具有键DefaultConnection
。 默认连接字符串是 localdb,这在用户模式下运行,不需要密码。 应用程序在部署期间,DefaultConnection
使用环境变量的值可以重写密钥值。 环境变量可以存储敏感凭据与完整的连接字符串。
警告
环境变量通常存储在普通的未加密的文本。 如果计算机或进程受到攻击,可以由不受信任方访问环境变量。 可能需要更多措施,防止用户机密泄露。
在环境变量中使用分层键时,冒号分隔符 (:
) 可能无法适用于所有平台(例如 Bash)。 所有平台均支持采用双下划线 (__
),并可以用冒号自动替换。
密码管理器工具在 ASP.NET Core 项目的开发过程中存储敏感数据。 在此上下文中,一种敏感数据是应用程序密码。 应用程序机密存储在项目树不同的位置。 与特定项目关联或在多个项目之间共享的应用程序机密。 应用机密不会签入源代码管理。
警告
机密管理器工具不会加密存储的机密,不应被视为受信任存储区。 它是仅限开发目的。 在用户配置文件目录中的 JSON 配置文件中存储的键和值。
机密管理器工具抽象化实现详细信息,例如位置和方式存储的值。 如果不知道这些实现的详细信息,可以使用该工具。 值存储在本地计算机上 JSON 配置文件中的系统保护的用户配置文件文件夹中:
文件系统路径:
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
文件系统路径:
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
在前面文件路径中,替换<user_secrets_id>
与UserSecretsId
中指定值 .csproj文件。
不编写代码,取决于使用机密管理器工具中保存的数据的格式的位置。 这些实现细节可能会更改。 例如,机密的值不会加密,但可能在将来。
机密管理器工具是可与.NET Core CLI,在.NET Core SDK 2.1.300 捆绑或更高版本。 有关.NET Core SDK 2.1.300 之前的版本中,工具安装是必需的。
提示
运行dotnet --version
从命令行界面中,若要查看已安装的.NET Core SDK 版本号。
如果正在使用的.NET Core SDK 包括的工具,将显示警告:
The tool 'Microsoft.Extensions.SecretManager.Tools' is now included in the .NET Core SDK. Information on resolving this warning is available at (https://aka.ms/dotnetclitools-in-box).
安装Microsoft.Extensions.SecretManager.Tools ASP.NET Core 项目中的 NuGet 包。 例如:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> <UserSecretsId>1242d6d6-9df3-4031-b031-d9b27d13c25a</UserSecretsId> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.6" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" /> <PackageReference Include="System.Data.SqlClient" Version="4.5.0" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.1" /> </ItemGroup> </Project>
在验证工具的安装命令行界面中执行以下命令:
dotnet user-secrets -h
机密管理器工具会显示示例用法、 选项和命令帮助:
Usage: dotnet user-secrets [options] [command] Options: -?|-h|--help Show help information --version Show version information -v|--verbose Show verbose output -p|--project <PROJECT> Path to project. Defaults to searching the current directory. -c|--configuration <CONFIGURATION> The project configuration to use. Defaults to 'Debug'. --id The user secret ID to use. Commands: clear Deletes all the application secrets list Lists all the application secrets remove Removes the specified user secret set Sets the user secret to the specified value Use "dotnet user-secrets [command] --help" for more information about a command.
备注
您必须位于与相同的目录 .csproj文件中定义的工具在运行 .csproj文件的DotNetCliToolReference
元素。
机密管理器工具对存储在用户配置文件中的特定于项目的配置设置进行操作。
机密管理器工具在 .NET Core SDK 3.0.100 或更高版本中包含 init
命令。 若要使用用户机密,请在项目目录中运行以下命令:
dotnet user-secrets init
前面的命令将 UserSecretsId
元素添加到 .csproj文件的 PropertyGroup
中。 默认情况下,UserSecretsId
的内部文本是一个 GUID。 内部文本是任意的,但对项目是唯一的。
若要使用用户机密,定义UserSecretsId
中的元素PropertyGroup
的 .csproj文件。 UserSecretsId
的内部文本是任意的,但对项目是唯一的。 开发人员通常会生成的 GUID UserSecretsId
。
<PropertyGroup> <TargetFramework>netcoreapp2.1</TargetFramework> <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId> </PropertyGroup>
<PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> <UserSecretsId>1242d6d6-9df3-4031-b031-d9b27d13c25a</UserSecretsId> </PropertyGroup>
提示
在 Visual Studio 中,右键单击解决方案资源管理器中的项目并选择管理用户机密从上下文菜单。 添加了此笔势UserSecretsId
元素中,为填充 guid .csproj文件。
定义由一个键和其值组成的应用程序密码。 密钥是与项目相关联UserSecretsId
值。 例如,从在其中的目录运行以下命令 .csproj文件是否存在:
dotnet user-secrets set "Movies:ServiceApiKey" "12345"
在前面的示例中,冒号表示Movies
是一个对象文字与ServiceApiKey
属性。
机密管理器工具可以过使用从其他目录。 使用--project
选项可提供文件系统路径,在 .csproj文件存在。 例如:
dotnet user-secrets set "Movies:ServiceApiKey" "12345" --project "C:\apps\WebApp1\src\WebApp1"
Visual Studio 的 "管理用户机密" 手势在文本编辑器中打开一个密码文件。 内容替换为secrets.json与要存储的键 / 值对。 例如:
{ "Movies": { "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true", "ServiceApiKey": "12345" } }
JSON 结构平展后通过修改dotnet user-secrets remove
或dotnet user-secrets set
。 例如,运行dotnet user-secrets remove "Movies:ConnectionString"
折叠Movies
对象文字。 修改后的文件如下所示:
{ "Movies:ServiceApiKey": "12345" }
可以通过管道传递到 JSON 设置机密一批set
命令。 在以下示例中, input.json文件的内容通过管道传递给set
命令。
打开命令外壳中,并执行以下命令:
type .\input.json | dotnet user-secrets set
打开命令外壳中,并执行以下命令:
cat ./input.json | dotnet user-secrets set
ASP.NET Core 配置 API提供对机密 Manager 机密的访问。
如果你的项目以 .NET Framework 为目标,请安装UserSecrets NuGet 包。
在 ASP.NET Core 2.0 或更高版本中,当项目调用时,用户机密配置源会自动添加到 CreateDefaultBuilder 以使用预先配置的默认值初始化主机的新实例。 DevelopmentEnvironmentName 时,CreateDefaultBuilder
调用 AddUserSecrets:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
如果未调用 CreateDefaultBuilder
,请通过在 Startup
构造函数中调用 AddUserSecrets 来显式添加用户机密配置源。 仅在开发环境中运行应用时调用 AddUserSecrets
,如以下示例中所示:
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); }
public Startup(IWebHostEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); }
安装Microsoft.Extensions.Configuration.UserSecrets NuGet 包。
使用 Startup
构造函数中的 AddUserSecrets 调用添加用户机密配置源:
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); }
可以通过检索用户机密Configuration
API:
public class Startup { private string _moviesApiKey = null; public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { _moviesApiKey = Configuration["Movies:ServiceApiKey"]; } public void Configure(IApplicationBuilder app) { app.Run(async (context) => { var result = string.IsNullOrEmpty(_moviesApiKey) ? "Null" : "Not Null"; await context.Response.WriteAsync($"Secret is {result}"); }); } }
public class Startup { private string _moviesApiKey = null; public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { _moviesApiKey = Configuration["Movies:ServiceApiKey"]; } public void Configure(IApplicationBuilder app) { app.Run(async (context) => { var result = string.IsNullOrEmpty(_moviesApiKey) ? "Null" : "Not Null"; await context.Response.WriteAsync($"Secret is {result}"); }); } }
将整个对象文字映射到 POCO (具有属性的简单.NET 类) 可用于聚合相关的属性。
假定应用程序的secrets.json文件包含以下两个密码:
{ "Movies": { "ServiceApiKey": "12345", "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true" } }
若要映射到 POCO,前面的机密,使用Configuration
API 的对象 graph 绑定功能。 以下代码将绑定到自定义MovieSettings
POCO 和访问ServiceApiKey
属性值:
var moviesConfig = Configuration.GetSection("Movies") .Get<MovieSettings>(); _moviesApiKey = moviesConfig.ServiceApiKey;
var moviesConfig = new MovieSettings(); Configuration.GetSection("Movies").Bind(moviesConfig); _moviesApiKey = moviesConfig.ServiceApiKey;
Movies:ConnectionString
并Movies:ServiceApiKey
机密映射到相应的属性中MovieSettings
:
public class MovieSettings { public string ConnectionString { get; set; } public string ServiceApiKey { get; set; } }
以纯文本形式存储密码是不安全。 例如,数据库连接字符串存储在appsettings.json可能包括指定用户的密码:
{ "ConnectionStrings": { "Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User Id=johndoe;Password=pass123;MultipleActiveResultSets=true" } }
更安全的方法是将密码存储为机密。 例如:
dotnet user-secrets set "DbPassword" "pass123"
删除Password
中的连接字符串中的键 / 值对appsettings.json。 例如:
{ "ConnectionStrings": { "Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User Id=johndoe;MultipleActiveResultSets=true" } }
可以对 SqlConnectionStringBuilder 对象的 Password 属性设置机密的值,以完成连接字符串:
public class Startup { private string _connection = null; public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { var builder = new SqlConnectionStringBuilder( Configuration.GetConnectionString("Movies")); builder.Password = Configuration["DbPassword"]; _connection = builder.ConnectionString; } public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync($"DB Connection: {_connection}"); }); } }
public class Startup { private string _connection = null; public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { var builder = new SqlConnectionStringBuilder( Configuration.GetConnectionString("Movies")); builder.Password = Configuration["DbPassword"]; _connection = builder.ConnectionString; } public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync($"DB Connection: {_connection}"); }); } }
假定应用程序的secrets.json文件包含以下两个密码:
{ "Movies": { "ServiceApiKey": "12345", "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true" } }
从在其中的目录运行以下命令 .csproj文件是否存在:
dotnet user-secrets list
将显示以下输出:
Movies:ConnectionString = Server=(localdb)\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true Movies:ServiceApiKey = 12345
在前面的示例中,在项名称中的冒号表示对象层次结构内的secrets.json。
假定应用程序的secrets.json文件包含以下两个密码:
{ "Movies": { "ServiceApiKey": "12345", "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true" } }
从在其中的目录运行以下命令 .csproj文件是否存在:
dotnet user-secrets remove "Movies:ConnectionString"
应用程序的secrets.json已修改文件,以删除与关联的键 / 值对MoviesConnectionString
密钥:
{ "Movies": { "ServiceApiKey": "12345" } }
运行dotnet user-secrets list
会显示以下消息:
Movies:ServiceApiKey = 12345
假定应用程序的secrets.json文件包含以下两个密码:
{ "Movies": { "ServiceApiKey": "12345", "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true" } }
从在其中的目录运行以下命令 .csproj文件是否存在:
dotnet user-secrets clear
从已删除应用程序的所有用户机密secrets.json文件:
{}
运行dotnet user-secrets list
会显示以下消息:
No secrets configured for this application.