身份验证和授权

提示

此内容摘自电子书《使用 .NET MAUI 的企业应用程序模式》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

身份验证是从用户处获取标识凭据(例如名称和密码)并根据授权验证这些凭据的过程。 如果凭据有效,则提交凭据的实体被视为经过身份验证的标识。 一旦建立了标识,授权过程将确定该标识是否能够访问给定的资源。

有许多方法可以将身份验证和授权集成到与 ASP.NET Web 应用程序通信的 .NET MAUI 应用中,包括使用 ASP.NET Core 标识,Microsoft、Google、Facebook 或 Twitter 等外部身份验证提供程序,以及身份验证中间件。 eShopOnContainers 多平台应用通过使用 IdentityServer 4 的容器化标识微服务执行身份验证和授权。 应用从 IdentityServer 请求安全令牌来对用户进行身份验证或访问资源。 要使 IdentityServer 代表用户颁发令牌,用户必须登录到 IdentityServer。 但是,IdentityServer 不提供用于身份验证的用户界面或数据库。 因此,在 eShopOnContainers 参考应用程序中,ASP.NET Core 标识用于此目的。

身份验证

当应用程序需要知道当前用户的标识时,便需要进行身份验证。 ASP.NET Core 识别用户的主要机制是 ASP.NET Core 标识成员资格系统,它将用户信息存储在开发人员配置的数据存储中。 通常,此数据存储为 EntityFramework 存储,但自定义存储或第三方包可用于将标识信息存储在 Azure 存储、DocumentDB 或其他位置。

对于使用本地用户数据存储并通过 cookie 保留请求间的标识信息(这在 ASP.NET Web 应用程序中很常见)的身份验证方案,ASP.NET Core 标识是一个合适的解决方案。 然而,cookie 并不总是保留和传输数据的自然方式。 例如,公开从应用访问的 RESTful 终结点的 ASP.NET Core Web 应用程序通常需要使用持有者令牌身份验证,因为在这种情况下无法使用 cookie。 但是,可以轻松检索持有者令牌并将其包含在从应用发出的 Web 请求的授权标头中。

使用 IdentityServer 4 颁发持有者令牌

IdentityServer 4 是适用于 ASP.NET Core 的开源 OpenID Connect 和 OAuth 2.0 框架,可用于许多身份验证和授权方案,包括为本地 ASP.NET Core 标识用户颁发安全令牌。

注意

OpenID Connect 和 OAuth 2.0 非常相似,只是职责不同。

OpenID Connect 是基于 OAuth 2.0 协议构建的身份验证层。 OAuth 2 是一种允许应用程序从安全令牌服务请求访问令牌并使用它们与 API 通信的协议。 这种委托降低了客户端应用程序和 API 的复杂性,因为可以集中身份验证和授权。

OpenID Connect 和 OAuth 2.0 结合了身份验证和 API 访问这两个基本安全问题,而 IdentityServer 4 是这些协议的实现。

在使用客户端到微服务直接通信的应用程序中,例如 eShopOnContainers 参考应用程序,可以使用充当安全令牌服务 (STS) 的专用身份验证微服务来对用户进行身份验证,如下图所示。 有关客户端到微服务直接通信的详细信息,请参阅微服务

Authentication by a dedicated authentication microservice.

eShopOnContainers 多平台应用与标识微服务通信,该微服务使用 IdentityServer 4 执行身份验证和 API 访问控制。 因此,此多平台应用从 IdentityServer 请求令牌,用于对用户进行身份验证或访问资源:

  • 使用 IdentityServer 对用户进行身份验证是通过多平台应用请求一个标识令牌来实现的,该令牌代表身份验证过程的结果。 它至少包含用户的标识符,以及有关如何以及何时对用户进行身份验证的信息。 它还可能包括其他标识数据。
  • 使用 IdentityServer 访问资源是通过多平台应用请求一个访问令牌来实现的,该令牌允许访问一个 API 资源。 客户端请求访问令牌并将其转发到 API。 访问令牌包含有关客户端和用户的信息(如果有)。 然后,API 使用该信息来授权访问其数据。

注意

客户端必须先向 IdentityServer 注册,然后才能成功请求令牌。 有关添加客户端的详细信息,请参阅定义客户端

将 IdentityServer 添加到 Web 应用程序

