Django之认证和权限控制

Django之认证和权限控制

  • django的settings文件自带的框架:
  • django框架自带的认证
      • 认证方式一般来说有三种
      • django.contrib.auth的认证及权限
  • 认证及权限概览
  • rest_framework框架的认证和权限
  • 一. 权限
      • 全局配置
      • 局部配置
      • 自定义权限
      • has_permission 和 has_object_permission 区别
          • 这里还有点问题,不是很清楚:
          • 大胆地猜想:
      • 局部禁用
      • 匿名用户全局配置
      • 自定义组合权限
      • 如果未指明,则采用如下默认配置
      • 提供一个小Demo
        • models.py
        • settings.py
        • 认证类
        • 自定义的权限类
        • view.py
  • 二. 用户认证
      • rest_framework的认证
        • 认证(Authentication)
      • rest framework框架认证基类
      • Django rest framework内置的认证类
      • 在这里主要说下jwt认证
      • urls.py配置
      • settings.py
      • 手动生成token
      • 结合项目的应用:
      • 自定义认证
    • 注意
    • 结语:

django的settings文件自带的框架:

# 不用的话可以自行注释掉
INSTALLED_APPS = [
	# 自带
    'django.contrib.admin',      		# 后台管理框架
    'django.contrib.auth',				# 认证权限框架  需要结合django自带的auth_user表
    'django.contrib.contenttypes',		# 内容类型框架
    'django.contrib.sessions',			# 会话管理框架
    'django.contrib.messages',			# 模板消息框架
    'django.contrib.staticfiles',		# 静态文件框架
    # 非自带
    'rest_framework',					# rest_framework框架
    ]

django框架自带的认证

认证方式一般来说有三种

1 传统的认证方式,客户端每次访问都要带上用户名和密码,这种认证方式不安全。
2.session 认证 ,客户端访问带上服务端发给的session_id 。
3.token 认证

前提是需要结合django自带的auth_user表

# 使用了auth模块来认证用户
user = authenticate(username=phone, password=password)

django.contrib.auth的认证及权限

auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组和权限管理。

auth可以和admin模块配合使用, 快速建立网站的管理系统。

在INSTALLED_APPS中添加’django.contrib.auth’使用该APP, auth模块默认启用。

from django.contrib.auth.models import User

认证及权限概览

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问

  • 在执行视图的dispatch()方法前,会先进行视图访问权限的判断
  • 在通过get_object()获取具体对象时,会进行模型对象访问权限的判断

rest_framework框架的认证和权限

官方文档地址:
1.认证
2.权限
3.jwt的git地址: https://github.com/jpadilla/django-rest-framework-jwt/blob/master/docs/index.md#JWT_AUTH_COOKIE
4.jwt文档:https://jpadilla.github.io/django-rest-framework-jwt/
先认证,再判断权限

首先要来认证,最起码知道啥样的用户可以访问此接口,已登录用户还是未登录用户?
与身份验证和限制一起,权限确定是应该授予还是拒绝访问请求。
在允许任何其他代码继续之前,权限检查始终在视图的最开始运行。权限检查通常使用 request.user 和 request.auth
属性中的身份验证信息来确定是否应允许传入请求。

权限用于授予或拒绝不同类别的用户访问API的不同部分。

一. 权限

rest_framework框架可以做到对象级别的权限控制(其实没有多精细,粒度比较大)

全局配置

在settings文件夹

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 默认提供的可选权限跟局部配置一样
    )
}

局部配置

在具体的视图中通过permission_classes属性来设置

permission_classes = (IsAuthenticatedOrReadOnly,) # 给具体的类视图配置权限

默认提供的可选权限

AllowAny                         # 允许所有用户
IsAuthenticated                  # 仅通过认证的用户
IsAdminUser                      # 仅管理员用户
IsAuthenticatedOrReadOnly        # 认证的用户可以完全操作,否则只能get读取

自定义权限

