製作安全的 .NET 微服務和 Web 應用程式

提示

本內容節錄自《.NET 微服務:容器化 .NET 應用程式的結構》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

微服務和 Web 應用程式中的安全性有許多層面,單就這個主題就可以寫好幾本書,就如同本書一樣。 因此,在本節中,我們將著重於驗證、授權和應用程式秘密。

在 .NET 微服務和 Web 應用程式中實作驗證

您通常必須將服務發佈的資源和 API 限制給某些信任的使用者或用戶端。 制定這類 API 層級信任決策的第一個步驟是驗證。 驗證是以可靠方式驗證使用者身分識別的程序。

在微服務案例中,驗證通常會集中處理。 如果您使用 API 閘道,則閘道會是適合驗證的位置,如圖 9-1 所示。 如果您使用此方法,請確定若沒有 API 閘道,就無法直接到達個別微服務,除非有其他安全性機制可驗證訊息,而不論其是否來自閘道。

Diagram showing how the client mobile app interacts with the backend.

圖 9-1. 使用 API 閘道的集中式驗證

當 API 閘道將驗證集中時,會在將要求轉送給微服務時新增使用者資訊。 如果可以直接存取服務,則驗證服務 (例如 Azure Active Directory 或專用驗證微服務) 會作為安全性權杖服務 (STS),可用來驗證使用者。 信任決策會在服務與安全性權杖或 Cookie 之間共用。 (如果有需要,可以透過實作 Cookie 共用以在 ASP.NET Core 應用程式之間共用這些權杖。)圖 9-2 說明的就是這種模式。

Diagram showing authentication through backend microservices.

圖 9-2: 由識別微服務驗證並透過授權權杖共用信任

有人直接存取微服務時,包含驗證和授權的信任將由專用微服務發行的安全性權杖處理,於微服務間共用。

使用 ASP.NET Core Identity 進行驗證

ASP.NET Core 中用來識別應用程式使用者的主要機制是 ASP.NET Core Identity 成員資格系統。 ASP.NET Core Identity 會將使用者資訊 (包括登入資訊、角色和宣告) 儲存在開發人員所設定的資料存放區中。 一般而言,ASP.NET Core Identity 資料存放區是 Microsoft.AspNetCore.Identity.EntityFrameworkCore 套件中所提供的 Entity Framework 存放區。 不過,您可以使用自訂存放區或其他協力廠商套件,將身分識別資訊儲存在 Azure 表格儲存體、CosmosDB 或其他位置。

提示

ASP.NET Core 2.1 和更新版本提供 ASP.NET Core 身分識別作為 Razor 類別庫,因此您不會在專案中看到太多必要程式碼,如同舊版的情況一樣。 如需如何自訂 Identity 程式碼以符合需求的詳細資訊,請參閱 ASP.NET Core 專案中的 Scaffold 身分識別

下列程式碼擷取自 ASP.NET Core Web 應用程式 MVC 3.1 專案範本,並已選取個別使用者帳戶驗證。 程式碼示範如何使用 Entity Framework Core 在 Program.cs 檔案中設定 ASP.NET Core Identity。

//...
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
    options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddRazorPages();
//...

設定 ASP.NET Core 身分識別之後,您可以藉由新增 app.UseAuthentication()endpoints.MapRazorPages() 來啟用它,如服務 Program.cs 檔案中的下列程式碼所示:

//...
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
});
//...

重要

上述程式碼中的字行必須遵照顯示順序,才能讓 Identity 正常運作。

您可以在下列幾個情況下使用 ASP.NET Code Identity:

  • 使用 UserManager 類型 (userManager.CreateAsync) 建立新的使用者資訊。

  • 使用 SignInManager 類型驗證使用者。 您可以使用 signInManager.SignInAsync 直接登入,或是用 signInManager.PasswordSignInAsync 確認使用者的密碼是否正確,然後將使用者登入。

  • 根據儲存在 Cookie 中的資訊 (由 ASP.NET Core Identity 中介軟體讀取) 識別使用者;如此一來,來自瀏覽器的後續要求就會包含已登入使用者的身分識別和宣告。

ASP.NET Core Identity 也支援雙因素驗證

針對使用本機使用者資料存放區,以及使用 Cookie 在要求之間保存身分識別 (通常適用於 MVC Web 應用程式) 的驗證案例,ASP.NET Core Identity 是建議的解決方案。

使用外部提供者進行驗證

ASP.NET Core 也支援使用外部驗證提供者,讓使用者透過 OAuth 2.0 流程登入。 這表示使用者可以使用來自 Microsoft、Google、Facebook 或 Twitter 等提供者的現有驗證程序進行登入,並將這些身分識別與您應用程式中的 ASP.NET Core Identity 建立關聯。

