在 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 the development of an ASP.NET Core app. 永遠不會將密碼或其他機密資料儲存在原始程式碼中。Never store passwords or other sensitive data in source code. 不應使用生產環境祕密進行開發或測試。Production secrets shouldn't be used for development or test. 您可以透過 Azure Key Vault 設定提供者儲存及保護 Azure 測試與生產祕密。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.

藉由呼叫設定環境變數值的讀取AddEnvironmentVariablesStartup建構函式: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 replaced by a colon.

Secret ManagerSecret Manager

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.

Secret Manager 工具的運作方式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.

不要撰寫相依於使用 Secret Manager 工具所儲存的資料格式的位置的程式碼。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.

安裝 Secret Manager 工具Install the Secret Manager tool

Secret Manager 工具是使用.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

Secret Manager 工具會顯示範例使用方式、 選項和命令說明: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

Secret Manager 工具作儲存在您的使用者設定檔中的專案特定的組態設定。The Secret Manager tool operates on project-specific configuration settings stored in your user profile.

Secret Manager 工具包括init命令,在.NET Core SDK 3.0.100 或更新版本。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項目內PropertyGroup.csproj檔案。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.

可以從其他目錄太使用 Secret Manager 工具。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管理使用者祕密軌跡會開啟secrets.json在文字編輯器中的檔案。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

來設定批次的祕密,請使用管線傳送至 JSONset命令。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提供 Secret Manager 祕密的存取。The ASP.NET Core Configuration API provides access to Secret Manager secrets.

如果您的專案以.NET Framework 為目標,安裝Microsoft.Extensions.Configuration.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. CreateDefaultBuilder 呼叫AddUserSecretsEnvironmentNameDevelopment:CreateDefaultBuilder calls AddUserSecrets when the EnvironmentName is Development:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

CreateDefaultBuilder不是呼叫,藉由呼叫明確新增使用者密碼設定來源AddUserSecretsStartup建構函式。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();
}

安裝Microsoft.Extensions.Configuration.UserSecrets NuGet 套件。Install the Microsoft.Extensions.Configuration.UserSecrets NuGet package.

新增使用者密碼設定來源,藉由呼叫AddUserSecretsStartup建構函式: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)
    {
        var result = string.IsNullOrEmpty(_moviesApiKey) ? "Null" : "Not Null";
        app.Run(async (context) =>
        {
            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)
    {
        var result = string.IsNullOrEmpty(_moviesApiKey) ? "Null" : "Not Null";
        app.Run(async (context) =>
        {
            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