Làm thế nào để buộc người dùng đăng xuất trong Django?


78

Trong ứng dụng Django của tôi trong một số điều kiện nhất định, tôi muốn có thể buộc người dùng đăng xuất bằng tên người dùng. Không nhất thiết là người dùng hiện tại đã đăng nhập, mà là người dùng khác. Vì vậy, phương thức yêu cầu trong chế độ xem của tôi không có bất kỳ thông tin phiên nào về người dùng mà tôi muốn đăng xuất.

Tôi quen thuộc với django.auth và auth. phương thức đăng xuất, nhưng nó nhận yêu cầu làm đối số. Có "cách Django" để đăng xuất người dùng nếu tất cả những gì tôi có là tên người dùng không? Hay tôi phải cuộn SQL đăng xuất của riêng mình?


Tại sao bạn muốn đăng xuất một người dùng khác với người dùng đã đăng nhập? nếu bạn đang sử dụng phiên có độ dài Trình duyệt, những người dùng chưa đăng nhập đã đăng xuất.
Rama Vadakattu

3
Giả sử rằng tôi cần đăng xuất người dùng khi mật khẩu đã được thay đổi. Tôi có hơn 100 nghìn người dùng và khoảng 140 nghìn phiên trong bảng phiên. Làm thế nào để xử lý điều này một cách hiệu quả?
kjagiello

1
@kjagiello Hãy xem phần phụ trợ github.com/QueraTeam/django-qsessions . Sử dụng nó, bạn có thể dễ dàng đăng xuất một người sử dụng: user.session_set.all().delete(). Tuyên bố từ chối trách nhiệm: Tôi là tác giả của django-qsessions.
Mohammad Javad Naderi

Câu trả lời:


83

Tôi không nghĩ rằng có một cách bị xử phạt để làm điều này ở Django.

Id người dùng được lưu trữ trong đối tượng phiên, nhưng nó được mã hóa. Thật không may, điều đó có nghĩa là bạn sẽ phải lặp lại tất cả các phiên, giải mã và so sánh ...

Hai bước:

Đầu tiên hãy xóa các đối tượng phiên cho người dùng mục tiêu của bạn. Nếu họ đăng nhập từ nhiều máy tính, họ sẽ có nhiều đối tượng phiên.

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]

Sau đó, nếu bạn cần, hãy khóa chúng lại ....

user.is_active = False
user.save()

1
Cảm ơn bạn đã gợi ý, giải pháp đường may khá thô bạo này, giải pháp mà tôi đang cố gắng tránh. Tuy nhiên, nếu không có tùy chọn nào khác, tôi có thể phải làm với nó, có thể với một cải tiến nhỏ thay vì nhận "tất cả" các phiên, hãy lấy những phiên đã được cập nhật trong vòng "x" phút trước, hy vọng điều đó sẽ cải thiện đáng kể hiệu suất.
Sergey Golovchenko

Không thành vẫn đề. Lọc dữ liệu phiên vào lần cập nhật cuối cùng sẽ là một cải tiến đáng giá.
Harold

4
Đáng chú ý là Django 1.7 hỗ trợ tính năng vô hiệu hóa phiên khi thay đổi mật khẩu .
DavidM

7
Đối với django 1.8.2, chuỗi cuối cùng cần một số thay đổi (chuyển đổi cả hai phần tử thành str), như: [s.delete () for s trong Session.objects.all () if str (s.get_decoded (). Get ('_ auth_user_id ')) == str (user.id)]
iqmaker

60

Mặc dù câu trả lời của Harold hoạt động trong trường hợp cụ thể này, tôi có thể thấy ít nhất hai vấn đề quan trọng với nó:

  1. Giải pháp này chỉ có thể được sử dụng với một công cụ phiên cơ sở dữ liệu . Trong các tình huống khác (bộ nhớ cache, tệp, cookie), Sessionmô hình sẽ không được sử dụng.
  2. Khi số lượng phiên và người dùng trong cơ sở dữ liệu tăng lên, điều này trở nên khá kém hiệu quả.

Để giải quyết những vấn đề đó, tôi khuyên bạn nên thực hiện một cách tiếp cận vấn đề khác. Ý tưởng là lưu trữ ở đâu đó ngày người dùng đăng nhập trong một phiên nhất định và lần cuối cùng bạn yêu cầu người dùng đăng xuất.

Sau đó, bất cứ khi nào ai đó truy cập trang web của bạn, nếu ngày đăng nhập thấp hơn ngày đăng xuất, bạn có thể buộc đăng xuất người dùng. Như dan đã nói, không có sự khác biệt thực tế nào giữa việc đăng xuất người dùng ngay lập tức hoặc theo yêu cầu tiếp theo của anh ta đối với trang web của bạn.

Bây giờ, chúng ta hãy xem một triển khai khả thi của giải pháp này, cho django 1.3b1 . Trong ba bước:

