jwt实战

1 JWT梳理

1.1 原理及其实现

1.1.1 与传统不同之处
1)基于传统的token验证

用户登录,服务端给返回token,并将token保存在服务端 以后用户再来访问时,需要携带token,服务端获取token后,再去数据库中获取token进行校验。

2)jwt

用户登录,服务端给用户返回一个token(服务端不保存),以后用户再来访问,需要携带token,服务端获取token后,再做token校验。 优势:相较于传统的token相比,它无需在服务端保存token。

1.1.2 jwt实现过程
  • 用户提交用户名和密码给服务器,使用jwt创建一个token,并给用户返回

eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.
eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im1hcGVsIiwiZXhwIjoxNjA0MjE5MTk4fQ.
VT32zm5t-LWItoRO5h4p2RFFV4bbOxneAqvQkK7yEno

 

注意:jwt生成的token是由三段字符串组成,并且用 . 连接起来

1)第一段
HEADER,内部包含算法/token类型
json转化成字符串后,做base64url(base64加密:+_.)
{
    'alg': 'HS256',
    'typ': 'JWT'
}

 

2)第二段
payload,自定义值
json转化字符串,然后base64url加密(base64加密:+_.)
{
    'id': '123123',
    'name': 'MP',
    'exp': 1516239022       # 超时时间
}

 

3)第三段
第一步:第1,2部分密文拼接起来
eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im1hcGVsIiwiZXhwIjoxNjA0MjE5MTk4fQ
第二步:对前2部分密文进行HS256加密 + 加盐
第三步:对HS256加密后的密文再做base64url加密
  • 以后用户再来访问的时候,需要携带token,后端需要对token进行校验

    • 获取token

      • 第一步:对token进行切割

      eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im1hcGVsIiwiZXhwIjoxNjA0MjE5MTk4fQ.VT32zm5t-LWItoRO5h4p2RFFV4bbOxneAqvQkK7yEno

       

      • 第二步:对第二段进行base64url解密,并获取payload信息,检测token是否已经超时

      {
          'id': '123123',
          'name': 'MP',
          'exp': 1516239022       # 超时时间
      }

       

      • 第三步:把第一、二段拼接,再次执行HS256加密

      第一步:第1,2部分密文拼接起来
      eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im1hcGVsIiwiZXhwIjoxNjA0MjE5MTk4f

      第二步:对前2部分密文进行HS256加密 + 加盐
      密文 = base64解密(VT32zm5t-LWItoRO5h4p2RFFV4bbOxneAqvQkK7yEno)
      如果相等,表示token未被修改过(认证通过)
1.1.3 应用
pip install pyjwt

 

1.2 jwt原理代码

1.2.1 models.py
from django.db import models
​
class Person(models.Model):
    username = models.CharField(max_length=30)
    password = models.CharField(max_length=256)
    class Meta:
        db_table = 'person'

 

1.2.2 views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Person
import uuid
salt = str(uuid.uuid4())
# 验证时候需要的盐,不能改变
class JWTLoginView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        person_obj = Person.objects.filter(username=username,password=password).first()
        if person_obj:
            import jwt
            # 导入jwt
            import datetime
            # 导入过期时间
            # 构造header
            headers = {
                'typ': 'jwt',
                'alg': 'HS256'
            }
            # 构造payload
            pyload = {
                'user_id': person_obj.pk,
                'username': username,
                'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5)           # 超时时间
            }
            token = jwt.encode(payload=pyload, key=salt, algorithm='HS256', headers=headers).decode('utf-8')
            '''
            'data': "eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im1hcGVsIiwiZXhwIjoxNjA0MjE3NTEyfQ.vVm5PBVkFPscsmWc58eMYBR_pt9Gn2r7anN4pyNQc8Y"
            '''
            return Response(
                {'code':1001, 'data': token}
            )
​
class JWTOrderView(APIView):
    def get(self, request, *args, **kwargs):
        # 获取token并判断token的合法性
        token = request.query_params.get('token')
        # 1.切割
        # 2.解密第二段/判断过期
        # 3.验证第三段合法性
        import jwt
        from jwt import exceptions
        payload = None
        msg = None
        try:
            payload = jwt.decode(token, salt, True)
            # True集成时间校验和内部合法性校验
        except exceptions.ExpiredSignatureError:
            msg = 'token已经失效'
        except jwt.DecodeError:
            msg = 'token认证失败'
        except jwt.InvalidIssuer:
            msg = '非法的token'if not payload:
            return Response(
                {'code':1003, 'error':msg}
            )
        print(payload['user_id'], payload['username'])
        return Response('验证通过,可以继续访问')

 

