如何授权来自不同 AAD 租户的特定客户端守护程序?

Posted

技术标签:

【中文标题】如何授权来自不同 AAD 租户的特定客户端守护程序?【英文标题】:How to authorize a specific client daemon from a different AAD tenant? 【发布时间】:2021-06-25 16:34:01 【问题描述】:

在我的服务器到服务器方案中,我在(Azure Active Directory)租户 A(我控制)中有一个 ASP.NET Core 服务,我想允许在租户 B 中注册特定的(守护程序)客户端应用程序(我无法控制)访问我的服务。

我发现了一些通过基于角色的访问控制进行多租户授权的示例,但我不想使用角色,因为我不了解允许租户 B 中的管理员决定谁获得与我的应用程序交谈的适当“角色”。相反,我想自己决定,访问控制列表似乎是一个很好的方法。

理论上,我认为我可以使用 B 颁发的 JWT 不记名令牌,并在 A 中使用它们,但我无法使其正常工作:根据我的尝试,服务器上的身份验证失败,类似 401

WWW-Authenticate: Bearer error="invalid_token", error_description="The issuer '(null)' is invalid"

WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

即使我的不记名令牌似乎在 jwt.io 中验证良好(“签名验证”,“iss”:“https://sts.windows.net/4510468d-3790-4a1a-8209-84281b2d1596/”)。

我的代码在 git repo 中,这是我的启动:

type Startup(configuration: IConfiguration) =
    member _.Configuration = configuration

    // This method gets called by the runtime. Use this method to add services to the container.
    // see https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-web-api for more
    member _.ConfigureServices(services: IServiceCollection) =
        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(fun o ->

                configuration.Bind(o)
                o.TokenValidationParameters <- new Microsoft.IdentityModel.Tokens.TokenValidationParameters(ValidateAudience=true)
                )
            .Services.AddControllers() |> ignore

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    member _.Configure(app: IApplicationBuilder, env: IWebHostEnvironment) =
        if (env.IsDevelopment()) then
            app.UseDeveloperExceptionPage() |> ignore
        app.UseHttpsRedirection()
           .UseRouting()
           .UseAuthentication()
           .UseAuthorization()
           .UseEndpoints(fun endpoints ->
                endpoints.MapControllers() |> ignore
            ) |> ignore

appsettings.json:


  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    
  ,
  "AllowedHosts": "*",
  "AzureAd": 
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "wilsonsoft.onmicrosoft.com",
    "ClientId": "88ac0449-3fae-4113-a1ba-fb4f2d041702",
    "TenantId": "c4568757-6752-4ed0-a24a-b5ab2df02011"
  

一个典型的请求:

GET https://localhost:44336/weatherforecast/who HTTP/1.1
Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJodHRwczovL3dpbHNvbnNvZnQub25taWNyb3NvZnQuY29tL0hlbGxvV2VhdGhlciIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzQ1MTA0NjhkLTM3OTAtNGExYS04MjA5LTg0MjgxYjJkMTU5Ni8iLCJpYXQiOjE2MTcwMzI5NDAsIm5iZiI6MTYxNzAzMjk0MCwiZXhwIjoxNjE3MDM2ODQwLCJhaW8iOiJFMlpnWUNqYlpMKzk5TGErZWQ5blYrUEhsUmI5QUE9PSIsImFwcGlkIjoiNDE1OTA3NjMtZWM3YS00Mzc2LTkwNmYtY2VlMTM4NzExMzg0IiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNDUxMDQ2OGQtMzc5MC00YTFhLTgyMDktODQyODFiMmQxNTk2LyIsIm9pZCI6IjVkOGQ0ZTZkLTQ4MzQtNDA3Ny1hZGIyLTQ5ODNjYWQxMGY4ZiIsInJoIjoiMC5BUTBBalVZUVJaQTNHa3FDQ1lRb0d5MFZsbU1IV1VGNjdIWkRrR19PNFRoeEU0UU5BQUEuIiwic3ViIjoiNWQ4ZDRlNmQtNDgzNC00MDc3LWFkYjItNDk4M2NhZDEwZjhmIiwidGlkIjoiNDUxMDQ2OGQtMzc5MC00YTFhLTgyMDktODQyODFiMmQxNTk2IiwidXRpIjoiYWE4MDEtX2d1a21PQUpBcGFQOThBQSIsInZlciI6IjEuMCJ9.DwpWaOqoZgNoDka6-0FYQr1ivllL2taXdqtat_65x_kuT6r3uiknhL19Fu6dFmJ7UCgjc3-JZh5Bee0uZvVHbwCjZKHsUNrDEANnDkK4hGFzSKyU3NL7X9iRdPeBl3-GUSRGbPeozHTF93epSEhDyY3PkS1ICEfdAG7yi8cerBzmuy-lsUWs90sWlrWVYjDRtFWzwlovNgS6mPkx2cKlsC34WK6QXafJYqPcA5XW1EqZGyA5S0qvQS0VaheABEfkTIC8pEijieImWKIqFefd2G7blBB1Qdng4NAPcHOhmnRSiClCrwXS_5hOYsUXFS4xVAMPIZx9peWXMlk6XyQzNg
Host: localhost:44336

回复:

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Server: Microsoft-IIS/10.0
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
X-Powered-By: ASP.NET
Date: Mon, 29 Mar 2021 16:06:48 GMT

0

谁能建议这个错误消息试图告诉我什么和/或是否有更好的方法来实现我在 ASP.NET Core 中授权不同租户中的特定客户端的目标?

【问题讨论】:

【参考方案1】:

答案:在多租户场景中,您必须针对“通用”端点进行身份验证,并且还必须使用自定义 TokenValidationParameters.IssuerValidator 在检查“iss”声明之前对其进行转换。这将允许您使用 jwt 令牌进行身份验证。详情请见https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-web-api。

那么,授权就是检查应用 ID 声明的简单问题。

// This method gets called by the runtime. Use this method to add services to the container.

member _.ConfigureServices(services: IServiceCollection) =
    IdentityModelEventSource.ShowPII <- true
    services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(fun jwtOptions ->
            jwtOptions.TokenValidationParameters <-
                new Microsoft.IdentityModel.Tokens.TokenValidationParameters(
                    ValidateAudience = true,
                    ValidateIssuer = true
                    )
            jwtOptions.Authority <- "https://login.microsoftonline.com/common"
            jwtOptions.Audience <- "https://wilsonsoft.onmicrosoft.com/HelloWeather"
            jwtOptions.TokenValidationParameters.IssuerValidator <-
                fun issuer jwt tokenValidationParams ->
                    // In order to support multi-tenant auth, replace tenantid placeholder with actual tenantId,
                    // e.g. "https://sts.windows.net/tenantid/" becomes "https://sts.windows.net/c4568757-6752-4ed0-a24a-b5ab2df02011/"
                    // See https://thomaslevesque.com/2018/12/24/multitenant-azure-ad-issuer-validation-in-asp-net-core/ for more
                    let validatedIssuer =
                        match jwt with
                        | :? JwtSecurityToken as jwt ->
                            match jwt.Payload.TryGetValue("tid") with
                            | true, (:? string as tenantId) ->
                                let validIssuers =
                                    (Seq.append tokenValidationParams.ValidIssuers [tokenValidationParams.ValidIssuer])
                                    |> Seq.filter (System.String.IsNullOrEmpty >> not)
                                    |> Seq.map (fun i -> i.Replace("tenantid", tenantId))
                                validIssuers |> Seq.tryFind (fun i -> i = issuer)
                            | _ -> None
                        | _ -> None
                    match validatedIssuer with
                    | Some i -> i
                    | None ->
                        $"IDX10205: Issuer validation failed. Issuer: 'issuer'. Did not match: validationParameters.ValidIssuer: 'tokenValidationParams.ValidIssuer' or validationParameters.ValidIssuers: 'tokenValidationParams.ValidIssuers'."
                        |> SecurityTokenInvalidIssuerException
                        |> raise
            )
        .Services.AddAuthorization(fun authzOptions ->
            // only allow specific appIds (from trusted issuers) access to this app
            authzOptions.AddPolicy(
                "ACL",
                (fun policy ->
                    policy.RequireAssertion(fun ctx ->
                        ctx.User.HasClaim(fun claim ->
                            claim.Type = "appid" && claim.Value = "083d3ba2-ed4e-4e11-b7ef-d8cc46ffe346")
                        )
                    |> ignore
                ))
            )
        .AddControllers() |> ignore

Fixed version.

【讨论】:

以上是关于如何授权来自不同 AAD 租户的特定客户端守护程序?的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS在我的天蓝色租户中注册

如何识别来自不同租户nestjs多租户jwt的jwt令牌

AAD B2C 中具有客户端凭据授予流的 Azure 应用服务轻松身份验证

如何针对单个应用程序对来自不同领域的用户进行身份验证?

向 AAD B2C 注册 AzureFunctions 应用程序

来自守护程序的错误响应:获取 https://nvcr.io/v2/:未授权:需要身份验证