Abp授权失败重定向至登录页,修改为返回401

Posted hiwwwk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Abp授权失败重定向至登录页,修改为返回401相关的知识,希望对你有一定的参考价值。

问题描述

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 https://localhost:44324/api/identity/users - - - 302 0 - 268.2960ms
[01:02:56 INF] Request starting HTTP/2 GET https://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 中,依此类推。

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

第一种.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;
        ;
    );
    
    ...

以上是关于Abp授权失败重定向至登录页,修改为返回401的主要内容,如果未能解决你的问题,请参考以下文章

使用 WebClient 访问时,ASP.NET MVC 应用程序返回未授权 (401) 进行重定向

如果刷新(JWT)令牌未经授权(401响应),AngularJS重定向到登录

使 Web API 身份验证返回 401 而不是重定向到登录页面

Grails Spring Security登录失败未重定向到登录视图

Spring Security:在 401 的情况下重定向到登录页面

使用授权标头重定向到操作