1.2.3 urls.py
# -*- coding: utf-8 -*-
from django.urls import path
from . import views
urlpatterns = [
    path('login/', views.JWTLoginView.as_view()),   # 登录成功生成token
    path('jwt/', views.JWTOrderView.as_view()),     # 验证token
]

 

2 JWT实战实现

2.1 架构

2.1.1 创建所需
  • 注意事项

settings中的
​
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':[
        'app01.extensions.auth.JwtQueryAuthentication'
    #     默认所有页面都得登陆成功才能访问
    ]
}
'DEFAULT_AUTHENTICATION_CLASSES' 来源于 APIView 源码
'app01.extensions.auth.JwtQueryAuthentication' 来源于auth.py 的路径

 

2.2 代码实现

2.2.1 settings.py
# 末尾处添加
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':[
        'app01.extensions.auth.JwtQueryAuthentication'
    #     默认所有页面都得登陆成功才能访问
    ]
}

 

2.2.2 apps/app01/extensions/auth.py
# -*- coding: utf-8 -*-
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.conf import settings
class JwtQueryAuthentication(BaseAuthentication):
​
    def authenticate(self, request):
        # 获取token并判断token的合法性
        token = request.query_params.get('token')
        # 1.切割
        # 2.解密第二段/判断过期
        # 3.验证第三段合法性
        import jwt
        from jwt import exceptions
        salt = settings.SECRET_KEY
        payload = None
        msg = None
        try:
            payload = jwt.decode(token, salt, True)
            # True集成时间校验和内部合法性校验
        except exceptions.ExpiredSignatureError:
            raise AuthenticationFailed(
                {'code': 1003, 'error':'token已经失效'}
            )
        except jwt.DecodeError:
            raise AuthenticationFailed(
                {'code': 1003, 'error':'token认证失败'}
            )
        except jwt.InvalidIssuer:
            raise AuthenticationFailed(
                {'code': 1003, 'error':'非法的token'}
            )
​
        return (payload,token)
​
​
# 三种操作
# 1.抛出异常,后续不再执行;
# 2.return一个元祖(1,2),认证通过,在视图中如果调用request.user,就是元祖中的第一个值:request.auth,
# 3.None

 

2.2.3 apps/app01/utils/jwt_auth.py
# -*- coding: utf-8 -*-
import jwt
# 导入jwt
import datetime
# 导入过期时间
from django.conf import settings
​
​
def create_token(payload, timeout=1):
    salt = settings.SECRET_KEY
    # 构造header
    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }
    # 构造payload
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)  # 超时时间
​
    token = jwt.encode(payload=payload, key=salt, algorithm='HS256', headers=headers).decode('utf-8')
​
    return token

 

2.2.4 views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Person
from app01.extensions.auth import JwtQueryAuthentication
from app01.utils.jwt_auth import create_token
​
class ProLoginView(APIView):
    authentication_classes = []
    # 优先级更高(为了防止登录页面不停验证产生死循环)
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        person_obj = Person.objects.filter(username=username,password=password).first()
        if not person_obj:
            return Response(
                {'code':1000, 'data': '用户名或者密码错误'}
            )
        token = create_token({'user_id': person_obj.id, 'name':person_obj.username})
        return Response(
            {'code': 1001, 'data':token}
        )
​
​
class ProOrderView(APIView):
    # authentication_classes = [JwtQueryAuthentication] 不用添加
    def get(self, request, *args, **kwargs):
        print(request.data)
        return Response('订单列表')

 

2.2.5 urls/py
# -*- coding: utf-8 -*-
from django.urls import path
from . import views
urlpatterns = [
    path('pro_login/', views.ProLoginView.as_view()),  # 登录成功生成token
    path('pro_jwt/', views.ProOrderView.as_view()),    # 验证token
]

 

 

2.3 编码解码问题

  • Python encode() 方法以 encoding 指定的编码格式编码字符串。errors参数可以指定不同的错误处理方案。

str.encode(encoding='UTF-8',errors='strict')

 

  • decode() 方法以 encoding 指定的编码格式解码字符串。默认编码为字符串编码

