収支や資産を一元的に把握し、自分の家計と世帯の家計を分けて管理したい。そのために作ったのがNestraです。実務で使う技術を深めながら、REST APIとKeycloakによる認証・認可を学ぶ個人開発でもあります。
全体の構成
利用者はブラウザーでVercel配信のReactを使います。Reactからの認証とAPI通信は、Oracle Cloud上のCaddyを経由してKeycloakとSpring Bootへ届きます。両者のMySQLデータベースは分離しています。
Oracle Cloud / Docker Compose
MySQLサーバーは共用し、データベースを分離。外部公開はCaddyの80 / 443番ポートのみ。
運用費用を抑えることを選定の前提にしています。図は公開環境向けに用意した構成で、現在の稼働を示すものではありません。
認証はKeycloakへ
利用者がReactからログインを始め、Keycloakで認証します。Reactへ戻った後、認可コードとPKCEの検証値でアクセストークンを取得します。ReactがそのトークンをAPIへ送り、Spring SecurityがJWTを検証します。
OpenID Connect / 認可コードフロー + PKCE
-
利用者 Reactログインを開始Keycloakへ移動
-
Keycloak利用者を認証認可コードを返す
-
React KeycloakReactへ戻り、PKCEでコードを交換アクセストークンを取得
-
React Spring BootアクセストークンをAPIへ送信Spring SecurityがJWTを検証
パスワード管理をAPIから切り離すため、認証はKeycloakに集めました。家計データの利用可否はAPIで判断します。React側でも、トークンをWeb Storageへ保存しない構成にしています。
誰の家計かは、サーバー側で確かめる
サーバーは検証済みJWTの利用者識別子subから利用者を特定します。世帯が指定された場合は最新の所属と役割を確認し、個人または世帯の操作範囲を決めます。クライアントが指定したownerIdを認可の根拠にはしません。
sub誰が操作しているかownerId認可の根拠にしない世帯を切り替える指定は、操作する権限の証明にはなりません。X-Household-Idを受け取るたびに、データベース上の最新の所属と役割を確認します。リクエストで任意のownerIdを渡して、アクセス先を決める仕組みにはしていません。
口座などの参照先も、解決した範囲内で取り直します。世帯の選択だけを確認して、別の家計の口座を更新できてしまう抜け道を防ぐためです。
整合性はどこで守るか
収支の下書きを確定するときは、対象をロックしてから共通の収支処理を呼びます。台帳の履歴は追記のみとし、データベースのトリガーでも更新・削除を防ぎます。
テーブルの変更はFlywayで管理し、JPAは定義との一致を検証するだけにしています。APIには入出力用のDTOを設け、データベースの構造をそのまま返しません。
実装から学んだこと
今回扱ったのは、ログイン後の境界まで含めた設計です。認証が通っても、選択した世帯や参照先の口座を信用できるとは限りません。所有範囲を解決し、その範囲内でデータを取得するところまでを一続きにしました。
検証ではMySQLを使った結合テストと既存データの移行テストを用意しています。現在の品質確認はローカルの検証コマンドとリリース文書が中心で、CIは未導入です。
Codexには実装補助、文書整理、テスト追加を任せました。要件・技術選定・公開範囲は自分で決め、生成されたコードと文書をレビューしています。
既知の制約と今後の改善
- ソースコードとデモは現在公開していません。外部環境でのデプロイ・切り戻しの検証も今後の課題です。
- 追加の外部連携には、認証情報の設定と運用上の判断が必要です。
- 画面は日英に対応していますが、入力内容の自動翻訳は未導入です。翻訳系APIの利用経験はあるものの、個人開発では費用面から見送っています。