Django テナントでサブドメインのログインが中断される理由: 現実世界のパズル
すべてのサブドメインが異なるテナントにサービスを提供し、ユーザー認証をシームレスに統合するマルチテナント Django アプリケーションを構築することを想像してください。すべてが完璧に見えますが、サブドメインのログイン ページで恐ろしい事態が発生するまでは 500 内部サーバーエラー。あなたは頭をかいて、なぜそうなるのかと不思議に思いました。 プライマリドメイン ログインは問題なく機能しますが、サブドメインのログインは機能しません。 🤔
この問題は、管理パネルにログインできるため、システムがユーザーを明確に認識しているという矛盾のように感じられ、イライラさせられます。ログインすると、テナント固有のページにアクセスでき、フォームを正常に送信することもできます。ただし、ログイン ページにアクセスすると、次のエラーが表示されます。 「予期しないトークン」 ボンネットの下で実際に何が起こっているのでしょうか?
関連のある例を共有しましょう。これは、家に 2 つのドアがあるようなものです。1 つは来客用 (メイン ドメイン)、もう 1 つは家族用 (サブドメイン) です。客用ドアは正常に作動しますが、家族用ドアが詰まります。キーが正しいことはわかっていますが、データベース スキーマ クエリにおける予期せぬ不一致など、ロック メカニズムにもっと深い問題があります。
問題の根本は、Django Rest フレームワークの仕組みにあります。 トークン認証 と相互作用します ジャンゴテナント 図書館。具体的には、トークンは パブリックスキーマ テナント スキーマの代わりに、 外部キー違反 エラー。この問題を詳しく調べて原因を明らかにし、すべてのサブドメインのログイン ドアを修正しましょう。 🔧
指示 | 使用例 |
---|---|
schema_context() | マルチテナントの Django セットアップでスキーマを切り替えることができます。例: with schema_context('tenant_name'): 指定されたテナントのデータベース スキーマで操作が実行されるようにします。 |
authenticate() | 資格情報を使用してユーザーを認証します。例: user =authenticate(request, username=username,password=password) は、指定された資格情報が有効かどうかを確認します。 |
Token.objects.get_or_create() | ユーザーの既存のトークンを取得するか、存在しない場合は作成します。例: トークン、作成済み = Token.objects.get_or_create(user=user)。 |
csrf_exempt | 特定のビューの CSRF 保護を無効にします。例: @csrf_exempt は、外部またはブラウザ以外の API リクエストを処理するときに使用されます。 |
connection.tenant.schema_name | Django マルチテナント アプリの現在のテナントのスキーマ名を取得します。例: tenant_schema_name = connection.tenant.schema_name。 |
JsonResponse() | JSON 形式のデータを HTTP 応答として返します。例: return JsonResponse({"ステータス": "成功", "トークン": token.key})。 |
APIClient() | テストで HTTP リクエストをシミュレートできる Django Rest Framework テスト クライアント。例: self.client = APIClient()。 |
localStorage.setItem() | キーと値のペアをブラウザのローカル ストレージに保存します。例: localStorage.setItem('token', data.token) は、将来の使用に備えてトークンを保存します。 |
Swal.fire() | SweetAlert2 ライブラリを使用してアラート ポップアップを表示します。例: Swal.fire({icon: 'error', title: 'Login Failed'}) は、スタイル付きのエラー メッセージを表示します。 |
TestCase | Django で単体テストを作成するために使用されます。例: class TenantLoginTest(TestCase): スキーマ固有のログイン テスト用のテスト クラスを作成します。 |
Django-Tenants でのテナント固有の認証の習得
上記で提供されたスクリプトは、トークンが パブリックスキーマ 適切なテナント スキーマの代わりに。この現象は、Django Rest Framework (DRF) がトークン モデルと対話するときにスキーマを自動的に切り替えないために発生します。これを解決するために、 ジャンゴテナント 図書館の スキーマ_コンテキスト メソッドを使用すると、正しいテナントのスキーマ内でデータベース クエリを明示的に実行できるようになります。これにより、プライマリ ドメイン経由でアクセスするかサブドメイン経由でアクセスするかに関係なく、ユーザー認証とトークンの取得が各テナントでシームレスに機能することが保証されます。この調整を行わないと、システムが間違ったスキーマでユーザー レコードを検索するため、ForeignKeyViolation エラーが発生します。
「dual_login_view」関数は、データベース接続がテナント スキーマを指していることを確認しながらユーザーを認証する方法を示します。まず、リクエスト ペイロードからユーザー名とパスワードを抽出します。次に、「authenticate」メソッドを使用して資格情報を検証します。成功すると、ユーザーがログインし、DRF の「Token.objects.get_or_create()」メソッドを使用してトークンが生成されます。このクエリが正しいスキーマをターゲットにしていることを確認するために、「schema_context」関数がロジックをラップし、データベース コンテキストをアクティブなテナント スキーマに切り替えます。これにより、システムが正しいユーザーとトークンのレコードを見つけられることが保証され、スキーマの不一致エラーが排除されます。
`TenantAwareLoginAPIView` クラスは、モジュラー アプローチに Django Rest Framework の APIView を採用することでソリューションを強化します。ユーザーの資格情報を含む POST リクエストを受け入れ、「authenticate」を使用して検証し、資格情報が正しい場合はトークンを生成します。重要なのは、「schema_context」を使用して、正しいテナント スキーマ内ですべての操作を実行することです。このクラスベースのビューは、エラー処理を一元化し、明確で構造化された応答を提供するため、最新の API 実装に最適です。たとえば、JSON トークンを返すと、フロントエンドがそれをローカル ストレージに保存し、後続の認証されたリクエストに使用できるようになります。
フロントエンドでは、JavaScript フォーム送信スクリプトが、ログイン エンドポイントに対して安全で構造化されたリクエストを行う上で重要な役割を果たします。これにより、デフォルトのフォーム動作が防止され、入力フィールドが検証され、フェッチ API リクエストを介して CSRF トークンとともに認証情報が送信されます。成功した応答を受信すると、トークンは「localStorage」に保存され、ユーザーはリダイレクトされます。サーバーがエラーを返した場合、SweetAlert2 ライブラリはわかりやすい警告メッセージを表示します。これにより、ユーザー エクスペリエンスがよりスムーズになり、適切なエラー フィードバックが保証されます。たとえば、テナントのサブドメインにアクセスすると、有効な資格情報でログインしたユーザーにはすぐに成功メッセージが表示され、アプリケーション ダッシュボードにリダイレクトされます。 🔒
最適化されたスキーマ クエリを使用した Django テナントでのサブドメイン ログインの問題の処理
明示的なスキーマ選択とエラー処理を備えた Django ORM を使用したバックエンド ソリューション。
# Import necessary libraries
from django.db import connection
from rest_framework.authtoken.models import Token
from django.contrib.auth import authenticate, login
from django.http import JsonResponse
from django_tenants.utils import schema_context
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def dual_login_view(request):
"""Handle login for multi-tenant subdomains with correct schema."""
if request.method == "POST":
username = request.POST.get("login")
password = request.POST.get("password")
tenant_schema_name = connection.tenant.schema_name
try:
# Switch to the correct tenant schema
with schema_context(tenant_schema_name):
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# Generate or retrieve token
token, created = Token.objects.get_or_create(user=user)
return JsonResponse({"status": "success", "token": token.key})
else:
return JsonResponse({"status": "error", "message": "Invalid credentials"}, status=400)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
return JsonResponse({"status": "error", "message": "Invalid request method"}, status=405)
テナント対応スキーマを使用した明示的なトークン管理
マルチテナント アーキテクチャでログインするための、モジュール化され再利用可能な Django API ビュー。
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate
from rest_framework.authtoken.models import Token
from django_tenants.utils import schema_context
class TenantAwareLoginAPIView(APIView):
"""Login endpoint that ensures tenant-aware schema handling."""
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
tenant_schema_name = request.tenant.schema_name
if not username or not password:
return Response({"error": "Username and password required"}, status=status.HTTP_400_BAD_REQUEST)
try:
with schema_context(tenant_schema_name):
user = authenticate(request, username=username, password=password)
if user is None:
return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
# Generate or retrieve token for the user
token, created = Token.objects.get_or_create(user=user)
return Response({"token": f"Token {token.key}"}, status=status.HTTP_200_OK)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
サブドメインのログイン要求を処理するためのフロントエンド スクリプト
フォームの送信を処理し、テナントのサブドメインのトークンベースのログインを処理する JavaScript ソリューション。
<script>
document.querySelector('form').addEventListener('submit', function(event) {
event.preventDefault();
let form = event.target;
let formData = new FormData(form);
fetch("{% url 'tenant_aware_login' %}", {
method: 'POST',
body: JSON.stringify(Object.fromEntries(formData)),
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
})
.then(response => {
if (!response.ok) throw new Error('Server Error');
return response.json();
})
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = '/';
} else {
Swal.fire({
icon: 'error',
title: 'Login Failed',
text: data.error || 'Invalid credentials'
});
}
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
スキーマ対応トークン認証を検証するための単体テスト
Python で単体テストを行い、API がスキーマの切り替えを正しく処理することを確認します。
from django.test import TestCase
from rest_framework.test import APIClient
from django_tenants.utils import schema_context
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class TenantLoginTest(TestCase):
def setUp(self):
self.client = APIClient()
with schema_context('test_tenant'): # Switch to tenant schema
self.user = User.objects.create_user(username='testuser', password='testpass')
def test_successful_login(self):
with schema_context('test_tenant'):
response = self.client.post('/api/login/', {
'username': 'testuser',
'password': 'testpass'
})
self.assertEqual(response.status_code, 200)
self.assertIn('token', response.json())
def test_invalid_login(self):
with schema_context('test_tenant'):
response = self.client.post('/api/login/', {
'username': 'wronguser',
'password': 'wrongpass'
})
self.assertEqual(response.status_code, 401)
self.assertIn('error', response.json())
マルチテナント Django アプリにおけるテナント固有のトークン クエリの役割を理解する
大きな側面の 1 つは、 マルチテナントの Django アプリ データベース操作が常に正しいテナント スキーマ内で行われるようにすることです。この場合の問題は、Django のデフォルトの動作が単一の共有スキーマを前提としており、トークンまたはユーザーがスキーマ内で見つからない場合にエラーが発生するために発生します。 パブリックスキーマ。のようなツールを活用することで、 schema_context からの関数 ジャンゴテナント ライブラリでは、テナント固有のクエリを実行するためにスキーマを明示的に切り替えます。これにより、ユーザーとトークンの認証クエリが正しいスキーマに確実に送信されます。
見落とされがちなもう 1 つの重要な詳細は、 Token.objects.get_or_create() が動作します。デフォルトでは、アクティブなデータベース スキーマでユーザー レコードを検索します。現在のスキーマが正しくない場合、クエリは次のエラーで失敗します。 外部キー違反 エラー。これを修正するために、トークン モデルに関係するクエリが適切なテナント スキーマ コンテキスト内で発生するようにします。この調整を行わないと、ユーザーの ID がデフォルトのスキーマに見つからないため、有効なユーザーであっても認証に失敗します。
さらに、フロントエンド コードは、これらのバックエンド プロセスと効果的に通信する上で重要な役割を果たします。フェッチ API が確実に送信するようにする CSRFトークン JSON 応答を適切に処理することが重要です。たとえば、API 呼び出しを try-catch ブロックでラップし、次のような使いやすいライブラリを使用してエラーを処理します。 SweetAlert2 使いやすさが向上します。これらの機能強化により、サブドメイン間の切り替えやスキーマ固有のエラーが発生した場合でも、ログイン フローがシームレスに維持されることが保証されます。たとえば、すべての企業 (テナント) がサブドメインを使用する SaaS プラットフォームを想像してください。スキーマ コンテキストを修正することで、すべての従業員が中断することなくスムーズにログインできるようになります。 🚀
マルチテナント Django のログイン問題に関するよくある質問
- 原因は何ですか 500 内部サーバー エラー ログイン中?
- エラーが発生する理由は、 Token.objects.get_or_create() 間違ったスキーマをクエリするため、ユーザー レコードの検索時に不一致が発生します。
- トークン クエリが正しいテナント スキーマを指していることを確認するにはどうすればよいですか?
- 使用 schema_context() からの ジャンゴテナント ライブラリを使用してクエリの実行をラップし、正しいスキーマに切り替えます。
- 管理パネルのログインは機能するのに、ユーザーのログインが失敗するのはなぜですか?
- Django 管理者はスキーマ コンテキストを自動的に調整しますが、カスタム ビューは authenticate() または Token.objects 明示的に設定しない限り、そうではない可能性があります。
- フロントエンドでログイン トークンを取得して保存するにはどうすればよいですか?
- フェッチ API を使用して認証情報を送信し、次を使用して応答トークンを保存します。 localStorage.setItem() 永続的な認証の場合。
- ログインに失敗した場合に、より適切なエラー メッセージを表示するにはどうすればよいですか?
- 次のようなライブラリを使用してフロントエンド アラートを実装します。 SweetAlert2 不正な資格情報またはサーバーの問題をユーザーに通知するため。
テナントのサブドメイン間でのスムーズなログインの確保
Django マルチテナント アプリでのログインの失敗を解決するには、すべてのデータベース クエリが適切なスキーマで動作することを確認する必要があります。スキーマ コンテキストなどのツールを明示的に使用することで、ユーザー トークンが正しいテナント データベースからフェッチされることを保証し、スキーマの競合を回避できます。
ユーザーがサブドメインでのみログイン失敗に直面する SaaS プラットフォームで作業していることを想像してください。スキーマを適切に切り替えると、これらの問題が解決され、シームレスな認証が保証されます。この修正を採用すると、改善されるだけでなく、 ユーザーエクスペリエンス だけでなく、各テナントに対する安全で効率的なデータ アクセスも保証します。 🔧
Django とテナントのサブドメインの問題を理解するためのソースと参考資料
- 詳細なドキュメントについては、 ジャンゴテナント マルチテナント アプリケーションでのスキーマ管理について説明するライブラリ。以下で入手可能です: Django テナントのドキュメント 。
- トークン認証に関する公式 Django Rest Framework (DRF) ドキュメント。詳細については、以下をご覧ください。 DRFトークン認証 。
- マルチテナント環境での schema_context の使用に関する包括的なガイド。次の場所にあります: GitHub - Django テナント 。
- Django アプリケーションでの CSRF トークンの処理に関する洞察: Django CSRF ドキュメント 。
- ユーザー認証を含む、マルチテナント SaaS プラットフォームを設計するためのベスト プラクティス: SaaS Pegasus マルチテナント ガイド 。