.NET-5 对未经授权的用户隐藏 swagger 端点

问题描述 投票:0回答:5

我有一个使用 OpenApi 的 .NET 5 API。

是否可以在用户获得 JWT 持有者令牌授权之前隐藏所有 API 端点(但登录端点除外)?

这是我在startup.cs中使用的代码

services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { 
                Title = "API", Version = "v1",
                Description = "API (.NET 5.0)",
                Contact = new OpenApiContact()
                {
                    Name = "Contact",
                    Url = null,
                    Email = "[email protected]"
                }
            });
            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = @"Autorización JWT utilizando el esquema Bearer en header. <br />
                  Introducir el token JWT generado por AuthApi.",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.Http,
                Scheme = "Bearer"
            });
            c.AddSecurityRequirement(new OpenApiSecurityRequirement()
  {
    {
      new OpenApiSecurityScheme
      {
        Reference = new OpenApiReference
          {
            Type = ReferenceType.SecurityScheme,
            Id = "Bearer"
          },
          Scheme = "oauth2",
          Name = "Bearer",
          In = ParameterLocation.Header,

        },
        new List<string>()
      }
    });
        });
swagger-ui openapi .net-5
5个回答
3
投票

我成功地在身份验证之前隐藏了 swagger 端点,方法是攻击中间件以从未经身份验证的用户的 swagger.json 文件中删除端点,并使用 swagger 请求/响应拦截器来保留接收到的令牌并在用户登录后刷新页面以重新获取 swagger。 json 文件。

我把解决方案写在这里: https://medium.com/@milad665/hide-endpoints-in-swagger-ui-for-unauthenticated-users-4054a4e15b89


3
投票

首先创建并添加一个新的

DocumentFilter
,它会为未经授权的用户从您的
swagger.json
中删除所有信息。您可以非常具体地删除或保留哪些内容,但此示例只是删除所有端点和架构,但保留授权所需的身份验证信息。

public class RequireAuthenticationDocumentFilter : IDocumentFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public RequireAuthenticationDocumentFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        bool isAuthenticated =
            _httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false;
        
        if (!isAuthenticated)
        {
            swaggerDoc.Paths.Clear();
            context.SchemaRepository.Schemas.Clear();
        }
    }
}

然后添加

RequireAuthenticationDocumentFilter
。您现在应该在
swagger.json
和 SwaggerUI 中看到没有端点或架构。

