asp.net core3.1策略授权问题

问题一:

core2.2升级到3.1之后 2.2中策略授权使用context.Resource 在3.1版本中不再是AuthorizationFilterContext类型,而是Endpoint类型。不能再通过context.Resource来获取http请求头相关的数据。下边的代码在core3.1中已经无效

AuthorizationFilterContext filterContext = context.Resource as AuthorizationFilterContext;
var httpcontent = filterContext.HttpContext;

新的方式应该是通过IHttpContextAccessor来获取http请求相关的数据。 首先在Startup.cs类中注入依赖:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

然后在自定义授权策略中使用

var httpContext = _httpContextAccessor.HttpContext;

问题二:

core3.1中自定义授权失败后的返回内容引发组件内部异常问题。 在自定义授权策略中获取到httpContext之后很容易想到在授权失败后直接通过httpContext写入返回值,然后返回。代码类似如下:

await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { code=0,msg="授权失败"}));
context.Fail();

这么做确实可以在接口中获取到想要的返回值。但是,这么做会触发一个.net core组件内部的异常:

An unhandled exception was thrown by the application.|Microsoft.AspNetCore.Server.Kestrel System.InvalidOperationException: Headers are read-only, response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
   at Microsoft.AspNetCore.Mvc.Formatters.OutputFormatter.WriteResponseHeaders(OutputFormatterWriteContext context)
   at Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter.WriteAsync(OutputFormatterWriteContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)|url: 

这是因为在自定义授权处理中向http请求的Response写入数据导致的,在授权处理之后.net core组件会默认的再次向Response中写入数据,但是组件写入的时候Response中已经开始返回内容了,所以组件报了异常。 有时候在自定义授权中不是默认返回值,而是授权失败后直接重定向到新的接口中,如下

httpContext.Response.Redirect("/user/login");

在自定义授权中直接这么操作的话是无效的!

正确的方式应该是在Startup.cs中配置自定义策略授权的时候统一处理授权失败的返回内容。如下:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddCookie(JwtBearerDefaults.AuthenticationScheme, b =>
            {
                b.LoginPath = "/user/login";
                b.Cookie.Name = "SessionId";
                b.Cookie.Domain = ".liemei.net";
                b.Cookie.Path = "/";
                b.Cookie.HttpOnly = true;
                //b.Cookie.Expiration = TimeSpan.FromSeconds(elvaSettings.SessionTimeOut);
                //b.Cookie.Expiration = new TimeSpan(0, 0, elvaSettings.SessionTimeOut);
                b.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
                {
                    OnRedirectToLogin= content => 
                    {
                        content.Response.WriteAsync(JsonConvert.SerializeObject(new { code = 0, msg = "授权失败" }));
                        return Task.CompletedTask;
                    }
                };
            }); 

如果在授权失败后需要重定向到一个新的接口中,那么就在b.LoginPath 后边设置要重定向的路由。 如果在重定向之后要返回统一的json内容,那就定义跳转登录的事件,在事件中将要返回的内容写入Response.

完整的代码如下:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddAuthorization(option=> {
        option.AddPolicy("auth1",policy=> {
            policy.Requirements.Add(new AdultPolicyRequirement(12));
        });
    });
    services.AddSingleton<IAuthorizationHandler, AdultAuthorizationHandler>();

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddCookie(JwtBearerDefaults.AuthenticationScheme, b =>
    {
        b.LoginPath = "/user/login";
        b.Cookie.Name = "SessionId";
        b.Cookie.Domain = ".liemei.net";
        b.Cookie.Path = "/";
        b.Cookie.HttpOnly = true;
        //b.Cookie.Expiration = TimeSpan.FromSeconds(elvaSettings.SessionTimeOut);
        //b.Cookie.Expiration = new TimeSpan(0, 0, elvaSettings.SessionTimeOut);
        b.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
        {
            OnRedirectToLogin= content => 
            {
                content.Response.WriteAsync(JsonConvert.SerializeObject(new { code = 0, msg = "授权失败" }));
                return Task.CompletedTask;
            }
        };
    }); 
}
        
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseAuthentication();
    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

自定义策略授权:

 public class AdultPolicyRequirement: IAuthorizationRequirement
    {
        public int Age { get; set; }
        public AdultPolicyRequirement(int age) 
        {
            this.Age = age;
        }
    }
    public class AdultAuthorizationHandler : AuthorizationHandler<AdultPolicyRequirement>
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public AdultAuthorizationHandler(IHttpContextAccessor httpContextAccessor) 
        {
            _httpContextAccessor = httpContextAccessor;
        }
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AdultPolicyRequirement requirement)
        {
            var httpContext = _httpContextAccessor.HttpContext;
            var request = httpContext.Request;
            int age = 0;
            if (request.Query.Keys.Contains("age")) 
            {
                age = Convert.ToInt32(request.Query["age"]);
            }
            if (age > requirement.Age)
            {
                //通过验证,这句代码必须要有
                context.Succeed(requirement);
            }
            else 
            {
                if (context.Resource is Endpoint endpoint)
                {
                    
                }
                httpContext.Response.Redirect("/user/login");
                //await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { code=1,msg="ok"}));
                //await httpContext.Response.Body.FlushAsync();
                context.Fail();
            }
            
        }
    }

控制器中:

[ApiController]
[Route("[controller]")]
[Authorize("auth1")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}
原文地址:https://www.cnblogs.com/liemei/p/13275982.html