2

我目前遇到的问题是,当我在我的任何视图上使用来自 django.contrib.auth.decorators 的 login_required 装饰器时,只要装饰器重定向(到登录页面)并返回,我的 POST 参数就不会到达受保护的视图再次进入受保护的视图。关于如何解决这个问题的建议(最好保持 login_required 装饰器和 POST 方法的便利性)感谢!

这个页面似乎是关于此事的有争议的 Django 票。尽管错误/增强是根据模板与视图逻辑来构建的,而不是仅仅使视图可以访问参数,这是我的问题。

4

3 回答 3

2

我使用我认为可以接受的会话开发了以下解决方案。处理重定向和替换视图很棘手,这种方法似乎是在获得所需功能的同时不摆弄框架和不对抗 HTTP 协议的最佳平衡。此方法的不利方面是每个受保护视图检查会话变量所需的额外工作。

  1. 创建一个自定义装饰器(login_required2下面的),如果用户通过身份验证,则返回请求的视图,否则返回项目的登录视图。
  2. 登录视图:
    1. 将原始POST参数存储在会话变量中。
    2. 将原始数据存储HTTP_REFERER在会话变量中
    3. 如果用户正确验证,则返回与请求路径对应的视图(请求路径在整个登录过程中保持相同,并且与用户最初通过登录视图时请求的路径相同。)
  3. 因此,任何受保护的视图都必须在使用请求POSTMETA['HTTP_REFERER']

代码如下:

def login_view(request):    
    from django.conf import settings
    from django.core.urlresolvers import resolve

    USERNAME_FIELD_KEY = 'username'
    PASSWORD_FIELD_KEY = 'password'

    message = '' #A message to display to the user
    error_message = '' #An error message to display to the user

    #If the request's path is not the login URL, the user did not explicitly request 
    # the login page and we assume this view is protecting another.
    protecting_a_view = request.path != settings.LOGIN_URL

    post_params_present = bool(request.POST)

    #Any POST with username and password is considered a login attempt, regardless off what other POST parameters there may be
    login_attempt = request.POST and request.POST.has_key(USERNAME_FIELD_KEY) and request.POST.has_key(PASSWORD_FIELD_KEY)

    if protecting_a_view:
        message = 'You must login for access.'
        if not request.session.has_key(ACTUAL_REFERER_KEY):
            #Store the HTTP_REFERER if not already
            request.session[ACTUAL_REFERER_KEY] = request.META.get(HTTP_REFERER_KEY)

    if protecting_a_view and post_params_present and not login_attempt: 
        #Store the POST parameters for the protected view
        request.session[FORWARDED_POST_PARAMS_KEY] = request.POST

    if login_attempt:
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data[USERNAME_FIELD_KEY]
            password = form.cleaned_data[PASSWORD_FIELD_KEY]
            user = auth.authenticate(username=username, password=password)
            if user is not None:
                if user.is_active:
                    auth.login(request, user)
                    if protecting_a_view:
                        actual_view, actual_args, actual_kwargs = resolve(request.path) #request.path refers to the protected view
                        return actual_view(request, *actual_args, **actual_kwargs)
                    else:
                        HttpResponseRedirect('/')
                else:
                    message = 'That account is inactive.'
            else:
                error_message = 'That username or password is incorrect.'
    else:
        form = LoginForm()

    context_dict = {
        'form': form,
        'message': message,
        'error_message': error_message,
    }
    return render_to_response2('my_app/login.html', context_dict)

@login_required2
def protected_view(request):
    post_params = {}

    if request.POST:
        post_params = request.POST
    elif request.session.has_key(FORWARDED_POST_PARAMS_KEY):
        post_params = request.session[FORWARDED_POST_PARAMS_KEY]
        del request.session[FORWARDED_POST_PARAMS_KEY]
    if post_params:
        #Process post_params as if it were request.POST here:
        pass

    #assuming this view ends with a redirect.  Otherwise could render view normally
    if request.session.has_key(ACTUAL_REFERER_KEY):
        redirect_location = request.session.get(ACTUAL_REFERER_KEY)
    elif request.META.get(HTTP_REFERER_KEY) != request.path:
        redirect_location = request.META.get(HTTP_REFERER_KEY)
    else:
        redirect_location = ROOT_PATH
    return HttpResponseRedirect(redirect_location)