services.AddSwaggerGen(options =>
{
    options.DocumentFilter<RequireAuthenticationDocumentFilter>();
}

下一步是配置 SwaggerUI 以在页面重新加载之间保留身份验证令牌。然后,

RequestInterceptor
(您可以注入的 JavaScript 函数)在请求
swagger.json
时使用持久令牌。

app.UseSwaggerUI(options =>
{
    options.EnablePersistAuthorization();
    options.UseRequestInterceptor(
        "(request) => {" +
        // "  debugger;" +
        "  if (!request.url.endsWith('swagger.json')) return request;" +
        "  var json = window.localStorage?.authorized;" +
        "  if (!json) return request;" +
        "  var auth = JSON.parse(json);" +
        "  var token = auth?.oauth2?.token?.access_token;" +
        "  if (!token) return request;" +
        "  request.headers.Authorization = 'Bearer ' + token;" +
        "  return request;" +
        "}");
}

请注意,在 SwaggerUI 页面加载时请求

swagger.json
。通过 SwaggerUI 授权后,您需要手动重新加载页面,以便再次请求您的
swagger.json
,但这次使用持久的授权信息。

在检查

RequireAuthenticationDocumentFilter
中的身份验证时遇到问题时,请确保在将 Swagger 和 SwaggerUI 添加到 ASP.NET Core 中间件管道之前进行身份验证和授权。

...
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI();
...

2
投票

您将需要实现自己的中间件并检查端点路径。如果它以“/swagger”开头,那么您应该挑战身份验证。

下面是其他人编写的代码这里

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using System;

/// <summary>
/// The extension methods that extends <see cref="IApplicationBuilder" /> for authentication purposes
/// </summary>
public static class ApplicationBuilderExtensions
{
    /// <summary>
    /// Requires authentication for paths that starts with <paramref name="pathPrefix" />
    /// </summary>
    /// <param name="app">The application builder</param>
    /// <param name="pathPrefix">The path prefix</param>
    /// <returns>The application builder</returns>
    public static IApplicationBuilder RequireAuthenticationOn(this IApplicationBuilder app, string pathPrefix)
    {
        return app.Use((context, next) =>
        {
            // First check if the current path is the swagger path
            if (context.Request.Path.HasValue && context.Request.Path.Value.StartsWith(pathPrefix, StringComparison.InvariantCultureIgnoreCase))
            {
                // Secondly check if the current user is authenticated
                if (!context.User.Identity.IsAuthenticated)
                {
                    return context.ChallengeAsync();
                }
            }

            return next();
        });
    }
}

然后在你的startup.cs中(以下顺序很重要)

app.RequireAuthenticationOn("/swagger");
app.UseSwagger();
app.UseSwaggerUI();

1
投票

我最终使用 appsettings.json 参数隐藏了 swagger enpoints,这并不完全是我所要求的,但我会发布解决方案,以防它对某人有所帮助,因为它可能有助于过滤登录用户:

有一些注释块和未使用的代码可能对您有用,因为它附带了我在网上找到的示例。

Swagger 忽略过滤器类:

public class SwaggerIgnoreFilter : IDocumentFilter
{
    private IServiceProvider _provider;

    public SwaggerIgnoreFilter(IServiceProvider provider)
    {
        if (provider == null) throw new ArgumentNullException(nameof(provider));

        this._provider = provider;
    }
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(i => i.GetTypes()).ToList();

        var http = this._provider.GetRequiredService<IHttpContextAccessor>();
        var authorizedIds = new[] { "00000000-1111-2222-1111-000000000000" };   // All the authorized user id's.
                                                                                // When using this in a real application, you should store these safely using appsettings or some other method.
        var userId = http.HttpContext.User.Claims.Where(x => x.Type == "jti").Select(x => x.Value).FirstOrDefault();
        var show = http.HttpContext.User.Identity.IsAuthenticated && authorizedIds.Contains(userId);
        //var Securitytoken = new JwtSecurityTokenHandler().CreateToken(tokenDescriptor);
        //var tokenstring = new JwtSecurityTokenHandler().WriteToken(Securitytoken);
        //var token = new JwtSecurityTokenHandler().ReadJwtToken(tokenstring);
        //var claim = token.Claims.First(c => c.Type == "email").Value;
        Parametros parametros = new Parametros();
        if (!show)
        {
            var descriptions = context.ApiDescriptions.ToList();

            foreach (var description in descriptions)
            {
                // Expose login so users can login through Swagger. 
                if (description.HttpMethod == "POST" && description.RelativePath == "denarioapi/v1/auth/login")
                    continue;

                var route = "/" + description.RelativePath.TrimEnd('/');
                OpenApiPathItem path;
                swaggerDoc.Paths.TryGetValue(route, out path);

                switch(route)
                {
                    case string s when s.Contains("/Contabilidad"):
                        if (parametros.contabilidadApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    case string s when s.Contains("/Identificativos"):
                        if (parametros.identificativosApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    case string s when s.Contains("/Centros"):
                        if (parametros.centrosApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    case string s when s.Contains("/Contratos"):
                        if (parametros.contratosApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    
                    case string s when s.Contains("/Planificacion"):
                        if (parametros.planificacionApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    case string s when s.Contains("/Puestotrabajo"):
                        if (parametros.puestotrabajoApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    
                    case string s when s.Contains("/Usuarios"):
                        if (parametros.usuariosApi != "1")
                        {
                            swaggerDoc.Paths.Remove(route);
                        }
                        break;
                    
                    default:
                        break;
                }

                // remove method or entire path (if there are no more methods in this path)
                //switch (description.HttpMethod)
                //{
                    //case "DELETE": path. = null; break;
                    //case "GET": path.Get = null; break;
                    //case "HEAD": path.Head = null; break;
                    //case "OPTIONS": path.Options = null; break;
                    //case "PATCH": path.Patch = null; break;
                    //case "POST": path.Post = null; break;
                    //case "PUT": path.Put = null; break;
                    //default: throw new ArgumentOutOfRangeException("Method name not mapped to operation");
                //}

                //if (path.Delete == null && path.Get == null &&
                //    path.Head == null && path.Options == null &&
                //    path.Patch == null && path.Post == null && path.Put == null)
                //swaggerDoc.Paths.Remove(route);
            }

        }




        foreach (var definition in swaggerDoc.Components.Schemas)
        {
            var type = allTypes.FirstOrDefault(x => x.Name == definition.Key);
            if (type != null)
            {
                var properties = type.GetProperties();
                foreach (var prop in properties.ToList())
                {
                    var ignoreAttribute = prop.GetCustomAttribute(typeof(OpenApiIgnoreAttribute), false);

                    if (ignoreAttribute != null)
                    {
                        definition.Value.Properties.Remove(prop.Name);
                    }
                }
            }
        }
    }
}

Startup.cs 配置服务:

services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "API",
                Version = "v1",
                Description = "API (.NET 5.0)",
                Contact = new OpenApiContact()
                {
                    Name = "Contact name",
                    Url = null,
                    Email = "[email protected]"
                }
            });
            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = @"Description",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.Http,
                Scheme = "Bearer"
            });
            c.DocumentFilter<SwaggerIgnoreFilter>();
            c.AddSecurityRequirement(new OpenApiSecurityRequirement()
  {
        {
          new OpenApiSecurityScheme
          {
            Reference = new OpenApiReference
              {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
              },
              Scheme = "oauth2",
              Name = "Bearer",
              In = ParameterLocation.Header,

            },
            new List<string>()
          }
    });
        });

0
投票

我的实现:

    string controlJs = "(response) => { " +
"if (!window.timerMostrar) { " +
"   window.timerMostrar = setInterval( " +
"    function() {" +
"        const lista = document.querySelectorAll('.opblock-tag-section'); " +
"        const listaArray = [...lista]; " +
"        if (document.querySelector('.authorize').classList.contains('unlocked')) " +
"        { " +
"           listaArray.forEach(title => { " +
"                if (title.querySelectorAll('#operations-tag-Login').length == 0) { " +
"                    title.style.display = 'none'; " +
"                } else { " +
"                    title.style.display = ''; " +
"                } " +
"            }); " +
"            document.querySelector('.models').style.display = 'none';  "+
"        } else { " +
"            listaArray.forEach(title => { " +
"                title.style.display = ''; " +
"            }); " +
"            document.querySelector('.models').style.display = '';  " +
"        } " +
"    }, 1000); " +
"} " +
"return response; }";

 app.UseSwaggerUI(c =>
 {
    ...
    c.UseResponseInterceptor(controlJs);
 })

您必须将“登录”一词更改为您的登录控制器的名称。

© www.soinside.com 2019 - 2024. All rights reserved.