JWT 不记名令牌:“观众 'api://...' 无效”[关闭]

Posted

技术标签:

【中文标题】JWT 不记名令牌:“观众 \'api://...\' 无效”[关闭]【英文标题】:JWT Bearer Token: "The audience 'api://...' is invalid" [closed]JWT 不记名令牌:“观众 'api://...' 无效”[关闭] 【发布时间】:2021-12-20 10:39:57 【问题描述】:

我正在处理 Les Jackson 的 The Complete ASP.NET Core 3 API Tutorial,但我被困住了。我在第 14 章,“保护我们的 API”。

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

此时,我们仍在 localhost 上运行 API,接下来将其托管在 Azure 上。

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

将 Azure AD JWT 添加到 API:

本章列出了以下步骤:

    在 Azure AD 中注册我们的 API 在 Azure 中公开我们的 API 更新我们的 API 清单 添加其他配置元素 添加新的包引用 更新 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"]";
    )
    ;

请注意,此 opt.Audience to:

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

并且 opt.Authority :

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

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

然后我已经添加到Configure():

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

我已将[Authorize] 添加到我的控制器端点之一。

所有这些,API 项目一切正常。

构建客户端:

本章列出了以下步骤:

    在 Azure AD 中注册客户端应用程序 在 Azure 中创建 客户端密码 配置客户端 API 权限 编写我们的客户端应用程序

所以我做了 #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 中设置的简单包装器。 config.Authority 是

    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"

显然我做错了什么,但是什么?

【问题讨论】:

我没有看到你定义你的范围的任何东西,另外是否遵循任何官方文件? 我按照书中的例子,作者没有做任何关于范围的事情。 【参考方案1】:

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

您需要在构建安全 API 的第 4 步中将受众设置为 api... 才能正常工作。

【讨论】:

以上是关于JWT 不记名令牌:“观众 'api://...' 无效”[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

JWT 不记名令牌流

JWT中令牌之前的不记名

为啥我的 JWT 不记名身份验证在令牌说 5 分钟后将令牌识别为过期?

添加基于策略的授权会跳过 JWT 不记名令牌身份验证检查吗?

jwt 不记名令牌是不是应该在服务器端的某个地方持续存在

修改 OWIN OAuth 中间件以使用 JWT 不记名令牌