Django(15):身份和权限认证

目录

  • 1.Django中的身份认证模块
    • 1.1 用户模型
    • 1.2 认证模块
    • 1.3 项目搭建演示
  • 2.权限管理架构
    • 2.1 权限相关数据模型
    • 2.2 权限相关功能函数
    • 2.3 权限分配函数
    • 2.4 权限设置
  • 3.资源访问管理

1.Django中的身份认证模块

1.1 用户模型

Django中有内建的用户模块django.contrib.auth.models.User,该类型通过定义网站中用户的基本数据完成身份认证功能支持。代码如下:

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

可以看到它继承了AbstractUser,其代码如下:

class AbstractUser(AbstractBaseUser, PermissionsMixin):"""An abstract base class implementing a fully featured User model withadmin-compliant permissions.Username and password are required. Other fields are optional."""username_validator = UnicodeUsernameValidator()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=150, blank=True)last_name = models.CharField(_("last name"), max_length=150, blank=True)email = models.EmailField(_("email address"), blank=True)is_staff = models.BooleanField(_("staff status"),default=False,help_text=_("Designates whether the user can log into this admin site."),)is_active = models.BooleanField(_("active"),default=True,help_text=_("Designates whether this user should be treated as active. ""Unselect this instead of deleting accounts."),)date_joined = models.DateTimeField(_("date joined"), default=timezone.now)objects = UserManager()EMAIL_FIELD = "email"USERNAME_FIELD = "username"REQUIRED_FIELDS = ["email"]class Meta:verbose_name = _("user")verbose_name_plural = _("users")abstract = Truedef clean(self):super().clean()self.email = self.__class__.objects.normalize_email(self.email)def get_full_name(self):"""Return the first_name plus the last_name, with a space in between."""full_name = "%s %s" % (self.first_name, self.last_name)return full_name.strip()def get_short_name(self):"""Return the short name for the user."""return self.first_namedef email_user(self, subject, message, from_email=None, **kwargs):"""Send an email to this user."""send_mail(subject, message, from_email, [self.email], **kwargs)

可以看到,AbstractUser有自己的属性和方法,同时也从AbstractBaseUser, PermissionsMixin继承了其他属性和方法,具体内容可参看源码。

同时,如果用户没有身份信息,访问网站时,Django框架通过django.contrib.auth.models.AnonymousUser类型为其赋予了一个匿名身份。其源码如下:

class AnonymousUser:id = Nonepk = Noneusername = ""is_staff = Falseis_active = Falseis_superuser = False_groups = EmptyManager(Group)_user_permissions = EmptyManager(Permission)def __str__(self):return "AnonymousUser"def __eq__(self, other):return isinstance(other, self.__class__)def __hash__(self):return 1  # instances always return the same hash valuedef __int__(self):raise TypeError("Cannot cast AnonymousUser to int. Are you trying to use it in place of ""User?")def save(self):raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")def delete(self):raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")def set_password(self, raw_password):raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")def check_password(self, raw_password):raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")@propertydef groups(self):return self._groups@propertydef user_permissions(self):return self._user_permissionsdef get_user_permissions(self, obj=None):return _user_get_permissions(self, obj, "user")def get_group_permissions(self, obj=None):return set()def get_all_permissions(self, obj=None):return _user_get_permissions(self, obj, "all")def has_perm(self, perm, obj=None):return _user_has_perm(self, perm, obj=obj)def has_perms(self, perm_list, obj=None):if not is_iterable(perm_list) or isinstance(perm_list, str):raise ValueError("perm_list must be an iterable of permissions.")return all(self.has_perm(perm, obj) for perm in perm_list)def has_module_perms(self, module):return _user_has_module_perms(self, module)@propertydef is_anonymous(self):return True@propertydef is_authenticated(self):return Falsedef get_username(self):return self.username

正常情况下,上面的用户信息是不能满足我们的业务需求的,在实际项目中,可以根据需求自定义用户的扩展资料,将扩展资料和内建用户进行一对一关联,这样可以使用户的资料会大幅提升。

1.2 认证模块

Django框架内建的django.contrib.auth模块封装了身份认证和状态保持操作,可以使用django.contrib.auth.authenticate完成核心的身份认证处理。源代码如下:

@sensitive_variables("credentials")
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):backend_signature = inspect.signature(backend.authenticate)try:backend_signature.bind(request, **credentials)except TypeError:# This backend doesn't accept these credentials as arguments. Try# the next one.continuetry:user = backend.authenticate(request, **credentials)except PermissionDenied:# This backend says to stop in our tracks - this user should not be# allowed in at all.breakif user is None:continue# Annotate the user object with the path of the backend.user.backend = backend_pathreturn user# The credentials supplied are invalid to all backends, fire signaluser_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)

