Django之用户认证系统分析

1.用户模型在django.contrib.auth.models.py包中定义了classUser(AbstractUser)类(1)User模型字段我在django中使用的是MySql,

大家好,又见面了,我是全栈君,今天给大家准备了Idea注册码。

Django自带一个用户认证系统,这个系统处理用户账户、组、权限和基于cookie的会话,下面将通过分析django源码的方式仔对该系统进行详细分析

1. 用户模型

  在django.contrib.auth.models.py包中定义了class User(AbstractUser)类

(1)User模型字段

  我在django中使用的是MySql,通过auth_user表查看User字段,当然大家也可以直接通过源码查看

  Django之用户认证系统分析

  下面依次对各字段进行说明:

  id:用户ID,主键,auto_increment

  password:密码(哈希值,元数据),Django 不储存原始密码,原始密码可以是任意长度的,包含任何字符

  last_login:缺省情况下设置为用户最后一次登录的日期时间

  is_superuser:布尔值,指明用户拥有所有权限(包括显式赋予和非显式赋予的)

  username:用户名,必选项,只能是字母数字(字母、数字和下划线),Changed in Django 1.2: 用户名现在可以包含 @ 、 + 、 . 和 – 字符

  first_name:可选项

  last_name:可选项

  email:可选项,电子邮件地址

  is_staff:布尔值,指明这个用户是否可以进入管理站点

  is_active:指明这个用户是否是活动的,建议把这个标记设置为False来代替删除用户账户,这样就不会影响指向用户的外键。

注意:登录验证时不会检查is_active标志,也就是说这个属性不控制用户是否可以登录,因此,如果在登录时需要检查is_active 标志,需要你在自己的登录视图中 实现。但是用于 
login() 视图的 
AuthenticationForm 函数会执行这个 检查,因此应当在 Django 站点中进行认证并执行 
has_perm() 之类的权限检查方法,所有那些方法或函数 对于不活动的用户都会返回 False 。

  date_joined:缺省情况下设置为用户账户创建的日期时间

(2)User方法

  使用django的shell查看User方法

  Django之用户认证系统分析

  下面选取主要的方法进行说明:

  1)is_anonymous

  总是返回False,这是一个区别User和AnonymousUser的方法,通常你会更喜欢用is_authenticated方法

  Django之用户认证系统分析

  2)is_authenticated  

  Django之用户认证系统分析

  总是返回True,这是一个测试用户是否经过验证的方法,这并不表示任何权限,也不测试用户是否是活动的,这只是验证用户是否合法。

  3) get_full_name()

  返回first_name加上last_name,中间加一个空格

  4)set_password(raw_password)

  根据原始字符串设置用户密码,要注意密码的哈希算法。不保存 User 对象

  5) check_password(raw_password)

  Django之用户认证系统分析

  6)get_group_permissions(obj=None)

  通过用户的组返回用户的一套权限字符串,如果有 obj 参数,则只返回这个特定对象的组权限

  7)get_all_permissions(obj=None)

  通过用户的组和用户权限返回用户的一套权限字符串,如果有 obj 参数,则只返回这个特定对象的组权限

  8)has_perm(perm, obj=None)

  如果用户有特定的权限则返回 True ,这里的 perm 的格式为 ” label>. codename>”,如果用户是不活动的,这个方法总是返回 False,如果有 obj 参数,这个方法不会检查模型的权限,只会检查这个特定对象的 权限

  9)has_perms(perm_list, obj=None)

  如果用户有列表中每个特定的权限则返回 True ,这里的 perm 的格式为” label>. codename>” 。如果用户是不活动的,这个方法 总是返回 False

  10)has_module_perms(package_name)

  如果用户在给定的包( Django 应用标签)中有任何一个权限则返回 True 。如果用户是不活动的,这个方法总是返回 False

  11)email_user(subject, message, from_email=None)

  发送一个电子邮件给用户。如果 from_email 为 None ,则 使用 DEFAULT_FROM_EMAIL

2. 用户登录

  Django在django.contrib.auth模块中提供了两个函数:authenticate和login,在django.contrib.auth.views包中的LoginView类也完美的展示了authenticate和login两个函数的使用方法,下面先通过源码分析LoginView类中对这两个函数的使用,再仔细介绍这两个函数的实现。

(1)LoginView类实现

  先上张图:

  Django之用户认证系统分析

  从上图很清楚的说明了用户登录流程:

  1)通过AuthenticationForm基类BaseForm的is_valid函数验证表单信息的合法性,通过clean_xx方法实现,我们会再clean函数中发现使用了authenticate函数

  2)通过LoginView的form_valid函数调用auth_login函数(就是django.contrib.auth中的login)实现用户登录

