django restframework框架二 認證
@(python之路)[django restframework框架二 認證]
django restframework框架二 認證
需求分析
為用戶訪問時,顯示用戶未登錄
原始方法
urls.py
from django.conf.urls import url,include
urlpatterns = [
url(r'^api/(?P<version>\w+)', include("app01.urls")),
]
app01.urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'user/$', views.UserView.as_view()),
# url(r'^auth/$', views.UserView.as_view()),
]
views.py
from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning
class UserView(APIView):
def get(self,request,*args,**kwargs):
token = request.query_params.get("token")
if not token:
return HttpResponse("未認證")
print(request.version)
return HttpResponse("get")
def post(self,request,*args,**kwargs):
return HttpResponse("post")
def put(self,request,*args,**kwargs):
return HttpResponse("put")
def delete(self,request,*args,**kwargs):
return HttpResponse("delete")
利用rest framework方法去做
from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthtication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed("認證失敗")
return ("小公子","www.xxxx.com")
class UserView(APIView):
authentication_classes = [MyAuthtication,]
def get(self,request,*args,**kwargs):
return HttpResponse("get")
def post(self,request,*args,**kwargs):
return HttpResponse("post")
def put(self,request,*args,**kwargs):
return HttpResponse("put")
def delete(self,request,*args,**kwargs):
return HttpResponse("delete")


源碼分析
?我們已經知道,認證是封裝在了新的reqeust里邊了。忘記點我
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), [MyAuthtication,]對象
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
我們可以看看認證具體做了什么authenticators
1
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
2
self.authentication_classes實例化所有的認證
class APIView(View):
……
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
……
我們主要關注的是這里。當我們在自己的views.py中定義好了,他就會優先查找views.py中的了。
3
我們關注一下這個認證傳遞到了哪里了
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
從上邊我們知道,他首先封裝到了request里邊了,傳入的地方在這里
class Request(object):
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
)
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or () ####### 就是這里[MyAuthentcators]
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
上述只是認證的封裝真正的處理在這里dispatch
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response,
這些處理都在self.initial(request, *args, **kwargs)這里
def initial(self, request, *args, **kwargs):
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
# 處理認證
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
self.perform_authentication(request)
def perform_authentication(self, request):
request.user
我們猜測request.user會觸發認證對象列表。他們應該回有關系。
僅僅猜測是不好使的我們需要證明
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
因為沒有hasattr(self, '_user'):所以他會走
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
我們需要關注的是self.authenticators
class Request(object):
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
)
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
if self.parser_context is None:
self.parser_context = {}
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
他就是原來的self.authenticators,也就是說我們自己的[MyAuthtion對象,]
所以他會循環我們的[MyAuthtion對象,],所以 user_auth_tuple = authenticator.authenticate(self)這里就會返回一個元組。下面捕獲異常,
為什么元組返回兩個元組
def _authenticate(self):
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
?返回后一個給self.user,一個給self.auth。
所以,返回有三種情況。
第一種:
raise AuthenticationFailed("認證失敗")
拋出異常,認證失敗
第二種:
return ("xiaogozi",'123123')
返回元組,認證成功
第三種:
返回None
匿名用戶登陸
?如果不是not None,self._authenticator = authenticator
def _not_authenticated(self):
self._authenticator = None
# 如果我們配置UNAUTHENTICATED_USER;就會執行
# self.user
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
# 如果我們配置UNAUTHENTICATED_TOKEN
# 就會執行self.auth
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
settings.py添加
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
"ALLOWED_VERSIONS":["v1","v2"],
"UNAUTHENTICATED_USER":None, # 允許用戶匿名登陸
"UNAUTHENTICATED_TOKEN":None, #
}
也可以這樣寫
"UNAUTHENTICATED_USER":lambda:None, # 允許用戶匿名登陸
"UNAUTHENTICATED_TOKEN":lambda:None, #
推薦用第一種方式
?當我們自己寫的MyAuthtication返回(user,auth)的時候,我們認為認證成功,
并將元組分別賦值給request.user/request.auth;
?raise AuthenticationFailed("認證失敗") 此時認證失??;
?返回None,表示匿名用戶。
認證與數據庫關聯
我們返回的是用戶和認證信息,這種信息我們最好寫在數據庫中;
整理:
urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'user/$', views.UserView.as_view()),
url(r'auth/$', views.Auth.as_view()),
# url(r'^auth/$', views.UserView.as_view()),
]
views.py
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class MyAuthtication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed("認證失敗")
return ("小公子","www.xxxx.com")
class UserView(APIView):
authentication_classes = [MyAuthtication,]
def get(self,request,*args,**kwargs):
self.dispatch
return HttpResponse("get")
def post(self,request,*args,**kwargs):
return HttpResponse("post")
def put(self,request,*args,**kwargs):
return HttpResponse("put")
def delete(self,request,*args,**kwargs):
return HttpResponse("delete")
class Auth(APIView):
def post(self,request,*args,**kwargs):
response = {"code":1000}
user = request.data.get("username")
pwd = request.data.get("password")
obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
if not obj:
response["code"] = 1001
response["msg"] = "用戶名密碼錯誤"
return JsonResponse(responsejson_dumps_params={"ensure_ascii":False})
import uuid
token = str(uuid.uuid4())
models.UserInfo.objects.update_or_create(user=obj,defaults={"token":token})
response["token"] = token
return JsonResponse(responsejson_dumps_params={"ensure_ascii":False})
models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class UserToken(models.Model):
user = models.OneToOneField("UserInfo")
token = models.CharField(max_length=64)
或者:
我們把認證這里拿出來,形成 一個單獨的文件例如:在app01下新建一個目錄utils。
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class TokenAuthtication(BaseAuthentication):
def authenticate(self, request):
"""
:param request:
:return:
(user,auth) 表示認證成功,并將元組分別復制給request.user/request.auth
raise AuthenticationFailed('認證失敗') 表示認證失敗
None, 表示匿名用戶
"""
token = request.query_params.get('token')
if not token:
raise AuthenticationFailed('用戶Token未攜帶')
token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
raise AuthenticationFailed('token已失效或錯誤')
return (token_obj.user.username,token_obj)
只需要在settings.py添加一行;
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':['app01.utils.auth.TokenAuthtication',]
}
這樣就將驗證添加到全局,訪問所有都需要token認證;
如果有的頁面不需要我們只需要在那個類下添加:
class AuthView(APIView):
authentication_classes = []
……
這樣這個AuthView就不需要驗證。
補充
class MyAuthtication(BaseAuthentication):
pass
我們繼承的是BaseAuthentication:
class BaseAuthentication(object):
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
# 如果認證失敗可以返回響應頭;
def authenticate_header(self, request):
pass
其他方式認證
class CSRFCheck(CsrfViewMiddleware)
class BasicAuthentication(BaseAuthentication):
class SessionAuthentication(BaseAuthentication):
class TokenAuthentication(BaseAuthentication)
class RemoteUserAuthentication(BaseAuthentication)

浙公網安備 33010602011771號