APIViewのauthentication_classesとpermission_classesについて分かったところまで。。。
書きかけ。。。
APIViewのオリジナルコード
django-rest-framework/views.py at master · encode/django-rest-framework · GitHub
# APIViewのプロパティ 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 permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES #コイツ content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # Allow dependency injection of other settings to make testing easier. settings = api_settings schema = DefaultSchema()
APIViewの継承時にauthentication_classesを設定すると、その認証が必要になり、またpermission_classesを設定するとその権限を持っているものしかAPIViewを起動することができなくなる。これはとてもわかりやすく使いやすいと思っていたが具体的にどんな仕組みで動いているのか理解できていなかったので少し調べてみる。
APIView継承のコード例
authentication_classesはどのように使われているか?
authentication_classesが使われているメソッドは何か?
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L268 def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
これのみである。このメソッドでautehtication_classのインスタンスのリストを作成する。
つぎにこのリストはどこで使われているかが次の疑問である。てことでこのメソッドがどこで使われているか見ると以下の2つのメソッドが見つかった。
- get_authenticate_header
- initialize_request
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L183 def get_authenticate_header(self, request): """ If a request is unauthenticated, determine the WWW-Authenticate header to use for 401 responses, if any. """ authenticators = self.get_authenticators() if authenticators: return authenticators[0].authenticate_header(request)
適切な認証情報が与えられずに失敗した場合にどのような認証タイプを提供するかリクエストヘッダに提供するために使うメソッドだと考えられる。
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L385 def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ 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 )
このメソッドで使われているのは分かったけど、このメソッドはどこで使われているか??? それはdisatchメソッドである。
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L485 def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ 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) # Get the appropriate handler method 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)
このメソッドはdjangoのオリジナルのViewにas_viewメソッドが存在する。これによってdispatchメソッドが呼び出される。
このdispatchメソッド内のinitialize_requestメソッドが呼ばれ、requestオブジェクトが初期化される。またその後、initialメソッドがdispatchメソッド内で呼ばれる。
これが何なのか見てみると認証につながっていることが分かる。
# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/views.py#L399 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 self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
perform_authenticationメソッドにつながっている。
perform_authenticationメソッドでは、request.userを実行しているだけである。しかしここが肝でrequest.userのuserは@propertyがついているものでその内部は、認証を行っている。てことで、perform_authenticationとRequest,userメソッドを参照する。
# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/views.py#L316 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
# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/request.py#L140 class Request: """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers(list/tuple). The parsers to use for parsing the request content. - authenticators(list/tuple). The authenticators used to try authenticating the request's user. """ 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,) ...省略 @property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user ...省略 def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ 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()
user_auth_tupleは各authenticateクラスのauthenticateメソッドの戻り値である。 Basic, Session, Tokenでの認証が存在していて、おのおの戻り値は以下のようになっている。
認証クラス | 認証クラスのauthenticateメソッドの戻り値(=user_auth_tuple) | 該当コード |
---|---|---|
BasicAuthentication | (user, None) | https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L53 |
SessionAuthentication | (user, None) | https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L112 |
TokenAuthentication | (token.user, token) | https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L151 |
そういうわけで認証が成功するとuserオブジェクトがrequestオブジェクトに格納される流れとなる。
permissonについては以下で。
https://torajirousan.hatenadiary.jp/entry/2021/01/03/223928