Web请求中的身份验证

2018-02-22  本文已影响188人  大爷的二舅

Django使用会话和中间件将认证系统挂接到请求对象。 这些提供了代表当前用户的每个请求的request.user属性。 如果当前用户尚未登录,则此属性将设置为AnonymousUser的一个实例,否则它将是User的一个实例。 您可以使用is_authenticated()来区分它们,如下所示:

if request.user.is_authenticated():
    # Do something for authenticated users.
else:
    # Do something for anonymous users.
如何登录用户

要从视图登录用户,请使用login()。 它需要一个HttpRequest对象和一个User对象。 login()使用Django的会话框架将用户的ID保存在会话中。 请注意,匿名会话期间的任何数据集都将在用户登录后保留在会话中。此示例显示如何使用authenticate()和login():

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        if user.is_active:
            login(request, user)
            # Redirect to a success page.
        else:
            # Return a 'disabled account' error message
    else:
        # Return an 'invalid login' error message.

首先调用authenticate()首先,当您手动登录用户时,必须在调用login()之前调用authenticate()。 authenticate()在用户身上设置一个属性,指出哪个认证后端成功认证了该用户,并且此信息在登录过程中稍后需要。 如果您尝试直接登录从数据库检索到的用户对象,则会引发错误。

如何登录用户

要注销通过login()登录的用户,请在您的视图中使用logout()。 它需要一个HttpRequest对象并且没有返回值。 例:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

请注意,如果用户未登录,logout()不会引发任何错误。当您调用logout()时,当前请求的会话数据将被彻底清除。 所有现有数据都将被删除。 这是为了防止其他人使用同一个Web浏览器登录并访问先前用户的会话数据。

如果你想把任何东西放到注销后立即可用的会话中,那么在调用logout()之后执行该操作。

限制对登录用户的访问
原始的方式

限制访问页面的简单原始方法是检查request.user.is_authenticated()并重定向到登录页面:

from django.shortcuts import redirect

def my_view(request):
    if not request.user.is_authenticated():
        return redirect('/login/?next=%s' % request.path)
    # ...

...或显示错误消息:

from django.shortcuts import render

def my_view(request):
    if not request.user.is_authenticated():
        return render(request, 'books/login_error.html')
    # ...
login_required装饰器

作为一个捷径,你可以使用方便的login_required()装饰器:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_required()执行以下操作:

默认情况下,用户在成功验证后应该重定向到的路径存储在名为“next”的查询字符串参数中。 如果您希望为此参数使用不同的名称,login_required()将采用可选的redirect_field_name参数:

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='my_redirect_field')
def my_view(request):
    ...

请注意,如果您为redirect_field_name提供值,则很可能还需要自定义您的登录模板,因为存储重定向路径的模板上下文变量将使用redirect_field_name的值作为其键值而不是“next”(缺省值)。 login_required()也采用可选的login_url参数。 例:

from django.contrib.auth.decorators import login_required

@login_required(login_url='/accounts/login/')
def my_view(request):
   ...

请注意,如果您未指定login_url参数,则需要确保LOGIN_URL和您的登录视图正确关联。 例如,使用默认值,将以下行添加到您的URLconf中:

from django.contrib.auth import views as auth_views

url(r'^accounts/login/$', auth_views.login),

LOGIN_URL还接受视图函数名称和指定的URL模式。 这使您可以在URLconf中自由重新映射登录视图,而无需更新设置。

注意:login_required装饰器不会检查用户的is_active标志。

限制对通过测试的登录用户的访问

要根据某些权限或某些其他测试来限制访问权限,您的操作基本上与上一节中所描述的相同。 简单的方法是直接在视图中的request.user上运行测试。 例如,此视图会检查以确保用户在所需的域中有电子邮件:

def my_view(request):
    if not request.user.email.endswith('@example.com'):
        return HttpResponse("You can't leave a review for this book.")
    # ...

作为一个快捷方式,你可以使用方便的user_passes_test修饰器:

from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

user_passes_test()需要一个必需的参数:一个可调用的用户对象,如果允许用户查看页面,则返回True。 请注意,user_passes_test()不会自动检查用户是否为匿名用户。 user_passes_test()接受两个可选参数:

  1. login_url。 让您指定未通过测试的用户将被重定向到的URL。 它可能是一个登录页面,如果你没有指定登录页面,则默认为LOGIN_URL。

  2. redirect_field_name。 与login_required()相同。 将其设置为None会将其从URL中删除,如果您将未通过测试的用户重定向到没有“下一页”的非登录页面,则可能需要执行此操作。

例如:

@user_passes_test(email_check, login_url='/login/')
def my_view(request):
    ...
permission_required装饰器

检查用户是否具有特定权限是相对常见的任务。 出于这个原因,Django为这种情况提供了一个快捷方式 - permission_required()装饰器:

from django.contrib.auth.decorators import permission_required

@permission_required('reviews.can_vote')
def my_view(request):
    ...

就像has_perm()方法一样,权限名称的形式为“<app label>.<permission codename>”
(即reviews.can_vote在审查申请中对模型的许可)。 装饰器也可以获取权限列表。 请注意,permission_required()也采用可选的login_url参数。 例:

from django.contrib.auth.decorators import permission_required

@permission_required('reviews.can_vote', login_url='/loginpage/')
def my_view(request):
    ...

和login_required()装饰器一样,login_url默认为LOGIN_URL。 如果给出raise_exception参数,装饰器将引发PermissionDenied,提示403(HTTP禁止)视图,而不是重定向到登录页面。

会话无效,密码更改

如果您的AUTH_USER_MODEL从AbstractBaseUser继承,或者实现它自己的get_session_auth_hash()
方法,认证会话将包含此函数返回的散列。 在AbstractBaseUser的情况下,这是密码字段的哈希消息验证码(HMAC)。

如果启用了SessionAuthenticationMiddleware,Django会验证与每个请求一起发送的哈希与服务器端计算出的哈希相匹配。 这允许用户通过更改其密码来注销其所有会话。

Django包含的默认密码更改视图,django.contrib.auth.views.password_change()和django.contrib.auth管理中的user_change_password视图,使用新密码哈希更新会话,以便用户更改自己的密码 不会注销。 如果您有自定义密码更改视图并希望有类似的行为,请使用此功能:

django.contrib.auth.decorators.update_session_auth_hash (request, user)

此函数接受当前请求和从中派生新会话哈希的更新用户对象,并适当地更新会话哈希。 用法示例:

from django.contrib.auth import update_session_auth_hash

def password_change(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        if form.is_valid():
            form.save()
            update_session_auth_hash(request, form.user)
    else:
        ...

由于get_session_auth_hash()基于SECRET_KEY,因此更新站点以使用新秘密将使所有现有会话无效。

上一篇 下一篇

猜你喜欢

热点阅读