在 ASP.NET Core 的開發中安全儲存應用程式秘密

作者:Rick AndersonKirk Larkin

檢視或下載範例程式碼 \(英文\) (如何下載)

本檔說明如何在開發電腦上管理 ASP.NET Core 應用程式的敏感性資料。 永遠不要將密碼或其他敏感性資料儲存在原始程式碼中。 生產秘密不應該用於開發或測試。 不應使用應用程式部署秘密。 相反地,應該透過環境變數或 Azure 金鑰保存庫等受控制的方法來存取生產秘密。 您可以透過 Azure Key Vault 設定提供者儲存及保護 Azure 測試與生產祕密。

若要在 .NET 主控台應用程式中使用使用者密碼,請參閱 此 GitHub 問題

環境變數

環境變數可用來避免在程式碼或本機組態檔中儲存應用程式秘密。 環境變數會覆寫所有先前指定之組態來源的組態值。

請考慮啟用個別使用者帳戶 安全性的 ASP.NET Core Web 應用程式 。 預設資料庫連接字串包含在專案 appsettings.json 檔案中,且索引鍵 DefaultConnection 為 。 預設連接字串適用于 LocalDB,其會在使用者模式中執行,而且不需要密碼。 在應用程式部署期間, DefaultConnection 可以使用環境變數的值覆寫索引鍵值。 環境變數可能會使用敏感性認證來儲存完整的連接字串。

警告

環境變數通常會以純文字、未加密的文字儲存。 如果電腦或進程遭到入侵,則不受信任的合作物件可以存取環境變數。 可能需要額外的措施,以防止洩漏使用者秘密。

: 分隔符號不適用於所有平台上的環境變數階層式機碼。 __,雙底線,為:

  • 所有平台都支援。 例如,Bash 不支援 : 分隔符號,但支援 __
  • 自動由 : 取代

秘密管理員

秘密管理員工具會在開發 ASP.NET Core 專案時儲存敏感性資料。 在此內容中,機密資料片段是應用程式秘密。 應用程式秘密會儲存在專案樹狀目錄的個別位置。 應用程式秘密會與特定專案相關聯,或跨數個專案共用。 應用程式秘密不會簽入原始檔控制。

警告

秘密管理員工具不會加密儲存的秘密,不應被視為受信任的存放區。 僅供開發之用。 索引鍵和值會儲存在 JS 使用者設定檔目錄中的 ON 組態檔中。

秘密管理員工具的運作方式

秘密管理員工具會隱藏實作詳細資料,例如儲存值的位置和方式。 您可以在不知道這些實作詳細資料的情況下使用此工具。 這些值會儲存在 JS 本機電腦使用者設定檔資料夾中的 ON 檔案中:

檔案系統路徑:

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

在上述檔案路徑中,將 取代 <user_secrets_id>UserSecretsId 專案檔中指定的值。

請勿撰寫程式碼,此程式碼取決於使用秘密管理員工具儲存的資料位置或格式。 這些實作詳細資料可能會變更。 例如,秘密值不會加密,但可能在未來。

啟用秘密儲存體

Secret Manager 工具會針對儲存在使用者設定檔中的專案特定組態設定運作。

秘密管理員工具組含 init 命令。 若要使用使用者密碼,請在專案目錄中執行下列命令:

dotnet user-secrets init

上述命令會在專案檔的 內 PropertyGroup 加入 UserSecretsId 專案。 根據預設,的內部文字 UserSecretsId 是 GUID。 內部文字是任意的,但對專案而言是唯一的。

<PropertyGroup>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId>
</PropertyGroup>

在 Visual Studio 中,以滑鼠右鍵按一下 方案總管 中的專案,然後從操作功能表中選取 [ 管理使用者密碼 ]。 此筆勢會將填入 GUID 的專案新增 UserSecretsId 至專案檔。

設定秘密

定義由金鑰及其值組成的應用程式秘密。 秘密與專案 UserSecretsId 的值相關聯。 例如,從專案檔所在的目錄執行下列命令:

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

在上述範例中,冒號表示 Movies 是具有 ServiceApiKey 屬性的物件常值。

秘密管理員工具也可以從其他目錄使用。 --project使用 選項來提供專案檔所在的檔案系統路徑。 例如:

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

JSVisual Studio 中的 ON 結構扁平化

