关于微服务和 Web 应用程序中的安全性,有很多方面,可以轻松写成几本类似这样的书。 因此,在本部分中,我们将重点介绍身份验证、授权和应用程序机密。
在 .NET 微服务和 Web 应用程序中实现身份验证
服务发布的资源和 API 通常必须仅限于某些受信任的用户或客户端。 进行此类 API 级信任决策的第一步是身份验证。 身份验证是可靠地验证用户身份的过程。
在微服务场景中,身份验证通常以集中方式处理。 如果使用 API 网关,则网关是进行身份验证的好位置,如图 9-1 所示。 如果使用此方法,请确保无法直接(没有 API 网关)访问各个微服务,除非有额外的安全性来验证消息是否来自网关。
图 9-1. 使用 API 网关进行集中身份验证
当 API 网关集中身份验证时,它会在将请求转发到微服务时添加用户信息。 如果可以直接访问服务,可以使用 Azure Active Directory 等身份验证服务或充当安全令牌服务(STS)的专用身份验证微服务对用户进行身份验证。 信任决策在包含安全令牌或 Cookie 的服务之间共享。 (这些令牌可以通过实现 Cookie 共享在 ASP.NET 核心应用程序之间共享(如果需要)。图 9-2 说明了此模式。
图 9-2. 身份微服务的身份验证; 通过授权令牌共享信任
直接访问微服务时,信任(包括身份验证和授权)由专用微服务颁发的安全令牌处理,在微服务之间共享。
使用 ASP.NET 核心标识进行身份验证
ASP.NET Core 中用于标识应用程序用户的主要机制是 ASP.NET 核心标识 成员身份系统。 ASP.NET Core 标识在开发人员配置的数据存储中存储用户信息(包括登录信息、角色和声明)。 通常,ASP.NET Core Identity 数据存储是由 Microsoft.AspNetCore.Identity.EntityFrameworkCore
包提供的 Entity Framework 存储。 但是,自定义存储或其他第三方包可用于在 Azure 表存储、CosmosDB 或其他位置存储标识信息。
小窍门
ASP.NET Core 2.1 及更高版本提供 ASP.NET Core Identity 作为 Razor 类库,因此在项目中看不到很多必要的代码,就像以前版本的情况一样。 有关如何根据需求自定义标识代码的详细信息,请参阅 ASP.NET Core 项目中的基架标识。
以下代码取自 ASP.NET 核心 Web 应用程序 MVC 项目模板,其中选择了单个用户帐户身份验证。 它演示如何在 Program.cs 文件中使用 Entity Framework Core 配置 ASP.NET 核心标识。
//...
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 Identity 后,可以通过添加app.UseAuthentication()
endpoints.MapRazorPages()
服务Program.cs文件中的以下代码来启用它:
//...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
//...
重要
上述代码中的行 必须按所示顺序 以保证标识正常运行。
使用 ASP.NET Core Identity 可实现多种方案:
使用 UserManager 类型创建新用户信息(userManager.CreateAsync)。
使用 SignInManager 类型对用户进行身份验证。 可以使用
signInManager.SignInAsync
直接登录,或signInManager.PasswordSignInAsync
确认用户的密码正确,然后登录。根据 Cookie 中存储的信息(由 ASP.NET Core Identity 中间件读取)标识用户,以便来自浏览器的后续请求将包括已登录用户的标识和声明。
ASP.NET 核心标识还支持 双重身份验证。
对于使用本地用户数据存储并在请求之间通过 Cookie 保留身份(这在 MVC Web 应用程序中是典型的)的身份验证方案,建议使用 ASP.NET Core 身份标识。
使用外部提供程序进行身份验证
ASP.NET Core 还支持使用 外部身份验证提供程序 让用户通过 OAuth 2.0 流登录。 这意味着用户可以使用来自Microsoft、Google、Facebook 或 Twitter 等提供程序的现有身份验证过程登录,并将这些标识与应用程序中 ASP.NET 核心标识相关联。
若要使用外部身份验证,除了包括前面提到的身份验证中间件之外,还必须使用 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 |
谷歌 | Microsoft.AspNetCore.Authentication.Google |
Microsoft.AspNetCore.Authentication.Facebook | |
推特 | Microsoft.AspNetCore.Authentication.Twitter |
在所有情况下,都必须完成依赖于供应商的应用程序注册过程,并且通常涉及:
- 获取客户端应用程序 ID。
- 获取客户端应用程序机密。
- 配置一个重定向 URL,该 URL 由授权中间件和已注册的提供程序处理。
- (可选)配置注销 URL 以在单一登录(SSO)方案中正确处理注销。
有关为外部提供程序配置应用的详细信息,请参阅 ASP.NET Core 文档中的外部提供程序身份验证)。
小窍门
所有详细信息均由前面提到的授权中间件和服务处理。 因此,在 Visual Studio 中创建 ASP.NET Core Web 应用程序项目时,只需选择 “个人用户帐户 身份验证”选项,如图 9-3 所示,除了注册前面提到的身份验证提供程序。
图 9-3. 在 Visual Studio 2019 中创建 Web 应用程序项目时,选择“个人用户帐户”选项以使用外部身份验证。
除了前面列出的外部身份验证提供程序之外,第三方包还提供中间件,用于使用更多外部身份验证提供程序。 有关列表,请参阅 GitHub 上的 AspNet.Security.OAuth.Providers 存储库。
还可以创建自己的外部身份验证中间件来解决一些特殊需求。
使用持有者令牌进行身份验证
使用 ASP.NET 核心标识(或标识和外部身份验证提供程序)进行身份验证适用于许多 Web 应用程序方案,其中将用户信息存储在 Cookie 中是合适的。 但是,在其他方案中,Cookie 并不是持久保存和传输数据的自然手段。
例如,在 ASP.NET Core Web API 中,公开可由单页应用程序(SPA)、原生客户端甚至其他 Web API 访问的 RESTful 终结点时,通常希望改用 Bearer Token 认证。 这类应用程序不使用 Cookie,但可以轻松获取持有者令牌,并将其包含在后续请求的授权头中。 若要启用令牌身份验证,ASP.NET Core 支持使用 OAuth 2.0 和 OpenID 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 核心标识中间件,因为标识服务将处理所有用户信息存储和身份验证。
从 ASP.NET 核心服务颁发安全令牌
如果希望为本地 ASP.NET 核心标识用户颁发安全令牌,而不是使用外部标识提供者,则可以利用一些良好的第三方库。
IdentityServer4 和 OpenIddict 是 OpenID Connect 提供程序,可与 ASP.NET Core Identity 轻松集成,以便从 ASP.NET Core 服务颁发安全令牌。 IdentityServer4 文档提供了有关使用库的深入说明。 但是,使用 IdentityServer4 颁发令牌的基本步骤如下所示。
在 Program.cs 中,通过调用 builder.Services.AddIdentityServer 来配置 IdentityServer4。
您在 Program.cs 中调用 app.UseIdentityServer,将 IdentityServer4 添加到应用程序的 HTTP 请求处理管道中。 这样,库就可以为 OpenID Connect 和 OAuth2 端点(如 /connect/token)提供服务。
通过设置以下数据来配置标识服务器:
用于签名的 凭据 。
用户可以请求访问的 标识和 API 资源 :
API 资源表示用户可以使用访问令牌访问的受保护数据或功能。 API 资源的示例是需要授权的 Web API(或 API 集)。
标识资源表示提供给客户端用于标识用户的信息(声明)。 声明可能包括用户名、电子邮件地址等。
要连接的 客户端 ,以便请求令牌。
用户信息的存储机制,例如 ASP.NET 核心标识 或替代项。
在指定 IdentityServer4 使用的客户端和资源时,可以将适当类型的集合传递给使用内存中客户端或资源存储的方法。 或者,对于更复杂的方案,可以通过依赖关系注入提供客户端或资源提供程序类型。
用于使用自定义 IClientStore 类型提供的内存中资源和客户端的 IdentityServer4 的示例配置可能如以下示例所示:
// Program.cs
builder.Services.AddSingleton<IClientStore, CustomClientStore>();
builder.Services.AddIdentityServer()
.AddSigningCredential("CN=sts")
.AddInMemoryApiResources(MyApiResourceProvider.GetAllResources())
.AddAspNetIdentity<ApplicationUser>();
//...
消耗安全令牌
针对 OpenID Connect 终结点进行身份验证或颁发自己的安全令牌涵盖一些方案。 但是,一个服务只需限制那些拥有由其他服务提供的有效安全令牌的用户访问权限,该怎么办?
对于这种情况,处理 JWT 令牌的身份验证中间件在 Microsoft.AspNetCore.Authentication.JwtBearer 包中可用。 JWT 代表“JSON Web 令牌”,是用于通信安全声明的常见安全令牌格式(由 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,以便在没有证书的环境中进行测试。 在实际部署中,应始终仅通过 HTTPS 传递 JWT 持有者令牌。
有了此中间件,JWT 令牌会自动从授权标头中提取。 然后,它们将被反序列化、验证(使用Audience
和Authority
参数中的值进行验证),并作为用户信息存储,供 MVC 操作或授权筛选器稍后引用。
JWT 持有者身份验证中间件还可以支持更复杂的场景,例如,当颁发机构不可用时,使用本地证书来验证令牌。 对于这种情况,您可以在TokenValidationParameters
对象中指定一个JwtBearerOptions
对象。
其他资源
在应用程序之间共享 Cookie
https://learn.microsoft.com/aspnet/core/security/cookie-sharing身份简介
https://learn.microsoft.com/aspnet/core/security/authentication/identity使用短信进行双重身份验证
https://learn.microsoft.com/aspnet/core/security/authentication/2fa使用 Facebook、Google 和其他外部提供程序启用身份验证
https://learn.microsoft.com/aspnet/core/security/authentication/social/米歇尔·安妮卡斯 OAuth 2 简介
https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2AspNet.Security.OAuth.Providers (适用于 ASP.NET OAuth 提供程序的 GitHub 存储库)
https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/tree/dev/srcIdentityServer4。 官方文档
https://identityserver4.readthedocs.io/en/latest/