【问题标题】:How can I map 'scope' values to Identity Claims?如何将“范围”值映射到身份声明?
【发布时间】:2021-12-07 18:54:10
【问题描述】:

我已经指定了一个需要my_custom_value 范围的授权策略,例如

services.AddAuthorization(AuthConfig.GetAuthorizationOptions);

// ...

public static void GetAuthorizationOptions(AuthorizationOptions options)
{
    options.AddPolicy("MyPolicy", policy =>
    {
        policy.RequireScope("my_custom_value");
    });

对受 MyPolicy 保护的端点的请求失败,因为 Principal 不包含任何范围

我可以看到我的身份验证令牌具有以下范围:

"scope": [
    "openid",
    "profile",
    "my_custom_value",
    "offline_access"
],

这些似乎没有被映射到委托人的声明中。当我稍后在用户尝试访问受保护的端点时检查声明时,没有范围。

policy.RequireAssertion(context =>
{
    if (context.User.HasClaim(c => c.Type == "scope")) // <-- always false
    {
        if (context.User.HasClaim(c => c.Value == "my_custom_value"))
        {
            return true;
        }
    }

为什么没有映射范围?我需要做什么来映射它们?

供参考,我试过了

options.ClaimActions.MapUniqueJsonKey(JwtClaimTypes.Scope, "scope");
options.Scope.Add("my_custom_value");

我是否应该实现自定义 IProfileService 以在 OnUserInformationReceived 事件中包含范围?

【问题讨论】:

    标签: asp.net-mvc authorization asp.net-identity identity claims-based-identity


    【解决方案1】:

    当使用 MVC 进行 oidc 身份验证时,只有 IdentityToken 声明被映射到 ClaimsPrincipal。我想不出将访问令牌声明映射或包含到 ClaimsPrincipal 的方法。

    我最终编写了一个验证访问令牌并执行所需声明检查的授权处理程序。我假设您阅读了 asp.net 5.0 中的授权策略。

    public class AccessTokenAuthorizationHandler : AuthorizationHandler<AccessTokenRequirement> {
    
    readonly IOptionsMonitor<OpenIdConnectOptions> _openIdConnectOptions;
    readonly ILogger<AccessTokenAuthorizationHandler> _logger;
    readonly IOptions<OpenIdOptions> _openIdOptions;
    
    public AccessTokenAuthorizationHandler(
      ILogger<AccessTokenAuthorizationHandler> logger, 
      IOptionsMonitor<OpenIdConnectOptions> openIdConnectOptions, 
      IOptions<OpenIdOptions> openIdOptions) {
      _logger = logger;
      _openIdConnectOptions = openIdConnectOptions;
      _openIdOptions = openIdOptions;
    }
    
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AccessTokenRequirement requirement) {
      if (context == null) {
        throw new ArgumentNullException(nameof(context));
      }
      if (requirement == null) {
        throw new ArgumentNullException(nameof(requirement));
      }
      if (context.Resource is Microsoft.AspNetCore.Mvc.ActionContext actionContext) {
        ClaimsPrincipal principal = await GetAccessTokenPrincipal(actionContext.HttpContext).ConfigureAwait(false);
       
        // verify your requirement
        if (condition met) {
          context.Succeed(requirement);
        }
      }
    }
    
    private async Task<ClaimsPrincipal> GetAccessTokenPrincipal(HttpContext httpContext) {
      if (httpContext == null) {
        return null;
      }
      String accessToken = await httpContext.GetUserAccessTokenAsync().ConfigureAwait(false);
      if (!String.IsNullOrWhiteSpace(accessToken)) {
        try {
          TokenValidationParameters validationParameters = await BuildValidationParameters();
          return new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var rawValidatedToken);
          
        }
        catch (SecurityTokenValidationException validationException) {
          _logger.LogWarning(validationException, "Access token not valid.");
        }
        catch (Exception ex) {
          _logger.LogError(ex, "Access token could not be validated.");
        }
      }
      return null;
    }
    
    private async Task<TokenValidationParameters> BuildValidationParameters() {
      var options = _openIdConnectOptions.Get(OpenIdConnectDefaults.AuthenticationScheme);
      var discoveryDocument = await options.ConfigurationManager.GetConfigurationAsync(CancellationToken.None);
      var signingKeys = discoveryDocument.SigningKeys;
      var validationParameters = new TokenValidationParameters {
        RequireExpirationTime = true,
        RequireSignedTokens = true,
        ValidateIssuer = true,
        ValidIssuer = options.Authority,
        ValidateIssuerSigningKey = true,
        IssuerSigningKeys = signingKeys,
        ValidateLifetime = true,
        ValidateAudience = true,
        ValidAudience = "your audience",
        ValidateActor = false,
        ValidTypes = new String[] { "at+jwt" },
        ClockSkew = TimeSpan.FromMinutes(2),
      };
      return validationParameters;
    }
    

    }

    我不高兴我不得不这样做,尽管我认为这样做是正确的。要检索我使用的访问令牌nuget package IdentityModel.AspNetCore, Version=3.0.0.0

    我不明白为什么没有更多的人有这个问题。当然,如果您的应用程序使用来自 api 的数据,您将传递访问令牌,并且访问令牌将成为声明主体。但是,如果您的 mvc 应用程序执行直接数据库访问(并且可能稍后被提取到 api),您需要能够以某种方式检查访问令牌的声明。也许我们在概念上有一些误解......

    关于配置文件服务。我认为尝试将访问令牌声明包含到身份令牌中不是正确的方法。我认为这甚至是不可能的,因为当为身份令牌调用服务时,您没有关于请求范围的信息。

    【讨论】:

    • 这是一个可行的解决方案,所以我会接受正确的答案。感谢您抽出宝贵的时间。不幸的是,它没有回答为什么没有将范围添加到声明中,而且我在任何地方都找不到答案,只能尝试这个和那个,但没有任何效果。更奇怪的是,当请求转发到后端时,api 确实 将范围添加到声明中。两者都是 .Net Core 3.1,所以我认为这里的默认行为会相似。即使调试两者我也看不出有什么区别。看来下一步是进入 .Net Core 代码并查看。
    猜你喜欢
    • 1970-01-01
    • 2020-09-12
    • 2011-04-03
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    相关资源
    最近更新 更多