Visual Studio 的 [管理使用者密碼] 手勢會在文字編輯器中開啟檔案 secrets.json 。 將 的內容 secrets.json 取代為要儲存的索引鍵/值組。 例如:

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

透過 或 dotnet user-secrets set 修改 JSdotnet user-secrets remove 之後,ON 結構會扁平化。 例如,執行 dotnet user-secrets remove "Movies:ConnectionString"Movies 折迭物件常值。 修改過的檔案類似下列 JS ON:

{
  "Movies:ServiceApiKey": "12345"
}

設定多個秘密

您可以透過將 ON 傳送 JS 至 set 命令,來設定一批秘密。 在下列範例中,檔案 input.json 的內容會透過管道傳送至 set 命令。

開啟命令殼層,然後執行下列命令:

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

存取秘密

若要存取秘密,請完成下列步驟:

  1. 註冊使用者秘密組態來源
  2. 透過設定 API 讀取秘密

註冊使用者秘密組態來源

使用者秘密設定 提供者 會向 .NET 組態 API 註冊適當的組態來源。

使用 dotnet new 或 Visual Studio 建立的 ASP.NET Core Web 應用程式會產生下列程式碼:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.CreateBuilder 會初始化具有預先設定之預設值的之 WebApplicationBuilder 類別的新執行個體。 初始化的 ( builder ) 會在 為 時 DevelopmentEnvironmentName 提供預設組態和呼叫 AddUserSecretsWebApplicationBuilder

透過設定 API 讀取秘密

請考慮閱讀金鑰的 Movies:ServiceApiKey 下列範例:

Program.cs 檔案:

var builder = WebApplication.CreateBuilder(args);
var movieApiKey = builder.Configuration["Movies:ServiceApiKey"];

var app = builder.Build();

app.MapGet("/", () => movieApiKey);

app.Run();

Razor 頁面頁面模型:

public class IndexModel : PageModel
{
    private readonly IConfiguration _config;

    public IndexModel(IConfiguration config)
    {
        _config = config;
    }

    public void OnGet()
    {
        var moviesApiKey = _config["Movies:ServiceApiKey"];

        // call Movies service with the API key
    }
}

如需詳細資訊,請參閱 ASP.NET Core 中的組態

將秘密對應至 POCO

將整個物件常值對應至 POCO(具有屬性的簡單 .NET 類別)對於匯總相關屬性很有用。

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

若要將上述秘密對應至 POCO,請使用 .NET 組態 API 的物件 圖形系結 功能。 下列程式碼系結至自訂 MovieSettings POCO 並存取 ServiceApiKey 屬性值:

var moviesConfig = 
    Configuration.GetSection("Movies").Get<MovieSettings>();
_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"
  }
}

秘密的值可以在物件的 Password 屬性上 SqlConnectionStringBuilder 設定,以完成連接字串:

using System.Data.SqlClient;

var builder = WebApplication.CreateBuilder(args);

var conStrBuilder = new SqlConnectionStringBuilder(
        builder.Configuration.GetConnectionString("Movies"));
conStrBuilder.Password = builder.Configuration["DbPassword"];
var connection = conStrBuilder.ConnectionString;

var app = builder.Build();

app.MapGet("/", () => connection);

app.Run();

列出秘密

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

從專案檔所在的目錄中執行下列命令:

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:ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true",
  "Movies:ServiceApiKey": "12345"
}

從專案檔所在的目錄中執行下列命令:

dotnet user-secrets remove "Movies:ConnectionString"

已修改應用程式的 secrets.json 檔案,以移除與 Movies:ConnectionString 索引鍵相關聯的機碼/值組:

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

dotnet user-secrets list 會顯示下列訊息:

Movies:ServiceApiKey = 12345

移除所有秘密

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

從專案檔所在的目錄中執行下列命令:

dotnet user-secrets clear

應用程式的所有使用者密碼都已從 secrets.json 檔案中刪除:

{}

執行 dotnet user-secrets list 會顯示下列訊息:

No secrets configured for this application.

使用 Visual Studio 管理使用者密碼

若要在 Visual Studio 中管理使用者密碼,請以滑鼠右鍵按一下方案總管中的專案,然後選取 [ 管理使用者密碼 ]:

Visual Studio showing Manage User Secrets

將使用者密碼從 ASP.NET Framework 移轉至 ASP.NET Core

請參閱這個 GitHub 問題

其他資源