1. lưu trữ trong phiên ngày đăng nhập cuối cùng

May mắn thay, hệ thống xác thực Django để lộ một tín hiệu được gọi user_logged_in. Bạn chỉ cần đăng ký các tín hiệu đó và lưu ngày hiện tại trong phiên. Ở cuối của bạn 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. yêu cầu buộc đăng xuất cho người dùng

Chúng ta chỉ cần thêm một trường và một phương thức vào Usermô hình. Có nhiều cách để đạt được điều đó ( hồ sơ người dùng , kế thừa mô hình , v.v.), mỗi cách đều có ưu và nhược điểm.

Để đơn giản, tôi sẽ sử dụng kế thừa mô hình ở đây, nếu bạn sử dụng giải pháp này, đừng quên viết một chương trình phụ trợ xác thực tùy chỉnh .

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()

Sau đó, nếu bạn muốn buộc đăng xuất cho người dùng johndoe, bạn chỉ cần:

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

3. thực hiện kiểm tra quyền truy cập

Cách tốt nhất ở đây là sử dụng một phần mềm trung gian như dan đề xuất. Phần mềm trung gian này sẽ truy cập request.user, vì vậy bạn cần đặt nó sau 'django.contrib.auth.middleware.AuthenticationMiddleware' trong MIDDLEWARE_CLASSEScài đặt của mình .

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)

Nên làm vậy.


Ghi chú

  • Lưu ý về hiệu suất của việc lưu trữ thêm một trường cho người dùng của bạn. Sử dụng kế thừa mô hình sẽ bổ sung thêm JOIN. Sử dụng hồ sơ người dùng sẽ thêm một truy vấn bổ sung. Sửa đổi trực tiếp Userlà cách tốt nhất để thực hiện một cách khôn ngoan, nhưng nó vẫn là một chủ đề khó hiểu .
  • Nếu bạn triển khai giải pháp đó trên một trang hiện có, có thể bạn sẽ gặp một số rắc rối với các phiên hiện có, phiên này sẽ không có 'LAST_LOGIN_DATE'khóa. Bạn có thể điều chỉnh một chút mã phần mềm trung gian để đối phó với trường hợp đó:

    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)
    
  • Trong django 1.2.x, không có user_logged_intín hiệu. Quay lại ghi đè loginhàm:

    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()
    

Tôi là người mới, vì vậy xin thứ lỗi nếu tôi hiểu sai, nhưng không nên như vậy: update_session_last_login (sender, user = 'user', request = 'request', ** kwargs):?
rix

Thực ra, tôi nghĩ Clément có nghĩa là: def update_session_last_login (người gửi, người dùng, yêu cầu, ** kwargs) :.
Dylan

47

Tôi cần một cái gì đó tương tự trong ứng dụng của mình. Trong trường hợp của tôi, nếu người dùng được đặt thành không hoạt động, tôi muốn đảm bảo rằng nếu người dùng đã đăng nhập thì họ sẽ bị đăng xuất và không thể tiếp tục sử dụng trang web. Sau khi đọc bài đăng này, tôi đã đi đến giải pháp sau:

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)

Chỉ cần thêm phần mềm trung gian này vào cài đặt của bạn và bạn bắt đầu. Trong trường hợp thay đổi mật khẩu, bạn có thể giới thiệu một trường mới trong mô hình userprofile buộc người dùng đăng xuất, kiểm tra giá trị của trường thay vì is_active ở trên và cũng bỏ đặt trường khi người dùng đăng nhập. Sau đó có thể được thực hiện với tín hiệu user_logged_in của Django .


1
Sử dụng trường mật khẩu đã thay đổi được đề cập. Nếu bạn đăng nhập trên 2 địa điểm và bạn thay đổi mật khẩu tại A, thì hãy đăng nhập tại A. Cờ mật khẩu đã thay đổi sẽ không được đặt và bạn sẽ vẫn đăng nhập tại B mà không cần phải nhập mật khẩu mới.
Đánh dấu

1
@Mark, bạn hoàn toàn chính xác. Tôi đã gặp sự cố này khi sử dụng WebSockets. Giải pháp mà tôi thích nghi ở đó là tự quản lý việc lưu trữ các kết nối để tôi có thể thao tác chúng. Có vẻ như một cách tiếp cận tương tự sẽ được yêu cầu ở đây, tức là, một bảng phiên khác ánh xạ người dùng đến id phiên.
Tony Abou-Assaleh

5
Giải pháp của bạn chính xác là những gì tôi đã nghĩ trước khi tìm kiếm các cách khác, và cuối cùng tôi đã làm như vậy sau khi truy cập trang này. Chỉ một nhận xét: tình trạng của bạn có thể được viết như thế này để (theo ý kiến ​​của tôi) rõ ràng hơn:if request.user.is_authenticated() and not request.user.is_active
Yoone

7