同时,使用django.contrib.auth封装的login()函数完成了状态保持,将用户保持到当前会话中;使用logout()函数可以移除状态保持。具体的内容可以参考源码。

1.3 项目搭建演示

1.创建项目

# 创建项目perm_demo
django-admin startproject perm_demo
# 创建blog子项目
cd perm_demo/
django-admin startapp blog

2.创建数据模型

在blog子项目中编辑models.py模块,添加用户扩展资料:

class UserProfile(models.Model):"""用户扩展资料"""C_GENDER = (("0", "女"),("1", "男"),)# 关联内建用户user = models.OneToOneField(verbose_name="用户", to=User, related_name="profile", on_delete=models.CASCADE)# 用户性别gender = models.CharField(verbose_name="性别", max_length=5, choices=C_GENDER, default="1")# 用户年龄age = models.IntegerField(verbose_name="年龄", default=0)# 联系方式phone = models.CharField(verbose_name="手机", max_length=15, null=True, blank=True)# 所述组织org = models.CharField(verbose_name="组织", max_length=200, null=True, blank=True)# 个人介绍intro = models.TextField(verbose_name="简介", null=True, blank=True)

3.创建表单模块

在blog项目中创建forms.py模块,添加代码:

from django import forms
from django.contrib.auth.models import Userclass UserRegisterForm(forms.ModelForm):"""用户注册表单"""confirm = forms.CharField(label='确认密码', min_length=6, max_length=18)class Meta:model = Userfields = ['username', 'password', 'confirm']def clean_username(self):"""自定义用户名称验证规则"""u_list = User.objects.filter(username=self.cleaned_data['username'])if len(u_list) > 0:raise forms.ValidationError("账号已经存在,请使用其他账号注册")return self.cleaned_data['username']def clean_confirm(self):"""自定义确认密码验证规则"""if self.cleaned_data['password'] != self.cleaned_data['confirm']:raise forms.ValidationError("两次密码输入不一致")return self.cleaned_data['confirm']

4.用户登录业务逻辑

编辑blog/views.py模块,代码如下:

from django.shortcuts import render, redirect, get_object_or_404, get_list_or_404
from django.urls import reverse
from django.contrib.auth import authenticate, login, logout
from django.views.decorators.http import require_GET
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import Userfrom . import forms
from .models import Articledef user_login(request):"""用户登录"""if request.method == "GET":return render(request, 'blog/login.html', {})elif request.method == "POST":# 接受登录数据username = request.POST.get("username")password = request.POST.get("password")# 验证表单数据user = authenticate(request, username=username, password=password)if user and user.is_active:login(request, user)# 返回首页return redirect(reverse("blog:user_index"))else:return render(request, 'blog/login.html', {'msg_code': "-1",'msg_info': "账号或者密码有误."})def user_logout(request):"""用户退出"""logout(request)return redirect(reverse("blog:user_index"))def user_register(request):"""用户注册"""if request.method == "GET":return render(request, "blog/register.html", {})elif request.method == "POST":# 接收用户注册数据form_register = forms.UserRegisterForm(request.POST)# 判断注册数据有效性if form_register.is_valid():# 验证通过,保存注册数据User.objects.create_user(username=form_register.instance.username,password=form_register.instance.password)# 跳转登录界面return redirect(reverse("blog:user_login"), kwargs={"msg_code": "0","msg_info": "账号注册成功"})else:return render(request, "blog/register.html", {"form": form_register,"msg_code": "-1","msg_info": "注册失败"})def user_index(request):# 查看文章列表articles = Article.objects.all()return render(request, "blog/index.html", {"articles": articles})

5.添加网页文件

创建blog/templates/blog文件夹,添加网页文件

添加基础模板文件base.html,其他文件继承它:

DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>身份认证-权限管理-{% block title %}{% endblock %}title>
head>
<body>
{% block page_body %}
{% endblock %}
body>
html>

添加登录网页文件login.html:

{% extends 'blog/base.html' %}{% block title %}个人博客用户登录{% endblock %}{% block page_body %}<h2>会员登录h2><p>{{ form.errors }}{{ msg_info }}p><form action="{% url 'blog:user_login' %}" method="POST">{% csrf_token %}<label for="username">账号: label><input type="text" name="username" id="username"><span style="color:red">{{ form.errors.username }}span><br /><label for="password">密码: label><input type="password" name="password" id="password"><span style="color:red">{{ form.errors.password }}span><br /><input type="submit" value="登录">form>
{% endblock %}