裡克·安德森 柯克·拉金 丹尼爾·羅斯 斯科特·艾迪

檢視或下載範例程式碼 \(英文\) (如何下載)

本檔說明如何在開發電腦上管理 ASP.NET Core 應用程式的敏感性資料。 永遠不要將密碼或其他敏感性資料儲存在原始程式碼中。 生產秘密不應該用於開發或測試。 不應使用應用程式部署秘密。 相反地,應該透過環境變數或 Azure 金鑰保存庫等受控制的方法來存取生產秘密。 您可以透過 Azure Key Vault 設定提供者儲存及保護 Azure 測試與生產祕密。

環境變數

環境變數可用來避免在程式碼或本機組態檔中儲存應用程式秘密。 環境變數會覆寫所有先前指定之組態來源的組態值。

請考慮啟用個別使用者帳戶 安全性的 ASP.NET Core Web 應用程式 。 預設資料庫連接字串包含在專案 appsettings.json 檔案中,且索引鍵 DefaultConnection 為 。 預設連接字串適用于 LocalDB,其會在使用者模式中執行,而且不需要密碼。 在應用程式部署期間, DefaultConnection 可以使用環境變數的值覆寫索引鍵值。 環境變數可能會使用敏感性認證來儲存完整的連接字串。

警告

環境變數通常會以純文字、未加密的文字儲存。 如果電腦或進程遭到入侵,則不受信任的合作物件可以存取環境變數。 可能需要額外的措施,以防止洩漏使用者秘密。

: 分隔符號不適用於所有平台上的環境變數階層式機碼。 __,雙底線,為:

  • 所有平台都支援。 例如,Bash 不支援 : 分隔符號,但支援 __
  • 自動由 : 取代

秘密管理員

秘密管理員工具會在開發 ASP.NET Core 專案時儲存敏感性資料。 在此內容中,機密資料片段是應用程式秘密。 應用程式秘密會儲存在專案樹狀目錄的個別位置。 應用程式秘密會與特定專案相關聯,或跨數個專案共用。 應用程式秘密不會簽入原始檔控制。

警告

秘密管理員工具不會加密儲存的秘密,不應被視為受信任的存放區。 僅供開發之用。 索引鍵和值會儲存在 JS 使用者設定檔目錄中的 ON 組態檔中。

秘密管理員工具的運作方式

秘密管理員工具會隱藏實作詳細資料,例如儲存值的位置和方式。 您可以在不知道這些實作詳細資料的情況下使用此工具。 這些值會儲存在 JS 本機電腦使用者設定檔資料夾中的 ON 檔案中:

檔案系統路徑:

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

在上述檔案路徑中,將 取代 <user_secrets_id>UserSecretsId 專案檔中指定的值。

請勿撰寫程式碼,此程式碼取決於使用秘密管理員工具儲存的資料位置或格式。 這些實作詳細資料可能會變更。 例如,秘密值不會加密,但可能在未來。

啟用秘密儲存體

Secret Manager 工具會針對儲存在使用者設定檔中的專案特定組態設定運作。

秘密管理員工具組含 .NET Core SDK 3.0.100 或更新版本中的 init 命令。 若要使用使用者密碼,請在專案目錄中執行下列命令:

dotnet user-secrets init

上述命令會在專案檔的 內 PropertyGroup 加入 UserSecretsId 專案。 根據預設,的內部文字 UserSecretsId 是 GUID。 內部文字是任意的,但對專案而言是唯一的。

<PropertyGroup>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId>
</PropertyGroup>

在 Visual Studio 中,以滑鼠右鍵按一下 方案總管 中的專案,然後從操作功能表中選取 [ 管理使用者密碼 ]。 此筆勢會將填入 GUID 的專案新增 UserSecretsId 至專案檔。

設定秘密

定義由金鑰及其值組成的應用程式秘密。 秘密與專案 UserSecretsId 的值相關聯。 例如,從專案檔所在的目錄執行下列命令:

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

在上述範例中,冒號表示 Movies 是具有 ServiceApiKey 屬性的物件常值。

秘密管理員工具也可以從其他目錄使用。 --project使用 選項來提供專案檔所在的檔案系統路徑。 例如:

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

JSVisual Studio 中的 ON 結構扁平化

Visual Studio 的 [管理使用者密碼] 手勢會在文字編輯器中開啟檔案 secrets.json 。 將 的內容 secrets.json 取代為要儲存的索引鍵/值組。 例如:

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