当你觉得提供的可选权限还是不能满足需要时,可以自定义权限,可以把自定义的权限放在全局配置上,也可以放在局部配置上

两种形式
1.函数的形式

 def get_permissions(self):
        if self.action == "read":
            return (IsAuthenticated(),) # 返回的是一个元祖
        return(AllowAny(),)

2.以类的形式

class MyPermission(BasePermission):
	message = "用户必须是类型为3的才能访问"      # 当禁止放行时,会返回的提示信息

    def has_permission(self, request, view):
    	# 加入自己的条件判断
    	if request.user.user_type != 3:
            return False   # False 禁止放行
        return True  # True 全部放行

    def has_object_permission(self, request, view, obj):
        """ 控制对obj 对象的访问权限,此案例解决所有对对象的访问"""
        if obj.id >6: # 不能访问id 大于6的对象的信息
            return False
        return True

has_permission 和 has_object_permission 区别

如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部

has_permission 是用户对这个视图有没有 GET POST PUT PATCH DELETE 权限的分别判断。has_object_permission 是用户过了 has_permission 判断有权限以后,再判断这个用户有没有对一个具体的对象有没有操作权限。
就好比一个用户是“老师”角色,“ has_permission ”可以用来判断这个老师有没有修改学生成绩的权限(他有没有教课)。但是如果更细致的管理,就要用“ has_object_permission ”判断这个老师能不能修改当前他要改的这个学生的成绩(他修改的是不是他教课班上的学生)。

has_permission会自动触发,而has_object_permission 需要手动通过下面代码,在view中触发,但是在restAPI,众多通用视图中,会自动触发,看情况而定

self.check_object_permissions(request, obj)
这里还有点问题,不是很清楚:

1.在不同的rest父类中,或许有的就会自己触发check_object_permissions,或许有的就需要手动触发
2.check_object_permissions 有待商榷???

大胆地猜想:

1.当你重写覆盖 .get_objects()时,需要手动的调用check_object_permissions
2.看见两张图片,有所感悟
官方文档地址:文档
Django之认证和权限控制_第1张图片

Django之认证和权限控制_第2张图片

局部禁用

permission_classes = []

匿名用户全局配置

此配置需要的时候加上,不需要不用写

REST_FRAMEWORK = {
	#匿名用户配置,只需要函数或类的对应的返回值,对应request.user="匿名"
	'UNAUTHENTICATED_USER':None, #匿名,request.user = None 
	#匿名token,只需要函数或类的对应的返回值,对应request.auth=None
	'UNAUTHENTICATED_TOKEN':None, #匿名,request.auth = None 
}

自定义组合权限

如果自定义的权限类继承自rest_framework.permissions.BasePermission,则可以使用标准Python按位运算符组合权限。例如,IsAuthenticatedOrReadOnly可以写成:
注意:它支持&(和),|。(或)和〜(不是)。

from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
from rest_framework.response import Response
from rest_framework.views import APIView

# 可以自定义
class ReadOnly(BasePermission):
    def has_permission(self, request, view):
        return request.method in SAFE_METHODS

class ExampleView(APIView):
    permission_classes = [IsAuthenticated|ReadOnly,]
    # 亦或者是
     permission_classes = [IsAuthenticated,ReadOnly]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)

如果未指明,则采用如下默认配置

'DEFAULT_PERMISSION_CLASSES': (
   'rest_framework.permissions.AllowAny',
)

提供一个小Demo

models.py

from django.db import models

class UserToken(models.Model):
    user = models.OneToOneField(to='User')
    token = models.CharField(max_length=64)

class User(models.Model):
    name = models.CharField(max_length=50,verbose_name='用户名字')
    user_type = (
        (1,'普通用户'),
        (2,'老师')
    )
    type = models.IntegerField(choices=user_type,verbose_name='用户类型')
    def __str__(self):
        return self.name

class Student(models.Model):
    name = models.CharField(max_length=50,verbose_name='学生姓名')
    age = models.IntegerField(verbose_name='年龄')
    teacher = models.ForeignKey(User,on_delete=models.CASCADE,related_name='student')
    def __str__(self):
        return self.name

