Abp授权失败后,如何设置返回401错误码而非重定向至登录页?

2026-05-17 00:521阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1384个文字,预计阅读时间需要6分钟。

Abp授权失败后,如何设置返回401错误码而非重定向至登录页?

问题描述:Abp 5.X 版本中,未经认证直接访问API时重定向至登录页。异常日志:[01:02:56 INF] Authorization failed. These requirements were not met: PermissionRequirement: AbpIdentity.Users [01:02:56 WRN] ---------- RemoteService

问题描述

Abp 5.X版本,未认证直接访问API重定向至登录页。

异常日志

[01:02:56 INF] Authorization failed. These requirements were not met: PermissionRequirement: AbpIdentity.Users [01:02:56 WRN] ---------- RemoteServiceErrorInfo ---------- { "code": "Volo.Authorization:010001", "message": "授权失败! 提供的策略尚未授予.", "details": null, "data": {}, "validationErrors": null } [01:02:56 WRN] Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown. Volo.Abp.Authorization.AbpAuthorizationException: Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown. ... at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) [01:02:56 WRN] Code:Volo.Authorization:010001 [01:02:56 INF] AuthenticationScheme: Identity.Application was challenged. [01:02:56 INF] Executed action Volo.Abp.Identity.IdentityUserController.GetListAsync (Volo.Abp.Identity.HttpApi) in 167.7765ms [01:02:56 INF] Executed endpoint 'Volo.Abp.Identity.IdentityUserController.GetListAsync (Volo.Abp.Identity.HttpApi)' [01:02:56 INF] Request finished HTTP/2 GET localhost:44324/api/identity/users - - - 302 0 - 268.2960ms [01:02:56 INF] Request starting HTTP/2 GET localhost:44324/Account/Login?ReturnUrl=%2Fapi%2Fidentity%2Fusers - - [01:02:56 INF] Executing endpoint '/Account/Login'

期望目标

访问API时,与Abp4.X行为一致。返回如下

{ "error": { "code": "Volo.Authorization:010001", "message": "Authorization failed! Given policy has not granted.", "details": null, "data": {}, "validationErrors": null } }

如何解决

该问题在**Abp5.X**版本之前就存在(如果Abp5.X生成模板的时候,选择分离IdentityServer则只会返回401且无返回值。不分离的话会走IdentityServerCookie认证,就会导致重定向至登录页,比如在Abp4.4.4新建一个Controller,并添加[Authorize]

[Route("api/test")] [Authorize] public class TestController : Test4Controller { [HttpGet] public Task TestAsync() { return Task.CompletedTask; } }

直接访问就会重定向至登录页。
但是,如果你是按照标准写法,通过Application.Contracts层创建接口,然后Controller层调用。

[Authorize] public class NewTestAppService : Test4AppService, INewTestAppService { public Task GetTestAsync() { return Task.CompletedTask; } }

[Route("api/new-test")] public class NewTestController : Test4Controller, INewTestAppService { private readonly INewTestAppService _newTestAppService; public NewTestController(INewTestAppService newTestAppService) { _newTestAppService = newTestAppService; } [HttpGet] public Task GetTestAsync() { return _newTestAppService.GetTestAsync(); } }

则会返回标准异常Json。

根据Issues/2643所说:当您调用需要身份验证的控制器时,身份验证中间件会发现当前用户未通过身份验证,并调用 ChallengeAsync(DefaultChallengeScheme 是标识 Cookie)。此时,请求已被短路。
如果匿名控制器调用应用程序服务方法,它将执行 ABP 筛选器和侦听器。框架抛出 AbpAuthorizationException,过滤器将异常包装到 401 中,依此类推。

代码上的原因是:通过abp new AbpDemo -u none创建的项目,会将Identity Server相关模块和接口项目集成在一起, Volo.Abp.Account.Web.IdentityServer模块配置了Identity Cookies认证方案,启动项里面也配置了JWT认证方案。其中AbpAccountWebIdentityServerModule配置IdentityCookies认证方案

// TODO: Try to reuse from AbpIdentityAspNetCoreModule context.Services .AddAuthentication(o => { // IdentityConstants.ApplicationScheme 为 Identity.Application o.DefaultScheme = IdentityConstants.ApplicationScheme; o.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies();

在Abp5.X版本中,Abp官方将认证行为保持一致(Issues/9926),从而导致了之后版本,匿名访问需认证API将会重定向至登录页。这是一次非常好的改动。

Abp授权失败后,如何设置返回401错误码而非重定向至登录页?

第一种.Net Core传统解决方案。

private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.ConfigureApplicationCookie(options => { options.ForwardDefaultSelector = ctx => { return ctx.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null; }; }); context.Services.AddAuthentication().AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = "Test"; options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; options.Events = new JwtBearerEvents { OnChallenge = async context => { context.HandleResponse(); context.Response.ContentType = "application/json;charset=utf-8"; context.Response.StatusCode = StatusCodes.Status401Unauthorized; var response = new RemoteServiceErrorResponse(new RemoteServiceErrorInfo("未认证")); await context.Response.WriteAsJsonAsync(response); }, OnForbidden = async context => { context.Response.ContentType = "application/json;charset=utf-8"; context.Response.StatusCode = StatusCodes.Status403Forbidden; var response = new RemoteServiceErrorResponse(new RemoteServiceErrorInfo("未授权")); await context.Response.WriteAsJsonAsync(response); } }; }); }

