当前位置: 首页 > 科技观察

浅谈.Net5.0中自定义授权响应

时间:2023-03-18 21:26:18 科技观察

本文转载自微信公众号《DotNET技术圈》,作者BenFoster。转载本文请联系DotNET技术圈公众号。在.NET5.0中自定义授权响应ASP.NETCore授权框架中一个经常请求的[1]功能是能够在授权失败时自定义HTTP响应。以前,唯一的方法是直接在您的控制器中(或通过过滤器)调用IAuthorizationService到授权服务,类似于基于资源的授权方法[2]或实现您自己的授权过滤器[3]。从.NET5.0开始,您现在可以通过实现IAuthorizationMiddlewareResultHandler接口来自定义HTTP响应;中间件在授权失败时由授权框架自动调用。Microsoft文档站点上对此进行了记录[4],但我花了一段时间才找到适合我的特定用例的内容。问题我一直在采取措施将旧的ASP.NETWebAPI应用程序移植到.NETCore5.0。此API具有分层URI结构,因此大多数端点将位于“站点”资源下,例如:/sites/sites/{siteId}/sites/{siteId}/blog验证用户是否有权访问给定的站点,应用程序该程序以前使用自定义操作过滤器来提取siteId路由参数并根据用户的声明对其进行验证。迁移到.NET5.0我想利用这种基于资源的授权的授权框架,但我不想在每个控制器中重复这种逻辑。我的解决方案是实现一个执行类似操作的授权处理程序,获取siteId参数并验证用户的访问权限:_logger=logger.NotNull(nameof(logger));}protectedoverrideTaskHandleRequirementAsync(AuthorizationHandlerContextcontext,SiteAccessRequirementrequirement){context.NotNull(nameof(context));requirement.NotNull(nameof(requirement));if(context.ResourceisHttpContext.httpContexthttpContext&Data&Route&Data()。Values.TryGetValue(SiteIdRouteParameter,outobject?routeValue)&&routeValueisstringsiteId){stringqualifiedId=$"sites/{siteId}";AccountPrincipalaccount=context.User.ToAccount();_logger.LogDebug("ValidatingaccesstoSite{SiteId}fromUser{UserId}.",qualifiedId,account.GetAuthIdentifier());if(account.CanAccessSite(qualifiedId)){context.Succeed(requirement);}else{_logger.LogWarning("Sitevalidationfailed.User{UserId}isnotpermittedtoaccess{SiteId}.",account.GetAuthIdentifier(),qualifiedId);}}returnTask.CompletedTask;}}然后将其注册为授权策略的一部分:services.AddAuthorization(options=>{options.FallbackPolicy=Policies.FallbackPolicy;options.AddPolicy("SiteAccess",Policies.SiteAccessPolicy);})publicstaticAuthorizationPolicySiteAccessPolicy=>ConfigureDefaults(newAuthorizationPolicyBuilder()).AddRequirements(newSiteAccessRequirement()).Build();privatestaticAuthorizationPolicyBuilderConfigureDefaults(AuthorizationPolicyBuilderbuilder)=>builder.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().RequiretClaim(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().RequiretClaim(JwtClaim)对于控制器和/或操作:[Authorize(Policy="SiteAccess")][HttpGet("{siteId}",Name=RouteNames.SiteRoute)]publicasyncTaskGetSiteAsync(stringsiteId,CancellationTokencancellationToken){varsite=await_session.LoadAsync($"sites/{siteId}",cancellationToken);returnssiteisnull?NotFound():Ok(Enrich(_mapper.Map(site),true));}当我尝试访问未映射到当前用户的站点时,我将收到HTTP403-禁止响应.这样虽然达到了保护站点资源的目的,但同时也泄露了用户没有权限访问的站点信息的弊端。所以最好返回HTTP404-NotFound响应。这在语义上也是有意义的,因为该站点不存在于用户的站点资源集合中。如果您想知道为什么我不将用户过滤器作为查询的一部分,那是因为用户/帐户与内容域是分开的,并且由于数据模型的设计以及我使用键值存储,认证访问的责任转移到应用层。解决方案为了实现上述目标,我们可以使用newIAuthorizationMiddlewareResultHandler并创建一个处理程序,在由于未满足我的站点访问要求而导致授权失败时转换HTTP响应:(RequestDelegaterequestDelegate,HttpContexthttpContext,AuthorizationPolicyauthorizationPolicy,PolicyAuthorizationResultpolicyAuthorizationResult){if(policyAuthorizationResult.Forbidden&&policyAuthorizationResult.AuthorizationFailure!=null){if(policyAuthorizationResult.AuthorizationFailure.FailedRequirements.Any(requirement=>requirementisSite){AccessConRequirement)。Response.StatusCode=(int)HttpStatusCode.NotFound;return;}//Othertransformationshere}await_handler.HandleAsync(requestDelegate,httpContext,authorizationPolicy,policyAuthorizationResult);}}在上面的代码中,我检查授权失败(结果被禁止)和失败的请求,相应地改变HTTP状态代码;否则我们调用内置的AuthorizationMiddlewareResultHandler。要连接自定义处理程序,它会在启动时注册:services.AddAuthorization(options=>{options.FallbackPolicy=Policies.FallbackPolicy;options.AddPolicy("SiteAccess",Policies.SiteAccessPolicy);}).AddSingleton();References[1]经常请求:https://github.com/dotnet/aspnetcore/issues/4670[2]基于资源的授权方式:https://docs.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-5.0[3]实现自己的授权过滤器:https://ignas.me/tech/custom-unauthorized-response-body/[4]记录:https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0