Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Djangoにおける複数ユーザー種別認証の設計アプローチ@DjangoCongress J...

delhi09
February 21, 2025

Djangoにおける複数ユーザー種別認証の設計アプローチ@DjangoCongress JP 2025

delhi09

February 21, 2025
Tweet

More Decks by delhi09

Other Decks in Programming

Transcript

  1. 2つ以上のユーザー種別の要件の例 サービス ユーザー種別1 システムの使い方 ユーザー種別2 システムの使い方 ECサイト 販売者 • 商品登録

    • 在庫変更 購入者 • カートに追加 • 購入 記事配信サイト ライター • 記事執筆 • 公開管理 購読者 • 記事にコメント • 有料記事の購入 ホテル予約サイト 事業者 • 予約管理 • 部屋情報の更新 宿泊者 • 予約 • キャンセル 2つ以上のユーザー種別 こういう要件がある時のDjangoでのユーザー認証の設計アプローチについて話す システムの使い方(≒ユー スケース)が大きく異なる
  2. ターゲット • ターゲット • Django中級者以上向け • 業務でDjangoを使っていて、設計も担当する人 • 想定している前提知識のレベル •

    Djangoの基本(Model、View...)は理解してる • ざっくりとでもいいので、DjangoのUserモデルのカスタマイ ズ方法を理解している
  3. 話さないこと: 理由 端的にいうと私に知見がないから • 前提: 弊社は受託開発がメイン事業 • 0→1の新規開発や事業が軌道に乗りきる前のお客さんが多い • →

    アーキテクチャは素早くユーザーに価値を提供できるモノリス を選択することが多い • サービス分割的な設計はお客さんの事業フェーズ的に選択肢に 入ってこないことが多い • ※ 直接ユーザーに価値を提供しない部分に時間を取られる • サービス間でデータやり取りする用のAPIの開発 • サービスを跨いだトランザクションの考慮 • etc...
  4. お品書き • 複数ユーザー種別の認証について考えることの重要性 • Djangoにおける複数ユーザー種別の認証 • 具体的な設計パターンの話 • 1.typeフィールドパターン •

    2.認証モデル共有パターン • 3.認証モデル切り替えパターン • 4.独自実装パターン • 全体のまとめ 課題設定の話
  5. 【再掲】2つ以上のユーザー種別の例 • サービスの「提供者」と「受け手」という観点で改めて見てみ る サービス 提供者 システムの使い方 受け手 システムの使い方 ECサイト

    販売者 • 商品登録 • 在庫変更 購入者 • カートに追加 • 購入 記事配信サイト ライター • 記事執筆 • 公開管理 購読者 • 記事にコメント • 有料記事の購入 ホテル予約サイト 事業者 • 予約管理 • 部屋情報の更新 宿泊者 • 予約 • キャンセル 提供者と受け手はシステムの使い方 (≒ユースケース)が大きく異なる
  6. 紹介する設計パターン 1. typeフィールドパターン(非推奨) 2. 認証モデル共有パターン 3. 認証モデル切り替えパターン 4. 自前実装パターン ※

    (参考)私の業務経験 • 2,4の設計で作られた本番稼働してるシステムにそれぞれ関わった経 験がある • 3は業務経験はないが社内でアイディアとしては時々聞くので PoC(Proof of Concept)を作って検証してみた
  7. typeフィールドパターンの問題点 • ①モデルの制約によるデータ保護ができない • (例)merchant_employee_no( 従業員番号) • → 販売者の場合は必ず必要なフィールド •

    →販売者と購入者でモデルを共有しているからフィール ドで必須制約がかけられない • merchant_employee_no = models.CharField("従業員番号", max_length=32, blank=True) • ②販売者/購入者それぞれに固有のフィールドを全てShopUserモ デルに持つことによるモデルの肥大化による保守性の低下を招 く
  8. ②なぜAbstractUserではなく AbstractBaseUserを継承するのか • ①のAuthenticationの命名の理由と関連する • 前提: Authenticationの責務は認証情報を持つことなので、でき るだけ関係のない情報は保持したくない(理想はログインIDとパ スワードだけ) •

    → AbstractUserはlast_nameとfirst_nameを保持している • → これらは認証とは関係がないのでAuthenticationには持たせた くない • (アカウント作成に氏名は不要とか逆にミドルネームも考慮 したいとかあるかもしれない) • → AbstractBaseUserを継承
  9. 新規会員登録Viewの実装 • UserManagerにcreate_user()という 便利メソッドがあるので使う • https://docs.djangoproject.com/j a/5.1/topics/auth/default/#creati ng-users • Autentication.usernameがDjangoの仕

    様でユニークなので、重複を防ぐ ためにprefixをつけた方がいい • -> やらない場合「 販売者側で 使用済みのユーザー名を購入 者ユーザーが登録できない」 という事象が発生(仕様として 許容するかは要件次第) ②作成したAuthenticationを渡して Merchantを作成 ①Authentication を作成
  10. なぜ独自ミドルウェアを実装した方がい いのか? • Viewの中で毎回以下のようなコードを書くことになる • if request.user.is_authenticated and request.user.is_merchant: •

    merchant = request.user.merchant • merchant.do_something() • → 処理の本質ではないコードを毎回書かないといけなくて無駄 なコスト & 可読性・保守性が下がる • → Viewに渡る前に共通処理にして見通しをよくしたい
  11. 独自ミドルウェア実装 • AuthenticationのForeign Keyで取得 したユーザーモデルをrequestオ ブジェクトに詰めてあげる • Viewからは request.merchant.do_something() のように使える

    • request.userはdelしてViewから参照できなく してしまうとよさそう(案件での実績なし) • 理由:コーディング規約で「request.userは使 わないこと」と書くより、コードレベルで参 照できなくした方が強制力が強い
  12. 退会機能って意外と難しい • 退会フラグは避けたい • 理由1: 退会フラグ = Falseをつけ忘れたら即重大なバグへ • 理由2:

    個人情報がDB上に丸っと残るのでサービスによって は個人情報保護ポリシーなどに抵触する可能性がある • 全部物理削除 & FKにSET_NULLする方式だと情報の追跡可能性に 問題が出る • この商品登録したの誰だろう? • 退職した誰かっかっぽいけど分からないですね... • 物理削除する情報と残す情報を分けるのが理想 • → 削除する情報と残す情報の仕分けが難しい
  13. メリット/デメリット • メリット • Djangoの認証機能を使える • 擬似的に複数種別のユーザーを表現しているだけで内部的に は1つの認証ユーザー • →

    Djangoのレールに乗っているから安心 • カスタマイズ実装が必要な箇所はあるがそんなに難しくない • デメリット? • フレームワークの制約でDB設計が歪む • -> どういうことか次のスライドで説明
  14. (補足)認証モデル共有パターンの別のア プローチ • Django中級者以上向けの有名な本である『Two Scoops of Django』の 著者の方が動画(2つで約30分)で紹介しているアプローチ • Multiple

    User Types With Custom Data Fields | Django • https://youtu.be/f0hdXr2MOEA • Multiple User Types With Custom Data Fields | Django(続編) • https://youtu.be/V3zRZ6XRols • プロキシモデルを使用したアプローチ • https://docs.djangoproject.com/ja/5.1/topics/db/models/#proxy- models • この動画も観て本発表と比較してみると面白いと思います!
  15. 生成されるテーブルのイメージ • 各ユーザーテーブ ルにusernameと passwordを持つ • →auththentication テーブル的なもの は不要 •

    Django Admin用に はauth.Userをその まま使用 customer, merchant, auth_userの3ユーザー種別のテーブルが作成される
  16. やること 1. MerchantモデルとCustomerモデルの定義 2. 各環境にAUTH_USER_MODELを設定 3. 新規会員登録機能の実装 4. ログイン機能の実装 5.

    Djangoのアプリを分ける 6. ユニットテスト 7. マイグレーション 本パターン特有のトピック
  17. 【解説】なぜAbstractUserではないのか? • AbstractUserを継承した3つのユーザーモデルがGroup、 Permissionとそれぞれ関連を作成しようとしてrelated_name重複 エラーが起きる $ docker compose exec admin_app

    python manage.py makemigrations SystemCheckError: System check identified some issues: ERRORS: auth.User.groups: (fields.E304) Reverse accessor 'Group.user_set' for 'auth.User.groups' clashes with reverse accessor for 'shop.Customer.groups'. HINT: Add or change a related_name argument to the definition for 'auth.User.groups' or 'shop.Customer.groups' (...省略)
  18. Djangoのアプリを分ける • 前提: 一般的な話としてDjangoのアプリは適切な粒度で分けたほ うがいい(tell-kさんのDjangoCongress JP 2022の発表を参照) • 『Djangoのアプリはどういう単位で作るべきか?』 •

    https://tell-k.github.io/djangocongressjp2022/ • ただ特に「認証モデル切り替えパターン」では認証の主体にな るユーザー種別ごとにアプリを分けたほうがいい理由がある
  19. 方法1: 全てのテストに@override_settings をつける • メリット • テスト実行が1コマンドで済む • デメリット •

    毎回デコレータを付与するのが手間 • 環境変数が増えたときに全テストにデ コレータ追加する必要がある
  20. テスト実行をAUTH_USER_MODELの種別 ごとに分ける • AUTH_USER_MODELがMerchantモデル前提のテスト • docker compose exec merchant_app python

    manage.py test merchant/ • AUTH_USER_MODELがCustomerモデル前提のテスト • docker compose exec customer_app python manage.py test customer/ • ※前提: AUTH_USER_MODELごとにテストケースがディレクトリ でグルーピングされている必要がある • → さっきのDjangoアプリを分けるのが大事という話と繋がる
  21. 方法2: テスト実行をAUTH_USER_MODELの 種別ごとに分ける • メリット • デコレータの付与が不要 • デメリット •

    全テスト実行する場合にコマンドを複数回実行する必要があ る • →全テスト実行はCIでやればいいから大きなデメリットではな いので、方法2の方がよさそう(私の意見)
  22. Django側が想定しているAUTH_USER_MODEL の使い方の範疇を超えている • AUTH_USER_MODELは自前でカスタマイズしたUserモデルを認 証モデルとして設定可能にするためのもの • → 1つのソースコードで環境ごとに認証ユーザーを切り替える 用途まではユースケースとして公式に書かれていない •

    https://docs.djangoproject.com/ja/5.1/topics/auth/customizing/# substituting-a-custom-user-model • Django doesn't have multiple users. (Jazzband所属の人の回答) • https://stackoverflow.com/questions/25841712/django-best- approach-for-creating-multiple-type- users/25842236#25842236
  23. そもそもセキュリティ観点で独自実装は 選択肢としてありなのか? • 回答: おすすめしない(後述)が論外と言えるほどではない • 例えば、IPAはセッション管理の仕組みの独自実装は推奨してい ない • 安全なウェブサイトの作り方

    - 1.4 セッション管理の不備 • 但し、本パターンは本当にゼロから認証機能を自作するわけで はない • django.contrib.authのソースコードを参考にする • Djangoが提供している認証機能用の関数は使う
  24. 私が設計担当者ならどれから考えるか • まずは認証モデル共有パターンから考える • 多くのケースで特に不満はなさそう • 「1つの認証ユーザー」というDjangoのレールにも乗ってるので 安心 • 認証モデル切り替えパターン:

    • ハック気味なパターン(主観)なので見えてないリスクがありそう で怖い • 認証モデル共有パターンと比較して得られるメリットを考えた時 に、リスクを冒すに値するほどのメリットはなさそう • 自前実装パターン: • 工数を割いたりセキュリティリスクを背負ったりしてまでやる価 値のあるケースは少なそう
  25. typeフィールドパターン • https://www.youtube.com/watch?v=V3zRZ6XRols • 『Multiple User Types With Custom Data

    Fields | Django』 • Django中級者以上向けの有名な本である『Two Scoops of Django』の著者の方の動画 • 動画内では推奨しないパターンとして紹介されている • 4:30頃から
  26. 認証モデル共有パターン • https://youtu.be/f0hdXr2MOEA • 『Multiple User Types | Django』 •

    Django中級者以上向けの有名な本である『Two Scoops of Django』の著者の方の動画 • 続編: https://www.youtube.com/watch?v=V3zRZ6XRols • https://nwpct1.hatenablog.com/entry/django-auth-patterns • 『 Django における認証処理実装パターン』 • c-bataさんの記事 • ユーザーモデルのカスタマイズ方法や認証バックエンドの話 など勉強になる
  27. 認証モデル共有パターン • https://speakerdeck.com/moro/identifying-user-idenity • 『Identifying User Idenity』 • Kaigi on

    Rails 2024の発表 • usersテーブルとuser_credentialsテーブルを分ける設計や退会 の表現方法などのアイディアを参考にさせて頂いた
  28. 認証モデル差し替えパターン • https://github.com/shimizukawa/django-multiple-type-user-auth • 弊社のshimizukawaさんの同パターンのPoC実装 • https://tell-k.github.io/djangocongressjp2022/ • 『Djangoのアプリはどういう単位で作るべきか?』 •

    「5.Djangoのアプリを分ける」で言及した弊社tell-kさんの DjangoCongress JP 2022の発表 • https://stackoverflow.com/questions/25841712/django-best- approach-for-creating-multiple-type-users/25842236#25842236 • “django best approach for creating multiple type users”という質 問へのJazzband所属の人の回答
  29. 自前実装パターン • https://www.ipa.go.jp/security/vuln/websecurity/session- management.html • 『安全なウェブサイトの作り方 - 1.4 セッション管理の不備』 •

    引用したIPAのドキュメント • https://scrapbox.io/shimizukawa/Djangoの認証を独自に実装してはいけ ない • shimizukawaさんがDjangoの認証を独自で実装すべきではないと考 えている理由のメモ • https://github.com/beproud/django-newauth • 弊社でDjangoの認証ライブラリの自作を試みたもの