Django使用DRF + Simple JWT 完成小程序使用自定义用户的注册、登录和认证

在已经配置好djangorestframework-simplejwt的前提下进行

模型类及序列化器

小程序用户模型类

这里的模型类并未继承django自带的用户模型类,好处是后面小程序用户也是没法进行admin端的,缺点是可能会对django自带的权限管理有影响,如果只有小程序端的用户的话没问题,但是如果还有其它用户的话就可能会出问题,因为在这个模型中是没有密码存在的,只要拿到小程序端生成的用户的code就能够通过登录接口获取到用户的token(小程序端的code每次生成是不同的,有效期应该是5分钟)

class User(models.Model):

    USER_TYPE = ((1, '顾客'),
                 (2, '商家'))

    GENDER = ((0, '男'),
              (1, '女'),
              (2, '未知'))

    username = models.CharField('用户名', max_length=20, blank=True)
    tel = models.BigIntegerField('手机号', unique=True, blank=True, null=True)
    openid = models.CharField('小程序openid', unique=True, max_length=100, blank=True, null=True)
    avatar_url = models.URLField('头像', help_text='头像', null=True, blank=True)
    unionid = models.CharField('小程序unionid', unique=True, max_length=100, blank=True, null=True)
    nickname = models.CharField('微信昵称', max_length=100, blank=True, null=True)
    gender = models.IntegerField('性别', choices=GENDER, default=2)
    type = models.IntegerField('用户类型', choices=USER_TYPE, default=1)
  # 用自定义的用户需要重写此方法
    @property
    def is_authenticated(self):
        """
        Always return True. This is a way to tell if the user has been
        authenticated in templates.
        """
        return True

    def __str__(self):
        return self.openid

    class Meta:
        db_table = 'user'
        verbose_name = '用户管理'
        verbose_name_plural = verbose_name

小程序用户序列化器

小程序用户表中的所有信息,当然可以自己去模型类中加一些,像注册时间或者是更新时间,上次登录时间等,此处只是演示,并未添加

class UserSerializer(serializers.ModelSerializer):
    """用户序列化器"""

    class Meta:
        model = User
        fields = '__all__'

自定义认证类

主要其实是用来获取到用户,其它的方法都是继承了simplejwt的JWTAuthentication中的方法。
在用户app目录下直接去新建一个文件 Authentication.py 写入如下内容

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed

from apps.user.models import User

# 自定义的解析token的方法 下面会用到
from utils.token_get_user import get_user

class MyJWTAuthentication(JWTAuthentication):
    """
    继承JWTAuthentication类, 返回自定义User对象
    """
	# get_user用于返回用户,重写后在其它地方使用request.user时可以直接得到自定义的小程序用户
    def get_user(self, validated_token):
        try:
            user_id = get_user_id(str(validated_token))  # 此处为自定义的解析token方法, 可以解码token得到其中的信息,重点是拿到user_id 用于后续的获取用户
        except KeyError:
            raise InvalidToken(_('Token不包含可识别的用户标识'))

        try:
            user = User.objects.get(**{'id': user_id})
        except User.DoesNotExist:
            raise AuthenticationFailed(_('未找到用户'), code='user_not_found')

        return user

get_user_id方法: 用于解析我们的token得到user_id

import jwt
import time

# ConvLife为我的项目名称,此处导入的是项目的settings,主要是拿到自定义的secret_key
from ConvLife import settings


def get_user_id(t):
    """根据token得到当前用户user_id"""
    try:
        decode_data = jwt.decode(t, secret_key=settings.SECRET_KEY, verify=False, algorithms=['HS256'])
        print(decode_data)
        if int(decode_data['exp']) < int(time.time()):
            return "token过期"
        return decode_data['user_id']
    except Exception as e:
        return "token错误:\n"+str(e)

小程序登录及手动签发token

用户视图

在登录这其实就是重写了create,思路大概:通过用户传入的code获取到openid(获取不到openid的情况就是code出错或者用户未输传入code),然后使用openid去用户表查找用户,如果不存在则新建,存在则读取到,然后针对读取或者新建的用户生成一个token,再返回给前端

# get_wx_openid 是调用微信开放接口,使用小程序传到后端的code去请求openid,openid作为唯一标识
from utils.wx import get_wx_openid

from utils.Authentication import MyJWTAuthentication


class WxLogin(mixins.CreateModelMixin, viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def create(self, request, *args, **kwargs):
		# code是小程序端可以直接获取到的,获取到后和userinfo一起Post到后端 
        code = request.data.get('code', '')
        
        # 通过传入的code来获取openid
        openid_res = get_wx_openid(code)
        try:
            openid = openid_res['openid'] or ''
        except KeyError as e:
            return Response({'message': '微信调用失败'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 尝试查找用户,如果用户存在则读取用户,用户不存在则新建用户
        try:
            user = User.objects.get(openid=openid)
        except User.DoesNotExist:
        	# userinfo 是小程序端可以直接获取到的,获取到后和code 一起Post到后端 
            user_info = request.data.get('userInfo')
            print(user_info)
            user = User.objects.create(openid=openid,
                                       avatar_url=user_info['avatarUrl'],
                                       nickname=str(user_info['nickName']),
                                       gender=user_info['gender'])
            user.save()

        # 手动签发jwt token
        refresh = RefreshToken.for_user(user)
        resp_data = {
            'user_id': user.id,
            "refresh": str(refresh),
            "access": str(refresh.access_token)
        }
        return Response(resp_data)

路由

这里直接配置成前端发送code和userinfo来请求的了,需要测试的话请求得先去在微信小程序端获取到code,要带着code去请求,不然会报错!

from .views import WxLogin

router = routers.SimpleRouter()

# 小程序用户
router.register(r'login/', WxLogin, basename="login")

urlpatterns = router.urls

使用错误code获取token,失败

java微信小程序登录获取头像等信息_java微信小程序登录获取头像等信息

使用正确code获取token,成功

java微信小程序登录获取头像等信息_java微信小程序登录获取头像等信息_02

使用

在需要小程序用户登录验证的视图中加入permission_classes = [permissions.IsAuthenticated]authentication_classes = (MyJWTAuthentication,),当获取用户收藏或者收藏时就会需要用户是登录用户并会使用我们自定义的类,在登录的视图类中不要加哦,只是在需要验证的视图类中加上。

例如:

from rest_framework import mixins
from rest_framework.permissions import IsAuthenticated

from .models import UserFav
from .serializers import UserFavSerializer

from utils.Authentication import MyJWTAuthentication


class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    create: 用户收藏藏品
    取消收藏商品

    list: 获取收藏藏品列表
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
	
	permission_classes = [IsAuthenticated]  # 增加此行
    authentication_classes = [MyJWTAuthentication, ]   # 增加此行

    filter_backends = [DjangoFilterBackend, filters.SearchFilter]
    search_fields = ('goods__good_name', 'goods__goods_brief')
    
    # 获取到当前用户的收藏列表
    def get_queryset(self):
        return UserFav.objects.filter(user=self.request.user.id)

新手写博客,主要想记录下自己学习中遇到的问题及解决的过程,很多不足的地方,如有讲得不明白的地方看官请见谅,也可以私信我,看到后会尽快回复的