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
{
public static class AppConfiguration
{
public static string SiteUrl { get; } = "http://swagger-with-jwt-bearer.net";
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 =>
{
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"
});
c.AddSecurityDefinition("api_key", new ApiKeyScheme()
{
Description = "Bearer スキームを使用した Authorization ヘッダー. 例: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
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",
}
};
[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)
{
var claims = new List<Claim>()
{
new Claim(JwtRegisteredClaimNames.Jti, user.Id),
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
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;
}
[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 開発には戻れないねぇ。