其中返回信息根据实际情况填写。

第二种方法是将行为尽量和Abp趋于一致。仅限**.NET 5.0/6.0**.
新建AuthorizationExceptionHandler类,继承IAbpAuthorizationExceptionHandler接口。

public class AuthorizationExceptionHandler : IAbpAuthorizationExceptionHandler { private readonly Func<object, Task> _clearCacheHeadersDelegate; public AuthorizationExceptionHandler() { _clearCacheHeadersDelegate = ClearCacheHeaders; } public Task HandleAsync(AbpAuthorizationException exception, HttpContext httpContext) { return HandleAndWrapExceptionAsync(exception, httpContext); } protected virtual async Task HandleAndWrapExceptionAsync(AbpAuthorizationException exception, HttpContext httpContext) { var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>(); var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>(); httpContext.Response.Clear(); httpContext.Response.StatusCode = (int)statusCodeFinder.GetStatusCode(httpContext, exception); httpContext.Response.OnStarting(_clearCacheHeadersDelegate, httpContext.Response); httpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); await httpContext.Response.WriteAsJsonAsync( new RemoteServiceErrorResponse( errorInfoConverter.Convert(exception) ) ); } private Task ClearCacheHeaders(object state) { var response = (HttpResponse)state; response.Headers[HeaderNames.CacheControl] = "no-cache"; response.Headers[HeaderNames.Pragma] = "no-cache"; response.Headers[HeaderNames.Expires] = "-1"; response.Headers.Remove(HeaderNames.ETag); return Task.CompletedTask; } }

新建AuthorizationMiddlewareResultHandler类,继承IAuthorizationMiddlewareResultHandler接口。

public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { private readonly IAbpAuthorizationExceptionHandler _authorizationExceptionHandler; public AuthorizationMiddlewareResultHandler(IAbpAuthorizationExceptionHandler authorizationExceptionHandler) { _authorizationExceptionHandler = authorizationExceptionHandler; } public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { if (authorizeResult.Challenged) { await context.ChallengeAsync(); await _authorizationExceptionHandler.HandleAsync( new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGranted), context); return; } if (authorizeResult.Forbidden) { await context.ForbidAsync(); await _authorizationExceptionHandler.HandleAsync( new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGranted), context); return; } await next(context); } }

将其注入到容器内。

public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>(); context.Services.Replace(ServiceDescriptor.Singleton<IAbpAuthorizationExceptionHandler, AuthorizationExceptionHandler>()); }

别忘记将任何以**/api**开头的请求转发到 JWT 方案。

private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.ConfigureApplicationCookie(options => { options.ForwardDefaultSelector = ctx => { return ctx.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null; }; }); ... }

