【问题标题】:IdentityServer4 shows 404 - Request Too LongIdentityServer4 显示 404 - 请求太长
【发布时间】:2020-08-02 13:48:53
【问题描述】:

我做错了什么,它返回了这个 404 “Bad Request-Request Too Long message”。 我的场景如下:我有一个 IdentityServer4 作为 IdentityProvider 和一个使用令牌进行访问的 WebApp。 在 IS4 中,我在 Claims 集合 (Request.User.Claims) 中为用户设置了大约 200 个角色。 当我打开另一个 WebApp 时,会弹出错误。

谁能告诉我如何正确发送 UserRoles 以在 WebApp 中使用。所以我可以使用 User.IsInRole({some-role})。 我只看到一些示例,它们发送了一些角色,但没有我需要的那么多。

当您需要有关某事的更多信息时,只需告诉我您需要知道什么来回答我的问题?

我使用以下网站设置 IS4:

https://deblokt.com/2020/01/24/04-part-1-identityserver4-asp-net-core-identity-net-core-3-1/ https://github.com/KevinDockx/SecuringAspNetCore2WithOAuth2AndOIDC 来自 PluralSite 课程。

更新:我做错了。下面的代码是我需要让 AuthControler API 授权工作,它卡在 BearerTokenHandler 中,'expires_at' 为空。所以我认为我需要以某种方式将 access_token 提供给 HttpClient 。请参阅下面的代码/设置:

IS4 Startup => ConfigurationService 结束:

    //Set named HttpClient settings for API to get roles of user
    services.AddHttpContextAccessor();
    services.AddTransient<BearerTokenHandler>();
    services.AddHttpClient("AuthClient", client =>
    {
        client.BaseAddress = new Uri("https://localhost:44318/");
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    }).AddHttpMessageHandler<BearerTokenHandler>();

CustomClaimPrincipal:

    public class CustomClaimsPrincipal : ClaimsPrincipal
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public CustomClaimsPrincipal(IHttpContextAccessor httpContextAccessor, IHttpClientFactory httpClientFactory, IPrincipal principal) : base(principal)
        {
            _httpContextAccessor = httpContextAccessor ??
               throw new ArgumentNullException(nameof(httpContextAccessor));
            _httpClientFactory = httpClientFactory ??
                 throw new ArgumentNullException(nameof(httpClientFactory));
        }

        public override bool IsInRole(string role)
        {
            var hasRole = base.IsInRole(role);

            if (!hasRole)
            {
                // get the saved identity token
                var identityToken = _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);

                var httpClient = _httpClientFactory.CreateClient("AuthClient");
                var request = new HttpRequestMessage(HttpMethod.Get, "/api/auth/isinrole");
                var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
                
                if (response.IsSuccessStatusCode)
                {
                    using (var responseStream = response.Content.ReadAsStreamAsync().Result)
                    {
                        var roles = JsonSerializer.DeserializeAsync<List<string>>(responseStream).Result;
                        hasRole = roles.Any(perm => perm == role);
                    }
                }
                else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden)
                {
                    hasRole = false;// RedirectToAction("AccessDenied", "Authorization");
                }
            }

            return hasRole;
        }
    }

IS4项目中的AuthController:

ApiController]
[Authorize]
public class AuthController : ControllerBase
{
    private readonly IdentityContext _context;

    public AuthController(IdentityContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    [HttpGet()]
    [Route("api/auth/isinrole")]
    public IActionResult IsInRole()
    {
        if (User.FindFirst("sub")?.Value != null)
        {
            var userID = Guid.Parse(User.FindFirst("sub")?.Value);
            var groupsRoles = _context.GroupRoles.Where(c => _context.UserGroups.Where(c => c.UserId == userID).Select(c => c.Group.Id).Any(d => d.Equals(c.GroupId))).Select(c => c.Role.Name);
            var userRoles = _context.UserRoles.Where(c => c.UserId == userID).Select(c => c.Role.Name);
            var roles = groupsRoles.Union(userRoles).Distinct().ToList();

            return Ok(JsonSerializer.Serialize(roles));
        }

        return Forbid();

    }
}

BearerTokenHandler:

public class BearerTokenHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IHttpClientFactory _httpClientFactory;

    public BearerTokenHandler(IHttpContextAccessor httpContextAccessor,
                IHttpClientFactory httpClientFactory)
    {
        _httpContextAccessor = httpContextAccessor ??
            throw new ArgumentNullException(nameof(httpContextAccessor));
        _httpClientFactory = httpClientFactory ??
                throw new ArgumentNullException(nameof(httpClientFactory));
    }
        

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        var accessToken = await GetAccessTokenAsync();

        if (!string.IsNullOrWhiteSpace(accessToken))
        {
            request.SetBearerToken(accessToken);
        }

