Django框架(二十)—— Django rest_framework-認證元件
阿新 • • 發佈:2018-12-13
目錄
Django rest_framework-認證元件
一、什麼是認證
只有認證通過的使用者才能訪問指定的url地址,比如:查詢課程資訊,需要登入之後才能檢視,沒有登入,就不能檢視,這時候需要用到認證元件
二、利用token記錄認證過的使用者
1、什麼是token
token是服務端生成的一串字串,以作客戶端進行請求的一個令牌,當第一次登入後,伺服器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求資料即可,無需再次帶上使用者名稱和密碼。session的儲存是需要空間
token在伺服器時可以不用儲存使用者資訊的,token傳遞的方式也不限於cookie傳遞,token也可以儲存起來
2、token的原理
當第一次登入認證過後,就會返回一個token到前臺,前臺之後傳送請求,就會帶上這個token字串,
3、cookie、session、token的區別
cookie是儲存在瀏覽器,以key:value的形式傳遞到伺服器認證使用者,使用者名稱密碼可能裸露,不安全
session是儲存在伺服器,產生隨機字串與使用者資訊對應,將隨機字串放在cookie中。伺服器會儲存一份,可能儲存到快取/資料庫/檔案。
token傳送請求的物件可以是瀏覽器,也可以是移動端。伺服器不需要記錄任何東西,每次都是一個無狀態的請求,每次都是通過解密來驗證是否合法
三、drf的認證元件
# 使用者資訊表
class UserInfo(models.Model):
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
# 使用者token
class UserToken(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to=UserInfo)
1、基本使用
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import APIException
from app01 import models
# 定義一個認證類
class LoginAuth(BaseAuthentication):
# 重寫authenticate方法
def authenticate(self, request):
# 從路由中獲取token,從request物件中獲取,如果token放在header中
# query_params 是Request封裝的原生request.GET
token = request.query_params.get('token')
ret = models.UserToken.objects.filter(token=token).first()
if ret:
# 能查到,說明認證通過,返回空,或者返回當前的使用者物件
# ret.user就是當前登入使用者物件,一旦retrun了,後面的認證類都不執行了
return ret.user,ret
# 如果查不到,丟擲異常
raise APIException('使用者認證失敗')
# view 層
from rest_framework.views import APIView
# 獲取隨機字串——token
def get_token(name):
md = hashlib.md5()
md.update(str(name).encode('utf-8'))
return md.hexdigest()
# 使用者登入
class Login(APIView):
def post(self, request, *args, **kwargs):
response = {'status': 100, 'msg': '登入成功'}
name = request.data.get('name')
pwd = request.data.get('pwd')
ret = models.UserInfo.objects.filter(name=name, pwd=pwd).first()
if ret:
token = get_token(name)
# 一旦使用者資訊校驗通過,就產生一個token儲存在Token表中
models.UserToken.objects.create(token=token, user=ret)
response['token'] = token
else:
response['status'] = 101
response['msg'] = '使用者名稱或密碼錯誤'
return JsonResponse(response, safe=False)
# 檢視所有圖書資訊
class Book(APIView):
# 指定authentication_classes,會迴圈列表例項化,進行認證
# 該方法是區域性使用認證
authentication_classes = [UserAuth.UserAuth, ]
def get(self, request, *args, **kwargs):
response = {'status': 100, 'msg': '查詢成功'}
ret = models.Book.objects.all()
ser = MySerializer.BookSerializer(instance=ret, many=True)
response['data'] = ser.data
return JsonResponse(response, safe=False)
2、全域性使用、區域性使用、區域性禁用認證
(1)全域性使用
- 在settings檔案中配置,配置完以後,就無需在檢視類中寫,已經是所有檢視類都需要認證
- 必須為
REST_FRAMEWORK
,key值必須為DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
}
(2)區域性使用
在需要使用的檢視類中寫,只對當前檢視類起認證作用,重新定義authentication_classes
class Book(APIView):
# 該方法是區域性使用認證
authentication_classes = [UserAuth.UserAuth, ]
def get(self, request, *args, **kwargs):
response = {'status': 100, 'msg': '查詢成功'}
ret = models.Book.objects.all()
ser = MySerializer.BookSerializer(instance=ret, many=True)
response['data'] = ser.data
return JsonResponse(response, safe=False)
(3)區域性禁用
在配置過全域性認證以後,有些檢視類不需要認證,可以區域性禁用認證,只需將authentication_classes
定義為空列表即可。例如:登入檢視,不應該有認證,就可以區域性禁用
# settings中
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
}
# view的檢視類中
class Book(APIView):
# 該方法是區域性使用認證
authentication_classes = []
def get(self, request, *args, **kwargs):
response = {'status': 100, 'msg': '查詢成功'}
ret = models.Book.objects.all()
ser = MySerializer.BookSerializer(instance=ret, many=True)
response['data'] = ser.data
return JsonResponse(response, safe=False)
四、原始碼分析
as_view ----------> view -------------> dispatch -------> Request包裝新的request ------> 認證、許可權、頻率 --------> 根據請求方式分發到不同的方法
url(r'books/',views.Book.as_view())
1、Book中沒有as_view
2、APIView的as_view
class APIView(View):
@classmethod
# cls 是 Book類
def as_view(cls, **initkwargs):
# view = super(APIView, Book).as_view(**initkwargs)
view = super(APIView, cls).as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
return csrf_exempt(view)
3、view = super(APIView, cls).as_view(**initkwargs) ---------------------> View中的as_view
class View(object):
@classonlymethod
# cls====> Book
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
# 例項化產生一個book物件
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# 調dispatch方法
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
4、return self.dispatch(request, *args, **kwargs) ----------------> dispatch
self====> Book物件,一層層找dispatch
APIView中找到dispatch
class APIView(View):
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# (a)初始化request,就是通過Request類來包裝原生request,得到包裝後的request
request = self.initialize_request(request, *args, **kwargs)
# 從現在開始request就是包裝後的request
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# (b) 認證、許可權、頻率
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
# http_method_names表示列表['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
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, *args, **kwargs)
return self.response
(a)request = self.initialize_request(request, *args, **kwargs) 包裝 request
self 是Book物件
class APIView(View):
# 預設的認證列表類
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
# (a-b)例項化初始化產生新的request物件
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), # 認證類例項化產生的物件的列表
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
(a------1)return Request( ··· ) ----------> Request類初始化
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,)
(b)self.initial(request, *args, **kwargs) -----> 認證、許可權、頻率
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
# (b------1) 認證
self.perform_authentication(request)
# (b------2)許可權
self.check_permissions(request)
# 頻率
self.check_throttles(request)
(b------1) self.perform_authentication(request) -------> 認證
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user
(b------1------1) 呼叫request的user方法,request是Request例項化產生的物件
class Request(object):
@property
def user(self):
# 這裡的self是request
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate() # 具體方法如下
return self._user
(b------1------1-------1) self._authenticate()
class Request(object):
def _authenticate(self):
# 這裡的self是request
# self.authenticators就是在Request例項化的時候,傳過來的認證類的物件的列表
# 即:[auth() for auth in self.authentication_classes]
for authenticator in self.authenticators:
try:
# 重寫的就是這個authenticate()方法,
# 兩個引數,第一個是物件本身(自動傳);第二個是這裡的self,就是request
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()