.NET Framework 4.8 で動いている Web API を .NET Core 3.1 で動いている IdentityServer4 で保護する

はじめに

大人の事情によって .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 を使う。

www.nuget.org

コードはほぼ 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 をサポートしているのはそこまでなので。

www.nuget.org

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 を呼び出すことに成功した。

f:id:griefworker:20200424110717p:plain

まとめ

.NET Framework 4.8 で動いている Web API を、.NET Core 3.1 で動いている IdentityServer4 で問題なく保護できた。 OpenID Connect および OAuth 2.0 はちゃんと仕様が存在していて、IdentityServer4 はその仕様を実装しているだけ。 組み合わせるライブラリのバージョンが違っていても動くだろうとは思っていたので、今回の実験で確証が持てて一安心だ。

アオアシ(20)

船橋学院戦はクライマックス。葦人が退場して1人少ないエスペリオンは、栗林を中心に10人でファイブレーンに挑み、見事同点に追いついたのは見事だった。退場してしまった葦人はまだ攻守コンプリートできるレベルではなかったことになるわけだけど、自分の力が足りていないことを認め立ち直ったのは流石。葦人は視野の広さ以上に、このメンタルの強さが最大の武器なのでは。自分を敵視していた阿久津に守備の教えを乞えるぐらいだから。

あと、葦人と花が和解したのも良かった。試合前の葦人の花に対する態度は最悪だったので、それで愛想付かしても仕方ないのに、涙を流すほど心配するなんて花さんマジ天使。ただ、葦人と花、杏里の三角関係は、杏里が脱落してしまいそうな気配。もうちょっと、花と杏里がバチバチやりあっているところを見たいんだが、サッカー漫画に求めることでは無いかなぁ。

stock

西中洲の水上公園にオープンしていた『stock』は、箱崎にある人気のパン屋『パンストック』の新店舗ということだった。パンストックは長い間行ってみたいと思っていたけど、場所が箱崎で駅からも離れていたので、子ども連れで公共交通機関使っていくのはキビシイ。一方で西中洲なら会社から近いので、昼休みに買いに行けそうだ。というわけで、買いに行ってきた。必要火急の用事で天神に行った妻が帰りに。

一番人気の明太フランス含む、目星しいものを合計7点ほど購入。

明太フランスは思ってたより明太子が辛かったが、さすが一番人気なだけあって、今回買った中で一番美味だった。子供用に買ったブリオッシュも、また買って欲しいと言ってきたので気にいったようだ。それ以外はまぁまぁ。期待が大き過ぎたのがいけなかったな。自分も妻も、ハード系パンよりはソフト系が好きみたいだ。それこそ街のパン屋さん的な。とはいえ、一度はパンストックのパンを食べてみたいと思っていたので、stock のパンを食べて、自分の中のパンストック熱はおさまった。まぁ満足。

r.gnavi.co.jp

五等分の花嫁(14)

五等分の花嫁は14巻でついに完結。風太郎は四葉を選んだけど、四葉がすんなり告白を受け入れなかったのは予想通り。転校の原因を作って他の姉妹に負い目を感じていたし、他の姉妹の気持ちも知っているし。そんな四葉に三玖は絶対に背中を押してあげないと宣言するが、それは他の姉妹の想いも背負ってほしいという、ある種のエールに思えた。自分は四葉になれないけど、四葉も自分にはなれない、と思えるくらい自分を好きになれた三玖の成長は感慨深いものがある。

そしてクライマックス。結婚式が終わって披露宴の前に最後の五つ子ゲームが。 ドレスの数が多すぎる伏線があったので、絶対何か仕掛けてくるとは思っていた。 風太郎が全員を当てていくところは胸熱で、五つ子を見分けられるようになったのはまさに愛だな。全ヒロインルートを描くことに挑戦するマンガもあるけど、五等分の花嫁はこの結末が一番。京都で出会ったのが四葉だと風太郎は気付いていないのが、再開してからの日々で四葉を選んだということなので、これがまた良い。