为了让 ASP.NET Core Web 应用程序使用 IdentityServer 4,必须将其添加到 Web 应用程序的 Visual Studio 解决方案中。 有关详细信息,请参阅 IdentityServer 文档中的设置和概述。 一旦 IdentityServer 包含在 Web 应用程序的 Visual Studio 解决方案中,就必须将其添加到其 HTTP 请求处理管道中,以便处理 OpenID Connect 和 OAuth 2.0 终结点的请求。 这是通过 Web 应用程序的 Startup 类中的 Configure 方法实现的,如以下代码示例所示:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseIdentity();
}

Web 应用程序的 HTTP 请求处理管道中的顺序很重要。 因此,必须在实现登录屏幕的 UI 框架之前将 IdentityServer 添加到管道中。

配置 IdentityServer

IdentityServer 应通过调用 services.AddIdentityServer 方法在 Web 应用程序的 Startup 类的 ConfigureServices 方法中进行配置,如以下来自 eShopOnContainers 参考应用程序的代码示例所示:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddIdentityServer(x => x.IssuerUri = "null")
        .AddSigningCredential(Certificate.Get())
        .AddAspNetIdentity<ApplicationUser>()
        .AddConfigurationStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)))
        .AddOperationalStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)))
        .Services.AddTransient<IProfileService, ProfileService>();
}

调用 services.AddIdentityServer 方法后,会调用额外的 Fluent API 来配置以下内容:

  • 用于签名的凭据。
  • 用户可能会请求访问的 API 和标识资源。
  • 将连接到请求令牌的客户端。
  • ASP.NET Core 标识。

提示

动态加载 IdentityServer 4 配置。 IdentityServer 4 的 API 允许从内存中的配置对象列表配置 IdentityServer。 在 eShopOnContainers 参考应用程序中,这些内存中的集合被硬编码到应用程序中。 但是,在生产场景中,可以从配置文件或数据库动态加载它们。

有关将 IdentityServer 配置为使用 ASP.NET Core 标识的信息,请参阅 IdentityServer 文档中的使用 ASP.NET Core 标识

配置 API 资源

配置 API 资源时,AddInMemoryApiResources 方法需要一个 IEnumerable<ApiResource> 集合。 以下代码示例演示了在 eShopOnContainers 参考应用程序中提供此集合的 GetApis 方法:

public static IEnumerable<ApiResource> GetApis()
{
    return new List<ApiResource>
    {
        new ApiResource("orders", "Orders Service"),
        new ApiResource("basket", "Basket Service")
    };
}

此方法指定 IdentityServer 应保护订单和购物篮 API。 因此,调用这些 API 时将需要 IdentityServer 托管的访问令牌。 有关 ApiResource 类型的详细信息,请参阅 IdentityServer 4 文档中的 API 资源

配置标识资源

配置标识资源时,AddInMemoryIdentityResources 方法需要一个 IEnumerable<IdentityResource> 集合。 标识资源是用户 ID、名称或电子邮件地址等数据。 每个标识资源都有唯一的名称,可以为其分配任意声明类型,该类型将包含在用户的标识令牌中。 以下代码示例演示了在 eShopOnContainers 参考应用程序中提供此集合的 GetResources 方法:

public static IEnumerable<IdentityResource> GetResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile()
    };
}

OpenID Connect 规范指定了一些标准标识资源。 最低要求是支持为用户发出唯一 ID。 这是通过公开 IdentityResources.OpenId 标识资源来实现的。

注意

IdentityResources 类支持 OpenID Connect 规范中定义的所有范围(openid、电子邮件、配置文件、电话和地址)。

IdentityServer 还支持定义自定义标识资源。 有关详细信息,请参阅 IdentityServer 文档中的定义自定义标识资源。 有关 IdentityResource 类型的详细信息,请参阅 IdentityServer 4 文档中的标识资源

配置客户端

客户端是可以从 IdentityServer 请求令牌的应用程序。 通常,必须为每个客户端至少定义以下设置:

  • 唯一的客户端 ID。
  • 允许与令牌服务的交互(称为授权类型)。
  • 标识和访问令牌发送到的位置(称为重定向 URI)。
  • 允许客户端访问的资源列表(称为范围)。

配置客户端时,AddInMemoryClients 方法需要一个 IEnumerable<Client> 集合。 以下代码示例显示了 eShopOnContainers 多平台应用在 GetClients 方法中的配置,该方法在 eShopOnContainers 参考应用程序中提供了此集合:

