From 923454b7c01a08c5cac77be0962443a02db4543f Mon Sep 17 00:00:00 2001 From: lihuacai Date: Thu, 16 Nov 2023 01:13:52 +0800 Subject: [PATCH] feat(auth): Correct typo and add JWT authentication The typo error in the EmployeeFilters was fixed where `fisrt_name` was changed to `first_name`. A new authentication using JWT (JSON Web Tokens) was added on various files, and new settings for JWT were also appended on settings.py. A new router is also added on urls.py for the JWT authentication, and an authentication field is activated on the Employee views. A file auth.py was created to handle customized authentication functionalities. This will establish a secure way to transmit information between parties and prevent unauthorized access. --- apidemo/settings.py | 52 ++++++++++++++++++++++++++++++++++++++++- apidemo/urls.py | 4 +++- core/auth.py | 56 +++++++++++++++++++++++++++++++++++++++++++++ employee/schemas.py | 2 +- employee/views.py | 5 ++-- 5 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 core/auth.py diff --git a/apidemo/settings.py b/apidemo/settings.py index 98c5086..796e357 100644 --- a/apidemo/settings.py +++ b/apidemo/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/ """ import os +from datetime import timedelta from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -38,6 +39,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'employee', + 'ninja_jwt', ] MIDDLEWARE = [ @@ -206,4 +208,52 @@ }, }, -} \ No newline at end of file +} + + +NINJA_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': False, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + 'JWK_URL': None, + 'LEEWAY': 0, + + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'ninja_jwt.authentication.default_user_authentication_rule', + # 'USER_AUTHENTICATION_RULE': 'core.auth.default_user_authentication_rule', + + 'AUTH_TOKEN_CLASSES': ('ninja_jwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + 'TOKEN_USER_CLASS': 'django.contrib.auth.models.User', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), + + # For Controller Schemas + # FOR OBTAIN PAIR + 'TOKEN_OBTAIN_PAIR_INPUT_SCHEMA': "core.auth.MyTokenObtainPairInputSchema", + # 'TOKEN_OBTAIN_PAIR_REFRESH_INPUT_SCHEMA': "ninja_jwt.schema.TokenRefreshInputSchema", + # # FOR SLIDING TOKEN + # 'TOKEN_OBTAIN_SLIDING_INPUT_SCHEMA': "ninja_jwt.schema.TokenObtainSlidingInputSchema", + # 'TOKEN_OBTAIN_SLIDING_REFRESH_INPUT_SCHEMA':"ninja_jwt.schema.TokenRefreshSlidingInputSchema", + # + # 'TOKEN_BLACKLIST_INPUT_SCHEMA': "ninja_jwt.schema.TokenBlacklistInputSchema", + # 'TOKEN_VERIFY_INPUT_SCHEMA': "ninja_jwt.schema.TokenVerifyInputSchema", +} + + +AUTHENTICATION_BACKENDS = [ + 'core.auth.CustomAuthBackend', +] diff --git a/apidemo/urls.py b/apidemo/urls.py index e10484d..be08b72 100644 --- a/apidemo/urls.py +++ b/apidemo/urls.py @@ -13,7 +13,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ - +from ninja_jwt.routers.obtain import obtain_pair_router from employee.views import router as employee_router @@ -26,6 +26,8 @@ api_v1 = NinjaAPI(version='1.0.0') api_v1.add_router('/employee/', employee_router) +api_v1.add_router('/token', tags=['Auth'], router=obtain_pair_router) + urlpatterns = [ path("admin/", admin.site.urls), diff --git a/core/auth.py b/core/auth.py new file mode 100644 index 0000000..23681b7 --- /dev/null +++ b/core/auth.py @@ -0,0 +1,56 @@ +# !/usr/bin/python3 +# -*- coding: utf-8 -*- +from typing import Dict, Type + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from ninja import Schema +from ninja_jwt.schema import TokenObtainInputSchemaBase +from ninja_jwt.tokens import RefreshToken + +from core.schemas import StandResponse + + +class UserSchema(Schema): + first_name: str + email: str + + +class MyTokenObtainPairOutSchema(Schema): + refresh: str + access: str + user: UserSchema + + +class MyTokenObtainPairInputSchema(TokenObtainInputSchemaBase): + @classmethod + def get_response_schema(cls) -> Type[Schema]: + # TODO now only work get pair token success + # not work at get token fail and refresh + return StandResponse[MyTokenObtainPairOutSchema] + + @classmethod + def get_token(cls, user) -> Dict: + values = {} + refresh = RefreshToken.for_user(user) + values["refresh"] = str(refresh) + values["access"] = str(refresh.access_token) + values.update( + user=UserSchema.from_orm(user) + ) + return {'data': values} + + +class CustomAuthBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + User = get_user_model() + + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + return None + + if user.check_password(password): + return user + else: + return None diff --git a/employee/schemas.py b/employee/schemas.py index a464c05..7256aca 100644 --- a/employee/schemas.py +++ b/employee/schemas.py @@ -19,6 +19,6 @@ class EmployeeOut(EmployeeIn): class EmployeeFilters(PageFilter): - first_name__contains: str = Field(None, alias="fisrt_name") + first_name__contains: str = Field(None, alias="first_name") last_name__contains: str = Field(None, alias="last_name") department_id: Optional[conint(ge=0)] diff --git a/employee/views.py b/employee/views.py index 0edaafe..40559c2 100644 --- a/employee/views.py +++ b/employee/views.py @@ -3,6 +3,7 @@ from typing import Optional, Union from ninja import Query, Router, Schema +from ninja_jwt.authentication import JWTAuth from pydantic.fields import Field from pydantic.types import conint @@ -10,13 +11,13 @@ from employee.employee_service_impl import employee_service_impl from employee.schemas import EmployeeFilters, EmployeeIn, EmployeeOut -router = Router(tags=["employees"]) +router = Router(tags=["employees"], auth=JWTAuth()) logger = logging.getLogger(__name__) class Filters(Schema): - first_name__contains: str = Field(None, alias="fisrt_name") + first_name__contains: str = Field(None, alias="first_name") last_name__contains: str = Field(None, alias="last_name") department_id: Optional[conint(ge=0)]