(2)authenticate函数

  authenticate() 用于验证指定用户的用户名和 密码。

  这个函数有两个关键字参数, username 和 password ,如果密码与 用户匹配则返回一个 User 对象,否则返回 None

  函数源码:

Django之用户认证系统分析
Django之用户认证系统分析

def authenticate(request=None, **credentials):
    """
    If the given credentials are valid, return a User object.
    """
    for backend, backend_path in _get_backends(return_tuples=True):
        try:
            inspect.getcallargs(backend.authenticate, request, **credentials)
        except TypeError:
            # This backend doesn't accept these credentials as arguments. Try the next one.
            continue
        try:
            user = backend.authenticate(request, **credentials)
        except PermissionDenied:
            # This backend says to stop in our tracks - this user should not be allowed in at all.
            break
        if user is None:
            continue
        # Annotate the user object with the path of the backend.
        user.backend = backend_path
        return user

    # The credentials supplied are invalid to all backends, fire signal
    user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)

View Code

(3)login函数

  login() 用于在视图中登录用户,它带有一个 HttpRequest 对象和一个 User 对象,并使用 Django 的会话框架在会话中保存用户 的 ID 

  函数源码:

Django之用户认证系统分析
Django之用户认证系统分析

def login(request, user, backend=None):
    """
    Persist a user id and a backend in the request. This way a user doesn't
    have to reauthenticate on every request. Note that data set during
    the anonymous session is retained when the user logs in.
    """
    session_auth_hash = ''
    if user is None:
        user = request.user
    if hasattr(user, 'get_session_auth_hash'):
        session_auth_hash = user.get_session_auth_hash()

    if SESSION_KEY in request.session:
        if _get_user_session_key(request) != user.pk or (
                session_auth_hash and
                not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
            # To avoid reusing another user's session, create a new, empty
            # session if the existing session corresponds to a different
            # authenticated user.
            request.session.flush()
    else:
        request.session.cycle_key()

    try:
        backend = backend or user.backend
    except AttributeError:
        backends = _get_backends(return_tuples=True)
        if len(backends) == 1:
            _, backend = backends[0]
        else:
            raise ValueError(
                'You have multiple authentication backends configured and '
                'therefore must provide the `backend` argument or set the '
                '`backend` attribute on the user.'
            )
    else:
        if not isinstance(backend, str):
            raise TypeError('backend must be a dotted import path string (got %r).' % backend)

    request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
    request.session[BACKEND_SESSION_KEY] = backend
    request.session[HASH_SESSION_KEY] = session_auth_hash
    if hasattr(request, 'user'):
        request.user = user
    rotate_token(request)
    user_logged_in.send(sender=user.__class__, request=request, user=user)

login()

(4)authenticate和login函数使用示例

from django.contrib.auth import authenticate, logindef 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)            # 重定向到一个登录成功页面。        else:            # 返回一个“帐户已禁用”错误信息。    else:        # 返回一个“非法用户名或密码”错误信息。

当你手动登录一个用户时, 必须 在调用 login() 之前调用 authenticate() 。在成功验证用户后,authenticate() 会在 User 上设置一个空属性,这个信息在以后登录过程中要用到。

3. 登录要求装饰器

(1)login_required函数原型