public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
{
    return new List<Client>
    {
        // Omitted for brevity
        new Client
        {
            ClientId = "xamarin",
            ClientName = "eShop Xamarin OpenId Client",
            AllowedGrantTypes = GrantTypes.Hybrid,
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },
            RedirectUris = { clientsUrl["Xamarin"] },
            RequireConsent = false,
            RequirePkce = true,
            PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" },
            AllowedCorsOrigins = { "http://eshopxamarin" },
            AllowedScopes = new List<string>
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.OfflineAccess,
                "orders",
                "basket"
            },
            AllowOfflineAccess = true,
            AllowAccessTokensViaBrowser = true
        },
    };
}

此配置指定以下属性的数据:

属性 说明
ClientId 客户端的唯一 ID。
ClientName 客户端显示名称,用于日志记录和同意屏幕。
AllowedGrantTypes 指定客户端希望如何与 IdentityServer 交互。 有关详细信息,请参阅配置身份验证流
ClientSecrets 指定从令牌终结点请求令牌时使用的客户端机密凭据。
RedirectUris 指定要向其返回令牌或授权代码的允许 URI。
RequireConsent 指定是否需要同意屏幕。
RequirePkce 指定使用授权代码的客户端是否必须发送证明密钥。
PostLogoutRedirectUris 指定注销后要重定向到的允许 URI。
AllowedCorsOrigins 指定客户端的源,以便 IdentityServer 可以允许来自该源的跨域调用。
AllowedScopes 指定客户端有权访问的资源。 默认情况下,客户端无权访问任何资源。
AllowOfflineAccess 指定客户端是否可以请求刷新令牌。

配置身份验证流

可以通过在 Client.AllowedGrantTypes 属性中指定授权类型来配置客户端和 IdentityServer 之间的身份验证流。 OpenID Connect 和 OAuth 2.0 规范定义了多个身份验证流,包括:

身份验证流程 说明
隐式 此流针对基于浏览器的应用程序进行了优化,应用于仅用户身份验证或身份验证和访问令牌请求。 所有令牌都通过浏览器传输,因此不允许使用刷新令牌等高级功能。
授权代码 此流提供了在反向通道(与浏览器前向通道相对)上检索令牌的能力,同时还支持客户端身份验证。
混合 此流是隐式和授权代码授予类型的组合。 标识令牌通过浏览器通道传输,并包含签名协议响应和其他项目,如授权代码。 成功验证响应后,应使用反向通道检索访问和刷新令牌。

提示

请考虑使用混合身份验证流。 混合身份验证流可缓解应用于浏览器通道的许多攻击,并且是希望检索访问令牌(可能还有刷新令牌)的本机应用程序的推荐流。

有关身份验证流的详细信息,请参阅 IdentityServer 4 文档中的授权类型

执行身份验证

要使 IdentityServer 代表用户颁发令牌,用户必须登录到 IdentityServer。 但是,IdentityServer 不提供用于身份验证的用户界面或数据库。 因此,在 eShopOnContainers 参考应用程序中,ASP.NET Core 标识用于此目的。

eShopOnContainers 多平台应用使用混合身份验证流向 IdentityServer 进行身份验证,如下图所示。

High-level overview of the sign in process.

<base endpoint>:5105/connect/authorize 发出了登录请求。 成功进行身份验证后,IdentityServer 返回包含授权代码和标识令牌的身份验证响应。 授权代码发送到 <base endpoint>:5105/connect/token,后者用访问、标识和刷新令牌进行响应。

eShopOnContainers 多平台应用通过向 <base endpoint>:5105/connect/endsession 发送带有附加参数的请求来注销 IdentityServer。 退出登录后,Identity Server 通过将注销后重定向 URI 发送回多平台应用来做出响应。 下图阐释了该过程。

High-level overview of the sign out process.

在 eShopOnContainers 多平台应用中,与 IdentityServer 的通信由 IdentityService 类执行,该类实现 IIdentityService 接口。 此接口指定实现类必须提供 CreateAuthorizationRequestCreateLogoutRequestGetTokenAsync 方法。

登录

当用户点击 LoginView 上的 LOGIN 按钮时,将会执行 LoginViewModel 类中的 SignInCommand,进而执行 SignInAsync 方法。 下面的代码示例演示此方法:

private async Task SignInAsync()
{
    await IsBusyFor(
        async () =>
        {
            LoginUrl = _identityService.CreateAuthorizationRequest();

            IsValid = true;
            IsLogin = true;
        });
}

此方法调用 IdentityService 类中的 CreateAuthorizationRequest 方法,如以下代码示例所示:

