Swashbuckle.AspNetCore を使うことで、 ASP.NET Core で作成した Web API を Swagger UI 上で試せるようになった。 ただ、大抵の Web API では OAuth2 なり JWT Bearer なりの認証が必要、という風に実装していると思う。 自分の場合は JWT Bearer。
Swagger UI ではそのあたりもサポートしていて、 今回は JwtBearerAuthentication を使っている Web API を Swagger UI 上で試せるか挑戦してみた。
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; namespace SwaggerWithJwtBearer { // アプリケーションの構成 // 本来は application.json か環境変数に持たせるべきだが、 // 今回は簡略化のために static クラスにしておく。 public static class AppConfiguration { public static string SiteUrl { get; } = "http://swagger-with-jwt-bearer.net"; // JWT の署名で使う秘密鍵 public static string SecretKey { get; } = Guid.NewGuid().ToString(); } public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); } public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(options => { // JwtBearer 認証しか使わないので、 // JwtBearer をデフォルトにする options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.Audience = AppConfiguration.SiteUrl; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidIssuer = AppConfiguration.SiteUrl, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(AppConfiguration.SecretKey)) }; }); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Title = "Sample API", Version = "v1" }); // Swagger UI で API を試すとき、リクエストヘッダーに // JwtBearer トークンを設定できるようにする c.AddSecurityDefinition("api_key", new ApiKeyScheme() { Description = "Bearer スキームを使用した Authorization ヘッダー. 例: \"Authorization: Bearer {token}\"", Name = "Authorization", In = "header", Type = "apiKey" }); // 設定した Bearer トークンをリクエストに含めるためのフィルタを追加 c.OperationFilter<SecurityRequirementsOperationFilter>(); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "SmartStamp API V1"); }); } } class SecurityRequirementsOperationFilter : IOperationFilter { public void Apply(Operation operation, OperationFilterContext context) { var allowAnonymous = context.MethodInfo .CustomAttributes .Any(x => x.AttributeType == typeof(AllowAnonymousAttribute)); if (allowAnonymous) { return; } var attributes = context.MethodInfo .GetCustomAttributes(true) .Concat(context.MethodInfo.DeclaringType.GetCustomAttributes(true)) .Where(x => x is AuthorizeAttribute); if (attributes.Any()) { operation.Security = new List<IDictionary<string, IEnumerable<string>>>(); operation.Security.Add(new Dictionary<string, IEnumerable<string>> { ["api_key"] = new string[0] }); } } } // ユーザー public class User { public string Id { get; set; } public string UserName { get; set; } public string Password { get; set; } } // パスワードを除いたユーザー情報を格納する public class UserViewModel { public string Id { get; set; } public string UserName { get; set; } } // トークンを取得するために渡された認証情報を格納する public class TokenInputModel { [Required] public string UserName { get; set; } [Required] public string Password { get; set; } } // 生成したトークンと有効期限を格納する public class TokenViewModel { public string Token { get; set; } public DateTime Expiration { get; set; } } [Route("api")] public class HomeController : Controller { // テスト用ユーザー static List<User> _testUsers = new List<User>() { new User() { Id = Guid.NewGuid().ToString(), UserName = "tnakamura", Password = "test1234", } }; // トークンを取得する API [HttpPost("token")] public IActionResult Token([FromBody]TokenInputModel inputModel) { if (ModelState.IsValid) { var user = _testUsers.FirstOrDefault(u => u.UserName == inputModel.UserName); if (user != null && user.Password == inputModel.Password) { var token = CreateJwtSecurityToken(user); return Ok(new TokenViewModel { Token = new JwtSecurityTokenHandler().WriteToken(token), Expiration = token.ValidTo, }); } } return BadRequest(); } JwtSecurityToken CreateJwtSecurityToken(User user) { // JWT に含めるクレーム var claims = new List<Claim>() { // JwtBeaerAuthentication 用 new Claim(JwtRegisteredClaimNames.Jti, user.Id), new Claim(JwtRegisteredClaimNames.Sub, user.UserName), // User.Identity プロパティ用 new Claim(ClaimTypes.Sid, user.Id), new Claim(ClaimTypes.Name, user.UserName), }; var token = new JwtSecurityToken( issuer: AppConfiguration.SiteUrl, audience: AppConfiguration.SiteUrl, claims: claims, expires: DateTime.UtcNow.AddDays(7), signingCredentials: new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfiguration.SecretKey)), SecurityAlgorithms.HmacSha256 ) ); return token; } // 認証が必要な API [Authorize] [HttpGet("me")] public IActionResult Me() { var id = User.Claims.First(c => c.Type == ClaimTypes.Sid).Value; var user = _testUsers.First(u => u.Id == id); return Ok(new UserViewModel { Id = user.Id, UserName = user.UserName, }); } } }
デバッグ実行し、/swagger
にアクセスすると Swagger UI が表示される。
認証必須の API を呼び出すと、401 が返ってくる。
アクセストークンを取得。
Swagger UI 上で Authorization ヘッダーの内容を設定できる。
Authorization ヘッダーにアクセストークンが設定されるようになったので、 認証必須の API を呼び出せるようになった。
もう Swagger UI の無い Web API 開発には戻れないねぇ。