添加注册网页文件register.html:

{% extends 'blog/base.html' %}{% block title %}个人博客用户注册{% endblock %}{% block page_body %}<h2>新用户注册<small>{{ form.errors }}small>h2><hr><form action="{% url 'blog:user_register' %}" method="POST">{% csrf_token %}<label for="username">账号:label><input type="text" name="username" id="username"><br /><label for="password">密码:label><input type="password" name="password" id="password"><br /><label for="confirm">确认密码:label><input type="password" name="confirm" id="confirm"><br /><input type="submit" value="提交注册">form>
{% endblock %}

添加首页网页文件index.html:

{% extends 'blog/base.html' %}{% block title %}个人博客首页{% endblock %}{% block page_body %}<h2>个人博客首页<small>尊敬的用户{{ request.user }},欢迎访问本系统small>h2><hr><a href="{% url 'blog:user_logout'%}">退出a><p>博客网页内容p><ul><li><a href="{% url 'blog:article_publish' %}">发表文章a>li>ul><ul>{% for article in articles %}<li>标题:<a href="{% url 'blog:article_detail' article.id %}">{{ article.title }}a> -- 作者:{{ article.author.username }} --<a href="{% url 'blog:article_delete' article.id %}">删除a>  <a href="{% url 'blog:article_update' article.id %}">编辑a>li>{% endfor %}ul>
{% endblock %}

6.添加路由

编辑blog/urls.py

from django.urls import path
from django.views.generic import TemplateViewfrom . import viewsapp_name = 'blog'urlpatterns = [path("perm_refused/", TemplateView.as_view(template_name='blog/permission_refused.html'), name="perm_refused"),path("login/", views.user_login, name="user_login"),path("logout/", views.user_logout, name="user_logout"),path("register/", views.user_register, name="user_register"),path("", views.user_index, name="user_index"),
]

7.启动项目

访问http://127.0.0.1:8000/blog/register,先注册一个用户,容纳后访问首页http://127.0.0.1:8000/blog/让先登录,登录后到了博客首页,如下:
在这里插入图片描述

2.权限管理架构

权限管理是指用户对网站资源的访问管理操作,对于不同的用户身份,对资源的访问方式应有所区分。在用户访问资源时,需要先判断是否有权限。

对于很多用户,可能有级别相似的权限,权限设置造成大量冗余,于是引入角色,通过角色对权限进行管理,用户拥有某个角色,相当于拥有该角色的权限。

2.1 权限相关数据模型

对于整个权限管理,Django内置了用户模块、角色模块、权限模块来尽心管理:

  • django.contrib.auth.models.User:用户模块中的用户类型
  • django.contrib.auth.models.Group:用户模块中的用户组类型
  • django.contrib.auth.models.Permission:用户模块中的权限类型

2.2 权限相关功能函数

Django内置的django.contrib.auth模块中,除了有上面用于描述实力的模块,也封装了用于身份认证和权限管理的函数。

  1. django.contrib.auth.authenticate:用户身份认证函数,传入用户身份信息,默认参数为账号、密码。如果认证通过,返回当前用户对象,否则返回None。
  2. django.contrib.auth.login:用户身份状态记录函数,传入请求对象和需要记录的用户对象。该函数负责将用户信息记录到所属的会话对象(如session)中存储。
  3. django.contrib.auth.loglout:登录状态清除函数,传入请求对象,将记录在当前请求所属会话中的用户对象user清空并设置匿名用户Anonymous。
  4. django.contrib.auth.decorators.login_required:用户身份认证资源访问装饰器,验证用户是否通过身份认证,如果验证通过,则允许该用户访问装饰器下面的函数,否则跳转到login_url参数指定的路径。如果不提供该参数,则自动获取配置文件里的LOGIN_UTL配置路径。
  5. django.contrib.auth.decorators.permission_required:数据资源访问装饰器,验证当前用户是否拥有制指定权限。如果验证通过,则允许该用户访问装饰器下面的函数,否则跳转到login_url参数指定的路径。如果不提供该参数,则自动获取配置文件里的LOGIN_UTL配置路径。

另外再第七章视图处理函数中,还有require_GET、require_POST、require_http_method等装饰器,用于限制请求类型。

2.3 权限分配函数