public string CreateAuthorizationRequest()
{
    // Create URI to authorization endpoint
    var authorizeRequest = new AuthorizeRequest(GlobalSetting.Instance.IdentityEndpoint);
    // Dictionary with values for the authorize request
    var dic = new Dictionary<string, string>();
    dic.Add("client_id", GlobalSetting.Instance.ClientId);
    dic.Add("client_secret", GlobalSetting.Instance.ClientSecret); 
    dic.Add("response_type", "code id_token");
    dic.Add("scope", "openid profile basket orders locations marketing offline_access");
    dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
    dic.Add("nonce", Guid.NewGuid().ToString("N"));
    dic.Add("code_challenge", CreateCodeChallenge());
    dic.Add("code_challenge_method", "S256");
    // Add CSRF token to protect against cross-site request forgery attacks.
    var currentCSRFToken = Guid.NewGuid().ToString("N");
    dic.Add("state", currentCSRFToken);
    
    var authorizeUri = authorizeRequest.Create(dic); 
    return authorizeUri;
}

此方法使用所需参数为 IdentityServer 的授权终结点创建 URI。 授权终结点位于作为用户设置公开的基本终结点的端口 5105 上的 /connect/authorize。 有关用户设置的详细信息,请参阅配置管理

注意

通过对 OAuth 实施代码交换证明密钥 (PKCE) 扩展来减少 eShopOnContainers 多平台应用的攻击面。 PKCE 保护授权代码在被截获时不被使用。 这是通过客户端生成一个机密验证程序来实现的,该验证程序的哈希在授权请求中传递,并且在兑换授权代码时以未经过哈希处理的方式显示。 有关 PKCE 的详细信息,请参阅 Internet 工程任务组网站上的OAuth 公共客户端的代码交换证明密钥

返回的 URI 存储在 LoginViewModel 类的 LoginUrl 属性中。 当 IsLogin 属性变为 true 时,LoginView 中的 WebView 变为可见。 当 LoginUrl 属性设置为 IdentityServer 的授权终结点时,WebView 数据将其 Source 属性绑定到 LoginViewModel 类的 LoginUrl 属性并将登录请求绑定到 IdentityServer。 当 IdentityServer 收到此请求且用户未经过身份验证时,WebView 会重定向到下图所示的已配置登录页。

Login page displayed by the WebView.

登录完成后,WebView 将重定向到返回 URI。 此 WebView 导航将导致执行 LoginViewModel 类中的 NavigateAsync 方法,如以下代码示例所示:

private async Task NavigateAsync(string url)
{
    var unescapedUrl = System.Net.WebUtility.UrlDecode(url);

    if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback, StringComparison.OrdinalIgnoreCase))
    {
        _settingsService.AuthAccessToken = string.Empty;
        _settingsService.AuthIdToken = string.Empty;
        IsLogin = false;
        LoginUrl = _identityService.CreateAuthorizationRequest();
    }
    else if (unescapedUrl.Contains(GlobalSetting.Instance.Callback, StringComparison.OrdinalIgnoreCase))
    {
        var authResponse = new AuthorizeResponse(url);
        if (!string.IsNullOrWhiteSpace(authResponse.Code))
        {
            var userToken = await _identityService.GetTokenAsync(authResponse.Code);
            string accessToken = userToken.AccessToken;

            if (!string.IsNullOrWhiteSpace(accessToken))
            {
                _settingsService.AuthAccessToken = accessToken;
                _settingsService.AuthIdToken = authResponse.IdentityToken;
                await NavigationService.NavigateToAsync("//Main/Catalog");
            }
        }
    }
}

此方法解析返回 URI 中包含的身份验证响应,如果存在有效的授权代码,它会向 IdentityServer 的令牌终结点发出请求,传递授权代码、PKCE 机密验证程序和其他必需参数。 令牌终结点位于作为用户设置公开的基本终结点的端口 5105 上的 /connect/token。 有关用户设置的详细信息,请参阅配置管理)。

提示

请确保验证返回 URI。 尽管 eShopOnContainers 多平台应用不验证返回 URI,但最佳做法是验证返回 URI 是否指向已知位置,以防止开放式重定向攻击。

如果令牌终结点收到有效的授权代码和 PKCE 机密验证程序,它将使用访问令牌、标识令牌和刷新令牌进行响应。 访问令牌(允许访问 API 资源)和标识令牌作为应用程序设置进行存储,并执行页面导航。 因此,eShopOnContainers 多平台应用中的整体效果是这样的:假设用户能够成功通过 IdentityServer 进行身份验证,他们将导航到 //Main/Catalog 路由,这是一个 TabbedPage,将 CatalogView 显示为其选定的选项卡。