透過 或 dotnet user-secrets set 修改 JSdotnet user-secrets remove 之後,ON 結構會扁平化。 例如,執行 dotnet user-secrets remove "Movies:ConnectionString"Movies 折迭物件常值。 修改過的檔案類似下列 JS ON:

{
  "Movies:ServiceApiKey": "12345"
}

設定多個秘密

您可以透過將 ON 傳送 JS 至 set 命令,來設定一批秘密。 在下列範例中,檔案 input.json 的內容會透過管道傳送至 set 命令。

開啟命令殼層,然後執行下列命令:

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

存取秘密

若要存取秘密,請完成下列步驟:

  1. 註冊使用者秘密組態來源
  2. 透過設定 API 讀取秘密

註冊使用者秘密組態來源

使用者秘密設定 提供者 會向 .NET 組態 API 註冊適當的組態來源。

專案呼叫 CreateDefaultBuilder 時,會自動在開發模式中新增使用者秘密組態來源。 CreateDefaultBuilder當 為 DevelopmentEnvironmentName 呼叫 AddUserSecrets

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

未呼叫 時 CreateDefaultBuilder ,請在 中 ConfigureAppConfiguration 呼叫 AddUserSecrets ,明確新增使用者秘密組態來源。 AddUserSecrets只有在開發環境中執行應用程式時呼叫 ,如下列範例所示:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new HostBuilder()
            .ConfigureAppConfiguration((hostContext, builder) =>
            {
                // Add other providers for JSON, etc.

                if (hostContext.HostingEnvironment.IsDevelopment())
                {
                    builder.AddUserSecrets<Program>();
                }
            })
            .Build();
        
        host.Run();
    }
}

透過設定 API 讀取秘密

如果註冊使用者密碼組態來源,.NET 組態 API 可以讀取秘密。 建構函式插入 可用來取得 .NET 組態 API 的存取權。 請考慮閱讀金鑰的 Movies:ServiceApiKey 下列範例:

啟動類別:

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

Razor 頁面頁面模型:

public class IndexModel : PageModel
{
    private readonly IConfiguration _config;

    public IndexModel(IConfiguration config)
    {
        _config = config;
    }

    public void OnGet()
    {
        var moviesApiKey = _config["Movies:ServiceApiKey"];

        // call Movies service with the API key
    }
}

如需詳細資訊,請參閱 頁面 的啟動 和 存取設定中的 Razor 存取組態。

將秘密對應至 POCO

將整個物件常值對應至 POCO(具有屬性的簡單 .NET 類別)對於匯總相關屬性很有用。

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

若要將上述秘密對應至 POCO,請使用 .NET 組態 API 的物件 圖形系結 功能。 下列程式碼系結至自訂 MovieSettings POCO 並存取 ServiceApiKey 屬性值:

var moviesConfig = 
    Configuration.GetSection("Movies").Get<MovieSettings>();
_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"
  }
}

秘密的值可以在物件的 Password 屬性上 SqlConnectionStringBuilder 設定,以完成連接字串:

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;

        // code omitted for brevity
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync($"DB Connection: {_connection}");
        });
    }
}

列出秘密

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

從專案檔所在的目錄中執行下列命令:

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:ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true",
  "Movies:ServiceApiKey": "12345"
}

從專案檔所在的目錄中執行下列命令:

dotnet user-secrets remove "Movies:ConnectionString"

已修改應用程式的 secrets.json 檔案,以移除與 MoviesConnectionString 索引鍵相關聯的機碼/值組:

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

dotnet user-secrets list 會顯示下列訊息:

Movies:ServiceApiKey = 12345

移除所有秘密

假設應用程式的 secrets.json 檔案包含下列兩個秘密:

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

從專案檔所在的目錄中執行下列命令:

dotnet user-secrets clear

應用程式的所有使用者密碼都已從 secrets.json 檔案中刪除:

{}

執行 dotnet user-secrets list 會顯示下列訊息:

No secrets configured for this application.

使用 Visual Studio 管理使用者密碼

若要在 Visual Studio 中管理使用者密碼,請以滑鼠右鍵按一下方案總管中的專案,然後選取 [ 管理使用者密碼 ]:

Visual Studio showing Manage User Secrets

將使用者密碼從 ASP.NET Framework 移轉至 ASP.NET Core

請參閱這個 GitHub 問題

其他資源