入口:dispatch():self.initial(request, *args, **kwargs)#三大認證
python
# 認證模塊 校驗用戶是否登錄,登錄用戶,非法用戶,遊客 self.perform_authentication(request) # 權限認證,校驗用戶是否擁有權限,校驗對象是登錄用戶和遊客 self.check_permissions(request) # 頻率認證,訪問接口的次數在設定的時間範圍內是否過快 # ,配置訪問頻率,每次訪問都要緩存訪問次數,超過必定的次數後須要等待時間。 self.check_throttles(request)
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES # 三大認證的默認設置 # 配置視圖類的認證 authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 配置視圖類的頻率 throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES # 配置視圖類的權限
咱們在settings.py中尋找認證和權限的配置算法
而後在咱們本身的settings中進行註冊,如過本身不重寫的話,也能夠不用在本身的settings中進行配置數據庫
REST_FRAMEWORK = { # 渲染模塊 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'], # 異常模塊 # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', 'EXCEPTION_HANDLER': 'utils.exception.exception_handler', # 認證配置 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ], # 權限配置 # 認證配置,權限配置是捆綁的,一塊兒出現 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ], }
class BasicAuthentication(BaseAuthentication): """ HTTP Basic authentication against username/password. """ www_authenticate_realm = 'api' def authenticate(self, request): """ Returns a `User` if a correct username and password have been supplied using HTTP Basic authentication. Otherwise returns `None`. """ auth = get_authorization_header(request).split() # print(auth) if not auth or auth[0].lower() != b'basic': return None if len(auth) == 1: msg = _('Invalid basic header. No credentials provided.') raise exceptions.AuthenticationFailed(msg) elif len(auth) > 2: msg = _('Invalid basic header. Credentials string should not contain spaces.') raise exceptions.AuthenticationFailed(msg) try: auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') except (TypeError, UnicodeDecodeError, binascii.Error): msg = _('Invalid basic header. Credentials not correctly base64 encoded.') raise exceptions.AuthenticationFailed(msg) userid, password = auth_parts[0], auth_parts[2] return self.authenticate_credentials(userid, password, request) def authenticate_credentials(self, userid, password, request=None): """ Authenticate the userid and password against username and password with optional request for context. """ credentials = { get_user_model().USERNAME_FIELD: userid, 'password': password } user = authenticate(request=request, **credentials) if user is None: raise exceptions.AuthenticationFailed(_('Invalid username/password.')) if not user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) return (user, None) def authenticate_header(self, request): return 'Basic realm="%s"' % self.www_authenticate_realm
class AllowAny(BasePermission): """ Allow any access. This isn't strictly required, since you could use an empty permission_classes list, but it's useful because it makes the intention more explicit. """ def has_permission(self, request, view): return True # 認證經過,若是給源碼改爲False,那麼就不會由權限 # 必須認證經過的 class IsAuthenticated(BasePermission): """ Allows access only to authenticated users. """ def has_permission(self, request, view): return bool(request.user and request.user.is_authenticated)
實際開發,系統和第三方提供的認證與權限類已經夠用了,特別特殊的需求才須要自定義django
""" 認證模塊工做原理 1)繼承BaseAuthentication類,重寫authenticate方法 2)認證規則(authenticate方法實現體): 沒有攜帶認證信息,直接返回None => 遊客 有認證信息,校驗失敗,拋異常 => 非法用戶 有認證信息,校驗出User對象 => 合法用戶 """ """ 認證類的認證核心規則 def authenticate(self, request): token = get_token(request) try: user = get_user(token) # 校驗算法 except: raise AuthenticationFailed() return (user, token) """
# 自定義認證類 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class TokenAuthentication(BaseAuthentication): prefix = 'Token' def authenticate(self, request): # 拿到前臺的token auth = request.META.get('HTTP_AUTHORIZATION') # 沒有返回None,有進行校驗 if not auth: return None auth_list = auth.split() if not (len(auth_list) == 2 and auth_list[0].lower() == self.prefix.lower()): raise AuthenticationFailed('非法用戶') token = auth_list[1] # 校驗算法 user = _get_obj(token) # 校驗失敗拋異常,成功返回(user, token) return (user, token)
# 校驗算法(認證類)與簽發算法配套 """ 拆封token:一段 二段 三段 用戶名:b64decode(一段) 用戶主鍵:b64decode(二段) 碰撞解密:md5(用戶名+用戶主鍵+服務器祕鑰) == 三段 """ import base64, json, hashlib from django.conf import settings from api.models import User def _get_obj(token): token_list = token.split('.') if len(token_list) != 3: raise AuthenticationFailed('token異常') username = json.loads(base64.b64decode(token_list[0])).get('username') pk = json.loads(base64.b64decode(token_list[1])).get('pk') md5_dic = { 'username': username, 'pk': pk, 'key': settings.SECRET_KEY } if token_list[2] != hashlib.md5(json.dumps(md5_dic).encode()).hexdigest(): raise AuthenticationFailed('token內容異常') user_obj = User.objects.get(pk=pk, username=username) return user_obj
# 自定義權限類 """ 權限模塊工做原理 1)繼承BasePermission類,重寫has_permission方法 2)權限規則(has_permission方法實現體): 返回True,表明有權限 返回False,表明無權限 """ from rest_framework.permissions import BasePermission class SuperUserPermission(BasePermission): def has_permission(self, request, view): # print(request.user) # print(request.auth) return request.user and request.user.is_superuser#肯定那些用戶有訪問權限
查看全部用戶信息,前提:必須是登陸的超級管理員json
# 登陸接口:若是是超級管理員登陸,返回一個能夠交易出超級管理員的token字符串 # 只要有用戶登陸,就能夠返回一個與登陸用戶相關的token字符串 => 返回給前臺 => 簽發token => user_obj -> token_str from rest_framework.generics import GenericAPIView class LoginAPIView(APIView): # 登陸接口必定要作:局部禁用 認證 與 權限 校驗 authentication_classes = [] permission_classes = [] def post(self, request, *args, **kwargs): serializer = serializers.LoginModelSerializer(data=request.data) # 重點:校驗成功後,就能夠返回信息,必定不能調用save方法,由於該post方法只完成數據庫查操做 # 因此校驗會獲得user對象,而且在校驗過程當中,會完成token簽發(user_obj -> token_str) serializer.is_valid(raise_exception=True) return APIResponse(data={ 'username': serializer.user.username, 'token': serializer.token })
from django.contrib.auth import authenticate class LoginModelSerializer(ModelSerializer): # username和password字段默認會走系統校驗,而系統的post請求校驗,必定當作增方式校驗,因此用戶名會出現 重複 的異常 # 因此自定義兩個字段接收前臺的帳號密碼 usr = serializers.CharField(write_only=True) pwd = serializers.CharField(write_only=True) class Meta: model = models.User fields = ('usr', 'pwd') def validate(self, attrs): usr = attrs.get('usr') pwd = attrs.get('pwd') try: user_obj = authenticate(username=usr, password=pwd) except: raise ValidationError({'user': '提供的用戶信息有誤'}) # 拓展名稱空間 self.user = user_obj # 簽發token self.token = _get_token(user_obj) return attrs
# 自定義簽發token # 分析:拿user獲得token,後期還須要經過token獲得user # token:用戶名(base64加密).用戶主鍵(base64加密).用戶名+用戶主鍵+服務器祕鑰(md5加密) # eg: YWJj.Ao12bd.2c953ca5144a6c0a187a264ef08e1af1 # 簽發算法:b64encode(用戶名).b64encode(用戶主鍵).md5(用戶名+用戶主鍵+服務器祕鑰) # 校驗算法(認證類)與簽發算法配套 """ 拆封token:一段 二段 三段 用戶名:b64decode(一段) 用戶主鍵:b64decode(二段) 碰撞解密:md5(用戶名+用戶主鍵+服務器祕鑰) == 三段 """ def _get_token(obj): import base64, json, hashlib from django.conf import settings t1 = base64.b64encode(json.dumps({'username': obj.username}).encode()).decode() t2 = base64.b64encode(json.dumps({'pk': obj.id}).encode()).decode() t3_json = json.dumps({ 'username': obj.username, 'pk': obj.id, 'key': settings.SECRET_KEY }) t3 = hashlib.md5(t3_json.encode()).hexdigest() return '%s.%s.%s' % (t1, t2, t3)
# 頻率模塊局部配置throttle_classes = [ThreeTimeUserThrottle]
api
from rest_framework.permissions import IsAuthenticated from utils.throttles import ThreeTimeUserThrottle class UserCenterAPIView(APIView): # 認證全局配置嗎,權限局部配置 # authentication_classes = [] permission_classes = [IsAuthenticated] # 頻率模塊局部配置 throttle_classes = [ThreeTimeUserThrottle] def get(self, request, *args, **kwargs): user = request.user serializer = serializers.UserModelSerializer(user) return APIResponse(data=serializer.data)
# @Author : OceanSkychen # @File : serializers.py # 只參與序列化 class UserModelSerializer(ModelSerializer): # 改了原數據庫字段的序列化方式 password = SerializerMethodField() def get_password(self, obj): return '########' #設置密碼返回爲########### class Meta: model = models.User fields = ('username', 'password', 'mobile', 'email', 'first_name', 'last_name')
1)定義類繼承SimpleRateThrottle,重寫get_cache_key方法,設置scope類屬性
2)scope就是一個認證字符串,在配置文件中配置scope字符串對應的頻率設置
3)get_cache_key的返回值是字符串,該字符串是緩存訪問次數的緩存key緩存
# @Author : OceanSkychen # @File : throttles.py from rest_framework.throttling import SimpleRateThrottle # 1)定義類繼承SimpleRateThrottle,重寫get_cache_key方法,設置scope類屬性 # 2)scope就是一個認證字符串,在配置文件中配置scope字符串對應的頻率設置 # 3)get_cache_key的返回值是字符串,該字符串是緩存訪問次數的緩存key class ThreeTimeUserThrottle(SimpleRateThrottle): scope = 'three' # 當前用戶緩存的key def get_cache_key(self, request, view): return 'throttle:user_%s' % (request.user.id)
# drf的配置 REST_FRAMEWORK = { # 異常模塊 'EXCEPTION_HANDLER': 'utils.exception.exception_handler', # 認證模塊 'DEFAULT_AUTHENTICATION_CLASSES': [ # jwt認證類 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', ], # 權限模塊 'DEFAULT_PERMISSION_CLASSES': [ # 'rest_framework.permissions.IsAuthenticated', # 自定義權限類 ], # 頻率設置 'DEFAULT_THROTTLE_RATES': { 'three': '3/min', }, }
""" 1)任何人都能直接訪問的接口 請求不是是get、仍是post等,不須要作任何校驗 2)必須登陸後才能訪問的接口 任何請求方式均可能作該方式的限制,請求必須在請求頭中攜帶認證信息 - authorization 3)前臺的認證信息獲取只能經過登陸接口 前臺提供帳號密碼等信息,去後臺換認證信息token 4)前臺如何完成登陸註銷 前臺登陸成功通常在cookie中保存認證信息token,分離註銷就是前臺主動清除保存的token信息 """