2016-06-28 30 views
1

我使用的是Swashbuckle(5.3.2),它会生成一个很好的API文档。基于角色生成Swashbuckle API文档/ api_key

为了澄清我的问题,我建立了一个没有真正意义的小例子项目。

API只能与有效的API密钥一起使用。 为此,我介绍了一个验证api_key并读出相应角色的ApiKeyFilter。

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; 
    } 
} 

控制器/资源只能如果请求者具有相应的角色访问。

PetController

[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) 
    { 
    } 
} 

CarController

[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):

  1. Admin_api_key:

    • 汽车
      • GET/API /汽车
      • 后/ API /汽车
      • 获得/ API /汽车/ {ID }
    • 宠物
      • GET/API /宠物
      • 后/ API /宠物
      • 获得/ API /宠物/(编号)
  2. CarOwner_api_key:

    • 汽车
      • get/api/cars
      • 后/ API /汽车
      • GET/API /汽车/(编号)
  3. PetLover_api_key:

    • 汽车
      • 获得/ API /汽车
    • 宠物
      • GET/API /宠物
      • 后/ API /宠物
      • 获得/ API /宠物/(编号)
  4. invalid_api_key:

    • 没什么可显示的

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

我已经有了一个寻找到一个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

回答

2

我现在发现了一个适用于我的解决方案。

我使用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)); 
     } 
    }