settings.py

# 全局认证及权限
REST_FRAMEWORK = {
    # 全局使用的认证类,这个是我自定义的认证类
    "DEFAULT_AUTHENTICATION_CLASSES":['authpermis.utils.auth.Authtication', ]
}

认证类

from rest_framework import exceptions
from authpermis import models
from rest_framework.authentication import BaseAuthentication

# 自定义认证
class Authtication(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        if token == None:
            token = request.POST.get('token')
        print(token)
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户认证失败')
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        return 'Basic realm="api" 认证失败返回给浏览器的请求头'

自定义的权限类

from rest_framework.permissions import BasePermission

class Check_student_Permission(BasePermission):
    message = '你不是老师,无权查看学生信息'

    def has_permission(self, request, view):
        print(request.user)
        user_type = request.user.type
        print('当前用户的类型为:%s' % str(user_type))
        if user_type == 1:
            return False
        if user_type == 2:
            return True
        return False


# 老师可以查看学生,但是老师只能更改自己学生的信息
class Update_age_Permission(BasePermission):
    message = '你不是此学生的老师,乱改啥?'

    def has_permission(self, request, view):
        if request.user.type == 1:
            return False
        if request.user.type == 2:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        # 老师的id
        teacher_id = request.user.id
        print('老师id为:%s' % teacher_id)
        print('学生所属老师id:%s' % obj.teacher_id)
        # 如果老师的id和学生所属老师id不一致
        if obj.teacher_id != teacher_id:
            return False
        return True

view.py

class All_student(APIView):
    '''
    只有老师可以调用此接口
    '''
    permission_classes = [Check_student_Permission]

    def get(self, request):
        students = models.Student.objects.all()
        stus = serializer.Studentserializer(instance=students, many=True)
        return Response(stus.data)


# 修改学生的年龄,老师可以调用此接口,但是只有学生的老师可以修改
class Update_student(APIView):
    '''
    获取到学生的的id和要更改的年龄
    '''
    permission_classes = [Update_age_Permission]

    def post(self, request):
        id = request.POST.get('id')
        age = request.POST.get('age')
        # 获取到学生的信息
        stu = models.Student.objects.get(pk=id)
        stu.age = age
        stu.save()
        # 在这里调用权限进行查看,是否要返回此对象
        self.check_object_permissions(request, stu)
        return Response({'code': 200, '学生年龄修改为': stu.age})

urls.py

urlpatterns = [
    url(r"^all_student/$", views.All_student.as_view(), name="all_student"),
    url(r"^update_student/$", views.Update_student.as_view(), name="update_student"),

]

操作提前准备:

将数据库注册到admin后台,添加两个老师,一个普通用户,并分别设置token

Django之认证和权限控制_第3张图片
其中张三,李四是老师.王五是普通用户,并分别以自己名字的拼音作为token

1**.测试all_student
以普通用户王五的身份去请求all_student,正确情况下得到如图:**
Django之认证和权限控制_第4张图片
2.在以老师zhangsan的身份请求all_student
Django之认证和权限控制_第5张图片
成功请求到所有的学生
3.测试update_student
首先使用普通用户的王五来请求接口,正确情况下得到如图:

Django之认证和权限控制_第6张图片
4.再次以老师李四的身份请求修改张三的某个学生的年龄:

很明显还是更改不了,只有学生所属的老师才有权限更改

Django之认证和权限控制_第7张图片
5.以老师张三的token去修改张三学生的年龄

修改成功,权限完美实现

Django之认证和权限控制_第8张图片

# permissions.SAFE_METHODS: 它所指的就是:GET、HEAD或OPTIONS请求。可以简单的理解为只读,用return来控制是否可读
if request.method in permissions.SAFE_METHODS:
            return ... 

二. 用户认证

rest_framework的认证

自定义认证

认证说实在到底跟权限有啥区别呢?
认证就是通过token或session或jwt来验证用户身份,说白了就是照妖镜

认证(Authentication)

REST framework 提供了灵活的认证方式:

可以在 API 的不同部分使用不同的认证策略。
支持同时使用多个身份验证策略。
提供与传入请求关联的用户(user)和令牌(token)信息。
request.user

 request.user 通常会返回 django.contrib.auth.models.User

的一个实例,但其行为取决于正在使用的身份验证策略。

如果请求未经身份验证,则 request.user 的默认值是
django.contrib.auth.models.AnonymousUser 的实例(就是匿名用户)

request.auth

 request.auth 返回任何附加的认证上下文(authentication context)。

request.auth的确切行为取决于正在使用的身份验证策略,但它通常可能是请求经过身份验证的令牌(token)实例。

如果请求未经身份验证,或者没有附加上下文(context),则 request.auth 的默认值为 None。

rest framework框架认证基类

所有自定义的身份验证类都应该扩展自BaseAuthentication,必须具有authenticate方法,同时返回一个元祖,第一个为user,第二个为token

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

Django rest framework内置的认证类

##路径:rest_framework.authentication

BasicAuthentication  #基于浏览器进行认证,浏览器弹框,可以弹出登录框,username和password形式认证.多用于测试
SessionAuthentication #基于django的session进行认证
RemoteUserAuthentication #基于django admin中的用户进行认证,这也是官网的示例
TokenAuthentication #基于drf内部的token认证
JSONWebTokenAuthentication # 结合django的auth_user表,实现的jwt认证

在这里主要说下jwt认证

官方文档:jwt

API参考 BasicAuthentication 该认证方案使用 HTTP Basic
Authentication,并根据用户的用户名和密码进行签名。Basic Authentication 通常只适用于测试。

如果成功通过身份验证,BasicAuthentication 将提供以下凭据。

request.user 是一个 Django User 实力.

request.auth 是 None.

未经身份验证的响应被拒绝将导致 HTTP 401 Unauthorized 的响应和相应的 WWW-Authenticate header。

SessionAuthentication 此认证方案使用 Django 的默认 session 后端进行认证。Session
身份验证适用于与您的网站在同一会话环境中运行的 AJAX 客户端。

如果成功通过身份验证,则 SessionAuthentication 会提供以下凭据。

request.user 是一个 Django User 实例. request.auth 是 None. 未经身份验证的响应被拒绝将导致
HTTP 403 Forbidden 响应。

如果您在 SessionAuthentication 中使用 AJAX 风格的 API,则需要确保为任何 “不安全” 的 HTTP
方法调用(例如 PUT,PATCH,POST 或 DELETE 请求)包含有效的 CSRF 令牌。

JSONWebTokenAuthentication

urls.py配置

# 下面的url配置,只是会让你看到jwt的可视化视图,并无卵用
urlpatterns = [
	# Django REST framework JWT提供了登录签发JWT的视图,可以直接使用.访问此接口的视图,可以通过User表的用户名和密码,得到token,Django REST framework JWT提供了登录签发JWT的视图,可以直接使用
	url(r'^auth/$', obtain_jwt_token),
    # 加上此行的话,等于是在可浏览的API界面中,添加REST框架的登录和注销视图,可以进行登录和登出
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
    # 如果JWT_ALLOW_REFRESH为True,则可以“刷新” 未过期的令牌以获得具有更新的过期时间的全新令牌。添加如下网址格式:应用为使用未过期的token来请求此接口,获取到全新的token
    url(r'^refresh-token/', refresh_jwt_token),
]

settings.py


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        # 下面两个可以不要,但是想要看到可视化的jwt视图,就会用到,可视化API使用到的session来保持状态
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

JWT_AUTH = {
	# 指明token的有效期
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    # 启用令牌刷新功能。来自的令牌rest_framework_jwt.views.obtain_jwt_token将有一个orig_iat字段。默认是False
    'rest_framework_jwt';True
}

手动生成token

Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法

from rest_framework_jwt.settings import api_settings
 
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
 # 此处的user,必须是django自带的User对象,并非自定义
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

jwt的可视化视图:

Django之认证和权限控制_第9张图片

结合项目的应用:

当使用rest框架的jwt做认证时,肯定要结合django自带的auth_user表,当你想自定义用户表时,不管你是跟auth_user关联,还是其他方法,反正只要查表时能找到auth_user就行,然后你就可以在类视图中使用user=request.user,获取到当前用户是谁了,在登陆或注册时,可以手动的生成token,返回到前端.

自定义认证

在子应用中创建utils文件夹,新建auth,py(自定义就好)文件

from rest_framework import exceptions
from authpermis import models
from rest_framework.authentication import BaseAuthentication


class Authtication(BaseAuthentication):
	'''
	必须返回一个元祖,内含两个参数:一个是当前用户的记录,一个是当前用户的认证记录
	前者对应视图中的request.user,后者对应request.auth
	你也可以随便返回,就像我注释的那样
	'''
    def authenticate(self, request):
    	'''
    	authenticate 必须使用这个名字
    	'''
        token = request.GET.get('token')
        print(token)
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户认证失败')
        # 在rest framework内部会将整个两个字段赋值给request,以供后续操作使用
        # 第一个参数是当前用户的记录,第二个参数是用户的验证记录
        # return ('用户记录','用户认证记录')
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
    	'''
    	authenticate_header  必须使用这个名字
    	return 的自定义即可
    	'''
        return 'token----403 Permission Denied'

settings文件启用自定义的认证

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'authpermis', #认证及权限子应用(自定义)
]
# 全局认证及权限
REST_FRAMEWORK = {
    # 全局使用的认证类,路径为:子应用authpermis下的utils文件夹下的auth文件里面的Authtication类
    "DEFAULT_AUTHENTICATION_CLASSES":['authpermis.utils.auth.Authtication', ]
}

