IDW10201:在不记名令牌中找不到范围或角色声明

Posted

技术标签:

【中文标题】IDW10201:在不记名令牌中找不到范围或角色声明【英文标题】:IDW10201: Neither scope or roles claim was found in the bearer token 【发布时间】:2021-01-19 10:01:28 【问题描述】:

我有一个类似以下示例的 ASP.NET Core 3.1 项目:Sign-in a user with the Microsoft Identity Platform in a WPF Desktop application and call an ASP.NET Core Web API。

我正在使用 Identity web 1.0 版和 Azure AD,单租户应用程序。

我已经编辑了添加 appRoles 的清单,因为我只请求应用程序令牌,而不是用户令牌:

[... more json ...]
"appId": "<guid>",
"appRoles": [
    
        "allowedMemberTypes": [
            "Application"
        ],
        "description": "Accesses the application.",
        "displayName": "access_as_application",
        "id": "<unique guid>",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "access_as_application"
    
],
"oauth2AllowUrlPathMatching": false,
[... more json ...]

我还启用了idtyp 访问令牌声明,以指定这是一个应用程序令牌。:

[... more json ...]
"optionalClaims": 
    "idToken": [],
    "accessToken": [
        
            "name": "idtyp",
            "source": null,
            "essential": false,
            "additionalProperties": []
        
    ],
    "saml2Token": []
[... more json ...]

以下请求是通过 Postman 发出的。请注意/.default 与范围的使用,在与client credentials grant flow 相关的文档中提到。

POST /tenant_id/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

scope=api%3A%2F%2client_id%2F.default
&client_id=client_id
&grant_type=client_credentials
&client_secret=secret_key

请求返回一个access_token,可以用jwt.ms查看,看起来像这样,出于安全原因,实际数据已被占位符替换。:


  "typ": "JWT",
  "alg": "RS256",
  "x5t": "[...]",
  "kid": "[...]"
.
  "aud": "api://<client_id>",
  "iss": "https://sts.windows.net/<tenant_id>/",
  "iat": 1601803439,
  "nbf": 1601803439,
  "exp": 1601807339,
  "aio": "[...]==",
  "appid": "<app id>",
  "appidacr": "1",
  "idp": "https://sts.windows.net/<tenant_id>/",
  "idtyp": "app",
  "oid": "<guid>",
  "rh": "[..].",
  "roles": [
    "access_as_application"
  ],
  "sub": "<guid>",
  "tid": "<guid>",
  "uti": "[...]",
  "ver": "1.0"

我注意到上面的令牌不包括scp。这似乎是正确的,因为这是应用程序令牌而不是用户令牌。取而代之的是,它包含“角色”,作为应用程序令牌。

access_token 现在可以在 Postman Get 中用作承载:

GET /api/myapi
Host: https://localhost:5001
Authorization: Bearer access_token

对此请求的响应是500 internal error。 IE。出了点问题。 access_token 看起来像一个正确的应用程序令牌,因此错误似乎出在 ASP.NET Core 3.1 控制器端。

ASP.NET Core 3.1。托管自定义 API 的项目,有一个 startup.cs,其中包括以下代码:

services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

// This is added for the sole purpose to highlight the origin of the exception.
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>

    var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
    
    options.Events.OnTokenValidated = async context =>
    
        if (context.Principal.Claims.All(x => x.Type != ClaimConstants.Scope)
            && context.Principal.Claims.All(y => y.Type != ClaimConstants.Scp)
            && context.Principal.Claims.All(y => y.Type != ClaimConstants.Roles))
        
            // This where the exception originates from:
            throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
        
    ;
);

该项目的appsettings.json 包括:

"AzureAD": 
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "mydomain.onmicrosoft.com",
    "ClientId": "<client_id>",
    "TenantId": "<tenant_id>",
    "Audience": "api://<client_id>"
,

...控制器看起来像这样:

[Authorize]
[Route("api/[controller]")]
public class MyApiController : Controller

    [HttpGet]
    public async Task<string> Get()
    
        return "Hello world!";
    

