1.jwt的安裝配置 .
1.1安裝JWT
pip install djangorestframework-jwt==1.11.0
1.2
# jwt載荷中的有效期設定 JWT_AUTH = { # 1.token字首:headers中 Authorization 值的字首 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 2.token有效期:一天有效 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # 3.重新整理token:允許使用舊的token換新token,介面對接需要設定為true 'JWT_ALLOW_REFRESH': True, # 4.token有效期:token在24小時內過期, 可續期token 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24), # 5.自定義JWT載荷資訊:自定義返回格式,需要手工建立 'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler', }
""" Django settings for opwf project. Generated by 'django-admin startproject' using Django 2.0.13. For more information on this file, see https://docs.djangoproject.com/en/2.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.0/ref/settings/ """ import datetime import os, sys # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'uorj1ni^mnut@wo@c%)iv)%5=8dxlml4-j0!f3b%4#f*8a5)3t' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'corsheaders', 'user.apps.UserConfig', 'workflow.apps.WorkflowConfig', 'workerorder.apps.WorkerorderConfig', # 'jwt', # 'rest_framework_jwt', # 'rest_framework.authentication' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'corsheaders.middleware.CorsMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'opwf.urls' CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_WHITELIST = ( 'http://127.0.0.1:8080', 'http://localhost:8080', ) CORS_ALLOW_CREDENTIALS = True TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'opwf.wsgi.application' # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'opwf_db', 'USER': 'root', 'PASSWORD': '1', 'HOST': '127.0.0.1', 'PORT': '3306' } } # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators REST_FRAMEWORK = { # 文件報錯: AttributeError: ‘AutoSchema’ object has no attribute ‘get_link’ # 用下面的設定可以解決 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', # 預設設定是: # 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema', # 異常處理器 # 'EXCEPTION_HANDLER': 'user.utils.exception_handler', # Base API policies 預設渲染器類 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ], # 預設解析器類 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ], # 1.認證器(全域性) 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 在 DRF中配置JWT認證 # 'rest_framework.authentication.SessionAuthentication', # 使用session時的認證器 # 'rest_framework.authentication.BasicAuthentication' # 提交表單時的認證器 ], # 2.許可權配置(全域性): 順序靠上的嚴格 'DEFAULT_PERMISSION_CLASSES': [ # 'rest_framework.permissions.IsAdminUser', # 管理員可以訪問 # 'rest_framework.permissions.IsAuthenticated', # 認證使用者可以訪問 # 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 認證使用者可以訪問, 否則只能讀取 # 'rest_framework.permissions.AllowAny', # 所有使用者都可以訪問 # 'user.utils.VipPermission', #自定義許可權 ], # 3.限流(防爬蟲) 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle', ], # 3.1限流策略 # 'DEFAULT_THROTTLE_RATES': { # 'user': '100/hour', # 認證使用者每小時100次 # 'anon': '300/day', # 未認證使用者每天能訪問3次 # }, 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', 'DEFAULT_VERSIONING_CLASS': None, # 4.分頁(全域性):全域性分頁器, 例如 省市區的資料自定義分頁器, 不需要分頁 # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # # 每頁返回數量 # 'PAGE_SIZE': 3, # 5.過濾器後端 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', # 'django_filters.rest_framework.backends.DjangoFilterBackend', 包路徑有變化 ], # 5.1過濾排序(全域性):Filtering 過濾排序 'SEARCH_PARAM': 'search', 'ORDERING_PARAM': 'ordering', 'NUM_PROXIES': None, # 6.版本控制:Versioning 介面版本控制 'DEFAULT_VERSION': None, 'ALLOWED_VERSIONS': None, 'VERSION_PARAM': 'version', # Authentication 認證 # 未認證使用者使用的使用者型別 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', # 未認證使用者使用的Token值 'UNAUTHENTICATED_TOKEN': None, # View configuration 'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name', 'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description', 'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing 'TEST_REQUEST_RENDERER_CLASSES': [ 'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.JSONRenderer' ], 'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', # Hyperlink settings 'URL_FORMAT_OVERRIDE': 'format', 'FORMAT_SUFFIX_KWARG': 'format', 'URL_FIELD_NAME': 'url', # Encoding 'UNICODE_JSON': True, 'COMPACT_JSON': True, 'STRICT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, # Browseable API 'HTML_SELECT_CUTOFF': 1000, 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", # Schemas 'SCHEMA_COERCE_PATH_PK': True, 'SCHEMA_COERCE_METHOD_NAMES': { 'retrieve': 'read', 'destroy': 'delete' }, # 'Access-Control-Allow-Origin':'http://localhost:8080', # 'Access-Control-Allow-Credentials': True } AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.0/howto/static-files/ STATIC_URL = '/static/' AUTH_USER_MODEL = 'user.User' # jwt載荷中的有效期設定 JWT_AUTH = { # 1.token字首:headers中 Authorization 值的字首 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 2.token有效期:一天有效 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # 3.重新整理token:允許使用舊的token換新token 'JWT_ALLOW_REFRESH': True, # 4.token有效期:token在24小時內過期, 可續期token 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24), # 5.自定義JWT載荷資訊:自定義返回格式,需要手工建立 'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler', }
from django.urls import include, path from rest_framework.authtoken.views import obtain_auth_token from user import views from rest_framework.routers import SimpleRouter, DefaultRouter from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token # 自動生成路由方法, 必須使用檢視集 # router = SimpleRouter() # 沒有根路由 /user/ 無法識別 router = DefaultRouter() # 有根路由 router.register(r'user', views.UserViewSet) urlpatterns = [ path('index/', views.index), # 函式檢視 path('login/', obtain_jwt_token), # 獲取token,登入檢視 path('register/',views.Register.as_view()), #註冊使用者 path('refresh/', refresh_jwt_token), # 重新整理token path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # 認證地址 ] urlpatterns += router.urls # 模組地址 # print(router.urls) # obtain_jwt_token = ObtainJSONWebToken.as_view() # 獲取token # refresh_jwt_token = RefreshJSONWebToken.as_view() # 重新整理token # verify_jwt_token = VerifyJSONWebToken.as_view() # 修改token
def jwt_response_payload_handler(token, user=None, request=None, role=None): """ 自定義jwt認證成功返回資料 :token 返回的jwt :user 當前登入的使用者資訊[物件] :request 當前本次客戶端提交過來的資料 :role 角色 """ if user.first_name: name = user.first_name else: name = user.username return { 'authenticated': 'true', 'id': user.id, "role": role, 'name': name, 'username': user.username, 'email': user.email, 'token': token, }
1.6 user/utils.py 生成token
#生成jwt_token def create_token(user): #user:接收的使用者物件 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token
2.程式碼實踐 .
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class Vip(models.Model): vip_choices = ( ('1','普通會員'), ('2','高階會員') ) vip_name = models.CharField(verbose_name='vip名稱',max_length=20) vip_type = models.CharField(verbose_name='vip種類',max_length=20,choices=vip_choices) desc = models.CharField(verbose_name='vip描述',max_length=255) class Meta: db_table = 'tb_vip' verbose_name = 'Vip表' class User(AbstractUser): nike_name = models.CharField(verbose_name='暱稱',max_length=30,null=True) phone = models.CharField(verbose_name='手機號',max_length=30,null=True) email = models.CharField(verbose_name='郵箱',max_length=255,null=True) address = models.CharField(verbose_name='地址',max_length=255,null=True) vip = models.ForeignKey(Vip,on_delete=models.CASCADE,null=True,default=None) class Meta: db_table = 'tb_user' verbose_name = '使用者表'
from django.urls import path from . import views from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('register/',views.Register.as_view()), #註冊 path('login/',obtain_jwt_token), #登入 path('test/',views.Test.as_view()), #測試許可權用的 ]
from django.shortcuts import render # Create your views here. from rest_framework.views import APIView,Response from .serializers import * from .models import * from user.utils import create_token from rest_framework.permissions import IsAuthenticated,IsAdminUser,AllowAny from user.utils import VipPermission class Register(APIView): def post(self,requset): print(requset.data) username = requset.data.get('username') password = requset.data.get('password') phone = requset.data.get('phone') email = requset.data.get('email') address = requset.data.get('address') if not all([username,password,phone,email,address]): return Response({'msg':'資料不完整','code':400}) #create新增 # user_obj = User.objects.create(username=username,password=make_password(password), # phone=phone,email=email,address=address) # # token = create_token(user_obj) # # data = { # 'id':user_obj.pk, # 'username':user_obj.username, # 'phone':user_obj.phone, # 'email':user_obj.email, # 'address':user_obj.address, # 'token':token # } #序列化新增 user_serializer = UserSerializers(data=requset.data) user_serializer.is_valid() user_serializer.save() return Response({'msg':'註冊成功','code':200,'data':user_serializer.data}) class Test(APIView): # permission_classes = (IsAuthenticated,) #只有認證使用者可以訪問 # permission_classes = (IsAdminUser,) #只有管理員使用者可以訪問 # permission_classes = (AllowAny,) #所有使用者都可以訪問 # permission_classes = (VipPermission,) #自定義許可權 def get(self,request): return Response({'msg':'ok','code':200})
from rest_framework_jwt.serializers import api_settings from rest_framework.permissions import BasePermission from .models import * #生成jwt_token def create_token(user): #user:接收的使用者物件 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token #重寫payload_handler的載荷資訊 def jwt_response_payload_handler(token,user=None,request=None): return { 'id':user.pk, 'username':user.username, 'token':token } #自定義許可權 需要繼承BasePermission class VipPermission(BasePermission): message = '只有黃金VIP才能訪問' def has_permission(self, request, view): print(request.user) user_obj = User.objects.filter(id=request.user.pk).first() if user_obj.vip.id !=2: #判斷關聯欄位是否是黃金vip return False return True
from django.contrib.auth.hashers import make_password from rest_framework import serializers from .models import * from user.utils import create_token class UserSerializers(serializers.Serializer): username = serializers.CharField(max_length=30) phone = serializers.CharField(max_length=30) email = serializers.CharField(max_length=30) address = serializers.CharField(max_length=30) password = serializers.CharField(max_length=128) token = serializers.CharField(max_length=128,read_only=True) def create(self, validated_data): #1.make_password加密: validated_data["password"] = make_password(validated_data.get('password')) user_obj = User.objects.create(**validated_data) token = create_token(user_obj) user_obj.token = token return user_obj #2.set_password加密 # user_obj = User.objects.create(**validated_data) # user_obj.set_password(validated_data.get('password')) # user_obj.save() # token = create_token(user_obj) # user_obj.token = token # return user_obj
3.postman測試介面 .
3.1 測試註冊介面
http://127.0.0.1:8000/user/register/
3.2 測試登入介面
http://127.0.0.1:8000/user/login/
3.2 測試登入介面
http://127.0.0.1:8000/user/test/