当然你也可以给指定的视图设置空的认证以及添加其他自定义的认证,请结合业务而行

class MyView(APIView):
    """
    不对此类视图进行认证
    """
    authentication_classes = []
    def get(self,request):
    	pass
    

注意

1.request.user
当没有使用rest框架所衍生的view作为父类的时候,它获取到的值,就是AnonymousUser,也就是非认证用户
当使用了rest框架的自定义认证后,获取到的就是当前用户的记录

有待验证!!! DetailView 当父类的会,可以使用request.user?
from django.views.generic import TemplateView, ListView, View, DetailView, CreateView
上面所导入的都可以使用request.user ???
是这个的原因?
@method_decorator(login_required, name=‘dispatch’)

request.auth rest框架有值,非rest框架报错,没有此属性

2.rest_framework框架设置的认证及权限对于非rest的view不起作用

结语:

有时,django项目包含两个系统,前台系统使用的是vue等框架,后台管理系统使用的是django自带的模板,此时就会存在最少两套认证和权限系统,模板一般采用的是session,vue一般采用的是cookie,session和jwt,如果使用jwt的话,其实也是依托于cookie来返回到前端的,
当用户对页面发起请求的时候,而这个页面是drf接口,就会进行认证,看请求是否携带token,是否能成功认证,然后判断权限,是否有权限获取当前接口,如果有对象级别的权限时,还会判断能否获取到对象.如果当前接口可以允许未登录用户访问的话,可以得到页面,如果只允许登录用户的话,就会显示权限不够,或跳转到登录页面.当前端已经存储有token时,再次访问其他resrful接口时,就要带上token,而这些restful接口是不会返回token的
当用户对页面发起请求的时候,而这个页面是模板接口,那么就不会进行jwt(token)的验证,而是会进行后台模板系统所用的验证系统,但是有可能后端代码需要返回token值,以便于前端存储后在向restful接口发请求时带上,比如:前台登陆页面是后端模板页面,当用户登录后,将token加入到cookie中,以便于前端存储后在向restful接口发请求时带上

你可能感兴趣的:(django)