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 相关文章推荐
使用grappelli为django admin后台添加模板
Nov 18 Python
使用Python脚本操作MongoDB的教程
Apr 16 Python
python通过ssh-powershell监控windows的方法
Jun 02 Python
python开发之字符串string操作方法实例详解
Nov 12 Python
Python数据结构之栈、队列的实现代码分享
Dec 04 Python
Django中的CBV和FBV示例介绍
Feb 25 Python
用Django实现一个可运行的区块链应用
Mar 08 Python
对PyQt5中树结构的实现方法详解
Jun 17 Python
Python提取转移文件夹内所有.jpg文件并查看每一帧的方法
Jun 27 Python
django haystack实现全文检索的示例代码
Jun 24 Python
使用pandas生成/读取csv文件的方法实例
Jul 09 Python
python中对列表的删除和添加方法详解
Feb 24 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
为查询结果建立向后/向前按钮
2006/10/09 PHP
php中在PDO中使用事务(Transaction)
2011/05/14 PHP
PHP临时文件的安全性分析
2014/07/04 PHP
php ajax实现文件上传进度条
2016/03/29 PHP
浅谈laravel框架sql中groupBy之后排序的问题
2019/10/17 PHP
收藏Javascript中常用的55个经典技巧
2007/08/12 Javascript
jquery 表单取值常用代码
2009/12/22 Javascript
jQuery 位置函数offset,innerWidth,innerHeight,outerWidth,outerHeight,scrollTop,scrollLeft
2010/03/23 Javascript
cloudgamer出品ImageZoom 图片放大效果
2010/04/01 Javascript
js读取注册表的键值示例
2013/09/25 Javascript
javascript根据像素点取位置示例
2014/01/27 Javascript
js闭包实例汇总
2014/11/09 Javascript
JS实现让访问者自助选择网页文字颜色的方法
2015/02/24 Javascript
JQuery中attr方法和removeAttr方法用法实例
2015/05/18 Javascript
javascript实现数组内值索引随机化及创建随机数组的方法
2015/08/10 Javascript
基于jquery实现放大镜效果
2015/08/17 Javascript
JS时间特效最常用的三款
2015/08/19 Javascript
Kendo Grid editing 自定义验证报错提示的解决方法
2016/11/18 Javascript
jQuery实现的简单图片轮播效果完整示例
2018/02/08 jQuery
使用live-server快速搭建本地服务器+自动刷新的方法
2018/03/09 Javascript
微信小程序开发实现的IP地址查询功能示例
2019/03/28 Javascript
微信小程序中限制激励式视频广告位显示次数(实现思路)
2019/12/06 Javascript
python中__call__方法示例分析
2014/10/11 Python
Python 正则表达式入门(中级篇)
2016/12/07 Python
python虚拟环境virtualenv的使用教程
2017/10/20 Python
Python使用matplotlib实现交换式图形显示功能示例
2019/09/06 Python
美国户外生活方式品牌:Eddie Bauer
2016/12/28 全球购物
error和exception有什么区别
2012/10/02 面试题
交通安全演讲稿
2014/01/07 职场文书
护士节活动总结
2014/08/29 职场文书
村党支部书记四风问题个人对照检查材料思想汇报
2014/10/06 职场文书
2015年医院创卫工作总结
2015/04/22 职场文书
超搞笑婚前保证书
2015/05/08 职场文书
幼儿园门卫安全责任书
2015/05/08 职场文书
管理者们如何制定2019年的工作计划?
2019/07/01 职场文书
JavaScript中reduce()的用法
2022/05/11 Javascript