Dans ASP.NET Core, à l'aide de Swashbuckle.AspNetCore
, comment protéger l'accès à mon interface utilisateur Swagger de la même manière que le décorer avec l'attribut [Authorize]
?
Je veux que l'attribut (équivalent de) [Authorize]
s'exécute, comme pour un contrôleur / action normalement décoré, quand quelqu'un essaie d'accéder à /swagger
-URL sur mon application Web, de sorte que mon AuthenticationHandler<T>
soit exécuté .
3 Réponses :
L'intergiciel Swagger est totalement indépendant du pipeline MVC, il n'est donc pas possible dès le départ. Cependant, avec un peu de rétro-ingénierie, j'ai trouvé une solution de contournement. Cela implique de réimplémenter la plupart des intergiciels dans un contrôleur personnalisé, donc c'est un peu compliqué, et évidemment cela peut rompre avec une future mise à jour.
Tout d'abord, nous devons arrêter d'appeler IApplicationBuilder.UseSwagger
et IApplicationBuilder.UseSwaggerUI
, afin qu'il ne soit pas en conflit avec notre contrôleur.
Ensuite, nous devons ajouter tout ce qui a été ajouté par ces méthodes en modifiant notre Startup.cs
:
[Authorize] [Route("[controller]")] public class SwaggerController : ControllerBase { [HttpGet("{documentName}/swagger.json")] public ActionResult<string> GetSwaggerJson([FromServices] ISwaggerProvider swaggerProvider, [FromServices] IOptions<SwaggerOptions> swaggerOptions, [FromServices] IOptions<MvcJsonOptions> jsonOptions, [FromRoute] string documentName) { // documentName is the name provided via the AddSwaggerGen(c => { c.SwaggerDoc("documentName") }) var swaggerDoc = swaggerProvider.GetSwagger(documentName); // One last opportunity to modify the Swagger Document - this time with request context var options = swaggerOptions.Value; foreach (var filter in options.PreSerializeFilters) { filter(swaggerDoc, HttpContext.Request); } var swaggerSerializer = SwaggerSerializerFactory.Create(jsonOptions); var jsonBuilder = new StringBuilder(); using (var writer = new StringWriter(jsonBuilder)) { swaggerSerializer.Serialize(writer, swaggerDoc); return Content(jsonBuilder.ToString(), "application/json"); } } [HttpGet] [HttpGet("index.html")] public ActionResult<string> GetSwagger([FromServices] ISwaggerProvider swaggerProvider, [FromServices] IOptions<SwaggerUIOptions> swaggerUiOptions) { var options = swaggerUiOptions.Value; var serializer = CreateJsonSerializer(); var indexArguments = new Dictionary<string, string>() { { "%(DocumentTitle)", options.DocumentTitle }, { "%(HeadContent)", options.HeadContent }, { "%(ConfigObject)", SerializeToJson(serializer, options.ConfigObject) }, { "%(OAuthConfigObject)", SerializeToJson(serializer, options.OAuthConfigObject) } }; using (var stream = options.IndexStream()) { // Inject arguments before writing to response var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd()); foreach (var entry in indexArguments) { htmlBuilder.Replace(entry.Key, entry.Value); } return Content(htmlBuilder.ToString(), "text/html;charset=utf-8"); } } private JsonSerializer CreateJsonSerializer() { return JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new[] { new StringEnumConverter(true) }, NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None, StringEscapeHandling = StringEscapeHandling.EscapeHtml }); } private string SerializeToJson(JsonSerializer jsonSerializer, object obj) { var writer = new StringWriter(); jsonSerializer.Serialize(writer, obj); return writer.ToString(); } }
Enfin, il y a deux choses à swagger.json
: la génération du fichier swagger.json
et la génération de l'interface utilisateur swagger. Nous faisons cela avec un contrôleur personnalisé:
public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(c => { c.SwaggerDoc("documentName", new Info { Title = "My API", Version = "v1" }); }); // RouteTemplate is no longer used (route will be set via the controller) services.Configure<SwaggerOptions>(c => { }); // RoutePrefix is no longer used (route will be set via the controller) services.Configure<SwaggerUIOptions>(c => { // matches our controller route c.SwaggerEndpoint("/swagger/documentName/swagger.json", "My API V1"); }); } public void Configure(IApplicationBuilder app) { // we need a custom static files provider for the Swagger CSS etc.. const string EmbeddedFileNamespace = "Swashbuckle.AspNetCore.SwaggerUI.node_modules.swagger_ui_dist"; app.UseStaticFiles(new StaticFileOptions { RequestPath = "/swagger", // must match the swagger controller name FileProvider = new EmbeddedFileProvider(typeof(SwaggerUIMiddleware).GetTypeInfo().Assembly, EmbeddedFileNamespace), }); }
Eh bien, j'ai trouvé une solution simple au problème. Vous devez effectuer les opérations suivantes:
J'espère que cela t'aides.
Cela pourrait être la meilleure option, mais veuillez fournir plus de détails, afin que je puisse décider si c'est faisable. Merci.
J'ai implémenté quelque chose de similaire au middleware Owin pour l'API Web ASP.NET. Le middleware Owin / Katana est fondamentalement un précurseur du middleware ASP.NET Core, alors peut-être que quelqu'un le trouverait utile: stackoverflow.com/a/61602929/350384
Vous pouvez y parvenir avec une solution middleware simple
Intergiciel
services.AddTransient<SwaggerAuthenticationMiddleware>();
Au démarrage -> Configurer (assurez-vous d'ajouter des éléments swagger après l'authentification et l'autorisation)
app.UseAuthentication(); app.UseAuthorization(); //Enable Swagger and SwaggerUI app.UseMiddleware<SwaggerAuthenticationMiddleware>(); //can turn this into an extension if you ish app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "my test api"));
Dans Startup -> ConfigureServices, enregistrez le middleware
public class SwaggerAuthenticationMiddleware : IMiddleware { //CHANGE THIS TO SOMETHING STRONGER SO BRUTE FORCE ATTEMPTS CAN BE AVOIDED private const string UserName = "TestUser1"; private const string Password = "TestPassword1"; public async Task InvokeAsync(HttpContext context, RequestDelegate next) { //If we hit the swagger locally (in development) then don't worry about doing auth if (context.Request.Path.StartsWithSegments("/swagger") && !IsLocalRequest(context)) { string authHeader = context.Request.Headers["Authorization"]; if (authHeader != null && authHeader.StartsWith("Basic ")) { // Get the encoded username and password var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim(); // Decode from Base64 to string var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword)); // Split username and password var username = decodedUsernamePassword.Split(':', 2)[0]; var password = decodedUsernamePassword.Split(':', 2)[1]; // Check if login is correct if (IsAuthorized(username, password)) { await next.Invoke(context); return; } } // Return authentication type (causes browser to show login dialog) context.Response.Headers["WWW-Authenticate"] = "Basic"; // Return unauthorized context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } else { await next.Invoke(context); } } private bool IsAuthorized(string username, string password) => UserName == username && Password == password; private bool IsLocalRequest(HttpContext context) { if(context.Request.Host.Value.StartsWith("localhost:")) return true; //Handle running using the Microsoft.AspNetCore.TestHost and the site being run entirely locally in memory without an actual TCP/IP connection if (context.Connection.RemoteIpAddress == null && context.Connection.LocalIpAddress == null) return true; if (context.Connection.RemoteIpAddress != null && context.Connection.RemoteIpAddress.Equals(context.Connection.LocalIpAddress)) return true; return IPAddress.IsLoopback(context.Connection.RemoteIpAddress); } }
Solution intéressante! Votre authentification de base peut-elle être combinée avec une authentification utilisateur existante? Par coïncidence, j'ai une question très similaire et je me demande peut-être si votre solution fonctionnerait également: stackoverflow.com/questions/62727471
Ne remplit pas l'exigence de: "Je veux que l'attribut ... [Autoriser] s'exécute". L'authentification manuelle n'est pas une solution sûre pour l'avenir.
Il est actuellement convenu qu'il n'y a aucun moyen d'intégrer l'interface utilisateur de Swagger avec l'attribut [Authorize] sans travail significatif, même cela ne serait pas une preuve pour l'avenir avec la prochaine version de Swashbuckle etc. Cela offre un moyen simple de bloquer l'accès à l'interface utilisateur de Swagger, spécifiquement si vous avez une API hébergée publique et que toute l'interface utilisateur de Swagger est ouverte à la visualisation
Impressionnant. UserName.ToLower () == nom d'utilisateur échouera. Veuillez changer pour UserName.ToLower () == username.ToLower ()
bon point, il n'y a aucun besoin de faire plus bas du tout, je l'ai enlevé maintenant. @ user2021262 si la réponse vous a aidé, n'oubliez pas de la voter.