django 项目认证权限实际操作(使用了DRF)

Django REST framework 认证和权限:
1, 认证和权限是不同的
2, 权限控制可以限制用户对于视图的访问和对于具体数据对象的访问
3, 认证是检验是 能否通过强调的是检验身份
4, 项目采用的认证是 全局认证 权限代码 可以随时更改权限
5, 自定义认证主要是 获取前端的 token 值 然后进行 token 检验!

首先 定义试图类

from rest_framework.generics import GenericAPIView
from django_test.auth import AsAdminRole
class view(GenericAPIView):
    # 使用的认证方式!
    authentication_classes = (
        JWTAuthentication,
        CookieAuthentication
    )
    # 自定义全局权限
    permission_classes = (AsAdminRole, )

    '''AsAdminRole 是自定义的权限类!项目会自动调用权限检查!'''
    # 试用装饰器可以更改权限 view 类重新指定权限为 AsOperatorRole
    @AsOperatorRole   # 相当于 get = AsOperatorRole(get(self, request))
    def get(self,request):
        return HttpResponse( 'hello world!' )
在 setting文件之中配置权限,(也可以写在试图里面,这样不需要写在配置文件里面)
REST_FRAMEWORK = {
    # 认证和权限放在一起的!在view之中需要设置
    'DEFAULT_AUTHENTICATION_CLASSES': (
        #'rest_framework.authentication.BasicAuthentication',  # 基本认证
        #'rest_framework.authentication.SessionAuthentication',  # session认证
        # 项目采用自定义的认证(全局)
        'antilles.user.plugins.JWTAuthentication'
    ),
    # 权限设置!
    'DEFAULT_PERMISSION_CLASSES': (
        # 仅通过认证的用户可以通过!
        # 'rest_framework.permissions.IsAuthenticated',
        # 'rest_framework.permissions.AllowAny',  # 允许所有的!
        'django_test.auth.AsAdminRole', # 指定自定义的权限认证
    )
}
登录生成token:
class SessionView(APIView):
    permission_classes = ()

    def get(self, request):
        user = request.user
        if isinstance(user, AnonymousUser):
            raise AuthenticationFailed(
                detail='Incorrect authentication credentials.'
            )

        role = request.GET.get('role')
        if role is not None and not user.check_role(role):
            raise PermissionDenied(
                detail='Incorrect user role.'
            )

        return Response()

    @json_schema_validate({
        'type': 'object',
        'properties': {
            'user': {
                'type': 'string',
                'minLength': 1
            },
            'pass': {
                'type': 'string',
                'minLength': 1
            },
        },
    })
    def post(self, request):
        '''get auth token
        this api can be used in two method:
            request contains username/password in body, return token
            request contains exists valid token, return a new token
        '''
        if isinstance(request.user, AnonymousUser):
            return self.login(request)
        else:
            return self.renew_token(request)

    def login(self, request):
        try:
            username = request.data['user']
            password = request.data['pass']
            user = User.objects.get(username=username)
        except User.DoesNotExist as e:
            logger.exception('User[ %s ] is not exists', username)
            raise_from(
                LoginFail, e
            )
        else:
            if not user.is_activate():
                raise LoginFail(user)
            success = authenticate(user=user, password=password)
            if not success:
                user.login_fail()
                logger.info('User[ %s ] authenticated failed', username)
                raise LoginFail()
            user.login_success()
            # 登录返还给前端 token的值
            return Response({'token':  self.build_token(user)})

    def renew_token(self, request):
        user = request.user
        if not user.is_activate():
            raise LoginFail(user)
        return Response({'token':  self.build_token(user)})
    # 登录生成 token
    def build_token(self, user):
        import jwt
        from cryptography.fernet import Fernet
        from django.utils.timezone import now

        now = now()
        return jwt.encode(
            {
                'id': user.pk,
                'iss': 'antilles-user',
                'sub': user.username,
                'role': user.get_role_display(),
                'iat': now,
                'nbf': now,
                'exp': now + settings.TOKEN_EXPIRE,
                'jti': Fernet.generate_key(),
            },
            settings.SECRET_KEY,
            algorithm=settings.TOKEN_ALGORITHMS
        )
View Code
自定义权限控制类
#coding:utf-8
from abc import ABCMeta, abstractproperty

from django.contrib.auth.models import AnonymousUser
from rest_framework import permissions
from six import add_metaclass

ROLE_ADMIN = 300
ROLE_OPERATOR = 200
ROLE_USER = 100