用户通过身份认证后,可以在操作过程中根据实际场景进行用户组和用户权限的独立分配,相关函数如下:

  1. user.groups.set([group_list]):为用户设置用户组,可以设置到多个组里。
  2. user.groups.add(group1, group2, ...):为用户设置用户组。
  3. user.groups.remove(group1, group2, ...):将当前用户从指定用户组删除。
  4. user.groups.clear():把用户从所有的用户组删除。
  5. user.user_permissions.set([permission_list]):为用户设置权限,可以指定多个。
  6. user.user_permissions.add(perm1, perm2, ...):为用户设置权限。
  7. user.user_permissions.remove(perm1, perm2, ...):删除用户指定权限。
  8. user.user_permissions.clear():删除用户所有权限。

2.4 权限设置

在Django中,会自动设置在配置INSTALLED_APPS中的app内部模型类的默认访问权限,有增删改查4种,如下:

  • add:如blog子项目的Article模型,设置了权限blog.add_article;
  • change:如blog子项目的Article模型,设置了权限blog.change_article;
  • delete:如blog子项目的Article模型,设置了权限blog.delete_article;
  • view:如blog子项目的Article模型,设置了权限blog.view_article;

使用上一节perm_demo项目,添加模型类Article,如下:

class Article(models.Model):"""文章类型"""# 文章主键编号id = models.UUIDField(verbose_name="文章编号", primary_key=True, default=uuid4)# 文章标题title = models.CharField(verbose_name="文章编号", max_length=20)# 文章内容content = models.TextField(verbose_name="文章内容")# 发布时间publish_time = models.DateTimeField(verbose_name="发表时间", auto_now_add=True)# 修改时间update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)# 文章作者author = models.ForeignKey(verbose_name="作者", to=User, on_delete=models.CASCADE)def get_absolute_url(self):return reverse("blog:article_detail", kwargs={"article_id": self.id})

登录后台管理系统访问http://127.0.0.1:8000/admin/auth/user/4/change/可查看所有的权限,如下:
在这里插入图片描述

3.资源访问管理

可以通过装饰器@permission_required进行权限认证。

以上面perm_demo项目为例,编辑blog/views.py,在与文章相关的视图处理函数中添加权限认证操作:

@permission_required("blog.view_article")
@login_required
@require_GET
def user_index(request):# 查看文章列表print("index")articles = Article.objects.all()print(request.user.get_all_permissions())print(request.user.has_perm("blog.view_article"))return render(request, "blog/index.html", {"articles": articles})@login_required
@permission_required("blog.add_article", login_url='/blog/perm_refused/')
def article_publish(request):"""发表文章"""if request.method == "GET":return render(request, "blog/article_publish.html", {})elif request.method == "POST":# 接受文章数据form = forms.ArticleForm(request.POST)# 验证文章数据是否正确if form.is_valid():# 发表文章form.save()# 跳转到文章详情页面return redirect(form.instance)return render(request, "blog/article_publish.html", {'form': form})@permission_required("blog.view_article", login_url='/blog/perm_refused/')
@require_GET
def article_detail(request, article_id):"""查看文章"""if request.method == "GET":article = get_object_or_404(Article, pk=article_id)return render(request, "blog/article_detail.html", {"article": article})@login_required
@permission_required("blog.change_article", login_url='/blog/perm_refused/')
def article_update(request, article_id):"""修改文章"""article = get_object_or_404(Article, pk=article_id)if request.method == "GET":return render(request, "blog/article_update.html", {"article": article})elif request.method == "POST":# 接受文章数据form = forms.ArticleForm(request.POST, instance=article)# 验证文章数据是否正确if form.is_valid():print("文章修改数据验证通过")# 存储文章数据form.save()# 跳转到文章详情页面return redirect(form.instance)return render(request, "blog/article_update.html", {'form': form})@login_required
@permission_required("blog.delete_article", login_url='/blog/perm_refused/')
def article_delete(request, article_id):"""删除文章"""# 查询文章数据article = get_object_or_404(Article, pk=article_id)# 删除文章article.delete()# 返回首页return redirect(reverse("blog:user_index"))

上面的代码中看到,访问首页(user_index视图函数)和文章详情(article_detail视图函数)都需要当前用户拥有blog.view_article权限;发表文章(article_publish视图函数)需要当前用户拥有blog.add_article权限;修改文章(article_update视图函数)需要当前用户拥有blog.change_article权限;同理删除需要blog.delete_article权限。

上面的操作方式可以满足大部分需求,到那时如果有定制化的权限架构,可参考Django的设计进行实现。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部