web-dev-qa-db-ja.com

djangoでユーザーを強制的にログアウトさせる方法は?

My Django app特定の条件下で、ユーザー名でユーザーを強制的にログアウトできるようにする必要があります。必ずしも現在ログインしているユーザーではなく、他のユーザーです。ビューには、ログアウトしたいユーザーに関するセッション情報がありません。

私はDjango.authとauth.logoutメソッドに精通していますが、引数としてrequestを取ります。ユーザー名さえあればユーザーをログアウトさせる「ジャンゴウェイ」はありますか?または、自分でログアウトSQLを実行する必要がありますか?

70

Djangoでこれを行う認可された方法はまだないと思います。

ユーザーIDはセッションオブジェクトに格納されますが、エンコードされます。残念ながら、すべてのセッションを反復処理し、デコードして比較する必要があります...

2つのステップ:

最初に、ターゲットユーザーのセッションオブジェクトを削除します。複数のコンピューターからログインする場合、複数のセッションオブジェクトがあります。

from Django.contrib.sessions.models import Session
from Django.contrib.auth.models import User

# grab the user in question 
user = User.objects.get(username='johndoe')

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == user.id]

次に、必要に応じてロックアウトします。

user.is_active = False
user.save()
75
Harold

Haroldの答えはこの特定のケースで機能しますが、少なくとも2つの重要な問題が見られます。

  1. このソリューションは、データベースでのみ使用できます セッションエンジン 。他の状況(キャッシュ、ファイル、Cookie)では、Sessionモデルは使用されません。
  2. データベース内のセッションとユーザーの数が増えると、これは非常に非効率的になります。

これらの問題を解決するには、問題に別のアプローチを取ることをお勧めします。アイデアは、特定のセッションでユーザーがログインした日付と、最後にユーザーにログアウトを要求した日付をどこかに保存することです。

誰かがあなたのサイトにアクセスするたびに、ログイン日がログアウト日よりも低い場合、ユーザーを強制ログアウトできます。ダンが言ったように、ユーザーをすぐにログアウトするか、サイトへの次のリクエストでログアウトするかには、実際的な違いはありません。

次に、Django 1.3b1のこのソリューションの可能な実装を見てみましょう。 3つのステップで:

1.最終ログイン日をセッションに保存する

幸いなことに、Django= authシステムはuser_logged_inと呼ばれる signal を公開しています。そのシグナルを登録し、セッションの現在の日付を保存するだけです。 models.pyの下部:

from Django.contrib.auth.signals import user_logged_in
from datetime import datetime

def update_session_last_login(sender, user=user, request=request, **kwargs):
    if request:
        request.session['LAST_LOGIN_DATE'] = datetime.now()
user_logged_in.connect(update_session_last_login)

2.ユーザーの強制ログアウトを要求する

Userモデルにフィールドとメソッドを追加するだけです。それを達成する方法は複数あります( ユーザープロファイルモデル継承 など)、それぞれ長所と短所があります。

簡単にするために、ここではモデルの継承を使用します。このソリューションを使用する場合は、 カスタム認証バックエンドの作成 を忘れないでください。

from Django.contrib.auth.models import User
from Django.db import models
from datetime import datetime

class MyUser(User):
    force_logout_date = models.DateTimeField(null=True, blank=True)

    def force_logout(self):
        self.force_logout_date = datetime.now()
        self.save()

次に、ユーザーjohndoeのログアウトを強制するには、次の操作を行うだけです。

from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()

3.アクセスのチェックを実装する

ここでの最善の方法は、ダンが示唆したように ミドルウェア を使用することです。このミドルウェアはrequest.userにアクセスするため、'Django.contrib.auth.middleware.AuthenticationMiddleware'設定にafterMIDDLEWARE_CLASSESを追加する必要があります。

from Django.contrib.auth import logout

class ForceLogoutMiddleware(object):
    def process_request(self, request):
        if request.user.is_authenticated() and request.user.force_logout_date and \
           request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date:
            logout(request)

それはそれを行う必要があります。


  • ユーザー用の追加フィールドを保存することのパフォーマンスへの影響に注意してください。モデルの継承を使用すると、JOINが追加されます。ユーザープロファイルを使用すると、追加のクエリが追加されます。 Userを直接変更するのがパフォーマンスの面で最良の方法ですが、それでも 毛深いトピック です。
  • そのソリューションを既存のサイトに展開する場合、おそらく既存のセッションで問題が発生し、'LAST_LOGIN_DATE'キーはありません。その場合に対処するために、ミドルウェアコードを少し変更することができます。

    from Django.contrib.auth import logout
    
    class ForceLogoutMiddleware(object):
        def process_request(self, request):
            if request.user.is_authenticated() and request.user.force_logout_date and \
               ( 'LAST_LOGIN_DATE' not in request.session or \
                 request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ):
                logout(request)
    
  • Django 1.2.xでは、user_logged_inシグナルはありません。login関数のオーバーライドにフォールバックします。

    from Django.contrib.auth import login as dj_login
    from datetime import datetime
    
    def login(request, user):
        dj_login(request, user)
        request.session['LAST_LOGIN_DATE'] = datetime.now()
    
