安全存储中 ASP.NET Core 中开发的应用程序机密Safe storage of app secrets in development in ASP.NET Core

通过Rick AndersonDaniel Roth,和Scott AddieBy Rick Anderson, Daniel Roth, and Scott Addie

查看或下载示例代码如何下载View or download sample code (how to download)

本文档介绍在开发计算机上开发 ASP.NET Core 应用过程中存储和检索敏感数据的方法。This document explains techniques for storing and retrieving sensitive data during development of an ASP.NET Core app on a development machine. 永远不要将密码或其他敏感数据存储在源代码中。Never store passwords or other sensitive data in source code. 不应使用生产机密进行开发或测试。Production secrets shouldn't be used for development or test. 机密不应与应用一起部署。Secrets shouldn't be deployed with the app. 相反,机密应通过受控方式(如环境变量、Azure Key Vault 等)在生产环境中可用。可以通过Azure Key Vault 配置提供程序存储和保护 Azure 测试和生产机密。Instead, secrets should be made available in the production environment through a controlled means like environment variables, Azure Key Vault, etc. You can store and protect Azure test and production secrets with the Azure Key Vault configuration provider.

环境变量Environment variables

使用环境变量以避免在代码中或在本地配置文件中的应用机密的存储。Environment variables are used to avoid storage of app secrets in code or in local configuration files. 环境变量重写所有以前指定的配置源的配置的值。Environment variables override configuration values for all previously specified configuration sources.

通过在 Startup 构造函数中调用 AddEnvironmentVariables 来配置读取环境变量值:Configure the reading of environment variable values by calling AddEnvironmentVariables in the Startup constructor:

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 应用单个用户帐户已启用安全性。Consider an ASP.NET Core web app in which Individual User Accounts security is enabled. 默认数据库连接字符串包含在项目的appsettings.json文件具有键DefaultConnectionA default database connection string is included in the project's appsettings.json file with the key DefaultConnection. 默认连接字符串是 localdb,这在用户模式下运行,不需要密码。The default connection string is for LocalDB, which runs in user mode and doesn't require a password. 应用程序在部署期间,DefaultConnection使用环境变量的值可以重写密钥值。During app deployment, the DefaultConnection key value can be overridden with an environment variable's value. 环境变量可以存储敏感凭据与完整的连接字符串。The environment variable may store the complete connection string with sensitive credentials.

警告

环境变量通常存储在普通的未加密的文本。Environment variables are generally stored in plain, unencrypted text. 如果计算机或进程受到攻击,可以由不受信任方访问环境变量。If the machine or process is compromised, environment variables can be accessed by untrusted parties. 可能需要更多措施,防止用户机密泄露。Additional measures to prevent disclosure of user secrets may be required.

在环境变量中使用分层键时,冒号分隔符 (:) 可能无法适用于所有平台(例如 Bash)。When working with hierarchical keys in environment variables, a colon separator (:) may not work on all platforms (for example, Bash). 所有平台均支持采用双下划线 (__),并可以用冒号自动替换。A double underscore (__) is supported by all platforms and is automatically replaced by a colon.

机密管理器Secret Manager

密码管理器工具在 ASP.NET Core 项目的开发过程中存储敏感数据。The Secret Manager tool stores sensitive data during the development of an ASP.NET Core project. 在此上下文中,一种敏感数据是应用程序密码。In this context, a piece of sensitive data is an app secret. 应用程序机密存储在项目树不同的位置。App secrets are stored in a separate location from the project tree. 与特定项目关联或在多个项目之间共享的应用程序机密。The app secrets are associated with a specific project or shared across several projects. 应用机密不会签入源代码管理。The app secrets aren't checked into source control.

警告

机密管理器工具不会加密存储的机密,不应被视为受信任存储区。The Secret Manager tool doesn't encrypt the stored secrets and shouldn't be treated as a trusted store. 它是仅限开发目的。It's for development purposes only. 在用户配置文件目录中的 JSON 配置文件中存储的键和值。The keys and values are stored in a JSON configuration file in the user profile directory.

机密管理器工具的工作原理How the Secret Manager tool works