#角色具有三种权限的角色
USER_ROLES = [
    (ROLE_ADMIN, 'admin'),
    (ROLE_OPERATOR, 'operator'),
    (ROLE_USER, 'user'),
]
#抽象基础类 ABCMeta!
@add_metaclass(ABCMeta)
class BaseRole(permissions.BasePermission):
    def __new__(cls, func=None):
        # 在此处 func 指的是 get(self,request)
        """
        Overwrite __new__ for use premission class as decorator

        example:
        >>> class TestView(APIView):
        ...     # if use class as decorator, __new__ get a argument func
        ...     @AsUserRole
        ...     def get(self, request):
        ...         pass

        premission defined by decorator will **overwrite** class premission
        """
        if func:        # use as decorator
            func.permission_class = cls  # 增加一个函数属性值为 AsUserRole
            return func
        else:
            return permissions.BasePermission.__new__(cls)

    @abstractproperty
    def floor(self):
        pass

    # 自定义继承类需要重写 has_permission和 has_object_permission方法!
    def has_permission(self, request, view):
        '''此方法必须有返回值(True/false)'''
        if not request.user:
            return False
        # request.user获取当前的登陆用户User对象。如果当前用户没有登陆,那么request.user将会是我们所说的AnonymousUser对象
        if isinstance(request.user, AnonymousUser):
            return False

        # if defined method permission
        method = getattr(view, request.method.lower())
        # 取出 __new__ 方法增加的属性值(也是类对象)!是绑定到 方法 method 之中的
        method_permission = getattr(method, 'permission_class', None)
        # 判断权限级别是否大于自定义权限!权限是按照数据库role大小来衡量
        if method_permission:
            return request.user.role >= method_permission.floor

        return request.user.role >= self.floor


class AsUserRole(BaseRole):
    floor = ROLE_USER


class AsOperatorRole(BaseRole):
    floor = ROLE_OPERATOR


class AsAdminRole(BaseRole):
    floor = ROLE_ADMIN
View Code
自定义认证类!
import logging

from django.conf import settings
from rest_framework.authentication import (
    BaseAuthentication, get_authorization_header,
)
from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
from six import raise_from

from .managers.pam import auth
from .models import User

logger = logging.getLogger(__name__)

class JWTAuthentication(BaseAuthentication):
    keyword = 'Jwt'
    # 自定义认证需要重写 authenticate 方法!
    def authenticate(self, request):
        # 由于 header 的头部是 byte 类型此方法 转换成 字符串 然后以空格为界 转换列表
        # AUTHORIZATION這个就是之前服务端返回的,只不过Django会默认加上HTTP
        auth = get_authorization_header(request).split()
        # auth = ["Token", "c4840b5226a65806c586c239345fce66caf12409"]
        if not auth or auth[0].lower() != self.keyword.lower().encode():
            return None

        if len(auth) == 1:
            raise AuthenticationFailed(
                detail='Invalid token header. No credentials provided.'
            )
        elif len(auth) > 2:
            raise AuthenticationFailed(
                detail='Invalid token header.'
                'Token string should not contain spaces.'
            )

        try:
            token = auth[1].decode()# token = c4840b5226a65806c586c239345fce66caf12409
        except UnicodeError:
            raise AuthenticationFailed(
                detail='Invalid token header.'
                'Token string should not contain invalid characters.'
            )
        return self.authenticate_credentials(token)
    
    # token 认证!
    def authenticate_credentials(self, token):
        import jwt
        from jwt import InvalidTokenError
        try:
            payload = jwt.decode(
                token, settings.SECRET_KEY,
                options={
                    'verify_signature': True,
                    'verify_exp': True,
                    'verify_nbf': True,
                    'verify_iat': True,
                    'require_exp': True,
                    'require_nbf': True,
                    'require_iat': True,
                    'require_iss': True,
                    'require_jti': True,
                    'require_role': True,
                    'require_sub': True,
                    'require_mgt': True
                }
            )
            # sub: jwt所面向的用户
            user = User.objects.get(username=payload['sub'])

            payload_role = User.get_role_value(payload['role'])
            if payload_role > user.role:
                raise PermissionDenied(
                    'Insufficient permission.'
                )

            return user, payload
        except InvalidTokenError as e:
            raise_from(
                AuthenticationFailed, e
            )
        except User.DoesNotExist as e:
            raise_from(
                AuthenticationFailed, e
            )
    #  “jwt” 自定义的字符串 生成的token前面
    def authenticate_header(self, request):
        return self.keyword


class CookieAuthentication(JWTAuthentication):
    def authenticate(self, request):
        token = request.COOKIES.get('token')
        if token is None:
            return None
        token = token.decode()

        return self.authenticate_credentials(token)
View Code
验证结果


posted @ 2018-09-11 18:07  十七楼的羊  阅读(1535)  评论(0编辑  收藏  举报