Django JWT身份驗證

0X00 安裝及基礎使用

Django JWT是基於Django的auth模塊和user model的,所以如果不使用Django的model那麼是無法使用Django JWT的。其視圖的實現方法是基於Django restframework的APIView和serilizers。

廢話講到這裏,總的來說就是如果你對Django的model和restframework不是很熟,那麼用起來可能有點費力。

1.使用pip安裝

pip install djangorestframework-jwt

2.在你的settings.py,添加JSONWebTokenAuthentication到Django REST框架DEFAULT_AUTHENTICATION_CLASSES

SessionAuthentication和BasicAuthentication在使用restframework的調試界面需要用到的模塊。

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

3.urls.py

from django.conf.urls import url
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token

urlpatterns = [
    url(r'^login/', obtain_jwt_token), # 用於獲取token
    url(r'^token-verify/', verify_jwt_token), # 驗證令牌是否合法
    path(r'api-auth/', include('rest_framework.urls')), # 爲restframework調試頁面也開啓驗證
]

(1)obtain_jwt_token:提交用戶名和密碼,後臺判定是否合法,合法的話返回一個Token,否則認證失敗

- URL:/login/
- Request:
    - 請求方法:POST(表單)
    - 請求參數:username:'',password:''
Response:
    - 200,登錄成功,返回{"token":"xxxxxx"}
    - 400,身份驗證未通過

(2)verify_jwt_token:提交一個Token,後臺判定Token是否過期

- URL:/token-verify/
- Request:
    - 請求方法:POST(application/json)
    - 請求參數:{"token":"xxxx"}
Response:
    - 200,驗證通過,Token合法
    - 400,Token不合法或已過期

4.攜帶token請求接口

Token需要添加到HTTP請求頭中,見下圖

Key:Authorization
Value:JWT xxxxxxx

 

0X01 擴展Django User Model並與Django JWT結合

大概率會出現Django默認User Model字段不夠用的情況,那麼就需要繼承User模塊並添加字段。

添加一個APP用於新的User model

python manage.py startapp user_auth

1.models.py

User也是直接繼承於AbsractUser的,所以我們直接繼承該model並添加字段即可。

from django.contrib.auth.models import AbstractUser
from django.db import models

class UserInfo(AbstractUser):
    choice = (
        (1, 'admin'),
        (2, 'user')
    )
    user_type = models.IntegerField(choices=choice)

2.settings.py

修改Django Auth模塊指向的model,改爲我們重寫後的model

AUTH_USER_MODEL = "user_auth.UserInfo"  # 重寫user model後將用戶認證指向重寫後的model

3.保存更改

python manage.py makemigrations
python manage.py migrate

4.使用ModelViewSet和ModelSerializer

class UserSerializer(serializers.ModelSerializer):

    def create(self, validated_data):
        instance = UserInfo.objects.create_user(**validated_data)
        return instance

    def update(self, instance, validated_data):
        """
        只允許更新password字段
        :param instance:
        :param validated_data:
        :return:
        """
        instance.set_password(validated_data['password'])
        instance.save()
        return instance

    class Meta:
        model = UserInfo
        fields = ('id', 'username', 'user_type', 'date_joined', 'password')
        read_only_fields = ['date_joined']
        extra_kwargs = {'password': {'write_only': True}}

class UserViewSet(ModelViewSet):
    permission_classes = (UserReadAdminWrite,)
    queryset = UserInfo.objects.all()
    serializer_class = UserSerializer

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        if request.user.id == instance.id:
            return Response({'ERROR': '不能刪除正在使用的用戶'} ,status=status.HTTP_403_FORBIDDEN)
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

JWT更多設置參數及用法見 http://getblimp.github.io/django-rest-framework-jwt/

0X02 

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        #   設置訪問權限爲只讀
        # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
        #   設置訪問權限爲必須登錄
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        #  使用rest_framework的界面需要下列認證模塊
        #'rest_framework.authentication.SessionAuthentication',
        #'rest_framework.authentication.BasicAuthentication',
    ),
}

如果加載了sessionAuthentication和BasicAuthentication模塊,那麼通過了JWT身份認證後,會設置一個cookide,session_id=xxx,使用該session_id,不使用Token也可以正常訪問後臺接口,如果只允許Token驗證,需要註釋掉session模塊和basic模塊。

源碼:

Authentication類都必須繼承於BaseAuthentication類:

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

 

定義的Authentication類會在調用視圖函數之前被調用:

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()

 

dispatch方法應該是用於處理所有請求,dispatch函數:

    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)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    def initial(self, request, *args, **kwargs):
        """
        運行所有在視圖函數前應該被調用的東西
        """
        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:該函數只有一個request.user屬性,利用了property的特性,通過屬性轉換爲方法。

    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

調用的具體方法:

request.py的Request類的方法:

    @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

_authenticate方法:遍歷加載的所有authentication方法並認證

    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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章