有关页面导航的信息,请参阅导航。 有关 WebView 导航如何导致执行视图模型方法的信息,请参阅使用行为调用导航。 有关应用程序设置的信息,请参阅配置管理

注意

当应用配置为使用 SettingsView 中的模拟服务时,eShopOnContainers 还允许模拟登录。 在此模式下,应用不会与 IdentityServer 通信,而是允许用户使用任何凭据登录。

注销

当用户点击 ProfileView 中的 LOG OUT 按钮时,将会执行 ProfileViewModel 类中的 LogoutCommand,进而执行 LogoutAsync 方法。 此方法执行到 LoginView 页的页面导航,将设置为 trueLogoutParameter 实例作为参数传递。

创建并导航到视图时,将执行视图关联视图模型的 InitializeAsync 方法,然后执行 LoginViewModel 类的 Logout 方法,如以下代码示例所示:

private void Logout()
{
    var authIdToken = Settings.AuthIdToken;
    var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);

    if (!string.IsNullOrEmpty(logoutRequest))
    {
        // Logout
        LoginUrl = logoutRequest;
    }
    
    // Omitted for brevity
}

此方法调用 IdentityService 类中的 CreateLogoutRequest 方法,将从应用程序设置中检索到的标识令牌作为参数传递。 有关应用程序设置的详细信息,请参阅配置管理。 下面的代码示例说明 CreateLogoutRequest 方法:

public string CreateLogoutRequest(string token)
{
    // Omitted for brevity

    var (endpoint, callback) =
        (GlobalSetting.Instance.LogoutEndpoint, GlobalSetting.Instance.LogoutCallback);

    return $"{endpoint}?id_token_hint={token}&post_logout_redirect_uri={callback}";
}

此方法使用所需参数创建 IdentityServer 的结束会话终结点的 URI。 结束会话终结点位于作为用户设置公开的基本终结点的端口 5105 上的 /connect/endsession

返回的 URI 存储在 LoginViewModel 类的 LoginUrl 属性中。 当 IsLogin 属性为 true 时,LoginView 中的 WebView 可见。 WebView 数据将其 Source 属性绑定到 LoginViewModel 类的 LoginUrl 属性,当 LoginUrl 属性设置为 IdentityServer 的结束会话终结点时,向 IdentityServer 发出注销请求。 IdentityServer 收到这个请求后便会进行注销,前提是用户已登录。 身份验证使用由 ASP.NET Core 中的 cookie 身份验证中间件管理的 cookie 进行跟踪。 因此,注销 IdentityServer 会删除身份验证 cookie 并将注销后重定向 URI 发送回客户端。

WebView 将重定向到多平台应用中的注销后重定向 URI。 此 WebView 导航将导致执行 LoginViewModel 类中的 NavigateAsync 方法,如以下代码示例所示:

private async Task NavigateAsync(string url)
{
    var unescapedUrl = System.Net.WebUtility.UrlDecode(url);

    if (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback, StringComparison.OrdinalIgnoreCase))
    {
        _settingsService.AuthAccessToken = string.Empty;
        _settingsService.AuthIdToken = string.Empty;
        IsLogin = false;
        LoginUrl = _identityService.CreateAuthorizationRequest();
    }
    
    // Omitted for brevity
}

此方法从应用程序设置中清除标识令牌和访问令牌。 它将 IsLogin 属性设置为 false,这会导致 LoginView 页面上的 WebView 变得不可见。 最后,使用所需参数将 LoginUrl 属性设置为 IdentityServer 授权终结点的 URI,为用户下次启动登录做好准备。

有关页面导航的信息,请参阅导航。 有关 WebView 导航如何导致执行视图模型方法的信息,请参阅使用行为调用导航。 有关应用程序设置的信息,请参阅配置管理

注意

当应用配置为使用 SettingsView 中的模拟服务时,eShopOnContainers 还允许模拟注销。 在此模式下,应用不与 IdentityServer 通信,而是从应用程序设置中清除所有存储的令牌。

授权

经过身份验证后,ASP.NET Core Web API 通常需要授权访问,这允许服务向某些经过身份验证的用户提供 API,但并非全部用户。

可以通过将 Authorize 属性应用于控制器或操作来限制对 ASP.NET Core 路由的访问,从而将对控制器或操作的访问限制为经过身份验证的用户,如以下代码示例所示:

[Authorize]
public sealed class BasketController : Controller
{
    // Omitted for brevity
}

