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 )
自定义权限控制类
#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
自定义认证类!
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)
验证结果