代码之家  ›  专栏  ›  技术社区  ›  Jeff Dege

JWT Bearer代币:“观众”api://...“无效”

  •  0
  • Jeff Dege  · 技术社区  · 4 年前

    我正在处理莱斯·杰克逊的 The Complete ASP.NET Core 3 API Tutorial ,我被卡住了。我在第14章“保护我们的API”中。

    在本章中,我们应该做的是将我们构建的API配置为接受来自Azure Active Directory的JWT令牌,然后构建一个客户端,该客户端从Azure Active Directory获取JWT令牌并将其作为自定义头包含在针对API的调用中。

    目前,我们仍在本地主机上运行API,接下来将在Azure上托管它。

    在所有这些中,我正在Azure中使用我的默认目录。

    正在将Azure AD JWT添加到API:

    本章列出了以下步骤:

    1. 在Azure AD中注册我们的API
    2. 在Azure中展示我们的API
    3. 更新我们的API清单
    4. 添加其他配置元素
    5. 添加新的包引用
    6. 更新API项目源代码

    我已经完成了#1和#2,当我查看我的API时,我看到:

    Display name: CommandAPI_DEV
    Application (client) ID: 1e994557-5ae1-47bf-8ab7-b0ce2f8f3852
    Object ID: b8225518-eba3-4a6a-8c30-ae82095a4ba7
    Directory (tenant) ID: 9f9fbb85-6a89-4fac-a52a-845135fbe887
    Application ID URI: api://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852
    Managed application in local directory: CommandAPI_DEV
    

    对于#3,我已将其添加到清单中:

    "appRoles": [
        {
            "allowedMemberTypes": [
                "Application"
            ],
            "description": "Daemon apps in this role can consume the web api.",
            "displayName": "DaemonAppRole",
            "id": "be111a2a-ea62-47a9-8f55-5d8f84af3276",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "value": "DaemonAppRole"
        }
    ],
    

    对于#4,我创建了以下用户机密:

    {
      "UserID": "cmddbuser",
      "TenantId": "9f9fbb85-6a89-4fac-a52a-845135fbe887",
      "ResourceId": "app://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852",
      "Password": "pa55w0rd!",
      "Instance": "https://login.microsoftonline.com/",
      "Domain": "jdegejdege.onmicrosoft.com",
      "ClientId": "1e994557-5ae1-47bf-8ab7-b0ce2f8f3852"
    }
    

    #5只是添加了NuGet包。对于#6,我添加了ConfigureServices():

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(opt =>
        {
            opt.Audience = Configuration["ResourceId"];
            opt.Authority = $"{Configuration["Instance"]}{Configuration["TenantId"]}";
        })
        ;
    

    请注意,此选项。受众:

    "app://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852"
    

    然后选择。授权:

    "https://login.microsoftonline.com/9f9fbb85-6a89-4fac-a52a-845135fbe887" 
    

    其中这些值存储在用户机密中,但从Azure AD上的应用程序页面的“Essentials”部分复制。

    然后我添加到Configure()中:

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

    我补充道 [Authorize] 到我的一个控制器端点。

    有了这些,API项目的一切都很好。

    构建客户端:

    本章列出了以下步骤:

    1. 在Azure AD中注册客户端应用程序
    2. 创建 客户秘密 在Azure中
    3. 配置客户端API权限
    4. 编码我们的客户端应用程序

    所以我做了#1,当我在Azure AD中查看应用程序时,我看到:

    Display name: CommandAPI_Client_DEV
    Application (client) ID: d32007a5-642d-413a-82d9-4761e3030890
    Object ID: 94d13c40-5556-4e12-aa2c-be9619681712
    Directory (tenant) ID: 9f9fbb85-6a89-4fac-a52a-845135fbe887
    Application ID URI: Add an Application ID URI
    Managed application in local directory: CommandAPI_Client_DEV
    

    然后我做了#2,创建了一个客户端机密。当我这样做时,UI显示了三个字段:

    Description: CommandAPI_Client_DEV_secret
    Value: NTK*****************************
    Secret ID: 954fa788-c8b8-4265-97d7-a1bd83be3bcf
    

    (当我创建这个秘密时,它显示了37个字符。当我回去时,我只看到前三个字符后面跟着星号。)

    然后对于#3,我进入API权限,向我的API添加了一个新权限,然后授予管理员同意。

    我没有收到Microsoft身份验证弹出窗口,但Azure UI显示权限的状态为“授予默认目录”。

    对于#4,我创建了一个简单的控制台应用程序,将其放在appsettings.json中:

    {
        "Instance": "https://login.microsoftonline.com/{0}",
        "TenantId": "9f9fbb85-6a89-4fac-a52a-845135fbe887",
        "ClientId": "d32007a5-642d-413a-82d9-4761e3030890",
        "ClientSecret": "NTK*****************************",
        "BaseAddress": "https://localhost:5001/api/Commands/1",
        "ResourceId": "api://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852/.default"
    }
    

    请注意,“TenantId”与Azure AD中客户端应用程序配置的“目录(租户)ID”匹配。

    “ClientId”与Azure AD中客户端应用程序配置的“应用程序(客户端)ID”匹配。

    “ClientSecret”与创建机密时给定的值字段匹配。

    “ResourceId”与Azure AD中API配置的“应用程序ID URI”字段匹配。

    因此,客户端应用程序非常简单。

    主要调用RunAsync():

    static void Main(string[] args)
    {
        Console.WriteLine("Making the call...");
        RunAsync().GetAwaiter().GetResult();
    }
    
    private static async Task RunAsync()
    {
    

    AuthConfig是我们在appsettings.json中设置的一个简单包装器。配置。权威是

        AuthConfig config = AuthConfig.ReadFromJsonFile("appsettings.json");
        var authority = String.Format(CultureInfo.InstalledUICulture, config.Instance, config.TenantId);
        Console.WriteLine($"Authority: {authority}");
    

    我们呼叫Azure AD以获取我们的JWT:

        IConfidentialClientApplication app;
        app = ConfidentialClientApplicationBuilder
            .Create(config.ClientId)
            .WithClientSecret(config.ClientSecret)
            .WithAuthority(new Uri(authority))
            .Build();
    
        string[] ResourceIds = new string[] { config.ResourceId };
    
        AuthenticationResult result = null;
        try
        {
            result = await app.AcquireTokenForClient(ResourceIds).ExecuteAsync();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Token acquired\n");
            Console.WriteLine(result.AccessToken);
            Console.ResetColor();
        }
        catch (MsalClientException ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(ex.Message);
            Console.ResetColor();
        }
    
        if (!string.IsNullOrEmpty(result.AccessToken))
        {
    

    然后我们将JWT设置为承载令牌:

            var httpClient = new HttpClient();
            var defaultRequestHeaders = httpClient.DefaultRequestHeaders;
            if (defaultRequestHeaders.Accept == null ||
                !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json"))
            {
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            }
    
            defaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
    

    并调用API的安全端点:

            HttpResponseMessage response = await httpClient.GetAsync(config.BaseAddress);
            if (response.IsSuccessStatusCode)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                string json = await response.Content.ReadAsStringAsync();
                Console.WriteLine(json);
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"Failed to call the Web API: {response.StatusCode} ");
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"Content: {content}");
            }
            Console.ResetColor();
        }
    }
    

    当我这样做的时候,我会得到一个有效的JWT,它解码为:

    {
      "typ": "JWT",
      "alg": "RS256",
      "x5t": "l3sQ-50cCH4xBVZLHTGwnSR7680",
      "kid": "l3sQ-50cCH4xBVZLHTGwnSR7680"
    }.{
      "aud": "api://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852",
      "iss": "https://sts.windows.net/9f9fbb85-6a89-4fac-a52a-845135fbe887/",
      "iat": 1636246957,
      "nbf": 1636246957,
      "exp": 1636250857,
      "aio": "E2ZgYLhnIvUl2+2Z4Jno3ebX7pVvAAA=",
      "appid": "d32007a5-642d-413a-82d9-4761e3030890",
      "appidacr": "1",
      "idp": "https://sts.windows.net/9f9fbb85-6a89-4fac-a52a-845135fbe887/",
      "oid": "cc6633e2-df64-41be-bbb2-de750766e40a",
      "rh": "0.AUYAhbufn4lqrE-lKoRRNfvoh6UHINMtZDpBgtlHYeMDCJCAAAA.",
      "roles": [
        "DaemonAppRole"
      ],
      "sub": "cc6633e2-df64-41be-bbb2-de750766e40a",
      "tid": "9f9fbb85-6a89-4fac-a52a-845135fbe887",
      "uti": "tgVrQAPepkiMGHO04uHdAA",
      "ver": "1.0"
    }.[Signature]
    

    但是当我对API httpClient进行调用时。GetAsync()返回401,其中WwwAuthenticate标头设置为:

    {
      Bearer error="invalid_token", 
      error_description="The audience 'api://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852' is invalid"
    }
    

    很明显我做错了什么,但是呢?

    0 回复  |  直到 4 年前
        1
  •  1
  •   Aakash Sethi    4 年前

    问题似乎是令牌问题设置之间的不匹配 aud 字段为,即 "api://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852" 以及您的安全API对受众的期望,即 "app://1e994557-5ae1-47bf-8ab7-b0ce2f8f3852" .

    你需要让观众 api... 在构建安全API的步骤#4中,使其工作。