def login_required2(view_func):
    """
    A decorator that checks if the request has an authenticated user.
    If so it passes the request to the view.
    Otherwise, it passes the request to the login view, which is responsible
    for recognizing that the request was originally for another page and forwarding
    state along (GET, POST).

    See django.contrib.auth.decorators for how Django's auth decorators mesh 
    using _CheckLogin.  This decorator bypasses that for my ease of creation.
    """
    def login_required_decoration(request, *args, **kwargs):
        if request.user.is_authenticated():
            return view_func(request, *args, **kwargs)
        else:
            from django.conf import settings
            from django.core.urlresolvers import resolve

            login_url = settings.LOGIN_URL
            login_view, login_args, login_kwargs = resolve(login_url)
            #Here the user gets a login view instad of the view they requested
            return login_view(request, *login_args, **login_kwargs)
    return login_required_decoration
于 2009-03-14T19:44:10.930 回答
1

没有简单的方法可以做到这一点,更不用说您想使用“login_required”装饰器这一事实。您可以执行自己的视图来检查 is_authenticated 方法并执行正确的操作,例如序列化 POST 数据并传递,但这很容易出错。

简单的解决方法是将表单更改为执行 GET 而不是 POST。

于 2009-02-19T03:45:12.563 回答
0

这是一个相当古老的问题,但今天仍然存在需求。

根据先前答案中提供的提示,我为基于类的视图制作了以下 mixin:

from urllib.parse import urlparse, urlencode

from django.http import QueryDict, HttpRequest
from django.contrib.auth.mixins import AccessMixin

class loginRequiredOnPostMixin (AccessMixin):
    """
    Lets a CBV be freely accessible on GET requests, but requires login on POST.
    On redirect for unauthenticated POST requests, the POST parameters are stored in the session.
    Once the user authenticated and is redirected to the page as GET, the POST parameters are retrieved so the request can complete.
    """
    SESSION_REFERER_KEY = None
    SESSION_POST_PARAMS_KEY = None

    def get_session_referer_key(self):
        return self.SESSION_REFERER_KEY or 'POST_REDIRECT_REFERER'

    def get_session_post_params_key(self):
        return self.SESSION_POST_PARAMS_KEY or 'POST_PARAMS'

    def dispatch(self, request, *args, **kwargs):
        referer_key = self.get_session_referer_key()
        post_params_key = self.get_session_post_params_key()

        if request.method == 'POST' and bool(request.POST) and not request.user.is_authenticated:
            # save the referer url to match it later in case of GET to the same page (after login)
            if not request.session.has_key(referer_key): # so we don't overwrite previous values)
                parsed_url = urlparse(request.META.get('HTTP_REFERER'))
                request.session[referer_key] = parsed_url.path
                # save the POST params in the session
                request.session[post_params_key] = request.POST
            #redirect to login View
            return self.handle_no_permission()

        elif request.method == 'POST' and request.user.is_authenticated:
            # user logged in and rePOSTed... clean up and let the magic work on its on
            if request.session.has_key(referer_key):
                del request.session[referer_key]
            if request.session.has_key(post_params_key):
                del request.session[post_params_key]

        elif request.method == 'GET' and request.user.is_authenticated:
            print ("dispatch: GET authenticated")
            if request.session.has_key(referer_key):
                if request.session[referer_key] == request.get_full_path():
                    newRequest = HttpRequest()
                    newRequest.COOKIES = request.COOKIES
                    newRequest.META = request.META
                    newRequest.FILES = request.FILES
                    newRequest.path = request.path
                    newRequest.path_info = request.path_info
                    newRequest.method = 'POST'
                    newRequest.resolver_match = request.resolver_match
                    newRequest.content_type = request.content_type
                    newRequest.content_params = request.content_params
                    q = QueryDict(urlencode(request.session[post_params_key]))
                    newRequest.POST = q
                    newRequest.session = request.session
                    newRequest.user = request.user
                    self.request = newRequest
                    del request.session[referer_key]
                    del request.session[post_params_key]
                    return super().dispatch(newRequest, *args, **kwargs)
                # we clean up as it seems the user has moved on.
                del request.session[referer_key]
                del request.session[post_params_key]
        return super().dispatch(request, *args, **kwargs)

成功登录后,它实际上会重新创建 HttpRequest 对象并将其转换为 POST 请求,然后再调用super().dispatch(...)

如果在 a 中使用FormView,请确保使用足够的 POST 数据重新创建表单,如下所示:

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        return super().post(request, *args, **kwargs)

为我工作。享受!

于 2020-03-20T17:52:45.983 回答