如果未经授权的用户尝试访问标有 Authorize 属性的控制器或操作,API 框架将返回 401 (unauthorized) HTTP 状态代码。

注意

可以在 Authorize 属性上指定参数以将 API 限制到特定用户。 有关详细信息,请参阅 ASP.NET Core 文档:授权

IdentityServer 可以集成到授权工作流中,以便由它提供的访问令牌来控制授权。 下图显示了此方法。

Authorization by access token.

eShopOnContainers 多平台应用与标识微服务通信,并在身份验证过程中请求访问令牌。 然后,访问令牌作为访问请求的一部分转发到订购和购物篮微服务公开的 API。 访问令牌包含有关客户端和用户的信息。 然后,API 使用该信息来授权访问其数据。 有关如何配置 IdentityServer 以保护 API 的信息,请参阅配置 API 资源

配置 IdentityServer 以执行授权

若要使用 IdentityServer 执行授权,必须将其授权中间件添加到 Web 应用程序的 HTTP 请求管道中。 中间件添加到 Web 应用程序 Startup 类的 ConfigureAuth 方法中,该方法从 Configure 方法调用,并在 eShopOnContainers 参考应用程序的以下代码示例中进行了演示:

protected virtual void ConfigureAuth(IApplicationBuilder app)
{
    var identityUrl = Configuration.GetValue<string>("IdentityUrl");
    app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        Authority = identityUrl.ToString(),
        ScopeName = "basket",
        RequireHttpsMetadata = false,
    });
}

此方法可确保只能使用有效的访问令牌访问 API。 中间件会验证传入令牌,以确保它从受信任的颁发者发送,并验证令牌是否可用于接收它的 API。 因此,浏览订购或购物篮控制器将返回 401 (unauthorized) HTTP 状态代码,指示需要访问令牌。

注意

在使用 app.UseMvc()app.UseMvcWithDefaultRoute() 添加 MVC 之前,必须将 IdentityServer 的授权中间件添加到 Web 应用程序的 HTTP 请求管道。

向 API 发出访问请求

在向订购和购物篮微服务发出请求时,身份验证过程中从 IdentityServer 获取的访问令牌必须包含在请求中,如下代码示例所示:

var authToken = Settings.AuthAccessToken;
Order = await _ordersService.GetOrderAsync(order.OrderNumber, authToken);

访问令牌存储为应用程序设置,从特定于平台的存储中检索并包含在对 OrderService 类中的 GetOrderAsync 方法的调用中。

同样,在向受 IdentityServer 保护的 API 发送数据时也必须包含访问令牌,如以下代码示例所示:

var authToken = Settings.AuthAccessToken;
await _basketService.UpdateBasketAsync(
    new CustomerBasket
    {
        BuyerId = userInfo.UserId, 
        Items = BasketItems.ToList()
    }, 
    authToken);

访问令牌从设置中检索并包含在对 BasketService 类中的 UpdateBasketAsync 方法的调用中。

eShopOnContainers 多平台应用中的 RequestProvider 类使用 HttpClient 类向 eShopOnContainers 参考应用程序公开的 RESTful API 发出请求。 向需要授权的订购和购物篮 API 发出请求时,请求必须包含有效的访问令牌。 这是通过向 HttpClient 实例的标头添加访问令牌来实现的,如以下代码示例所示:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

HttpClient 类的 DefaultRequestHeaders 属性公开随每个请求发送的标头,访问令牌被添加到以字符串 Bearer 为前缀的 Authorization 标头中。 当请求发送到 RESTful API 时,会提取并验证 Authorization 标头的值,以确保它是从受信任的颁发者发送的,并用于确定用户是否有权调用接收它的 API。

有关 eShopOnContainers 多平台应用如何发出 Web 请求的详细信息,请参阅访问远程数据

总结

有许多方法可以将身份验证和授权集成到与 ASP.NET Web 应用程序通信的 .NET MAUI 应用中。 eShopOnContainers 多平台应用通过使用 IdentityServer 4 的容器化标识微服务执行身份验证和授权。 IdentityServer 是一个适用于 ASP.NET Core 的开源 OpenID Connect 和 OAuth 2.0 框架,它与 ASP.NET Core 标识集成以执行持有者令牌身份验证。

此多平台应用从 IdentityServer 请求安全令牌来对用户进行身份验证或访问资源。 访问资源时,访问令牌必须包含在对需要授权的 API 的请求中。 IdentityServer 的中间件将验证传入的访问令牌,以确保这些令牌是从受信任的颁发者发送的,并且可有效用于接收它们的 API。