ممکن است تعدادی از اکشن متدهای API طراحی شده، محافظت شده باشند. بنابراین OpenAPI Specification تولیدی نیز باید به همراه مستندات کافی در این مورد باشد تا استفاده کنندگان از آن بدانند چگونه باید با آن کار کنند. نگارش سوم OpenAPI Specification از اعتبارسنجی و احراز هویت مبتنی بر هدرها مانند basic و یا bearer، همچنین حالت کار با API Keys مانند هدرها، کوئری استرینگها و کوکیها و یا حالت OAuth2 و OpenID Connect پشتیبانی میکند و این موارد ذیل خواص securitySchemes و security در OpenAPI Specification ظاهر میشوند:
"securitySchemes":
{ "basicAuth":
{
"type":"http",
"description":"Input your username and password to access this API",
"scheme":"basic"
}
}
…
"security":[
{"basicAuth":[]}
]
- خاصیت securitySchemes انواع حالتهای اعتبارسنجی پشتیبانی شده را لیست میکند.
- خاصیت security کار اعمال Scheme تعریف شده را به کل API یا صرفا قسمتهای خاصی از آن، انجام میدهد.
در ادامه مثالی را بررسی خواهیم کرد که مبتنی بر basic authentication کار میکند و در این حالت به ازای هر درخواست به API، نیاز است یک نام کاربری و کلمهی عبور نیز ارسال شوند. البته روش توصیه شده، کار با JWT و یا OpenID Connect است؛ اما جهت تکمیل سادهتر این قسمت، بدون نیاز به برپایی مقدماتی پیچیده، کار با basic authentication را بررسی میکنیم و اصول کلی آن از دیدگاه مستندات OpenAPI Specification تفاوتی نمیکند.
افزودن Basic Authentication به API برنامه
برای پیاده سازی Basic Authentication نیاز به یک AuthenticationHandler سفارشی داریم:
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"));
}
}
}
}
کار این AuthenticationHandler سفارشی، با بازنویسی متد HandleAuthenticateAsync شروع میشود. در اینجا به دنبال هدر ویژهای با کلید Authorization میگردد. این هدر باید به همراه نام کاربری و کلمهی عبوری با حالت base64 encoded باشد. اگر این هدر وجود نداشت و یا مقدار هدر Authorization، با فرمتی که مدنظر ما است قابل decode و همچنین جداسازی نبود، شکست اعتبارسنجی اعلام میشود.
پس از دریافت مقدار هدر Authorization، ابتدا مقدار آنرا از base64 به حالت معمولی تبدیل کرده و سپس بر اساس حرف ":"، دو قسمت را از آن جداسازی میکنیم. قسمت اول را به عنوان نام کاربری و قسمت دوم را به عنوان کلمهی عبور پردازش خواهیم کرد. در این مثال جهت سادگی، این دو باید مساوی DNT و 123 باشند. اگر اینچنین بود، یک AuthenticationTicket دارای Claim ای حاوی نام کاربری را ایجاد کرده و آنرا به عنوان حاصل موفقیت آمیز بودن عملیات بازگشت میدهیم.
مرحلهی بعد، استفاده و معرفی این BasicAuthenticationHandler تهیه شده به برنامه است:
namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(defaultScheme: "Basic")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Basic", null);
در اینجا توسط متد services.AddAuthentication، این scheme جدید که نام رسمی آن Basic است، به همراه Handler آن، به برنامه معرفی میشود.
همچنین نیاز است میانافزار اعتبارسنجی را نیز با فراخوانی متد app.UseAuthentication، به برنامه اضافه کرد که باید پیش از فراخوانی app.UseMvc صورت گیرد تا به آن اعمال شود:
namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}
همچنین برای اینکه تمام اکشن متدهای موجود را نیز محافظت کنیم، میتوان فیلتر Authorize را به صورت سراسری اعمال کرد:
namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddMvc(setupAction =>
{
setupAction.Filters.Add(new AuthorizeFilter());
// ...
تکمیل مستندات API جهت انعکاس تنظیمات محافظت از اکشن متدهای آن
پس از تنظیم محافظت دسترسی به اکشن متدهای برنامه، اکنون نوبت به مستند کردن آن است و همانطور که در ابتدای بحث نیز عنوان شد، برای این منظور نیاز به تعریف خواص securitySchemes و security در OpenAPI Specification است:
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"
});
});
در اینجا توسط متد setupAction.AddSecurityDefinition، ابتدا یک نام تعریف میشود (از این نام، در قسمت بعدی تنظیمات استفاده خواهد شد). پارامتر دوم آن همان SecurityScheme است که توضیح داده شد. برای حالت basic auth، نوع آن Http است و اسکیمای آن basic. باید دقت داشت که مقدار خاصیت Scheme در اینجا، حساس به بزرگی و کوچکی حروف است.
پس از این تنظیم اگر برنامه را اجرا کنیم، یک دکمهی authorize اضافه شدهاست:
با کلیک بر روی آن، صفحهی ورود نام کاربری و کلمهی عبور ظاهر میشود:
اگر آنرا تکمیل کرده و سپس برای مثال لیست نویسندگان را درخواست کنیم (با کلیک بر روی دکمهی try it out آن و سپس کلیک بر روی دکمهی execute ذیل آن)، تنها خروجی 401 یا unauthorized را دریافت میکنیم:
- بنابراین برای تکمیل آن، مطابق نکات
قسمت چهارم، ابتدا باید status code مساوی 401 را به صورت سراسری، مستند کنیم:
namespace OpenAPISwaggerDoc.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(setupAction =>
{
setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status401Unauthorized));
- همچنین هرچند با کلیک بر روی دکمهی Authorize در Swagger UI و ورود نام کاربری و کلمهی عبور توسط آن، در همانجا پیام Authorized را دریافت کردیم، اما اطلاعات آن به ازای هر درخواست، به سمت سرور ارسال نمیشود. به همین جهت در حین درخواست لیست نویسندگان، پیام unauthorized را دریافت کردیم. برای رفع این مشکل نیاز است به OpenAPI Spec اعلام کنیم که تعامل با API، نیاز به Authentication دارد:
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>()
}
});
});
در اینجا OpenApiSecurityRequirement یک دیکشنری از نوع <<Dictionary<OpenApiSecurityScheme, IList<string است که کلید آن از نوع OpenApiSecurityScheme تعریف میشود و باید آنرا به نمونهای که توسط setupAction.AddSecurityDefinition پیشتر اضافه کردیم، متصل کنیم. این اتصال توسط خاصیت Reference آن و Id ای که به نام تعریف شدهی توسط آن اشاره میکند، صورت میگیرد. مقدار این دیکشنری نیز لیستی از رشتهها میتواند باشد (مانند توکنها و scopes در OpenID Connect) که در اینجا با یک لیست خالی مقدار دهی شدهاست.
پس از این تنظیمات، Swagger UI با افزودن یک آیکن قفل به مداخل APIهای محافظت شده، به صورت زیر تغییر میکند:
در این حالت اگر بر روی آیکن قفل کلیک کنیم، همان صفحهی دیالوگ ورود نام کاربری و کلمهی عبوری که پیشتر با کلیک بر روی دکمهی Authorize ظاهر شد، نمایش داده میشود. با تکمیل آن و کلیک مجدد بر روی آیکن قفل، جهت گشوده شدن پنل API و سپس کلیک بر روی try it out آن، برای مثال میتوان به API محافظت شدهی دریافت لیست نویسندگان، بدون مشکلی، دسترسی یافت:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: OpenAPISwaggerDoc-06.zip