在 ASP.NET Core 中的開發中安全儲存應用程式秘密Safe storage of app secrets in development in ASP.NET Core

Rick AndersonDaniel RothScott 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.

藉由AddEnvironmentVariables Startup在函式中呼叫來設定讀取環境變數值: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中,並具有索引鍵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 SDK 2.1.300 或更新版本中的 .NET Core CLI。The Secret Manager tool is bundled with the .NET Core CLI in .NET Core SDK 2.1.300 or later. 在2.1.300 之前的 .NET Core SDK 版本中,需要安裝工具。For .NET Core SDK versions before 2.1.300, tool installation is necessary.

提示

dotnet --version命令 shell 執行,以查看已安裝的 .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).

在 ASP.NET Core 專案中安裝Microsoft.extensions.secretmanager.tools 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>

在命令 shell 中執行下列命令來驗證工具安裝: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.

「密碼管理員」工具在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. 開發人員通常會產生的UserSecretsIdGUID。Developers 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"

在上述範例中,冒號表示MoviesServiceApiKey具有屬性的物件常值。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. 使用選項來提供 .csproj 檔案所在的檔案系統路徑。 --projectUse 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 的 [管理使用者秘密] 手勢會在文字編輯器中開啟一個秘密 json檔案。Visual Studio's Manage User Secrets gesture opens a secrets.json file in the text editor. 以要儲存的機碼值組取代密碼. 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 set進行修改之後壓平合併。The 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. 在下列範例中,輸入 json檔案的內容會以管道傳送至set命令。In the following example, the input.json file's contents are piped to the set command.

開啟命令 shell,然後執行下列命令:Open a command shell, and execute the following command:

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

存取秘密Access a secret

ASP.NET Core 設定 API提供秘密管理員密碼的存取權。The ASP.NET Core Configuration API provides access to Secret Manager secrets.

如果您的專案是以 .NET Framework 為目標,請安裝Usersecrets.xml 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. CreateDefaultBuilderAddUserSecrets為時Development呼叫: EnvironmentNameCreateDefaultBuilder 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();
}

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

使用在此AddUserSecrets Startup函式中的呼叫來新增使用者秘密設定來源: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();
}

您可以透過Configuration API 來抓取使用者秘密: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}");
        });
    }
}

將秘密對應至 POCOMap 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,請使用Configuration API 的物件圖形系結功能。To map the preceding secrets to a POCO, use the Configuration API's object graph binding feature. 下列程式碼會系結至MovieSettings自訂 POCO 並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;

和密碼會對應至中MovieSettings的個別屬性: Movies:ServiceApiKey Movies:ConnectionStringThe 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中的資料庫連接字串可能包含指定使用者的密碼: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"

appsettings的連接字串中移除索引鍵/值組。PasswordRemove 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

在上述範例中,索引鍵名稱中的冒號代表在私密金鑰內的物件階層。In 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"

應用程式的私密金鑰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

應用程式的所有使用者秘密都已從密碼 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