str.decode(encoding='UTF-8',errors='strict')
'''
encoding -- 要使用的编码,如"UTF-8"。
errors -- 设置不同错误的处理方案。默认为 'strict',意为编码错误引起一个UnicodeError。 其他可能得值有 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace' 以及通过codecs.register_error() 注册的任何值。
'''

 

  • son.dumps是将一个Python数据类型列表进行json格式的编码解析

import json #导入python 中的json模块
l = [‘iplaypython’,[1,2,3], {‘name’:’xiaoming’}] #创建一个l列表
encoded_json = json.dumps(l) # 将l列表,进行json格式化编码
print repr(l)
print encoded_json #输出结果

 

  • json.dump和dumps差一个s,功能作用大致上是一样,也是将数据转换成str格式,最终包括了讲数据写入json文件的一个操作步骤,json.dump(data, file-open,ascii=False),可以包含三个属性,第三个ascii是用来避免出现unicode写入的乱码的;

  • json.load加载json格式文件

  • loads用来将字符型数据转换成原来的数据格式,诸如列表和字典,就是原本是什么格式就还原成什么格式。

3 JWT实战实现(二)

3.1 主要代码

3.1.1 settings.py
"""
Django settings for syl project.

Generated by 'django-admin startproject' using Django 2.2.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import datetime
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import sys

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.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '8s8wp%i7i_cq)+f5nfx!%q&*$4)0pq-kv*)!7#n6&6v)8b3h^p'

# 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',
    'apps.user.apps.UserConfig',
    'corsheaders',
    'django_filters',
    # 过滤器
    # 'rest_framework_jwt',
    # 'rest_framework.authentication'
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'syl.urls'

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 = 'syl.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'syldb',
        'USER': 'root',
        'PASSWORD': '1',
        'HOST': '127.0.0.1',
        'PORT': 3306
    }
}
CORS_ALLOW_WHITELIST = (
    'http://localhost:8080',
    'http://127.0.0.1:8888'
)
CORS_ALLOW_CREDENTIALS = True

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',  # 所有用户都可以访问
    ],
    # 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': 1
    # 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
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

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.2/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.2/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',
}

# AUTHENTICATION_BACKENDS = ['user.utils.EmailAuthBackend']

 

3.1.2 views.py
import datetime
import random

from django.http import HttpResponse
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.authentication import BasicAuthentication,SessionAuthentication
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated,IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from rest_framework.pagination import PageNumberPagination
from rest_framework.views import APIView
from rest_framework.permissions import BasePermission, SAFE_METHODS
from user.models import User
from user.serializers import UserSerializer, UserUnActiveSerializer

def index(request):
    # 需要认证才能访问的视图
    return HttpResponse('hello')

# 分页(局部):自定义分页器 局部
class PageNum(PageNumberPagination):
    # 查询字符串中代表每页返回数据数量的参数名, 默认值: None
    page_size_query_param = 'page_size'
    # 查询字符串中代表页码的参数名, 有默认值: page
    # page_query_param = 'page'
    # 一页中最多的结果条数
    max_page_size = 2

# 自定义权限(局部)
class MyPermission(BasePermission):
    # has_permission 是用户对这个视图有没有 GET POST PUT PATCH DELETE 权限的分别判断
    def has_permission(self, request, view):
        print('has_perm')
    # print(view.kwargs.get("pk"), request.user.id)

    # """判断用户对模型有没有访问权"""
    # 任何用户对使用此权限类的视图都有访问权限
        if request.user.is_superuser:
    # 管理员对用户模型有访问权
            return True
        elif view.kwargs.get('pk') == str(request.user.id):
    # 携带的id和用户的id相同时有访问权
            return True
        return False

    # has_object_permission 是用户过了 has_permission 判断有权限以后,再判断这个用户有没有对一个具体的对象有没有操作权限
    # 这样设置以后,即使是django admin管理员也只能查询自己user标的信息,不能查询其他用户的单条信息

    def has_object_permission(self, request, view, obj):
        print('has_object_perm')
        """获取单个数据时,判断用户对某个数据对象是否有访问权限"""
        if request.user.id == obj.id:
            return True
        return False

class UserViewSet(viewsets.ModelViewSet):

    """
    完成产品的增删改查
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    # 优先使用 get_serializer_class 返回的序列化器

    # 自定义权限会覆盖掉全局权限!!!
    # # 1.认证:自定义认证类, 自定义会覆盖全局配置
    # authentication_classes = (BasicAuthentication, SessionAuthentication)
    # # 2.权限:自定义权限类
    # permission_classes = (MyPermission,)
    # 3.分页:自定义分页器 覆盖全局配置
    pagination_class = PageNum
    # 4.限流:自定义限流类
    throttle_classes = [UserRateThrottle]
    # 5.过滤:指定过滤方法类, 排序方法类, 一个或多个
    filter_backends = (DjangoFilterBackend, OrderingFilter) # 同时支持过滤和排序
    # 5.1指定排序字段, 不设置, 排序功能不起效
    ordering_fields = ('date_joined', 'id') # ?ordering=-id
    # 5.2指定过滤字段, 不设置, 过滤功能不起效
    filter_fields = ('username', 'phone', 'is_active') # ?username=tom&phone=&is_active=true


    # 根据不同的请求, 获得不同的序列化器
    def get_serializer_class(self):
        if self.action == 'unactived':
            return UserUnActiveSerializer
        else:
            return UserSerializer
    @action(methods=['get'], detail=False)
    def unactived(self, request, *args, **kwargs):
        # 获取查询集, 过滤出未激活的用户
        qs = self.queryset.filter(is_active=False)
        # 使用序列化器, 序列化查询集, 并且是
        ser = self.get_serializer(qs, many=True)
        return Response(ser.data)

    @action(methods=['get'], detail=False)
    def actived(self, request, *args, **kwargs):
        # 获取查询集, 过滤出未激活的用户
        qs = self.queryset.filter(is_active=True)
        # 使用序列化器, 序列化查询集, 并且是
        ser = self.get_serializer(qs, many=True)
        return Response(ser.data)

