Django的用户模块与权限系统的示例代码


Posted in Python onJuly 24, 2019

一 导言

设计一个好的用户系统往往不是那么容易,Django提供的用户系统可以快速实现基本的功能,并可以在此基础上继续扩展以满足我们的需求。

先看看Django的用户系统都提供哪些功能:

  1. 提供用户模块(User Model)
  2. 权限验证(默认添加已有模块的增加删除修改权限)
  3. 用户组与组权限功能
  4. 用户鉴权与登录功能
  5. 与用户登录验证相关的一些函数与装饰方法

如配置了Django的用户系统,仅需调用Django提供的几个函数,便可实现用户的登录,注销,权限验证等功能。例如以下情景

1.登录

# some_view.py
from django.contrib.auth import authenticate, login
 
def login(request):
  username = request.POST['username']
  password = request.POST['password']
 
  # Django提供的authenticate函数,验证用户名和密码是否在数据库中匹配
  user = authenticate(username=username, password=password)
 
  if user is not None:
    # Django提供的login函数,将当前登录用户信息保存到会话key中
    login(request, user)
 
    # 进行登录成功的操作,重定向到某处等
    ...
  else:
    # 返回用户名和密码错误信息
    ...

2.注销

# some_view.py
from django.contrib.auth import logout
 
def logout(request):
  # logout函数会清除当前用户保存在会话中的信息
  logout(request)

3.验证是否登录

# some_view.py
 
def some_fuction(request):
  user = request.user
  if user.is_authenticated:
    # 已登录用户,可以往下进行操作
  else:
    # 返回要求登录信息

4.验证是否有权限

# some_view.py
 
def some_fuction(request):
  user = request.user
  if user.has_perm('myapp.change_bar'):
    # 有权限,可以往下进行操作
  else:
    # 返回禁止访问等信息

二 用户模块

Django的用户模块类定义在auth应用中,如要直接使用Django的用户类,在setting配置文件中的INSTALLAPP添加一行django.contrib.auth。

这样就可以在代码中直接用User类创建用户:

from django.contrib.auth.models import User
user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
user.last_name = 'Lennon'
user.save()

看看Django的源码User类定义了什么属性:

class User(AbstractUser):
  """
  Users within the Django authentication system are represented by this
  model.
  Username, password and email are required. Other fields are optional.
  """
  class Meta(AbstractUser.Meta):
    swappable = 'AUTH_USER_MODEL'

…,啥都没有?

注意到User类继承AbstractUser类, 用户名和密码信息等定义在父类了。所以再看看AbstractUser类的定义, 限于篇幅仅列出其中一部分,源码在https://github.com/django/django/blob/master/django/contrib/auth/models.py

class AbstractUser(AbstractBaseUser, PermissionsMixin):
  username = models.CharField(
    _('username'),
    max_length=150,
    unique=True,
    help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
    validators=[username_validator],
    error_messages={
      'unique': _("A user with that username already exists."),
    },
  )
  first_name = models.CharField(_('first name'), max_length=30, blank=True)
  last_name = models.CharField(_('last name'), max_length=30, blank=True)
  email = models.EmailField(_('email address'), blank=True)
  date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
....

可以看到AbstractUser类中定义了username这个字段了,另外还有email、first_name、last_name等。AbstractUser类还继承AbstractBaseUser和PermissionsMixin类,AbstractBaseUser类提供password字段及将密码加密保存的相关方法,PermissionsMixin类提供User类权限认证功能。

总的来说,一个User类,定义了以下字段:

  • username: 用户名
  • password: 密码
  • first_name: 姓名
  • last_name: 姓名
  • email: 邮箱
  • groups: Group类多对多的关系对象管理器
  • user_permissions: Permission类多对多的关系对象管理器
  • is_staff: 是否工作人员
  • is_active: 是否激活
  • is_superuser: 是否管理员
  • last_login: 最近登录时间
  • date_joined: 注册时间

另外,生成一个User实例,可以使用以下属性:

  • is_authenticated: 只读,用来判断用户是否存在。只有AnonymousUser类这个属性为False。
  • is_anonymous: 就是用来区分 User 和 AnonymousUser 对象而已。

User类提供了许多方法方便我们执行各种操作:

  • set_password(raw_password): 可以将password转成hash值,记得再调用user.save()保存。
  • check_password(raw_password): 相反,将原生密码与保存在数据库中hash值密码对比(直接对比肯定不相同的)。
  • has_perm(perm, obj=None),has_perms(perm_list, obj=None): 验证该用户是否拥有某个权限或者权限列表
  • get_all_permissions(obj=None): 返回该用户拥有的所有权限

三 验证与登录用户