机密管理器工具抽象化实现详细信息,例如位置和方式存储的值。The Secret Manager tool abstracts away the implementation details, such as where and how the values are stored. 如果不知道这些实现的详细信息,可以使用该工具。You can use the tool without knowing these implementation details. 值存储在本地计算机上 JSON 配置文件中的系统保护的用户配置文件文件夹中:The values are stored in a JSON configuration file in a system-protected user profile folder on the local machine:

文件系统路径:File system path:

%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json

在前面文件路径中,替换<user_secrets_id>UserSecretsId中指定值 .csproj文件。In the preceding file paths, replace <user_secrets_id> with the UserSecretsId value specified in the .csproj file.

不编写代码,取决于使用机密管理器工具中保存的数据的格式的位置。Don't write code that depends on the location or format of data saved with the Secret Manager tool. 这些实现细节可能会更改。These implementation details may change. 例如,机密的值不会加密,但可能在将来。For example, the secret values aren't encrypted, but could be in the future.

安装机密管理器工具Install the Secret Manager tool

机密管理器工具是可与.NET Core CLI,在.NET Core SDK 2.1.300 捆绑或更高版本。The Secret Manager tool is bundled with the .NET Core CLI in .NET Core SDK 2.1.300 or later. 有关.NET Core SDK 2.1.300 之前的版本中,工具安装是必需的。For .NET Core SDK versions before 2.1.300, tool installation is necessary.

提示

运行dotnet --version从命令行界面中,若要查看已安装的.NET Core SDK 版本号。Run dotnet --version from a command shell to see the installed .NET Core SDK version number.

如果正在使用的.NET Core SDK 包括的工具,将显示警告:A warning is displayed if the .NET Core SDK being used includes the tool:

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 包。Install the Microsoft.Extensions.SecretManager.Tools NuGet package in your ASP.NET Core project. 例如:For example:

<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>

在验证工具的安装命令行界面中执行以下命令:Execute the following command in a command shell to validate the tool installation:

dotnet user-secrets -h

机密管理器工具会显示示例用法、 选项和命令帮助:The Secret Manager tool displays sample usage, options, and command help:

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元素。You must be in the same directory as the .csproj file to run tools defined in the .csproj file's DotNetCliToolReference elements.

启用密钥存储Enable secret storage

机密管理器工具对存储在用户配置文件中的特定于项目的配置设置进行操作。The Secret Manager tool operates on project-specific configuration settings stored in your user profile.

机密管理器工具在 .NET Core SDK 3.0.100 或更高版本中包含 init 命令。The Secret Manager tool includes an init command in .NET Core SDK 3.0.100 or later. 若要使用用户机密,请在项目目录中运行以下命令:To use user secrets, run the following command in the project directory:

dotnet user-secrets init

前面的命令将 UserSecretsId 元素添加到 .csproj文件的 PropertyGroup 中。The preceding command adds a UserSecretsId element within a PropertyGroup of the .csproj file. 默认情况下,UserSecretsId 的内部文本是一个 GUID。By default, the inner text of UserSecretsId is a GUID. 内部文本是任意的,但对项目是唯一的。The inner text is arbitrary, but is unique to the project.

若要使用用户机密,定义UserSecretsId中的元素PropertyGroup.csproj文件。To use user secrets, define a UserSecretsId element within a PropertyGroup of the .csproj file. UserSecretsId 的内部文本是任意的,但对项目是唯一的。The inner text of UserSecretsId is arbitrary, but is unique to the project. 开发人员通常会生成的 GUID UserSecretsIdDevelopers typically generate a GUID for the 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 中,右键单击解决方案资源管理器中的项目并选择管理用户机密从上下文菜单。In Visual Studio, right-click the project in Solution Explorer, and select Manage User Secrets from the context menu. 添加了此笔势UserSecretsId元素中,为填充 guid .csproj文件。This gesture adds a UserSecretsId element, populated with a GUID, to the .csproj file.

设置的机密Set a secret

定义由一个键和其值组成的应用程序密码。Define an app secret consisting of a key and its value. 密钥是与项目相关联UserSecretsId值。The secret is associated with the project's UserSecretsId value. 例如,从在其中的目录运行以下命令 .csproj文件是否存在:For example, run the following command from the directory in which the .csproj file exists:

dotnet user-secrets set "Movies:ServiceApiKey" "12345"

在前面的示例中,冒号表示Movies是一个对象文字与ServiceApiKey属性。In the preceding example, the colon denotes that Movies is an object literal with a ServiceApiKey property.

机密管理器工具可以过使用从其他目录。The Secret Manager tool can be used from other directories too. 使用--project选项可提供文件系统路径,在 .csproj文件存在。Use the --project option to supply the file system path at which the .csproj file exists. 例如:For example:

dotnet user-secrets set "Movies:ServiceApiKey" "12345" --project "C:\apps\WebApp1\src\WebApp1"

Visual Studio 中的 JSON 结构平展JSON structure flattening in Visual Studio

Visual Studio 的 "管理用户机密" 手势在文本编辑器中打开一个密码文件。Visual Studio's Manage User Secrets gesture opens a secrets.json file in the text editor. 内容替换为secrets.json与要存储的键 / 值对。Replace the contents of secrets.json with the key-value pairs to be stored. 例如:For example:

{
  "Movies": {
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true",
    "ServiceApiKey": "12345"
  }
}

JSON 结构平展后通过修改dotnet user-secrets removedotnet user-secrets setThe JSON structure is flattened after modifications via dotnet user-secrets remove or dotnet user-secrets set. 例如,运行dotnet user-secrets remove "Movies:ConnectionString"折叠Movies对象文字。For example, running dotnet user-secrets remove "Movies:ConnectionString" collapses the Movies object literal. 修改后的文件如下所示:The modified file resembles the following:

{
  "Movies:ServiceApiKey": "12345"
}

设置多个机密Set multiple secrets

可以通过管道传递到 JSON 设置机密一批set命令。A batch of secrets can be set by piping JSON to the set command. 在以下示例中, input.json文件的内容通过管道传递给set命令。In the following example, the input.json file's contents are piped to the set command.

打开命令外壳中,并执行以下命令:Open a command shell, and execute the following command:

type .\input.json | dotnet user-secrets set

访问机密Access a secret

ASP.NET Core 配置 API提供对机密 Manager 机密的访问。The ASP.NET Core Configuration API provides access to Secret Manager secrets.

如果你的项目以 .NET Framework 为目标,请安装UserSecrets NuGet 包。If your project targets .NET Framework, install the Microsoft.Extensions.Configuration.UserSecrets NuGet package.

在 ASP.NET Core 2.0 或更高版本中,当项目调用时,用户机密配置源会自动添加到 CreateDefaultBuilder 以使用预先配置的默认值初始化主机的新实例。In ASP.NET Core 2.0 or later, the user secrets configuration source is automatically added in development mode when the project calls CreateDefaultBuilder to initialize a new instance of the host with preconfigured defaults. DevelopmentEnvironmentName 时,CreateDefaultBuilder 调用 AddUserSecretsCreateDefaultBuilder calls AddUserSecrets when the EnvironmentName is Development:

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 来显式添加用户机密配置源。When CreateDefaultBuilder isn't called, add the user secrets configuration source explicitly by calling AddUserSecrets in the Startup constructor. 仅在开发环境中运行应用时调用 AddUserSecrets,如以下示例中所示:Call AddUserSecrets only when the app runs in the Development environment, as shown in the following example:

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 包。Install the Microsoft.Extensions.Configuration.UserSecrets NuGet package.

使用 Startup 构造函数中的 AddUserSecrets 调用添加用户机密配置源:Add the user secrets configuration source with a call to AddUserSecrets in the Startup constructor:

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();
}

可以通过检索用户机密ConfigurationAPI:User secrets can be retrieved via the 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 的机密Map secrets to a POCO

将整个对象文字映射到 POCO (具有属性的简单.NET 类) 可用于聚合相关的属性。Mapping an entire object literal to a POCO (a simple .NET class with properties) is useful for aggregating related properties.

假定应用程序的secrets.json文件包含以下两个密码:Assume the app's secrets.json file contains the following two secrets:

{
  "Movies": {
    "ServiceApiKey": "12345",
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

若要映射到 POCO,前面的机密,使用ConfigurationAPI 的对象 graph 绑定功能。To map the preceding secrets to a POCO, use the Configuration API's object graph binding feature. 以下代码将绑定到自定义MovieSettingsPOCO 和访问ServiceApiKey属性值:The following code binds to a custom MovieSettings POCO and accesses the ServiceApiKey property value:

var moviesConfig = Configuration.GetSection("Movies")
                                .Get<MovieSettings>();
_moviesApiKey = moviesConfig.ServiceApiKey;
var moviesConfig = new MovieSettings();
Configuration.GetSection("Movies").Bind(moviesConfig);
_moviesApiKey = moviesConfig.ServiceApiKey;

Movies:ConnectionStringMovies:ServiceApiKey机密映射到相应的属性中MovieSettings:The Movies:ConnectionString and Movies:ServiceApiKey secrets are mapped to the respective properties in MovieSettings:

public class MovieSettings
{
    public string ConnectionString { get; set; }

    public string ServiceApiKey { get; set; }
}

包含机密的字符串替换String replacement with secrets

以纯文本形式存储密码是不安全。Storing passwords in plain text is insecure. 例如,数据库连接字符串存储在appsettings.json可能包括指定用户的密码:For example, a database connection string stored in appsettings.json may include a password for the specified user:

{
  "ConnectionStrings": {
    "Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User Id=johndoe;Password=pass123;MultipleActiveResultSets=true"
  }
}

更安全的方法是将密码存储为机密。A more secure approach is to store the password as a secret. 例如:For example:

dotnet user-secrets set "DbPassword" "pass123"

删除Password中的连接字符串中的键 / 值对appsettings.jsonRemove the Password key-value pair from the connection string in appsettings.json. 例如:For example:

{
  "ConnectionStrings": {
    "Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User Id=johndoe;MultipleActiveResultSets=true"
  }
}

可以对 SqlConnectionStringBuilder 对象的 Password 属性设置机密的值,以完成连接字符串:The secret's value can be set on a SqlConnectionStringBuilder object's Password property to complete the connection string:

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}");
        });
    }
}

列出机密List the secrets

假定应用程序的secrets.json文件包含以下两个密码:Assume the app's secrets.json file contains the following two secrets:

{
  "Movies": {
    "ServiceApiKey": "12345",
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

从在其中的目录运行以下命令 .csproj文件是否存在:Run the following command from the directory in which the .csproj file exists:

dotnet user-secrets list

将显示以下输出:The following output appears:

Movies:ConnectionString = Server=(localdb)\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true
Movies:ServiceApiKey = 12345

在前面的示例中,在项名称中的冒号表示对象层次结构内的secrets.jsonIn the preceding example, a colon in the key names denotes the object hierarchy within secrets.json.

删除单个机密Remove a single secret

假定应用程序的secrets.json文件包含以下两个密码:Assume the app's secrets.json file contains the following two secrets:

{
  "Movies": {
    "ServiceApiKey": "12345",
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

从在其中的目录运行以下命令 .csproj文件是否存在:Run the following command from the directory in which the .csproj file exists:

dotnet user-secrets remove "Movies:ConnectionString"

应用程序的secrets.json已修改文件,以删除与关联的键 / 值对MoviesConnectionString密钥:The app's secrets.json file was modified to remove the key-value pair associated with the MoviesConnectionString key:

{
  "Movies": {
    "ServiceApiKey": "12345"
  }
}

运行dotnet user-secrets list会显示以下消息:Running dotnet user-secrets list displays the following message:

Movies:ServiceApiKey = 12345

删除所有机密Remove all secrets

假定应用程序的secrets.json文件包含以下两个密码:Assume the app's secrets.json file contains the following two secrets:

{
  "Movies": {
    "ServiceApiKey": "12345",
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

从在其中的目录运行以下命令 .csproj文件是否存在:Run the following command from the directory in which the .csproj file exists:

dotnet user-secrets clear

从已删除应用程序的所有用户机密secrets.json文件:All user secrets for the app have been deleted from the secrets.json file:

{}

运行dotnet user-secrets list会显示以下消息:Running dotnet user-secrets list displays the following message:

No secrets configured for this application.

其他资源Additional resources