若要使用外部驗證,除了在使用 app.UseAuthentication() 方法時納入之前所述的驗證中介軟體外,您還必須在 Program.cs 中註冊外部提供者,如下列範例所示:

//...
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddAuthentication()
    .AddMicrosoftAccount(microsoftOptions =>
    {
        microsoftOptions.ClientId = builder.Configuration["Authentication:Microsoft:ClientId"];
        microsoftOptions.ClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"];
    })
    .AddGoogle(googleOptions => { ... })
    .AddTwitter(twitterOptions => { ... })
    .AddFacebook(facebookOptions => { ... });
//...

下表顯示熱門的外部驗證提供者及其關聯的 NuGet 套件:

提供者 套件
Microsoft Microsoft.AspNetCore.Authentication.MicrosoftAccount
Google Microsoft.AspNetCore.Authentication.Google
Facebook Microsoft.AspNetCore.Authentication.Facebook
Twitter Microsoft.AspNetCore.Authentication.Twitter

在所有情況下,您都必須完成應用程式註冊程序,此程序會視廠商而定且通常需要:

  1. 取得用戶端應用程式識別碼。
  2. 取得用戶端應用程式祕密。
  3. 設定授權中介軟體和已註冊提供者所處理的重新導向 URL
  4. 您可以選擇性地設定登出 URL,以便在單一登入 (SSO) 案例中正確處理登出。

如需為外部提供者設定應用程式的詳細資訊,請參閱 ASP.NET Core 中的外部提供者驗證文件

提示

所有詳細資料都會由先前提及的授權中介軟體和服務處理。 因此,在 Visual Studio 中建立 ASP.NET Core Web 應用程式專案時,除了註冊之前所提到的驗證提供者之外,您只需要選擇 [個別使用者帳戶] 驗證選項,如圖 9-3 所示。

Screenshot of the New ASP.NET Core Web Application dialog.

圖 9-3。 在 Visual Studio 2019 中建立 Web 應用程式專案時,選取 [個別使用者帳戶] 選項以使用外部驗證。

除了先前所列的外部驗證提供者,還有協力廠商套件提供中介軟體以使用其他更多外部驗證提供者。 如需清單,請參閱 GitHub 上的 AspNet.Security.OAuth.Providers 存放庫。

您也可以建立自己的外部驗證中介軟體來解決某些特殊需求。

使用持有人權杖進行驗證

使用 ASP.NET Core Identity (或 Identity 加上外部驗證提供者) 進行驗證適用於許多 Web 應用程式案例,在這類案例中,適合將使用者資訊儲存在 Cookie 中。 不過在其他案例中,Cookie 不是保存及傳輸資料的自然方式。

例如,在公開 RESTful 端點的 ASP.NET Core Web API 中,由於這些端點可能是由單一頁面應用程式 (SPA)、原生用戶端或甚至是其他 Web API 存取,因此您通常會想要改用持有人權杖驗證。 這些類型的應用程式不適用於 Cookie,但可輕鬆地擷取持有人權杖,並將它包含在後續要求的授權標頭中。 若要啟用權杖驗證,ASP.NET Core 支援使用 OAuth 2.0OpenID Connect 的數個選項。

使用 OpenID Connect 或 OAuth 2.0 識別提供者進行驗證

如果使用者資訊儲存在 Azure Active Directory 或是支援 OpenID Connect 或 OAuth 2.0 的其他身分識別解決方案中,您可以使用 Microsoft.AspNetCore.Authentication.OpenIdConnect 套件透過 OpenID Connect 工作流程進行驗證。 例如,若要對 eShopOnContainers 中的 Identity.Api 微服務進行驗證,ASP.NET Core Web 應用程式可以使用該套件中的中介軟體,如以下 Program.cs 中的簡化範例所示:

// Program.cs

var identityUrl = builder.Configuration.GetValue<string>("IdentityUrl");
var callBackUrl = builder.Configuration.GetValue<string>("CallBackUrl");
var sessionCookieLifetime = builder.Configuration.GetValue("SessionCookieLifetimeMinutes", 60);

// Add Authentication services

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime))
.AddOpenIdConnect(options =>
{
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.Authority = identityUrl.ToString();
    options.SignedOutRedirectUri = callBackUrl.ToString();
    options.ClientId = useLoadTest ? "mvctest" : "mvc";
    options.ClientSecret = "secret";
    options.ResponseType = useLoadTest ? "code id_token token" : "code id_token";
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.RequireHttpsMetadata = false;
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("orders");
    options.Scope.Add("basket");
    options.Scope.Add("marketing");
    options.Scope.Add("locations");
    options.Scope.Add("webshoppingagg");
    options.Scope.Add("orders.signalrhub");
});

// Build the app
//…
app.UseAuthentication();
//…
app.UseEndpoints(endpoints =>
{
    //...
});