验证用户名和密码看起来很简单,对比提交的用户名和密码与数据库保存的一致即可。

如:

# some_view.py
def some_fuction(request):
  username = request.POST['username']
  password = request.POST['password']
  try:
    user = User.objects.get(username=username, password=password)
  except ObjectNotExists:
    # 用户名和密码不匹配一个用户
 
  ...

由于密码已经进行hash后保存,上述代码还得先把提交的password值先hash再去数据库中搜索,总得多写了一些代码。这些Django都已经考虑到了, 提供了一个authenticate函数验证是否存在该用户,就像导言的实例代码:

# some_view.py
from django.contrib.auth import authenticate, login
 
def login(request):
  username = request.POST['username']
  password = request.POST['password']
  user = authenticate(username=username, password=password)
  if user is not None:
    login(request, user)
    ...
  else:
    ...

这里重点说明一下authenticate和login函数。

1.authenticate(**credentials)

传入待验证的参数,默认是username和password,它会调用系统配置里的每一个authentication backend进行验证,验证通过返回一个User实例,否则返回None。

每一个authentication backend指认证后端,在setting.py中配置的AUTHENTICATION_BACKENDS变量,默认是[‘django.contrib.auth.backends.ModelBackend'],可以增加自定义的认证后端,或者使用第三方提供的。authenticate函数会按顺序调用每一个进行验证,如果第一个没有通过,它会使用第二个进行验证,直到所有的认证后端都失败后才返回None。

看看这个ModelBackend类如何返回认证用户(再次缩减源码):

class ModelBackend(object):
  def authenticate(self, request, username=None, password=None, **kwargs):
    if username is None:
      username = kwargs.get(UserModel.USERNAME_FIELD)
    try:
      user = UserModel._default_manager.get_by_natural_key(username)
    except UserModel.DoesNotExist:
 
      UserModel().set_password(password)
    else:
      if user.check_password(password) and self.user_can_authenticate(user):
        return user
# ...

实际上authenticate函数会调用每一个authentication backend类的authenticate方法, ModelBackend类与我们之前的验证方法稍有不同,它先从数据库用户表中取出与username对应的一个User实例,再通过User的check_password方法验证password是否正确,并返回这个User实例。

2.login(request, user, backend=None)

接收HttpRequest对象和一个User对象,login函数会将当前用户信息保存到会话cookie中,所以要使用Django用户系统的所有功能,也得安装Django默认的会话APP。

看看login函数做了什么:

def login(request, user, backend=None):
  # (清空当前的session信息)
  # ...
  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函数将当前用户的一个唯一标识信息保存在request.session的SESSION_KEY中, 下次请求时即可从会话cookie中拿到当前登录的用户对象。

如果要求登录用户才能访问相应的View,可以这么写:

from django.conf import settings
from django.shortcuts import redirect
 
def my_view(request):
  if not request.user.is_authenticated:
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

通过login函数可以将用户信息保存在会话中,当下一个请求到达view前,Django的会话中间件从会话cookie中取出用户对象,并赋值给request.user。这样,已登录的用户request.user.is_authenticated值为True,可以进行相应的操作。未登录用户request.user返回一个AnonymousUser对象,它的is_authenticated属性值永远为False,那么要将这个请求重定向到登录页面。

这两行代码可以通过在view函数上加一个装饰器实现,如

@login_required
def my_view(request):
  ...

装饰器login_required(redirect_field_name='next', login_url=None), 可传入login_url参数设置未登录用户的请求重定向的地址,否则重定向到settings.LOGIN_URL。

四 权限认证

User模型有两个多对多关系的字段: groups 和 user_permissions, 它们与Pemission模型有关。User与Pemission、User与Permission、Group与Permission均是多对多关系, Permission定义了具体的权限,其字段如下:

class Permission(models.Model):
  name = models.CharField(_('name'), max_length=255)   # 权限名称(用作显示)
  content_type = models.ForeignKey(    # 内容类型: 每个模型对应一个内容类型,用于定位指定模型
    ContentType,
    models.CASCADE,
    verbose_name=_('content type'),
  )
  codename = models.CharField(_('codename'), max_length=100)  # 权限的代码名称,用在如has_permission函数参数

给一个用户添加、删除权限很简单: myuser.user_permissions.set([permission_list]) myuser.user_permissions.add(permission, permission, …) myuser.user_permissions.remove(permission, permission, …) myuser.user_permissions.clear()

其中,permission是具体的permission对象。 也可以通过用户所在的组添加相应的权限: group.permissions.set([permission_list]) group.permissions.add(permission, permission, …) group.permissions.remove(permission, permission, …) group.permissions.clear()

只要这个用户加入该组也拥有组权限。通过User对象的get_all_permissions(obj=None)方法可以获得该用户的所有权限列表(字符列表),也可以通过get_group_permissions(obj=None)获得对应的组权限列表。

我们更经常要做的是在view中执行操作之前验证用户是否拥有指定权限,这里一般用到User对象的has_perm(perm, obj=None)方法或者has_perms(perm_list, obj=None)判断。

has_perm(perm, obj=None)方法中perm是”."格式的权限字符串, 如果有这个权限,方法会返回True。同样,`has_perms(perm_list, obj=None)`方法中perm_list中是"."格式的权限字符串列表。

同login_required装饰器,权限认证也有对应的permission_required装饰器:

permission_required(perm, login_url=None, raise_exception=False)

如果raise_exception=True, 权限没有认证通过则抛出PermissionDenied异常,返回默认的403页面。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
精确查找PHP WEBSHELL木马的方法(1)
Apr 12 Python
一个小示例告诉你Python语言的优雅之处
Jul 04 Python
利用Psyco提升Python运行速度
Dec 24 Python
python中self原理实例分析
Apr 30 Python
详谈Python2.6和Python3.0中对除法操作的异同
Apr 28 Python
tensorflow1.0学习之模型的保存与恢复(Saver)
Apr 23 Python
Python异步操作MySQL示例【使用aiomysql】
May 16 Python
用python打印菱形的实操方法和代码
Jun 25 Python
通过实例解析python and和or使用方法
Nov 14 Python
Ubuntu16安装Python3.9的实现步骤
Dec 15 Python
Python破解极验滑动验证码详细步骤
May 21 Python
Python pandas之求和运算和非空值个数统计
Aug 07 Python
python3字符串操作总结
Jul 24 #Python
django数据关系一对多、多对多模型、自关联的建立
Jul 24 #Python
django如何自己创建一个中间件
Jul 24 #Python
django如何通过类视图使用装饰器
Jul 24 #Python
django 类视图的使用方法详解
Jul 24 #Python
django如何实现视图重定向
Jul 24 #Python
python字符串分割及字符串的一些常规方法
Jul 24 #Python
You might like
截获网站title标签之家内容的例子
2006/10/09 PHP
优化PHP代码的53条建议
2008/03/27 PHP
discuz程序的PHP加密函数原理分析
2011/08/05 PHP
CI框架装载器Loader.php源码分析
2014/11/04 PHP
谈谈PHP中substr和substring的正确用法及相关参数的介绍
2015/12/16 PHP
浅谈php中fopen不能创建中文文件名文件的问题
2017/02/06 PHP
一个可以增加和删除行的table并可编辑表格中内容
2014/06/16 Javascript
2014 年最热门的21款JavaScript框架推荐
2014/12/25 Javascript
js实现图片无缝滚动
2015/12/23 Javascript
浅谈js中的延迟执行和定时执行
2016/05/31 Javascript
JavaScript数组去重由慢到快由繁到简(优化篇)
2016/08/26 Javascript
bootstrap可编辑下拉框jquery.editable-select
2017/10/12 jQuery
微信小程序开发背景图显示功能
2018/08/08 Javascript
javascript将非数值转换为数值
2018/09/13 Javascript
react-router4按需加载(踩坑填坑)
2019/01/06 Javascript
node.js ws模块搭建websocket服务端的方法示例
2019/04/25 Javascript
[00:09]DOTA2新版本PA至宝特效动作展示
2014/11/19 DOTA
在Django框架中编写Contact表单的教程
2015/07/17 Python
python 禁止函数修改列表的实现方法
2017/08/03 Python
详解Python自建logging模块
2018/01/29 Python
python 保存float类型的小数的位数方法
2018/10/17 Python
PyCharm+PySpark远程调试的环境配置的方法
2018/11/29 Python
Python实现删除排序数组中重复项的两种方法示例
2019/01/31 Python
selenium python 实现基本自动化测试的示例代码
2019/02/25 Python
Python中的类与类型示例详解
2019/07/10 Python
基于Python实现人脸自动戴口罩系统
2020/02/06 Python
python实现程序重启和系统重启方式
2020/04/16 Python
如何基于Python和Flask编写Prometheus监控
2020/11/25 Python
Python Selenium库的基本使用教程
2021/01/04 Python
CSS3绘制有活力的链接下划线
2016/07/14 HTML / CSS
web页面录屏实现
2019/02/12 HTML / CSS
标准毕业生自荐信范文
2013/11/04 职场文书
库房主管岗位职责
2013/12/31 职场文书
工商治理实习生的自我评价分享
2014/02/20 职场文书
html+css合并表格边框的示例代码
2021/03/31 HTML / CSS
python实现的人脸识别打卡系统
2021/05/08 Python