我正在使用 Swashbuckle (5.3.2),它生成了一个很好的 API 文档。

为了澄清我的问题,我建立了一个没有实际意义的小示例项目。

API 只能与有效的 API key 一起使用。
为此,我引入了一个 ApiKeyFilter 来验证 api_key 并读出相应的角色。

ApiKeyFilter

public class ApiKeyFilter : IAuthenticationFilter
{
    private static Dictionary<string, String[]> allowedApps = new Dictionary<string, String[]>();
    private readonly string authenticationScheme = "Bearer";
    private readonly string queryStringApiKey = "api_key";

    public bool AllowMultiple
    {
        get { return false; }
    }

    public ApiKeyFilter()
    {
        if (allowedApps.Count == 0)
        {
            allowedApps.Add("PetLover_api_key", new []{"PetLover"});
            allowedApps.Add("CarOwner_api_key", new []{"CarOwner"});
            allowedApps.Add("Admin_api_key", new []{"PetLover","CarOwner"});
        }
    }

    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        var req = context.Request;
        Dictionary<string, string> queryStrings = req.GetQueryNameValuePairs().ToDictionary(x => x.Key.ToLower(), x => x.Value);
        string rawAuthzHeader = null;
        if (queryStrings.ContainsKey(queryStringApiKey))
        {
            rawAuthzHeader = queryStrings[queryStringApiKey];
        }
        else if (req.Headers.Authorization != null && authenticationScheme.Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase))
        {
            rawAuthzHeader = req.Headers.Authorization.Parameter;
        }
        if (rawAuthzHeader != null && allowedApps.ContainsKey(rawAuthzHeader))
        {
            var currentPrincipal = new GenericPrincipal(new GenericIdentity(rawAuthzHeader), allowedApps[rawAuthzHeader]);
            context.Principal = currentPrincipal;
        }
        else
        {
            context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
        }

        return Task.FromResult(0);
    }

    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        context.Result = new ResultWithChallenge(context.Result);
        return Task.FromResult(0);
    }


}

public class ResultWithChallenge : IHttpActionResult
{
    private readonly string authenticationScheme = "amx";
    private readonly IHttpActionResult next;

    public ResultWithChallenge(IHttpActionResult next)
    {
        this.next = next;
    }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = await next.ExecuteAsync(cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(authenticationScheme));
        }

        return response;
    }
}

只有当请求者具有相应的角色时才能访问 Controller /资源。

宠物 Controller
[Authorize(Roles = "PetLover")]
[RoutePrefix("api/pets")]
public class PetController : ApiController
{
    // GET api/pet
    [Route]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/pet/5
    [Route("{id:int}")]
    public string Get(int id)
    {
        return "value";
    }

    // POST api/pet
    [Route]
    public void Post([FromBody]string value)
    {
    }

    // PUT api/pet/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/pet/5
    public void Delete(int id)
    {
    }
}

汽车 Controller
[RoutePrefix("api/cars")]
public class CarController : ApiController
{
    // GET api/car
    [AllowAnonymous]
    [Route]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/car/5
    [Authorize(Roles = "CarOwner")]
    [Route("{id:int}")]
    public string Get(int id)
    {
        return "value";
    }

    // POST api/car
    [Authorize(Roles = "CarOwner")]
    [Route]
    public void Post([FromBody]string value)
    {
    }
}

WebApiConfig
public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            config.Filters.Add(new ApiKeyFilter());

            //config.MessageHandlers.Add(new CustomAuthenticationMessageHandler());
        }
    }

到现在为止还挺好。这里没问题。

问题:

现在我希望在 API 生成期间考虑“用户”角色。我只想在文档中显示用户可以使用此 api_key 使用的资源和操作。

