IAuthorizationFilter и IOperationFilter с чванством - PullRequest
0 голосов
/ 07 августа 2020

Сводка

Я хочу иметь возможность добавить настраиваемый атрибут AuthorizeAttribute к методу, который затем распознается swagger и отображает всплывающее окно «Доступные авторизации». Проблема, с которой я столкнулся, заключается в том, чтобы IOperationFilter правильно работал с IAuthorizationFilter.

Код

startup.cs

        services.AddSwaggerGen(c =>
       {
           c.SwaggerDoc("v2", new OpenApiInfo { Title = "API", Version = "v2" });
           // Adds "(Auth)" to the summary so that you can see which endpoints have Authorization
           c.OperationFilter<AppendAuthorizeToSummaryOperationFilter<Filters.Authorization2>>();

           c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
           {
               Name = "Bearer",
               Description = "Standard Authorization header using the SessionKey scheme. Example: \"{token}\"",
               In = ParameterLocation.Header,
               Type = SecuritySchemeType.ApiKey
           });

           c.OperationFilter<Filters.AuthorizeCheckOperationFilter>();
       });

controller.cs

    [Filters.Authorization2]
    [HttpGet]
    public ApiResult<List<Request>> GetRequest(int ID)

Authorization2.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class Authorization2 : AuthorizeAttribute, IAuthorizationFilter
{
    public Authorization2()
    {

    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //Get the session from the cache
        Session sess = GetSession(context.HttpContext);
        if (sess.IsValid)
        {
            //set sess
        }
        else
        {
            //If it's not there then return with bad news
            context.Result = Unauthorized();
            context.HttpContext.Response.StatusCode = 401;
        }
    }
}

AuthorizeCheckOperationFilter.cs

internal class AuthorizeCheckOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        context.ApiDescription.TryGetMethodInfo(out var methodInfo);

        if (methodInfo == null)
            return;

        var hasAuthorizeAttribute = false;

        if (methodInfo.MemberType == MemberTypes.Method)
        {
            // NOTE: Check the controller itself has Authorize attribute
            hasAuthorizeAttribute = methodInfo.DeclaringType.GetCustomAttributes(true).OfType<Authorization2>().Any();

            // NOTE: Controller has Authorize attribute, so check the endpoint itself.
            //       Take into account the allow anonymous attribute
            if (hasAuthorizeAttribute)
                hasAuthorizeAttribute = !methodInfo.GetCustomAttributes(true).OfType<AllowAnonymousAttribute>().Any();
            else
                hasAuthorizeAttribute = methodInfo.GetCustomAttributes(true).OfType<Authorization2>().Any();
        }

        if (!hasAuthorizeAttribute)
            return;

        if (!operation.Responses.Any(r => r.Key == StatusCodes.Status401Unauthorized.ToString()))
            operation.Responses.Add(StatusCodes.Status401Unauthorized.ToString(), new OpenApiResponse { Description = "Unauthorized" });
        if (!operation.Responses.Any(r => r.Key == StatusCodes.Status403Forbidden.ToString()))
            operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse { Description = "Forbidden" });

        // NOTE: This adds the "Padlock" icon to the endpoint in swagger, 
        //       we can also pass through the names of the policies in the string[]
        //       which will indicate which permission you require.
        operation.Security = new List<OpenApiSecurityRequirement>
    {
        new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    },
                    Scheme = "oauth2",
                    Name = "Bearer",
                    In = ParameterLocation.Header
                },
                new List<string>()
            }
        }
    };
    }
}

При неизменном коде отображается замок на конечной точке и заголовок установлен, но метод выдает ошибку 500:

System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

Попытка 1

Я пытался добавить:

services.AddAuthentication(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);

, но получаю ту же ошибку. Если я попробую:

        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
                    options =>
                    {
                        options.LoginPath = new PathString("/auth/login");
                        options.AccessDeniedPath = new PathString("/auth/denied");
                    });

Мой запрос перенаправляется на loginPath, что вызывает ошибку 404.

Попытка 2

Если попробуйте другой тиктик и используйте TypeFilterAttribute как так :

изменить startup.cs

c.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            },
                            Scheme = "oauth2",
                            Name = "Bearer",
                            In = ParameterLocation.Header,

                        },
                        new List<string>()
                    }
                });
//c.OperationFilter<Filters.AuthorizeCheckOperationFilter>();

Authorization.cs

public class AuthorizationHandler : IAuthorizationFilter
{
    public AuthorizationHandler()
    {

    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //Get the session from the cache
        Session sess = GetSession(context.HttpContext);
        if (sess.IsValid)
        {
            //set sess
        }
        else
        {
            //If it's not there then return with bad news
            context.Result = Unauthorized();
            context.HttpContext.Response.StatusCode = 401;
        }
    }
}

public class AuthorizeAttribute : TypeFilterAttribute 
{
    public AuthorizeAttribute() : base(typeof(AuthorizationHandler))
    {

    }
}

и обновить метод для использования [Filters.Authorize] вызов метода работает как Ожидается, но теперь блокировку получают все методы, а не только те, которые имеют атрибут.

Вопрос

Как мне изменить свой код, чтобы заблокировать только методы с атрибутом и обработать авторизацию правильно?

...