はじめに
.NETで標準的(?)に利用されているOIDCプロバイダーであるIdentityServer 4ですが、現在のバージョンの更新は2022/11までになっています。移行先はDuende Softwareが公開するDuende IdentityServerです。
IdentityServer 4 はデータベースにEntityFrameworkを使っているか、利用中のバージョンが4.x以前かどうかで移行方法が大きく変わってきます。それぞれの移行方法はDuendeのドキュメントにまとめられているので確認してみてください。この記事では、Duende IdentityServerへの前段階として3.x -> 4.x への移行でドキュメントには書かれていない部分を中心に説明します。
IdentityServer 4とDuende IdentityServerの使用料的なはなし
今回はIdentityServer 4のv3.x -> v4.x のバージョンアップなので直接の話題ではありませんが、後継のDuende IdentityServerは独自のDUENDE™ SOFTWARE LICENSE AGREEMENTを利用しているため、今後Duende IdentityServerを商用で利用する場合は注意が必要です。
ライセンスの内容は各自確認していただくとして、お金の話だけだとざっくりこんな感じです。
- 個人が非商用で利用する場合は無料
- 事業体全体の収入が100万米ドル未満であれば無料
- 事業体全体の収入が100万米ドル以上であれば有償
→ 価格表
移行手順
ざっくり下記の3つのステップを実施します。
- ライブラリのバージョンアップ
- 3.x->4.xでブレーキングチェンジとなった項目の修正
- PersistedGrantDbContext及びConfigurationDbContextのマイグレーション適用(使っていれば)
うちの認証サーバーではPersistedGrantDbContextとConfigurationDbContextは利用していないので、この記事では触れませんがそれぞれのRDBMS毎のマイグレーション方法がMigrating Your IdentityServer4 v3 Database to IdentityServer4 v4に詳しくまとまっているので確認してみてください。
1. ライブラリのバージョンアップ
NuGetで置き換えてもよいですし、csprojファイルを直に編集してもよいです。
うちの環境では、PersistedGrantStoreに前述の通りデータベースではなくRedisを使っているので、関連するIdentityServer4.Contrib.RedisStore
も同時にバージョンアップしています。
<ItemGroup>
- <PackageReference Include="IdentityServer4" Version="3.0.2" />
+ <PackageReference Include="IdentityServer4" Version="4.1.2" />
- <PackageReference Include="IdentityServer4.Contrib.RedisStore" Version="3.1.2" />
+ <PackageReference Include="IdentityServer4.Contrib.RedisStore" Version="4.0.0" />
... 略 ...
</ItemGroup>
2. 3.x->4.xでブレーキングチェンジとなった項目の修正
IdentityServerのこのIssueが参考になります。Migration from v3 to V4対応が必要だった項目を見ていきましょう。
ApiResourceから細分化されたApiScopeを追加する
IdentityServer 4 v3.xでは、次のようにApiResourceの中にそのApiリソースで利用するスコープを直に記載していました。
public class Config
{
public IEnumerable<ApiResource> ApiResources =>
new ApiResource[] { new ApiResource("api1", "api1 description") };
// ... 略 ...
}
var builder = services.AddIdentityServer()
.AddOperationalStore(options =>
{
options.RedisConnectionString = "xxxxxx";
options.KeyPrefix = cfg.CachingStorePrefix;
})
.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.IdentityClients)
.AddProfileService<CustomProfileService>();
IdentityServer 4 v4.xでは、ApiResouce が参照する ApiScope を汎用的に扱えるように別々に定義するように仕様変更が行われています。今回のような単純なAPI定義であれば、ApiResouce と同じ内容の ApiScope を定義して Startup 時に登録してあげればよいです。
public class Config
{
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[] { new new ApiScope("api1", "api1") };
public IEnumerable<ApiResource> ApiResources =>
new ApiResource[] { new ApiResource("api1", "api1 description") };
// ... 略 ...
}
public void ConfigureServices(IServiceCollection services)
{
// ... 略 ...
var builder = services.AddIdentityServer()
.AddOperationalStore(options =>
{
options.RedisConnectionString = "xxxxxx";
options.KeyPrefix = cfg.CachingStorePrefix;
})
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.IdentityClients)
.AddProfileService<CustomProfileService>()
;
// ... 略 ...
}
詳細については、Defining ResourcesドキュメントにV4の変更点とマイグレーションステップを参照してください。
このドキュメントの v3.x 版と v4.x 版を見比べてもどのような変更があったかを確認できると思います。
https://identityserver4.readthedocs.io/en/3.1.0/topics/resources.html#defining-api-resources
https://identityserver4.readthedocs.io/en/latest/topics/resources.html#api-resources
クレームにaudクレームが含まれない場合は、EmitStaticAudienceClaim を有効にする
トークンのリクエスト方法によっては、audクレームが含まれない場合があります。
その場合はEmitStaticAudienceClaimプロパティーをtrueにしてあげると含まれるようになります。
public void ConfigureServices(IServiceCollection services)
{
// ... 略 ...
var builder = services.AddIdentityServer(options => {
options.EmitStaticAudienceClaim = false;
})
// ... 略 ...
}
削除されたPublicOriginの代わりにミドルウェアを作成する
v3.x では、リバースプロキシの後方に IdentityServer を配置した場合に、ホスト名を正しく解決するためにPublicOriginというプロパティーが用意されていました。v4.x ではこのプロパティーが削除されているため、startup 時に新たにミドルウェアを登録して解決してあげます。
https://github.com/IdentityServer/IdentityServer4/issues/4535
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ... 略 ...
app.Use(async (ctx, next) =>
{
ctx.SetIdentityServerOrigin("https://foo.com");
await next();
});
// ... 略 ...
}
token エンドポイントのリクエスト時にコンテンツタイプ multipart/form-data が拒否される
もともとOpenId Connectの仕様書では token エンドポイントへのリクエストには、application/x-www-form-urlencoded で行うように記載されていましたが、v4.x 以前まではコンテンツタイプが multipart/form-data であっても処理を受け付けていました。
v4.x では、このIssueの対応時にコンテンツタイプが application/x-www-form-urlencoded 出ない場合はエラーを返すように修正されています。
token エンドポイントリクエスト時にコンテンツタイプを multipart/form-data で送ってきているクライアントには、リクエスト時のコンテンツタイプを application/x-www-form-urlencoded に変更してもらう必要があります。
QuickStart変更に伴う修正
IdentityServer をアプリに組み込む場合は、リファレンス実装となる QuickStart をベースに組み込みを行うことが多いです。
QuickStart も v4.x リリース時に大きく変更が入っているので、QuickStart をそのまま利用していたりする場合はこのあたりも合わせて確認する必要があるかもしれません。
おわりに
とりあえず、コミットログをたよりに修正した内容を列挙してみました。
さて、次は Duende IdentityServer への移行だけれど、これもちょっと腰が重いなぁ。
このレベルのミドルウェアはMSが保守してほしい気がしないでもないです...