Có lẽ, một chút phần mềm trung gian tham chiếu đến danh sách những người dùng đã bị buộc đăng xuất. Lần tới khi người dùng cố gắng làm bất cứ điều gì, hãy đăng xuất họ sau đó chuyển hướng họ, v.v.

Tất nhiên, trừ khi họ cần đăng xuất ngay lập tức. Nhưng sau đó, họ sẽ không nhận thấy cho đến khi họ cố gắng đưa ra yêu cầu lần sau, vì vậy giải pháp trên có thể hoạt động.


6

Đây là câu trả lời cho câu hỏi của Balon:

Vâng, với khoảng 140 nghìn phiên để lặp lại, tôi có thể thấy tại sao câu trả lời của Harold có thể không nhanh như bạn có thể muốn!

Cách tôi muốn đề xuất là thêm một mô hình chỉ có hai thuộc tính là khóa ngoại UserSessionđối tượng. Sau đó, thêm một số phần mềm trung gian giúp mô hình này luôn cập nhật với các phiên người dùng hiện tại. Tôi đã sử dụng kiểu thiết lập này trước đây; trong trường hợp của tôi, tôi đã mượn sessionprofilemô-đun từ hệ thống Đăng nhập Một lần này cho phpBB (xem mã nguồn trong thư mục "django / sessionprofile") và mô-đun này (tôi nghĩ) sẽ phù hợp với nhu cầu của bạn.

Những gì bạn sẽ kết thúc với là một số chức năng quản lý ở đâu đó trong mã của bạn như thế này (giả sử các tên mã và bố cục giống như trong sessionprofilemô-đun được liên kết ở trên):

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]

(Tôi nghĩ rằng điều này cũng sẽ xóa các SessionProfileđối tượng, vì theo những gì tôi nhớ lại, hành vi mặc định của Django khi một đối tượng được tham chiếu bởi a ForeignKeybị xóa là xóa nó và cũng xóa đối tượng có chứa ForeignKey, nhưng nếu không, thì việc xóa nội dung sessionProfileskhi bạn hoàn thành.)


3

Bạn cũng có thể sử dụng hàm django trực tiếp để làm điều đó, nó sẽ cập nhật và đăng xuất tất cả các phiên khác cho người dùng, ngoại trừ phiên hiện tại.

from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)

Tài liệu cho update_session_auth_hash tại đây .


2

Với tư cách là Tony Abou-Assaleh, tôi cũng cần đăng xuất những người dùng được đặt thành không hoạt động, vì vậy tôi bắt đầu bằng cách triển khai giải pháp của anh ấy. Sau một thời gian, tôi phát hiện ra rằng phần mềm trung gian đang buộc truy vấn DB trên tất cả các yêu cầu (để kiểm tra xem người dùng có bị chặn hay không) và do đó làm ảnh hưởng đến hiệu suất trên các trang không yêu cầu đăng nhập.

Tôi có một đối tượng người dùng tùy chỉnh và Django> = 1.7, vì vậy những gì tôi kết thúc là ghi đè get_session_auth_hashchức năng của nó để làm mất hiệu lực phiên khi người dùng không hoạt động. Một cách triển khai khả thi là:

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

Để điều này hoạt động, django.contrib.auth.middleware.SessionAuthenticationMiddlewarenên ởsettings.MIDDLEWARE_CLASSES


0

Như những người khác đã nêu, bạn có thể lặp lại tất cả các phiên trong DB, giải mã tất cả chúng và xóa những phiên thuộc về người dùng đó. Nhưng nó chậm, đặc biệt nếu trang web của bạn có lưu lượng truy cập cao và có nhiều phiên.

Nếu bạn cần một giải pháp nhanh hơn, bạn có thể sử dụng chương trình phụ trợ phiên cho phép bạn truy vấn và nhận phiên của một người dùng cụ thể. Trong các phần phụ trợ của phiên này, Phiên có một khóa ngoại đối với Người dùng, vì vậy bạn không cần phải lặp lại trên tất cả các đối tượng phiên:

Sử dụng các chương trình phụ trợ này, việc xóa tất cả các phiên của người dùng có thể được thực hiện trong một dòng mã duy nhất:

user.session_set.all().delete()

Tuyên bố từ chối trách nhiệm: Tôi là tác giả của django-qsessions.


-1

từ django.contrib.sessions.models nhập Phiên

xóa phiên người dùng

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

-5

Ngay cả tôi cũng phải đối mặt với vấn đề này. Rất ít người gửi thư rác từ Ấn Độ tiếp tục đăng về những giải pháp tình yêu Baba và Molvi đó.

Những gì tôi đã làm là tại thời điểm đăng bài chỉ chèn mã này:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')

Câu trả lời này không trả lời câu hỏi. Ông không được yêu cầu cho một phương tiện để từ chối trả lời cho một người dùng đã xóa
Mohammed Shareef C
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.