ASP.NET Core 同时支持多种认证方式 | Swagger 支持

前言

上次,我们实现了《ASP.NET Core 同时支持多种认证方式》:

services.AddAuthentication()
    .AddDemoAuthentication(options => { })
    .AddJwtBearer(options =>
    {
        ...
    });

我们还希望为 Swagger 也添加多种认证支持。.

原来为支持 JWT 认证,Swagger 相关配置代码是这样的:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication9", Version = "v1" });

    c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
    {
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement {
    {
        new OpenApiSecurityScheme
        {
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "bearerAuth"
        }
        },
        new string[] { }
    }
    });
}

依葫芦画瓢,添加如下代码:

c.AddSecurityDefinition("demoAuth", new OpenApiSecurityScheme()
{
    
    Type = SecuritySchemeType.ApiKey,
    In = ParameterLocation.Query,
    Name = "_key",
    Scheme = "demo",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
    new OpenApiSecurityScheme
    {
    Reference = new OpenApiReference
    {
        Type = ReferenceType.SecurityScheme,
        Id = "demoAuth"
    }
    },
    new string[] { }
}
});

Swagger 可以正常显示 Authorize 页面:

ASP.NET Core 同时支持多种认证方式 | Swagger 支持

但是,发现一个问题, 每次请求都会发送所有的认证信息,即使我们的方法只需要其中的一种:

ASP.NET Core 同时支持多种认证方式 | Swagger 支持

解决思路

这是因为,我们使用了AddSecurityRequirement增加了全局的安全要求。

应该根据方法上设置的AuthenticationSchemes添加对应的安全要求。

针对这一需求,可以使用 IOperationFilter 接口实现操作筛选器。检索 ApiDescription 以获取相关信息,例如方法级别的AuthorizeAttribute

实现

首先,创建SecuritySchemeOperationFilter继承自IOperationFilter,实现 Apply 方法:

internal class SecuritySchemeOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (context != null && operation != null)
        {
            var authenticationSchemes = context.MethodInfo
                    .GetCustomAttributes(true)
                    .OfType<AuthorizeAttribute>()
                    .SelectMany(attr => attr.AuthenticationSchemes.Split(','))
                    .Distinct();

            string id = "";

            if (authenticationSchemes.Contains("Bearer"))
            {
                id = "bearerAuth";
            }
            else if (authenticationSchemes.Contains("Demo"))
            {
                id = "demoAuth";
            }

            operation.Security = new List<OpenApiSecurityRequirement>
            {
                new OpenApiSecurityRequirement {
                {
                    new OpenApiSecurityScheme
                    {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = id
                    }
                    },
                    new string[] { }
                }
            }
            };
        }
    }
}

在上面的代码中,我们检查 API 上指定的授权过滤器,并在授权过滤器的基础上添加了适当的安全要求。

另外,修改一下 Swagger 注册代码:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc(...);

    c.AddSecurityDefinition("bearerAuth", ...);
    c.AddSecurityDefinition("demoAuth", ...);
    
    c.OperationFilter<SecuritySchemeOperationFilter>();
});

现在可以看到,对于OnlyForBearer API,只发送Bearer授权头,而不再发送Query查询字符串:

ASP.NET Core 同时支持多种认证方式 | Swagger 支持

结论

今天,我们通过 IOperationFilter 实现了 Swagger 同时支持多种认证方式。