        return await base.SendAsync(request, cancellationToken);
    }

    public async Task<string> GetAccessTokenAsync()
    {
        // get the expires_at value & parse it
        var expiresAt = await _httpContextAccessor.HttpContext.GetTokenAsync("expires_at");
        var expiresAtAsDateTimeOffset = DateTimeOffset.Parse(expiresAt, CultureInfo.InvariantCulture);

        if ((expiresAtAsDateTimeOffset.AddSeconds(-60)).ToUniversalTime() > DateTime.UtcNow)
            return await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // no need to refresh, return the access token

        var idpClient = _httpClientFactory.CreateClient("IDPClient");

        // get the discovery document
        var discoveryReponse = await idpClient.GetDiscoveryDocumentAsync();

        // refresh the tokens
        var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

        var refreshResponse = await idpClient.RequestRefreshTokenAsync(new RefreshTokenRequest {
                Address = discoveryReponse.TokenEndpoint,
                ClientId = "mvc",
                ClientSecret = "secret",
                RefreshToken = refreshToken
            });

        // store the tokens             
        var updatedTokens = new List<AuthenticationToken>();
        updatedTokens.Add(new AuthenticationToken {
            Name = OpenIdConnectParameterNames.IdToken,
            Value = refreshResponse.IdentityToken
        });
        updatedTokens.Add(new AuthenticationToken {
            Name = OpenIdConnectParameterNames.AccessToken,
            Value = refreshResponse.AccessToken
        });
        updatedTokens.Add(new AuthenticationToken {
            Name = OpenIdConnectParameterNames.RefreshToken,
            Value = refreshResponse.RefreshToken
        });
        updatedTokens.Add(new AuthenticationToken {
            Name = "expires_at",
            Value = (DateTime.UtcNow + TimeSpan.FromSeconds(refreshResponse.ExpiresIn)).
                    ToString("o", CultureInfo.InvariantCulture)
        });

        // get authenticate result, containing the current principal & properties
        var currentAuthenticateResult = await _httpContextAccessor.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

        // store the updated tokens
        currentAuthenticateResult.Properties.StoreTokens(updatedTokens);

        // sign in
        await _httpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);

        return refreshResponse.AccessToken;
    }

【问题讨论】:

    标签: identityserver4 core identity user-roles


    【解决方案1】:

    如果您需要为给定用户添加 200 个角色,那么您肯定做错了。角色列表不应该包含在令牌中,我猜一个用户永远不会成为 200 个角色的一部分?只有一点?您应该只在令牌中包含实际用于给定用户的角色。

    这个视频Implementing authorization in web applications and APIs我是一个很好的起点

    例如,在您的客户端应用程序中,如果您需要在登录过程中查找其他信息,则可以使用以下声明转换:

    public class BonusLevelClaimTransformation : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            if (!principal.HasClaim(c => c.Type == "bonuslevel"))
            {
                //Lookup bonus level.....
                principal.Identities.First().AddClaim(new Claim("bonuslevel", "12345"));
            }
            return Task.FromResult(principal);
        }
    }
    

    或者,您可以在客户端中使用策略(不是基于角色)授权概念,并创建一个处理程序来为您查找其他权限,例如:

     options.AddPolicy("ViewReports", policy =>
       policy.Requirements.Add(new CanViewReportsRequirement(startHour: 9,
                                                             endHour: 18)));
    
    
    public class CanViewReportsRequirement : IAuthorizationRequirement
    {
        public int StartHour { get; }
        public int EndHour { get; }
    
        public CanViewReportsRequirement(int startHour, int endHour)
        {
            StartHour = startHour;
            EndHour = endHour;
        }
    }
    
    //You can have one more handler connected to the requirement above
    public class CheckIfAccountantHandler : AuthorizationHandler<CanViewReportsRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       CanViewReportsRequirement requirement)
        {
            
          bool result = CallCheckIfAccountantService();
    
          if(result)
                    context.Succeed(requirement);
    
            return Task.CompletedTask;
        }
    }
    

    我认为上面显示的政策就是你所追求的。

    【讨论】:

    • 好吧,也许我对角色有另一种定义。我将其视为一种权限列表,而不是“用户组”。我看了视频。但是我想使用现有的 User.IsInRole 方法来授予用户执行某些操作的权限以及使用该 Authorize 属性。所以我创建了一个 ApiController 来从数据库获取这个权限。在覆盖 CustomClaimsPrincipal.IsInRole 中,我添加了 httpclient 以调用此 ApiController 以获取角色/权限。但是我怎样才能在 ApiController 中获得“子”?请参阅我在问题中编写的设置/代码。
    • 我有点困惑为什么你需要继承 ClaimsPrincipal 并创建自己的处理程序。也对你的问题到底是什么感到困惑。如果您有 200 个角色,也许您应该尝试查看声明和授权策略。通常你不需要手动解析令牌,你应该让 AddJwtBearer() 处理程序为你做这件事。
    • 我想要完成的是,我的 IS4 为所有注册的 ClientApplications/API 执行所有身份验证和授权处理。为什么我有这么多角色是因为我有每个简单的控制器 4 个角色:列表、添加、编辑、删除。我的想法如下,当我登录 IS4 时,它会显示所有注册给用户的外部 Web 应用程序,当我打开其中一个时,这个外部应用程序应该要求 IS4 使用 Authorize-attr 检查每个控制器的授权。所以我的想法是使用 IsInRole 函数来做到这一点。现在我想做什么是不是更清楚了?
    • 通过令牌认证/授权,客户端不需要为每个控制器询问 IdentityServer。该信息应该通过令牌+可选的方式传递给客户端,询问用户信息端点的详细信息。这就是 openid-connect “离线”工作的重点
    • IdentityServer 应该包含您想要支持的角色列表,然后当用户进行身份验证时,您在用户数据库中查找他属于哪些角色,并且只有他所属的那些角色才会发送到客户端(如 CanPrint、CanMakeMayments、Office、Economy、IsBoss),然后应用程序检查 if.UserInRole("IsBoss") ,应用程序根本不需要完整列表。
    猜你喜欢
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    • 1970-01-01
    • 2018-08-09
    • 1970-01-01
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    相关资源
    最近更新 更多