同一应用程序上的 AAD OpenId 和承载身份验证

问题描述

我正在尝试使用 Azure Active Directory 身份验证创建身份验证,我的应用程序由 WebApis 和 WEB 页面组成,如果用户请求应用程序“控制器”,我想要实现的是将用户重定向到 Microsoft 登录页面" 页面,并在通过令牌调用 API 控制器时授权客户端应用程序。

我设法通过如下设置 Startup.Auth.cs 来做到这一点

public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationoptions());
        app.USEOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationoptions
        {
            
            ClientId = clientId,Authority = authority,RedirectUri = redirectUri,PostlogoutRedirectUri = redirectUri,Scope = OpenIdConnectScope.OpenIdProfile,ResponseType = OpenIdConnectResponseType.IdToken,TokenValidationParameters = new TokenValidationParameters()
            {
                ValidateIssuer = true
            },Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = OnAuthenticationFailed
            }
        });

                    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
               new WindowsAzureActiveDirectoryBearerAuthenticationoptions
               {
                   Tenant = ConfigurationManager.AppSettings["ida:Tenant"],TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true,ValidAudience = $"api://{clientId}" }
               });
    }

用 [Authorize] 属性装饰我的控制器

但是,当未经授权的客户端应用程序调用 API 时,它会返回以下内容

  <html>

<head>
    <title>Working...</title>
</head>

<body>
    <form method="POST" name="hiddenform" action="https://local:44341/">
        <input type="hidden" name="id_token" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiI3NjIzMjcyMC1hNmViLTRkOTctOTI3Yy1lMWFjNzRiODIyMjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubgluZS5jb20vYjFmZTVlMmEtZjc1Yy00NDQxLTlmYjItNWFjYzAxYmEwMGQwL3YyLjAiLCJpYXQiOjE2MTA3MzAJub25jZSI6IjYzNzQ2MzI3OTEwNDAxMzk3Ni5aR0k1TmpCaU1XWXRPRGsxT0MwmfpEWmlMV0kxWXprdE9ESTFZMlZrTWpjeU0yWTJNRFZqTWpnNU9EY3RNak0yWkMwME1qWmpMV0UwTTJZdE5XSXpNVFZsTlRKa1pHRXgiLCJvaWQiOiJiN2YzMjg1Mi1hMDRhLTRlOWUtYjFjOS02NTI5NDk3YTgyZDEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIyMjQxN0Byai5jb20iLCJyaCI6IjAuQUFBQUtsNy1zVnozUVVwoeHm6x2ad02ExHlw48lfRVZhEoHw8cZ5SWRFOGZfi3TJ3aOFKdaDZZ8Yt1no30Hku8pL3Vf7Lrq-BuKX8PNtkU45aVO1fZf8PFdM4UbpYnHOwvuG1NlPG8mTa1KheQTza9j3PTaiLq8e15jtSoFoHfIJbMZJoNTfvAF40kt9XvYee-rga80Oj1tJX78g_80MmRYORwArr1rq1n6EyPgFHaDDF5vD-zWOLDrXKyj2rwW-7LLpbtojtbsyCPdM5QPkLKnZZFanpvwRAFRTHaLdHINgalHhvhFP9kvRhVtaTUgwYzrFqlN8k3zSZrvnMOec7A" /><input type="hidden" name="state" value="OpenIdConnect.AuthenticationProperties=7_QsmCbQZ3vBxo1tTvZXcYRLeNQMBZfanq5zpZvqNjuudSAu52-UZnVGkkWmxeBh_rIHE3i_j8g_B751WFqQHR1CXJrdjBi6PZy-" /><input type="hidden" name="session_state" value="5938bc609" /><noscript>
            <p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript></form>
    <script language="javascript">
        document.forms[0].submit();
    </script>
</body>

</html>

但是当从我的函数删除 USEOpenIdConnectAuthentication 的调用时,它返回正确的响应消息,状态代码为 401

{    "Message": "Authorization has been denied for this request."}

话虽如此,谁能帮我设置 web api 的承载令牌身份验证,以及 web 应用程序页面的 openIdConnect。

谢谢,如果我的方法有问题,请告诉我,并根据我的要求推荐正确的方法

解决方法

不要在此处使用 UseOpenIdConnectAuthentication,它用于自定义 OpenID 支持,这在此处并不真正适用,使用开箱即用的 Azure AD 特定帮助程序,例如您接下来使用的帮助程序。

这是服务器端的一个很好的起点:https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-overview

对于客户端,通用调用方(控制台应用程序或任意服务):https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-overview

对于客户端,需要自我保护的交互式 Web 应用程序:https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-web-app-sign-user-overview

简而言之,您的 API 客户端必须向 Azure AD 请求令牌才能访问您的 API 端点。为了让 Azure AD 知道此令牌用于哪个资源,请为您的 API 创建应用程序注册,并使用其客户端 ID 和任何其他必要参数配置 API 应用程序。

当您的应用程序收到令牌时,它必须联系 Azure AD 以对其进行验证。对于此调用,它应使用您在 Azure AD 中为其生成的客户端 ID。

您可能希望在模拟真实 AD 登录时运行客户端,或者您可能将客户端作为某个 AD 主体(应用程序注册)运行。看看下面的 .NET Core 3.1 客户端应用程序示例是否可以帮助您。它使用其 ID 和机密模拟某个 AD 主体,因此它可以从 Azure 云外部运行并连接到受 Azure AD 保护的服务,而无需托管标识。

为了让下面的示例正常工作,请确保正确配置客户端应用程序和服务器 API 的主体(应用程序注册/企业应用程序)。这本身就是一个主题,上面链接的官方文档中对此进行了详细描述。

如果您的客户端作为 Azure AppService 或在 Azure VM 上运行,则最好使用 Azure 托管标识。这消除了对机密的需要并简化了对 AzureServiceTokenProvider 的构造函数调用(无参数 = 使用环境上下文 = 选择托管标识)。不幸的是,通过 Azure 进行本地开发并不容易。

using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace N
{
    public class Test
    {
        public async Task Main(IConfigurationRoot config,ILoggerFactory loggerFactory)
        {
            using var httpClient = new HttpClient(new HttpClientHandler
            {
                ServerCertificateCustomValidationCallback = (msg,cert,chain,errors) =>
                {
                    return true;
                }
            });

            var url = "https://localhost:5000";
            var clientId = "guid of your caller's identity App Registration";
            var clientSecret = "secret you produced for your caller's managed identity App Registration";
            var tenantId = "guid (Azure tenant id)";
            var runAs = $"RunAs=App;AppId={clientId};TenantId={tenantId};AppKey={clientSecret}";

            var tokenProvider = new AzureServiceTokenProvider(runAs);
            var accessToken = await tokenProvider.GetAccessTokenAsync("https://my-test-app-identity.azurewebsites.net");

            var requestMessage = new HttpRequestMessage(HttpMethod.Get,new Uri(uri));
            requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer",accessToken);

            var response = await httpClient.SendAsync(requestMessage);
            response.EnsureSuccessStatusCode();
            var content = await response.Content.ReadAsStringAsync();

            foreach (var header in response.Headers)
            {
                Console.WriteLine(header.Key + ": " + string.Join(",",header.Value));
            }
            Console.WriteLine(content);
        }
    }
}

.csproj 中的依赖项:

<PackageReference Include="Azure.Core" Version="1.4.1" />
<PackageReference Include="Azure.Identity" Version="1.2.2" />
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.1.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.1.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.17.1" />