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实现基本进制转换的方法
Jul 11 Python
Python实现字典依据value排序
Feb 24 Python
python用装饰器自动注册Tornado路由详解
Feb 14 Python
Django自定义用户认证示例详解
Mar 14 Python
python3 requests中使用ip代理池随机生成ip的实例
May 07 Python
Python基于SMTP协议实现发送邮件功能详解
Aug 14 Python
python3+PyQt5 实现Rich文本的行编辑方法
Jun 17 Python
Pandas操作CSV文件的读写实现方法
Nov 13 Python
Python编程快速上手——PDF文件操作案例分析
Feb 28 Python
浅谈python量化 双均线策略(金叉死叉)
Jun 03 Python
Python是怎样处理json模块的
Jul 16 Python
jupyter notebook 写代码自动补全的实现
Nov 02 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
PHP模拟SQL Server的两个日期处理函数
2006/10/09 PHP
完美实现GIF动画缩略图的php代码
2011/01/02 PHP
使用PHP遍历文件目录与清除目录中文件的实现详解
2013/06/24 PHP
php获取远程文件的内容和大小
2015/11/03 PHP
PHP中Trait及其应用详解
2017/02/14 PHP
实现laravel 插入操作日志到数据库的方法
2019/10/11 PHP
jquery 最简单易用的表单验证插件
2010/02/27 Javascript
document.createElement()用法
2013/03/13 Javascript
详解javascript事件冒泡
2016/01/09 Javascript
js判断checkbox是否选中个数的方法(超简单)
2016/08/19 Javascript
jquery实现图片跟随鼠标的实例
2017/10/17 jQuery
微信小程序如何利用getCurrentPages进行页面传值
2019/07/01 Javascript
vue实现短信验证码输入框
2020/04/17 Javascript
jQuery实现倒计时功能完整示例
2020/06/01 jQuery
vuex实现购物车功能
2020/06/28 Javascript
vscode 使用Prettier插件格式化配置使用代码详解
2020/08/10 Javascript
Java 生成随机字符的示例代码
2021/01/13 Javascript
Django的数据模型访问多对多键值的方法
2015/07/21 Python
解决Python传递中文参数的问题
2015/08/04 Python
Python实现识别手写数字 Python图片读入与处理
2020/03/23 Python
python日期相关操作实例小结
2019/06/24 Python
Python递归函数 二分查找算法实现解析
2019/08/12 Python
基于Python中isfile函数和isdir函数使用详解
2019/11/29 Python
Python StringIO及BytesIO包使用方法解析
2020/06/15 Python
python实现图片,视频人脸识别(dlib版)
2020/11/18 Python
英国工具中心:UK Tool Centre
2017/07/10 全球购物
请用用Java代码写一个堆栈
2012/01/26 面试题
经典C++面试题一
2016/11/06 面试题
应届生法律顾问求职信
2013/11/19 职场文书
店长助理岗位职责
2013/12/13 职场文书
会计核算科岗位职责
2014/03/19 职场文书
函授本科个人自我鉴定
2014/03/25 职场文书
新文化运动的口号
2014/06/21 职场文书
在校证明模板
2015/06/17 职场文书
Python3 类型标注支持操作
2021/06/02 Python
mybatis中注解与xml配置的对应关系和对比分析
2021/08/04 Java/Android