500 internal error的根本原因是抛出了这个异常:IDW10201: Neither scope or roles claim was found in the bearer token.异常。

更新:

(有关更多详细信息,请参阅下面的答案)。

“Implementing Authorization in your Applications with Microsoft identity platform - june 2020”上的这个视频表明缺少的部分是这个标志JwtSecurityTokenHandler.DefaultMapInboundClaims = false;,需要在startup.cs 中设置 - 例如:

public void ConfigureServices(IServiceCollection services)

    // By default, the claims mapping will map clain names in the old format to accommodate older SAML applications.
    //'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' instead of 'roles'
    // This flag ensures that the ClaimsIdentity claims collection will be build from the claims in the token
    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    
    [...more code...]

【问题讨论】:

使用像 wireshark 或 fiddler 这样的嗅探器,比较工作的 Postman 和不工作的 c#。首先检查正在使用的 TLS 版本。如果它们相同,则比较第一个请求中的标头。使 c# 看起来像 Postman 结果。 不确定这有什么帮助?邮递员不工作。我只使用邮递员。问题不在电线上。问题似乎是 access_token 缺少一些信息。 检查 TLS 版本。您可能会收到错误,因为您使用的是 TLS 1.0/1.1。业界 5 年前因为安全问题决定淘汰 TLS 1.0/1.1。今年 6 月,微软推出了一项安全更新,以禁用服务器上的 TLS 1.//1.1。所以客户端现在必须请求 TLS 1.2/1.3。由于您没有指定 TLS 版本,因此它默认为您使用的 Net 版本和您正在使用的 Windows 版本。还要确保您使用的是最新的 API。旧的 APi 可能正在使用旧版本的 TLS。 我可以确认通信使用的是 TLS 1.2,但我仍然不明白为什么这很重要,因为 Postman 和我的 ASP.NET Core 3.1 api 之间的通信都在我的本地 Windows 上运行10 bld 19042.541,工作得很好。问题不在网络上。 【参考方案1】:

如果您计划不使用内置范围或角色,这可能会有所帮助。您可以使用下面的 Azure B2C 示例启用“访问控制列表”身份验证。以下是一些官方文档的链接。

https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis#user-content-web-apis-called-by-daemon-apps-using-client-credential-flow

https://docs.microsoft.com/en-us/dotnet/api/microsoft.identity.web.microsoftidentityoptions.allowwebapitobeauthorizedbyacl?view=azure-dotnet-preview

将以下内容添加到您的 AD 配置中: "AllowWebApiToBeAuthorizedByACL": true

例子:

"AzureAdB2C": 
    "Instance": "https://xxx.b2clogin.com/",
    "ClientId": "xxxx",
    "Domain": "xxx.onmicrosoft.com",
    "SignUpSignInPolicyId": "xxx",
    "AllowWebApiToBeAuthorizedByACL": true
  ,

ACL/访问控制列表的含义: 访问控制列表:https://en.wikipedia.org/wiki/Access-control_list

【讨论】:

避免使用指向其他答案的链接,相反,您可以总结并添加步骤、信息或回答问题所需的任何内容。 @juagicre 我不认为链接到官方文档与“链接到其他答案”相同。另外,我认为阅读官方文档很重要,特别是在更改应用程序安全性的默认行为时。在此 SO 答案之前,文档有更大的机会进行更新。最后,我确实写了如何启用我在帖子中建议的内容。 这解决了我们的问题。谢谢! 这解决了我的问题!【参考方案2】:

只需将 DefaultMapInboundClaims 添加到您的 API 服务配置中

public void ConfigureServices(IServiceCollection services)

    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

【讨论】:

如果您想提供帮助,请解释为什么这不起作用[Authorize(Roles = "access_as_application")]。您可以在我对问题的回答中查看详细信息。 啊抱歉,我一直在研究项目中的这种行为,还没有刷新页面,所以我错过了你的更新 对不起,做了一个我不应该做的假设。【参考方案3】:

视频“Implementing Authorization in your Applications with Microsoft identity platform - june 2020”概述了缺少的部分是这个标志JwtSecurityTokenHandler.DefaultMapInboundClaims = false;,需要在startup.cs中设置 - 例如:

public void ConfigureServices(IServiceCollection services)

    services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

    // By default, the claims mapping will map claim names in the old format to accommodate older SAML applications.
    //'http://schemas.microsodt.com/ws/2008/06/identity/clains/role' instead of 'roles'
    // This flag ensures that the ClaimsIdentity claims collection will be build from the claims in the token
    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;


    // Notice that this part is different in the video, 
    // however in this context the following seems to be 
    // the correct way of setting the RoleClaimType:
    services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
    
        // The claim in the Jwt token where App roles are available.
        options.TokenValidationParameters.RoleClaimType = "roles";
    );

    [... more code ...]


备选方案 1

也可以像startup.cs这样为整个应用设置授权:


services.AddControllers(options =>

    var policy = new AuthorizationPolicyBuilder()
        .RequireClaim("roles", "access_as_application")
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
);

备选方案 2

也可以使用这样的策略:

services.AddAuthorization(config =>

    config.AddPolicy("Role", policy => 
        policy.RequireClaim("roles", "access_as_application"));
);

现在此策略可用于控制器请求,如下所示:

[HttpGet]
[Authorize(Policy = "Role")]
public async Task<string> Get()

    return "Hello world!";

文档中的更多内容:Policy based role checks。

【讨论】:

这可能是因为您的角色实际上不是角色,而是声称尝试添加诸如 policy.RequireClaim("roles", "access_as_application")) 之类的策略,而不是在控制器中使用该策略跨度> 没用。这样做了:services.AddControllers(options =&gt; var policy = new AuthorizationPolicyBuilder() .RequireClaim("roles", "access_as_application") .Build(); options.Filters.Add(new AuthorizeFilter(policy)); ) 嗯,它确实有效,因为它为整个应用程序全局设置了access_as_application 的要求。但是,如果我将[Authorize(Roles = "access_as_application")] 属性放在控制器本身上,它仍然会失败。 嗯,对我来说很奇怪,它可以工作services.AddAuthorization(config =&gt; config.AddPolicy("Role", policy =&gt; policy.RequireClaim("roles", "access_as_application")); );,但你的解决方案也很好 在控制器中,您应该指定的不是角色,而是您创建的策略[Authorize(Policy = "Role")]【参考方案4】:

当我收到此错误“IDW10202”时,是因为控制器中的这行代码。

HttpContext.ValidateAppRole("MyAppRole");

(这是谷歌返回的唯一结果,所以为了任何人的利益,把这个评论放在这里。如果有点跑题了,道歉。)

【讨论】:

【参考方案5】:

原因是您使用默认范围 scope=api%3A%2F%2client_id%2F.default 发出请求,该范围不包括 access_token 中的范围声明,当您公开该范围时,您应该使用为 ASP.NET Core 3.1 API 项目注册的特定范围Azure 门户中的 API。

【讨论】:

你确定吗?如果我在请求令牌时更改范围,则会收到此错误:"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://client_id/access_as_application is not valid.\r\nTrace ID" 这就是文档所说的:Client credentials grant flow and /.default(使用正确的链接更新) 您是否为您的客户正确配置了 Azure 应用程序,包括为您的 api://client_id/access_as_application 范围添加 api 权限?因为这是一种奇怪的情况,您 access_token 应该包含范围或角色声明,并且由于 .default 范围,azure 没有发出范围声明,并且您的 web api 应用程序似乎在 azure 中没有权限/角色,这就是为什么没有发出角色声明也是, 是的。我更新了清单,现在我实际上得到了角色,但我仍然遇到同样的异常。请查看原帖中的更新。

以上是关于IDW10201:在不记名令牌中找不到范围或角色声明的主要内容,如果未能解决你的问题,请参考以下文章

为多个 API 调用重用不记名令牌

从 Web API 的不记名令牌返回用户角色

如何将角色添加到 asp 身份承载令牌

不记名令牌变得太大

确定不记名令牌是不是已过期或刚刚被授权

JWT不记名令牌授权不起作用asp net core web api