請注意,當您使用此工作流程時,不需要 ASP.NET Core Identity 中介軟體,因為識別服務會處理所有使用者資訊的儲存與驗證。

從 ASP.NET Core 服務發行安全性權杖

如果您想要對本機 ASP.NET Core Identity 使用者發行安全性權杖,而不是使用外部識別提供者,您可以利用一些不錯的協力廠商程式庫。

IdentityServer4OpenIddict 是 OpenID Connect 提供者,可輕鬆地與 ASP.NET Core Identity 整合,讓您從 ASP.NET Core 服務發行安全性權杖。 IdentityServer4 文件深入說明如何使用程式庫。 不過,使用 IdentityServer4 發行權杖的基本步驟如下。

  1. 您可以呼叫 builder.Services.AddIdentityServer,在 Program.cs 中設定 IdentityServer4。

  2. 您可以在 Program.cs 中呼叫 app.UseIdentityServer,將 IdentityServer4 新增至應用程式的 HTTP 要求處理管線。 這可讓程式庫服務 OpenID Connect 和 OAuth2 端點的要求,例如 /connect/token。

  3. 若要設定識別伺服器,請設定下列資料:

    • 用於簽署的認證

    • 使用者可能要求存取的身分識別和 API 資源

      • 代表使用者可透過存取權杖存取之受保護資料或功能的 API 資源。 API 資源的一個範例是需要授權的 Web API (或一組 API)。

      • 代表資訊 (宣告) 的資源,這些資訊 (宣告) 已提供給用戶端來識別使用者。 宣告可能包含使用者名稱、電子郵件地址等等。

    • 將連線以要求權杖的用戶端

    • 使用者資訊的儲存機制,例如 ASP.NET Core Identity 或替代方案。

當您指定要讓 IdentityServer4 使用的用戶端和資源時,您可以將適當類型的 IEnumerable<T> 集合傳遞至接受記憶體內部用戶端或資源存放區的方法。 在更複雜的情況下,您可能會透過相依性插入提供用戶端或資源提供者類型。

讓 IdentityServer4 使用自訂 IClientStore 類型所提供之記憶體內部資源和用戶端的範例設定可能如下列範例所示:

// Program.cs

builder.Services.AddSingleton<IClientStore, CustomClientStore>();
builder.Services.AddIdentityServer()
    .AddSigningCredential("CN=sts")
    .AddInMemoryApiResources(MyApiResourceProvider.GetAllResources())
    .AddAspNetIdentity<ApplicationUser>();
//...

取用安全性權杖

對 OpenID Connect 端點進行驗證或發行您自己的安全性權杖涵蓋一些案例。 但如果服務只需要將存取權限制給具有其他服務所提供之有效安全性權杖的使用者,又如何?

針對該案例,Microsoft.AspNetCore.Authentication.JwtBearer 套件會提供用於處理 JWT 權杖的驗證中介軟體。 JWT 代表 "JSON Web Token",這是用於傳達安全性宣告的常見安全性權杖格式 (由 RFC 7519 定義)。 使用中介軟體來取用該等權杖的簡例看起來就像此程式碼片段,取自 eShopOnContainers 的 Ordering.Api 微服務。

// Program.cs

var identityUrl = builder.Configuration.GetValue<string>("IdentityUrl");

// Add Authentication services

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.Authority = identityUrl;
    options.RequireHttpsMetadata = false;
    options.Audience = "orders";
});

// Build the app

app.UseAuthentication();
//…
app.UseEndpoints(endpoints =>
{
    //...
});

此用法的參數如下:

  • Audience 代表權杖授與存取權之傳入權杖或資源的接收者。 如果此參數中指定的值不符合權杖中的參數,則會拒絕此權杖。

  • Authority 是發行權杖之驗證伺服器的位址。 JWT 持有人驗證中介軟體使用此 URI 來取得公開金鑰,以用來驗證權杖的簽章。 中介軟體也會確認權杖中的 iss 參數符合此 URI。

另一個參數 RequireHttpsMetadata;您可以將此參數設定為 false,以便在沒有憑證的環境中進行測試。 在真實世界部署中,JWT 持有人權杖一律只能透過 HTTPS 傳遞。

準備好此中介軟體之後,就會從授權標頭自動擷取 JWT 權杖。 這些權杖會接著還原序列化、驗證 (使用 AudienceAuthority 參數中的值) 並儲存為使用者資訊,以供 MVC 動作或授權篩選稍後參考。

JWT 持有人驗證中介軟體也可以支援更進階的案例;例如,在沒有授權單位的情況下,使用本機憑證來驗證權杖。 在此案例中,您可以在 JwtBearerOptions物件中指定 TokenValidationParameters 物件。

其他資源