かぐや様は告らせたい(18)

石上は裏主人公なだけあって、どんどん良い男に成長しているな。中学時代の事件の真相が広まれば、周囲の評価は逆転しそうだけど、あえてそうしないのは石上の矜恃ゆえか。その事件の真相も、大仏がつばめ先輩に教えようとしている。あぁ、これは先輩が善意で真相を広めようとして、石上と衝突して拗れてしまう展開ですわ。

一方、修学旅行編の中心人物は早坂みたいだ。かぐや様と並んで気に入っているヒロインなので、かぐやの付き人を辞める話が出たときは辞めないで欲しいと思ったが、早坂の一日を知ってからは、むしろ解放してあげて欲しいと思い直した。修学旅行の舞台は四宮本家がある京都。四宮家の人物が続々登場しそうで、会長やかぐやがどんな活躍をするのか楽しみだ。きっと綺麗にまとめてくれるに違いない。

チーム・ジャーニー

本作は『カイゼン・ジャーニー』の続編。途中入社ながらいきなり開発チームのリーダーに抜擢されてしまった主人公の太秦が、まだチームになれていないグループ状態のメンバーたちを、アドバイスをもらいつつ四苦八苦しながら「1人の人間のようなチーム」へまとめ上げていく。最終的には複数のチームにまたがったマネジメントを行うまでに至るわけだが、その過程を読者が追体験できるよう小説仕立てになっている。

太秦たちのチームはプロダクト開発でスクラムに取り組むようになるわけだけど、成長するのはプロダクトだけじゃない。チームも成長が必要。チームの成長を計画するには、スクラムのスプリントでは短い。そこでスクラムの型に捉われず、数スプリントをまとめた「ジャーニー」という考え方を導入したのが、本作の一番の功績だと思う。そして、ジャーニーによっては新しい役割を追加したり、人に割り当てる役割を変えたりといった、フォーメーションを柔軟に変更していくやり方は新しい観点だった。

小説仕立てになっているので、問題が発生している状況を想像しやすい。その一方で、解決編での、実際に解決していってる過程の描写が足りてない気がする。解決編と言いながら、真の解決は次の章に持ち越しも何回かあるし。あと、太秦は第一部と第二部で、別人と思うくらいの成長っぷりだった。

カイゼン・ジャーニーの続編なだけあって、蔵屋敷や西方、ウラットといったお馴染み(?)の面々が登場してきて、読んでいてフフッとなる。もちろん前作の主人公江島も。その江島がまさかの役職になっていて、いやぁ、出世したなぁ。もちろん、前作を読んでいなくても読み進めることができるようになっているので問題ない。読んでいればより楽しめる、という感じ。

Bitbucket のイシュークライアント『Bitissues』のソースコード公開

はじめて AppStore に公開したアプリである、Bitbucket イシュートラッカーの iOS アプリ『Bitissues』は、Apple Developer Program の費用を捻出できず、公開停止したまま放置してしまっていた。そして、GitHub が無料アカウントにプライベートリポジトリが解放されてからは、完全に GitHub に移行してしまって Bitbucket 自体もう使わなくなってしまった。

tnakamura.hatenablog.com

もう Bitbucket を使っていないから、Bitissues を保守するモチベーションは無いけど、このままお蔵入りしてしまうのも勿体ない。そこで、供養として GitHubソースコードを公開することに決めた。

github.com

Bitissues は最初 Objective-C で作り、一時的に流行った RubyMotion で書き直し、Swift が出て再度書き直した。公開するのは、Swift で書き直した Ver3 のもの。Swift が出た当初の実装なので、利用しているライブラリは Objective-C で使っていたものを引き継いでいる。ReactiveCocoa や AFNetworking は今だと使わないだろう。見所があるとしたら、ReactiveCocoa を使い MVVM で実装しているところかな。