输出应该看起来像 (/swagger/ui/index?api_key=XXX):
  • Admin_api_key:
  • 汽车
  • 获取/api/cars
  • 发布/api/cars
  • 获取/api/cars/{id}
  • 宠物
  • 获取/api/pets
  • 发布/api/pets
  • 获取/api/pets/{id}
  • CarOwner_api_key:
  • 汽车
  • 获取/api/cars
  • 发布/api/cars
  • 获取/api/cars/{id}
  • PetLover_api_key:
  • 汽车
  • 获取/api/cars
  • 宠物
  • 获取/api/pets
  • 发布/api/pets
  • 获取/api/pets/{id}
  • invalid_api_key:
  • 没有显示

  • 在 API 规范生成期间,我无法访问 HttpRequest 以读出任何查询字符串或任何 header 信息。

    我已经查看了 DelegatingHandler 但我无法在任何 Swashbuckle 过滤器(OperationFilter、DocumentFilter)中读出主体,而且我也无法读出 CustomProvider 中的主体。
    public class CustomAuthenticationMessageHandler : DelegatingHandler
        {
            private static Dictionary<string, String[]> allowedApps = new Dictionary<string, String[]>();
            private readonly string authenticationScheme = "Bearer";
            private readonly string queryStringApiKey = "api_key";
    
            public bool AllowMultiple
            {
                get { return false; }
            }
    
            public CustomAuthenticationMessageHandler()
            {
                if (allowedApps.Count == 0)
                {
                    allowedApps.Add("PetLover_api_key", new[] {"PetLover"});
                    allowedApps.Add("CarOwner_api_key", new[] {"CarOwner"});
                    allowedApps.Add("Admin_api_key", new[] {"PetLover", "CarOwner"});
                }
            }
    
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                var req = request;
                Dictionary<string, string> queryStrings = req.GetQueryNameValuePairs().ToDictionary(x => x.Key.ToLower(), x => x.Value);
                string rawAuthzHeader = null;
                if (queryStrings.ContainsKey(queryStringApiKey))
                {
                    rawAuthzHeader = queryStrings[queryStringApiKey];
                }
                else if (req.Headers.Authorization != null && authenticationScheme.Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase))
                {
                    rawAuthzHeader = req.Headers.Authorization.Parameter;
                }
                if (rawAuthzHeader != null && allowedApps.ContainsKey(rawAuthzHeader))
                {
                    var currentPrincipal = new GenericPrincipal(new GenericIdentity(rawAuthzHeader), allowedApps[rawAuthzHeader]);
                    request.GetRequestContext().Principal = currentPrincipal;
                }
                else
                {
    
                }
    
                return await base.SendAsync(request, cancellationToken);
            }
        }
    

    我发现了一些类似的问题/问题,但没有真正的答案。
    Web API Documentation using swagger
    Restrict access to certain API controllers in Swagger using Swashbuckle and ASP.NET Identity
    {hxxps://}github.com/domaindrivendev/Swashbuckle/issues/334
    {hxxps://}github.com/domaindrivendev/Swashbuckle/issues/735
    {hxxps://}github.com/domaindrivendev/Swashbuckle/issues/478

    最佳答案

    我现在找到了一个对我有用的解决方案。

    我使用 CustomAuthenticationMessageHandler (与我的问题相同)将规则放入 HttpContext。

    public class CustomAuthenticationMessageHandler : DelegatingHandler
        {
            private static Dictionary<string, String[]> allowedApps = new Dictionary<string, String[]>();
            private readonly string authenticationScheme = "Bearer";
            private readonly string queryStringApiKey = "api_key";
    
            public bool AllowMultiple
            {
                get { return false; }
            }
    
            public CustomAuthenticationMessageHandler()
            {
                if (allowedApps.Count == 0)
                {
                    allowedApps.Add("PetLover_api_key", new[] {"PetLover"});
                    allowedApps.Add("CarOwner_api_key", new[] {"CarOwner"});
                    allowedApps.Add("Admin_api_key", new[] {"PetLover", "CarOwner"});
                }
            }
    
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                var req = request;
                Dictionary<string, string> queryStrings = req.GetQueryNameValuePairs().ToDictionary(x => x.Key.ToLower(), x => x.Value);
                string rawAuthzHeader = null;
                if (queryStrings.ContainsKey(queryStringApiKey))
                {
                    rawAuthzHeader = queryStrings[queryStringApiKey];
                }
                else if (req.Headers.Authorization != null && authenticationScheme.Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase))
                {
                    rawAuthzHeader = req.Headers.Authorization.Parameter;
                }
                if (rawAuthzHeader != null && allowedApps.ContainsKey(rawAuthzHeader))
                {
                    var currentPrincipal = new GenericPrincipal(new GenericIdentity(rawAuthzHeader), allowedApps[rawAuthzHeader]);
                    request.GetRequestContext().Principal = currentPrincipal;
                }
                else
                {
    
                }
    
                return await base.SendAsync(request, cancellationToken);
            }
        }
    

    我引入了一个自定义的 Swashbuckle IDocumentFilter ,它从 HttpContext 中读取规则,并从 SwaggerDocument 中删除此规则不允许的操作和资源(基于 api_key)。
    public class AuthorizeRoleFilter : IDocumentFilter
        {
            public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
            {
                IPrincipal user = HttpContext.Current.User;
    
                foreach (ApiDescription apiDescription in apiExplorer.ApiDescriptions)
                {
                    var authorizeAttributes = apiDescription
                        .ActionDescriptor.GetCustomAttributes<AuthorizeAttribute>().ToList();
                    authorizeAttributes.AddRange(apiDescription
                        .ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AuthorizeAttribute>());
    
                    if (!authorizeAttributes.Any())
                        continue;
    
                    var roles =
                        authorizeAttributes
                            .SelectMany(attr => attr.Roles.Split(','))
                            .Distinct()
                            .ToList();
                    if (!user.Identity.IsAuthenticated || !roles.Any(role => user.IsInRole(role) || role == ""))
                    {
                        string key = "/" + apiDescription.RelativePath;
                        PathItem pathItem = swaggerDoc.paths[key];
                        switch (apiDescription.HttpMethod.Method.ToLower())
                        {
                            case "get":
                                pathItem.get = null;
                                break;
                            case "put":
                                pathItem.put = null;
                                break;
                            case "post":
                                pathItem.post = null;
                                break;
                            case "delete":
                                pathItem.delete = null;
                                break;
                            case "options":
                                pathItem.options = null;
                                break;
                            case "head":
                                pathItem.head = null;
                                break;
                            case "patch":
                                pathItem.patch = null;
                                break;
                        }
                        if (pathItem.get == null &&
                            pathItem.put == null &&
                            pathItem.post == null &&
                            pathItem.delete == null &&
                            pathItem.options == null &&
                            pathItem.head == null &&
                            pathItem.patch == null)
                        {
                            swaggerDoc.paths.Remove(key);
                        }
                    }
                }
    
                swaggerDoc.paths = swaggerDoc.paths.Count == 0 ? null : swaggerDoc.paths;
                swaggerDoc.definitions = swaggerDoc.paths == null ? null : swaggerDoc.definitions;
            }
        }
    

    我的 WebApiConfig 现在看起来像这样。我删除了我的 ApiKeyFilter 因为它不再需要了。

    WebApiConfig
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            //config.Filters.Add(new ApiKeyFilter());
    
            config.MessageHandlers.Add(new CustomAuthenticationMessageHandler());
        }
    }
    

    SwaggerConfig
    public class SwaggerConfig
    {
        public static void Register()
        {
            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                {
                    c.SingleApiVersion("v1", "SwashbuckleExample");
                    c.DocumentFilter<AuthorizeRoleFilter>();
                })
                .EnableSwaggerUi(c => { });
        }
    }
    

    附加

    在我的项目中,我使用了一个 CustomProvider,它根据 api_key 缓存 SwaggerDocument。
     public class CachingSwaggerProvider : ISwaggerProvider
        {
            private static ConcurrentDictionary<string, SwaggerDocument> _cache =
                new ConcurrentDictionary<string, SwaggerDocument>();
    
            private readonly ISwaggerProvider _swaggerProvider;
    
            public CachingSwaggerProvider(ISwaggerProvider swaggerProvider)
            {
                _swaggerProvider = swaggerProvider;
            }
    
            public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
            {
                HttpContext httpContext = HttpContext.Current;
                string name = httpContext.User.Identity.Name;
                var cacheKey = string.Format("{0}_{1}_{2}", rootUrl, apiVersion, name);
                return _cache.GetOrAdd(cacheKey, (key) => _swaggerProvider.GetSwagger(rootUrl, apiVersion));
            }
        }
    

    关于c# - 根据角色/api_key 生成 Swashbuckle API 文档,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38070950/

    10-09 01:17