在开发过程中保护机密

安全存储中 ASP.NET Core 中开发的应用程序机密

通过Rick AndersonDaniel 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 配置文件中的系统保护的用户配置文件文件夹中:

在前面文件路径中,替换<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 中的 JSON 结构平展

Visual Studio 的 "管理用户机密" 手势在文本编辑器中打开一个密码文件。 内容替换为secrets.json与要存储的键 / 值对。 例如:

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

JSON 结构平展后通过修改dotnet user-secrets removedotnet user-secrets set 例如,运行dotnet user-secrets remove "Movies:ConnectionString"折叠Movies对象文字。 修改后的文件如下所示:

{
  "Movies:ServiceApiKey": "12345"
}

设置多个机密

可以通过管道传递到 JSON 设置机密一批set命令。 在以下示例中, input.json文件的内容通过管道传递给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();
}

可以通过检索用户机密ConfigurationAPI:

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 的机密

将整个对象文字映射到 POCO (具有属性的简单.NET 类) 可用于聚合相关的属性。

假定应用程序的secrets.json文件包含以下两个密码:

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

若要映射到 POCO,前面的机密,使用ConfigurationAPI 的对象 graph 绑定功能。 以下代码将绑定到自定义MovieSettingsPOCO 和访问ServiceApiKey属性值:

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:

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.

其他资源

目录