class RegisterView(APIView):
    """
        用户注册, 权限是: 匿名用户可访问
    """
    # 自定义权限类
    permission_classes = (AllowAny,)

    def post(self, request):
        """
            接收邮箱和密码, 前端校验两遍一致性, 注册成功后返回成功, 然后用户自行登录获取token
            1. 随机用户名
            2. 生成用户
            3. 设置用户密码
            4. 保存用户
            :param request:
            :return: {'code':0,'msg':'注册成功'}
        """
        email = request.data.get('email')
        passwrod = request.data.get('password')
        if all([email, passwrod]):
            pass
        else:
            return Response({'code':9999,'msg':'参数不全'})

        rand_name = self.randomUsername()

        user = User(username=rand_name, email=email)
        user.set_password(passwrod)
        user.save()

        return Response({'code': 0, 'msg': '注册成功'})
    def randomUsername(self):
        """
            生成随机用户名: 格式: SYL + 年月日时分 + 5位随机数
            :return:
            """
        d = datetime.datetime.now()
        base = 'SYL'
        time_str = '%04d%02d%02d%02d%02d' % (d.year, d.month, d.day, d.hour, d.minute)
        rand_num = str(random.randint(10000, 99999))
        return base + time_str + rand_num

 

3.1.3 主路由urls.py
"""syl URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include('apps.user.urls'))
]

 

3.1.4 子路由urls.py
# -*- coding: utf-8 -*-
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('refresh/', refresh_jwt_token), # 刷新token
    path('api-auth/', include('rest_framework.urls',namespace='rest_framework')), # 认证地址
    path('register/', views.RegisterView.as_view()), # 注册视图, /user/register/
]
urlpatterns += router.urls # 模块地址
# print(router.urls)

 

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

# Create your models here.

class User(AbstractUser):
    phone = models.CharField('手机号', max_length=20)
    img = models.ImageField(upload_to='user', null=True)
    nick_name = models.CharField('昵称', max_length=20)
    address = models.CharField('地址', max_length=255)
    class Meta:
        db_table = 'tb_user'

 

3.1.6 utils.py
# -*- coding: utf-8 -*-

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,
        }

# 以前使用username进行用户验证,现在修改成email进行验证

class EmailAuthBackend:
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(username=username)
        except Exception as e:
            user = None
        if not user:
            try:
                user = User.objects.get(email=username)
            except Exception as e:
                user = None
        if user and user.check_password(password):
            return user
        else:
            return None
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

 

posted @ 2020-11-02 21:57  狐狸大大爱吃糖  阅读(529)  评论(0编辑  收藏  举报