はじめに
大人の事情によって .NET Framework 4.8 で動かさざるをえない ASP.NET Core 2.2 製 Web API を、OAuth2.0 か OpenID Connect に対応させる必要があり、IdentityServer4 を使う予定でいた。
その IdentityServer4 は 5/1 時点で v3.1.3 が最新。もうすぐ v4.0.0 が出そうですらある。そして、IdentityServer4 は v3.0.0 から .NET Core のみサポートになっていた。v3.1.3 は .NET Core 3.1 前提。
Web API と IdentityServer4 ホストで、ASP.NET Core のバージョンを揃えたい場合は、.NET Standard 2.0 をサポートしていた v2.5.4 まで IdentityServer4 のバージョンを下げることになるが、さすがに古すぎる。
そもそも、IdentityServer4 が実装している OAuth2.0 や OpenID Connect は、HTTP と同じように仕様がちゃんと決まっているわけで、仕様通りに実装されてさえいれば ASP.NET Core のバージョンが違っても大丈夫なはず。というわけで試してみた。
IdentityServer4 ホストを .NET Core 3.1 と ASP.NET Core 3.1 で作成
IdentityServer4 は v4.0.0 がもうそろそろ出そうだけど、プレビューを除いて一番新しいリリースである v3.1.3 を使う。
コードはほぼ IdentityServer4 のドキュメントのまんま。
using System.Collections.Generic; using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace IdentityServer4Sample { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>() .UseUrls("http://localhost:5000"); }); } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddInMemoryApiResources(Config.Apis) .AddInMemoryClients(Config.Clients) .AddDeveloperSigningCredential(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseIdentityServer(); } } public static class Config { public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("api1", "My API") }; public static IEnumerable<Client> Clients => new List<Client> { new Client { ClientId = "client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } } }; } }
Web API は .NET Framework 4.8 と ASP.NET Core 2.2 で作成
アクセストークンの検証には、IdentityServer4.AccessTokenValidation の v2.7.0 を使う。.NET Standard 2.0 をサポートしているのはそこまでなので。
Web API の実装は、ほぼ IdentityServer4 v2 系のドキュメントのまんま。
using System.Linq; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace SampleApi { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseUrls("http://localhost:5001") .UseStartup<Startup>(); } public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication(); app.UseMvc(); } } [Route("identity")] [Authorize] public class IdentityController : ControllerBase { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } } }
Web API を呼び出すクライアントを .NET Core 3.1 で作成
アクセストークンの取得に使うライブラリ IdentityModel は、.NET Standard 2.0 をサポートしているので .NET Framework 4.8 でも良かったけど、今回の実験においてクライアントは特に重要じゃ無いので .NET Core 3.1 にしておく。
using System; using System.Net.Http; using System.Threading.Tasks; using IdentityModel.Client; using Newtonsoft.Json.Linq; namespace SampleClient { class Program { static async Task Main(string[] args) { // メタデータからエンドポイントの情報を取得 var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); if (disco.IsError) { Console.WriteLine(disco.Error); goto END; } // トークンを取得 var tokenResponse = await client.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "client", ClientSecret = "secret", Scope = "api1" }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); goto END; } Console.WriteLine(tokenResponse.Json); // Web API を呼び出す var apiClient = new HttpClient(); apiClient.SetBearerToken(tokenResponse.AccessToken); var response = await apiClient.GetAsync("http://localhost:5001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); } END: Console.ReadLine(); } } }
実行結果
.NET Core 3.1 で動いている IdentityServer4 から取得したアクセストークンを使って、.NET Framework 4.8 で動いている Web API を呼び出すことに成功した。
まとめ
.NET Framework 4.8 で動いている Web API を、.NET Core 3.1 で動いている IdentityServer4 で問題なく保護できた。 OpenID Connect および OAuth 2.0 はちゃんと仕様が存在していて、IdentityServer4 はその仕様を実装しているだけ。 組み合わせるライブラリのバージョンが違っていても動くだろうとは思っていたので、今回の実験で確証が持てて一安心だ。