def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    actual_decorator = user_passes_test(
        lambda u: u.is_authenticated,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

(2)login_required()函数实现以下功能

  (a)如果用户没有登录,那么就重定向到settings.LOGIN_URL,并且在查询字符串中传递当前绝对路径,例如:http://localhost:8000/account/login/?next=/blog/,/blog/为当前访问页面

  (b)如果用户已经登录,则正常的执行视图,视图代码认为用户已经登录

(3)login_required()函数参数说明

  (a)缺省情况下,成功登陆后重定向的路径是保存在next参数中,如果想换另外一个名称,可以使用第二个参数redirect_field_name,如果将redirect_field_name=‘nextlink’,之前的链接会变成http://localhost:8000/account/login/?nextlink=/blog/,注意,如果你提供了一个值给 redirect_field_name ,那么你最好也同样自定义 你的登录模板。因为保存重定向路径的模板环境变量会使用redirect_field_name 作为关键值,而不使用缺省的 “next”

  (b)login_url参数,可选,默认为settings.LOGIN_URL

(4)示例

@login_required(redirect_field_name='nextlink')
def blog_title(request):
    blogs = BlogModel.objects.all()
    return render(request, 'titles.html', {'blogs':blogs})

4. 用户登出

  在视图中可以使用 django.contrib.auth.logout() 来登出通过 django.contrib.auth.login() 登录的用户。它带有一个 HttpRequest 对象,没有返回值

from django.contrib.auth import logout
def logout_view(request): 
    logout(request) 
    return redirect('/account/login/') #重定向到另一页面

  当调用 logout() 时,当前请求的会话数据会清空。 这是为了防止另一个用户使用同一个浏览器登录时会使用到前一个用户的会话数据。 如果要在用户登出后在会话中储存一些数据,那么得在 django.contrib.auth.logout() 之后储存。注意当用户没有登录时,调用 logout() 函数不会引发任何错误。

5. 修改密码

(1)django提供了python manage.py changepassword *username* 命令修改用户密码,如果给定了用户名,这个命令会提示你输入两次密码。 当两次输入的密码相同时,该用户的新密码会立即生效。如果没有给定用户,这个命令会 尝试改变与当前用户名匹配的用户的密码

(2)使用set_password() 方法和 check_password() 函数用于设置和检查密码,函数位于django.contrib.auth.base_user.py包中

from django.contrib.auth.models import User 
u = User.objects.get(username='john')
u.set_password('new password')
u.save() 

(3)Django提供了PasswordChangeView类实现重置密码,类位于django.contrib.auth.views包中

  下面演示如何使用PasswordChangeView类重置密码:

  a. 设置URL

from django.conf.urls import url
from django.contrib.auth import views as auth_views
urlpatterns = [
    url(r'password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
    url(r'password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
]

  b. 设置LOGIN_URL = ‘/account/login/’,若不设置会使用global_settings.py中设置的LOGIN_URL = ‘/accounts/login/’

  c. 测试,由于我使用的当前应用为account,启动浏览器输入localhost:8000/account/password_change/由于未登录,会转换到登录界面,会变成登录链接http://localhost:8000/account/login/?next=/account/password_change/

  Django之用户认证系统分析

  登录后,再重置密码http://localhost:8000/account/password_change/

  Django之用户认证系统分析

参考博客:
http://blog.chinaunix.net/uid-21633169-id-4352928.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/120035.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 测试用例八大要素以及设计方法

    测试用例八大要素以及设计方法测试用例(TestCase)是指对一项特定的软件产品进行测试任务的描述,体现测试方案、方法、技术和策略。其内容包括测试目标、测试环境、输入数据、测试步骤、预期结果、测试脚本等,最终形成文档。简单地认为,测试用例是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,用于核实是否满足某个特定软件需求测试用例八大要素1.测试用例编号由字母、字符、数字组合而成的字符串,有唯一性,易识别性。eg:1)系统测试:产品编号-ST-系统测试项名-系统测试子项名-编号2)集成测试:产品编号

  • 进程理论基础

    操作系统背景知识顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其

  • “3个男生同时追我,我能怎么办……”:分手后如何气死前任?哈哈哈这操作太骚了!

    钢铁是怎么炼成的 (@大猪蹄子研究所)  穿内裤的喵喵见过没? (@恶搞精选锦集 ) 还以为是喜剧效果 (@腐女大本营 )   熟玉米=属于me (@搞笑) &…

  • mysql 获取当前的时间戳

    mysql 获取当前的时间戳获取系统当前时间,类型:timestamp格式yyyy-MM-ddHH:mm:ss selectNOW(),CURRENT_TIMESTAMP(),SYSDATE();结果:三者基本没有区别,稍微一点的区别在于:NOW(),CURRENT_TIMESTAMP()都表示SQL开始执行的时间;SYSDATE()表示执行此SQL的时间selectNOW(),CURRE…

  • foreach跳出本次/当前循环与终止循环方法_js 跳出for循环

    foreach跳出本次/当前循环与终止循环方法_js 跳出for循环1、forEach跳出本次循环可使用return语句跳出本次循环,执行下一次循环vararr=[1,2,3,4,5,6]arr.forEach((item)=>{ if(item===3){ return }console.log(item)})将输出12456,3不会输出2、forEach终止循环forEach无法通过正常流程(如break)终止循环,但可通过抛出异常的方式实现终止循环vararr=[1,2,3,4,5,6]tr

    2022年10月21日
  • 【2012】笔试面试总结(一)

    【2012】笔试面试总结(一)

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号