本文共计1384个文字,预计阅读时间需要6分钟。

Abp授权失败后,如何设置返回401错误码而非重定向至登录页?

问题描述:Abp 5.X 版本中,未经认证直接访问API时重定向至登录页。异常日志:[01:02:56 INF] Authorization failed. These requirements were not met: PermissionRequirement: AbpIdentity.Users [01:02:56 WRN] ---------- RemoteService

问题描述

Abp 5.X版本,未认证直接访问API重定向至登录页。

异常日志

[01:02:56 INF] Authorization failed. These requirements were not met: PermissionRequirement: AbpIdentity.Users [01:02:56 WRN] ---------- RemoteServiceErrorInfo ---------- { "code": "Volo.Authorization:010001", "message": "授权失败! 提供的策略尚未授予.", "details": null, "data": {}, "validationErrors": null } [01:02:56 WRN] Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown. Volo.Abp.Authorization.AbpAuthorizationException: Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown. ... at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) [01:02:56 WRN] Code:Volo.Authorization:010001 [01:02:56 INF] AuthenticationScheme: Identity.Application was challenged. [01:02:56 INF] Executed action Volo.Abp.Identity.IdentityUserController.GetListAsync (Volo.Abp.Identity.HttpApi) in 167.7765ms [01:02:56 INF] Executed endpoint 'Volo.Abp.Identity.IdentityUserController.GetListAsync (Volo.Abp.Identity.HttpApi)' [01:02:56 INF] Request finished HTTP/2 GET localhost:44324/api/identity/users - - - 302 0 - 268.2960ms [01:02:56 INF] Request starting HTTP/2 GET localhost:44324/Account/Login?ReturnUrl=%2Fapi%2Fidentity%2Fusers - - [01:02:56 INF] Executing endpoint '/Account/Login'

期望目标

访问API时,与Abp4.X行为一致。返回如下

{ "error": { "code": "Volo.Authorization:010001", "message": "Authorization failed! Given policy has not granted.", "details": null, "data": {}, "validationErrors": null } }

如何解决

该问题在**Abp5.X**版本之前就存在(如果Abp5.X生成模板的时候,选择分离IdentityServer则只会返回401且无返回值。不分离的话会走IdentityServerCookie认证,就会导致重定向至登录页,比如在Abp4.4.4新建一个Controller,并添加[Authorize]

[Route("api/test")] [Authorize] public class TestController : Test4Controller { [HttpGet] public Task TestAsync() { return Task.CompletedTask; } }

直接访问就会重定向至登录页。
但是,如果你是按照标准写法,通过Application.Contracts层创建接口,然后Controller层调用。

[Authorize] public class NewTestAppService : Test4AppService, INewTestAppService { public Task GetTestAsync() { return Task.CompletedTask; } }

[Route("api/new-test")] public class NewTestController : Test4Controller, INewTestAppService { private readonly INewTestAppService _newTestAppService; public NewTestController(INewTestAppService newTestAppService) { _newTestAppService = newTestAppService; } [HttpGet] public Task GetTestAsync() { return _newTestAppService.GetTestAsync(); } }

则会返回标准异常Json。

根据Issues/2643所说:当您调用需要身份验证的控制器时,身份验证中间件会发现当前用户未通过身份验证,并调用 ChallengeAsync(DefaultChallengeScheme 是标识 Cookie)。此时,请求已被短路。
如果匿名控制器调用应用程序服务方法,它将执行 ABP 筛选器和侦听器。框架抛出 AbpAuthorizationException,过滤器将异常包装到 401 中,依此类推。

代码上的原因是:通过abp new AbpDemo -u none创建的项目,会将Identity Server相关模块和接口项目集成在一起, Volo.Abp.Account.Web.IdentityServer模块配置了Identity Cookies认证方案,启动项里面也配置了JWT认证方案。其中AbpAccountWebIdentityServerModule配置IdentityCookies认证方案

// TODO: Try to reuse from AbpIdentityAspNetCoreModule context.Services .AddAuthentication(o => { // IdentityConstants.ApplicationScheme 为 Identity.Application o.DefaultScheme = IdentityConstants.ApplicationScheme; o.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies();

在Abp5.X版本中,Abp官方将认证行为保持一致(Issues/9926),从而导致了之后版本,匿名访问需认证API将会重定向至登录页。这是一次非常好的改动。

Abp授权失败后,如何设置返回401错误码而非重定向至登录页?

第一种.Net Core传统解决方案。

private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.ConfigureApplicationCookie(options => { options.ForwardDefaultSelector = ctx => { return ctx.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null; }; }); context.Services.AddAuthentication().AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = "Test"; options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; options.Events = new JwtBearerEvents { OnChallenge = async context => { context.HandleResponse(); context.Response.ContentType = "application/json;charset=utf-8"; context.Response.StatusCode = StatusCodes.Status401Unauthorized; var response = new RemoteServiceErrorResponse(new RemoteServiceErrorInfo("未认证")); await context.Response.WriteAsJsonAsync(response); }, OnForbidden = async context => { context.Response.ContentType = "application/json;charset=utf-8"; context.Response.StatusCode = StatusCodes.Status403Forbidden; var response = new RemoteServiceErrorResponse(new RemoteServiceErrorInfo("未授权")); await context.Response.WriteAsJsonAsync(response); } }; }); }

