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 相关文章推荐
python字符串替换的2种方法
Nov 30 Python
Python复数属性和方法运算操作示例
Jul 21 Python
利用python求解物理学中的双弹簧质能系统详解
Sep 29 Python
python使用socket创建tcp服务器和客户端
Apr 12 Python
python爬取网页内容转换为PDF文件
Jul 28 Python
python绘制圆柱体的方法
Jul 02 Python
用Python写一个模拟qq聊天小程序的代码实例
Mar 06 Python
详解用Python练习画个美队盾牌
Mar 23 Python
PyQt5响应回车事件的方法
Jun 25 Python
Python AutoCAD 系统设置的实现方法
Apr 01 Python
pyqt5实现井字棋的示例代码
Dec 07 Python
Python学习之异常中的finally使用详解
Mar 16 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
比file_get_contents稳定的curl_get_contents分享
2012/01/11 PHP
PHP 简易输出CSV表格文件的方法详解
2013/06/20 PHP
php switch语句多个值匹配同一代码块应用示例
2014/07/29 PHP
thinkPHP中U方法加密传递参数功能示例
2018/05/29 PHP
音乐播放用的的几个函数
2006/09/07 Javascript
HTML颜色选择器实现代码
2010/11/23 Javascript
基于jquery实现的上传图片及图片大小验证、图片预览效果代码
2011/04/12 Javascript
javascript日期格式化方法汇总
2015/10/04 Javascript
template.js前端模板引擎使用详解
2017/10/10 Javascript
Angular父组件调用子组件的方法
2018/04/02 Javascript
Vue.js中使用iView日期选择器并设置开始时间结束时间校验功能
2018/08/12 Javascript
React事件处理的机制及原理
2018/12/03 Javascript
vue19 组建 Vue.extend component、组件模版、动态组件 的实例代码
2019/04/04 Javascript
angular4应用中输入的最小值和最大值的方法
2019/05/17 Javascript
新手入门带你学习JavaScript引擎运行原理
2019/06/24 Javascript
vue.js使用v-model实现父子组件间的双向通信示例
2020/02/05 Javascript
关于element的表单组件整理笔记
2021/02/05 Javascript
JavaScript 判断浏览器是否是IE
2021/02/19 Javascript
[01:20]2018DOTA2亚洲邀请赛总决赛战队Mineski晋级之路
2018/04/07 DOTA
python中redis的安装和使用
2016/12/04 Python
Python的numpy库中将矩阵转换为列表等函数的方法
2018/04/04 Python
Python爬虫框架Scrapy基本用法入门教程
2018/07/26 Python
Python文件读写常见用法总结
2019/02/22 Python
详解pytorch 0.4.0迁移指南
2019/06/16 Python
python线程定时器Timer实现原理解析
2019/11/30 Python
学习Python列表的基础知识汇总
2020/03/10 Python
Interflora澳大利亚:同日鲜花速递
2019/06/25 全球购物
店长岗位职责
2013/11/21 职场文书
大学新生军训方案
2014/05/03 职场文书
面试必备的求职信
2014/05/25 职场文书
2014年幼儿园教学工作总结
2014/12/04 职场文书
员工自我工作评价
2015/03/06 职场文书
写给纪委的违纪检讨书
2015/05/05 职场文书
2015年底工作总结范文
2015/05/15 职场文书
数据结构课程设计心得体会
2016/01/15 职场文书
技术入股协议书
2016/03/22 职场文书