مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
نویسنده: وحید نصیری
تاریخ: ۱۳۹۸/۰۲/۰۱ ۱۲:۳۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
"securitySchemes":
{ "basicAuth":
{
"type":"http",
"description":"Input your username and password to access this API",
"scheme":"basic"
}
}
…
"security":[
{"basicAuth":[]}
] using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace OpenAPISwaggerDoc.Web.Authentication
{
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return Task.FromResult(AuthenticateResult.Fail("Missing Authorization header"));
}
try
{
var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authenticationHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
if (username == "DNT" && password == "123")
{
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, username) };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Invalid username or password"));
}
catch
{
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header"));
}
}
}
} namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(defaultScheme: "Basic")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Basic", null); namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
} namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddMvc(setupAction =>
{
setupAction.Filters.Add(new AuthorizeFilter());
// ... namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(setupAction =>
{
// ...
setupAction.AddSecurityDefinition("basicAuth", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "basic",
Description = "Input your username and password to access this API"
});
});
namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(setupAction =>
{
setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status401Unauthorized)); namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(setupAction =>
{
// ...
setupAction.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "basicAuth"
}
},
new List<string>()
}
});
});
<PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="4.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="4.5.2" /> public static class ServiceCollectionExtensions
{
public static void AddCustomSwagger(this IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.EnableAnnotations();
options.DocumentFilter<AuthenticationDocumentFilter>();
options.SwaggerDoc("v1", new Info { Version = "v1", Title = "Test API" });
});
}
public static void UseSwaggerAndUI(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.DocExpansion(DocExpansion.None);
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API Docs");
});
}
} public class AuthenticationDocumentFilter : IDocumentFilter
{
private readonly IHttpContextAccessor httpContextAccessor;
public AuthenticationDocumentFilter(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
swaggerDoc.Definitions = new Dictionary<string, Schema>();
swaggerDoc.Paths = new Dictionary<string, PathItem>();
}
}
} public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
foreach (var item in swaggerDoc.Paths)
{
item.Value.Post = null;
item.Value.Put = null;
}
}
} public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddAuthorization();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options=>
{
options.AccessDeniedPath = "/Login";
options.Cookie.HttpOnly = true;
options.LoginPath = "/Login";
options.LogoutPath = "/Login";
options.ExpireTimeSpan = TimeSpan.FromDays(15);
options.SlidingExpiration = true;
options.Cookie.IsEssential = true;
options.ReturnUrlParameter = "returnUrl";
});
services.AddMvc();
services.AddCustomSwagger();
} public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseSwaggerAndUI();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
services.AddSwaggerGen(c =>
{
// ...
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(security);
}); https://localhost:5001/swagger/LibraryOpenAPISpecification/swagger.json
app.UseSwaggerUI(x => { x.SwaggerEndpoint("/swagger/v1/swagger.yaml", "Zeipt Dashboard API"); }); await Context.Response.WriteAsync("This is why you can't log in"); private readonly SignInKeysOption _signInKeyOptions;
private string failReason;
public CredentialAuthenticationHandler(
IOptionsMonitor < CustomAuthenticationOptions > options,
IOptionsMonitor < SignInKeysOption > signInKeyOptions,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock): base(options, logger, encoder, clock) {
_signInKeyOptions = signInKeyOptions.CurrentValue ??
throw new ArgumentNullException(nameof(signInKeyOptions));
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties) {
Response.StatusCode = 401;
if (failReason != null) {
Response.HttpContext.Features.Get < IHttpResponseFeature > () !.ReasonPhrase = failReason;
}
return Task.CompletedTask;
}
protected async override Task < AuthenticateResult > HandleAuthenticateAsync() {
string authorizationHeader = Request.Headers["Authorization"];
if (authorizationHeader == null) {
Logger.LogWarning("Authorization key in header is null or empty");
//string result;
//Context.Response.StatusCode = StatusCodes.Status401Unauthorized;
//result = JsonConvert.SerializeObject(new { error = "Authorization key in header is null or empty" });
//Context.Response.ContentType = "application/json";
//await Context.Response.WriteAsync(result);
failReason = "Authorization key in header is null or empty";
return AuthenticateResult.Fail("request doesn't contains header");
//return await Task.FromResult(AuthenticateResult.Fail("UnAuthenticate"));
}
} using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Options;
namespace OpenAPISwaggerDoc.Web.Authentication;
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private string _failReason;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
_failReason = "Missing Authorization header";
return Task.FromResult(AuthenticateResult.Fail(_failReason));
}
try
{
var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authenticationHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
if (string.Equals(username, "DNT", StringComparison.Ordinal) &&
string.Equals(password, "123", StringComparison.Ordinal))
{
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, username) };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
_failReason = "Invalid username or password";
return Task.FromResult(AuthenticateResult.Fail(_failReason));
}
catch
{
_failReason = "Invalid Authorization header";
return Task.FromResult(AuthenticateResult.Fail(_failReason));
}
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
await base.HandleChallengeAsync(properties);
if (Response.StatusCode == StatusCodes.Status401Unauthorized &&
!string.IsNullOrWhiteSpace(_failReason))
{
Response.Headers.Add("WWW-Authenticate", _failReason);
Response.ContentType = "application/json";
await WriteProblemDetailsAsync(_failReason);
}
}
private Task WriteProblemDetailsAsync(string detail)
{
var problemDetails = new ProblemDetails { Detail = detail, Status = Context.Response.StatusCode };
var result = new ObjectResult(problemDetails)
{
ContentTypes = new MediaTypeCollection(),
StatusCode = problemDetails.Status,
DeclaredType = problemDetails.GetType(),
};
var executor = Context.RequestServices.GetRequiredService<IActionResultExecutor<ObjectResult>>();
var routeData = Context.GetRouteData() ?? new RouteData();
var actionContext = new ActionContext(Context, routeData, new ActionDescriptor());
return executor.ExecuteAsync(actionContext, result);
}
}