55
Clément

アプリに似たようなものが必要でした。私の場合、ユーザーが非アクティブに設定されている場合、ユーザーが既にログインしているかどうかを確認し、ログアウトしてサイトを引き続き使用できないようにしました。この投稿を読んだ後、私は次の解決策を見つけました。

from Django.contrib.auth import logout

class ActiveUserMiddleware(object):
    def process_request(self, request):
        if not request.user.is_authenticated():
            return
        if not request.user.is_active:
           logout(request)

このミドルウェアを設定に追加するだけで、すぐに使用できます。パスワードを変更する場合、userprofileモデルに新しいフィールドを導入して、ユーザーを強制的にログアウトさせ、上記のis_activeの代わりにフィールドの値を確認し、ユーザーがログインしたときにフィールドの設定を解除できます。 Djangoの ser_logged_in シグナルで行われます。

44

おそらく、ログアウトを強制されたユーザーのリストを参照するミドルウェアのビット。次にユーザーが何かを実行しようとすると、ログアウトしてリダイレクトします。

もちろん、すぐにログアウトする必要がない限り。しかし、再度、彼らはとにかく次にリクエストを行おうとするまで気付かないので、上記の解決策はうまくいくかもしれません。

7
dan

これは、Balonのクエリへの応答です。

はい、約14万回のセッションを繰り返すことで、Haroldの答えがあなたが望むほど速くないかもしれない理由を見ることができます!

私が推奨する方法は、2つのプロパティのみが外部キーであるモデルをUserおよびSessionオブジェクトに追加することです。次に、このモデルを現在のユーザーセッションで最新の状態に保つミドルウェアを追加します。以前にこの種のセットアップを使用しました。私の場合、これからsessionprofileモジュールを借りました phpBBのシングルサインオンシステム (「Django/sessionprofile」フォルダーのソースコードを参照)およびこれ(と思います)あなたのニーズに合うでしょう。

最終的には、次のようなコードのどこかに管理機能があります(上記のsessionprofileモジュールと同じコード名とレイアウトを想定)。

from sessionprofile.models import SessionProfile
from Django.contrib.auth.models import User

# Find all SessionProfile objects corresponding to a given username
sessionProfiles = SessionProfile.objects.filter(user__username__exact='johndoe')

# Delete all corresponding sessions
[sp.session.delete() for sp in sessionProfiles]

(これは、SessionProfileオブジェクトも削除すると思います。思い出す限り、ForeignKeyによって参照されるオブジェクトが削除されたときのDjangoのデフォルトの動作は、それをカスケード接続し、 ForeignKeyですが、そうでない場合は、完了時にsessionProfilesの内容を削除するのは簡単です。

5
pythonian4000

Tony Abou-Assalehとして、非アクティブに設定されたユーザーをログアウトする必要もあったため、彼のソリューションを実装することから始めました。しばらくして、ミドルウェアがすべての要求に対してDBクエリを強制し(ユーザーがブロックされているかどうかを確認するため)、ログインを必要としないページのパフォーマンスが低下することがわかりました。

カスタムユーザーオブジェクトとDjango> = 1.7があるため、最終的には get_session_auth_hash ユーザーがアクティブでないときにセッションを無効にする機能。可能な実装は次のとおりです。

def get_session_auth_hash(self):
    if not self.is_active:
        return "inactive"
    return super(MyCustomUser, self).get_session_auth_hash()

これが機能するには、Django.contrib.auth.middleware.SessionAuthenticationMiddlewaresettings.MIDDLEWARE_CLASSES

2
Tzach

直接Django関数を使用してそれを行うこともできます。現在のセッションを除く、ユーザーの他のすべてのセッションを更新してログアウトします。

from Django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)
1
Pankaj Sharma

他の人が述べたように、DB内のallセッションを繰り返し、すべてのセッションをデコードし、そのユーザーに属するセッションを削除できます。ただし、特にサイトのトラフィックが多く、セッションが多い場合は時間がかかります。

より高速なソリューションが必要な場合は、特定のユーザーのセッションを照会および取得できるセッションバックエンドを使用できます。これらのセッションバックエンドでは、セッションはユーザーへの外部キーを持っているため、allセッションオブジェクトを反復処理する必要はありません。

これらのバックエンドを使用すると、ユーザーのすべてのセッションを削除するのに1行のコードを使用できます。

user.session_set.all().delete()

免責事項:私はDjango-qsessionsの著者です。