其中返回信息根据实际情况填写。

第二种方法是将行为尽量和Abp趋于一致。仅限**.NET 5.0/6.0**.
新建AuthorizationExceptionHandler类,继承IAbpAuthorizationExceptionHandler接口。

public class AuthorizationExceptionHandler : IAbpAuthorizationExceptionHandler { private readonly Func<object, Task> _clearCacheHeadersDelegate; public AuthorizationExceptionHandler() { _clearCacheHeadersDelegate = ClearCacheHeaders; } public Task HandleAsync(AbpAuthorizationException exception, HttpContext httpContext) { return HandleAndWrapExceptionAsync(exception, httpContext); } protected virtual async Task HandleAndWrapExceptionAsync(AbpAuthorizationException exception, HttpContext httpContext) { var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>(); var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>(); httpContext.Response.Clear(); httpContext.Response.StatusCode = (int)statusCodeFinder.GetStatusCode(httpContext, exception); httpContext.Response.OnStarting(_clearCacheHeadersDelegate, httpContext.Response); httpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true"); await httpContext.Response.WriteAsJsonAsync( new RemoteServiceErrorResponse( errorInfoConverter.Convert(exception) ) ); } private Task ClearCacheHeaders(object state) { var response = (HttpResponse)state; response.Headers[HeaderNames.CacheControl] = "no-cache"; response.Headers[HeaderNames.Pragma] = "no-cache"; response.Headers[HeaderNames.Expires] = "-1"; response.Headers.Remove(HeaderNames.ETag); return Task.CompletedTask; } }

新建AuthorizationMiddlewareResultHandler类,继承IAuthorizationMiddlewareResultHandler接口。

public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { private readonly IAbpAuthorizationExceptionHandler _authorizationExceptionHandler; public AuthorizationMiddlewareResultHandler(IAbpAuthorizationExceptionHandler authorizationExceptionHandler) { _authorizationExceptionHandler = authorizationExceptionHandler; } public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { if (authorizeResult.Challenged) { await context.ChallengeAsync(); await _authorizationExceptionHandler.HandleAsync( new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGranted), context); return; } if (authorizeResult.Forbidden) { await context.ForbidAsync(); await _authorizationExceptionHandler.HandleAsync( new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGranted), context); return; } await next(context); } }

将其注入到容器内。

public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>(); context.Services.Replace(ServiceDescriptor.Singleton<IAbpAuthorizationExceptionHandler, AuthorizationExceptionHandler>()); }

别忘记将任何以**/api**开头的请求转发到 JWT 方案。

private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { context.Services.ConfigureApplicationCookie(options => { options.ForwardDefaultSelector = ctx => { return ctx.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null; }; }); ... }