Django实现小程序的登陆验证功能,并维护登陆态

说明

此次本身作了一个小程序来玩,在登陆方面一直有些模糊,网上看了不少文档后,得出如下一种解决方案。前端

环境说明:
一、小程序只须要拿到openid,其余信息不存储。
二、Django自带的User类不适合。redis

具体操做流程:
一、用户点进小程序,就调用wx.login()获取临时登陆凭证code, wx.login()用户是无感知的,
二、经过wx.request()将code传到开发者服务器的后台程序,
三、后台拿到code以后,调用微信提供的接口,获取openid和session_key,
四、后台自定义User表,将openid做为用户名,不设置用户密码,若是用户不存在,则建立新用户,接着根据openid和session_key生成新的自定义登陆态3rd_session(这里使用skey表示)返回给小程序,
五、后台将skey存入缓存中(Redis),设置为2小时过时,
六、小程序接收到skey,说明登陆成功,将skey保存到本地Storage中,下次请求时,在请求头中携带skey,
七、后台接收到请求,从请求头中拿到skey,判断缓存中是否还有此skey,若是有,说明还在登陆态,容许执行请求相关操做,若是没有,说明须要从新登陆,给小程序返回401.数据库

第三方库: Django、Djando rest framework、Django-redisdjango

用户信息

自定义User类

models.pyjson

from django.db import models
from django.utils import timezone


class User(models.Model):
    openid = models.CharField(max_length=50, unique=True)
    created_date = models.DateTimeField(auto_now_add=True)
复制代码

User接口序列化

serializers.py小程序

from rest_framework import serializers
from django.utils import timezone
from .models import User


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = '__all__'
复制代码

登陆接口设计

views.pyapi

import hashlib
import json
import requests
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django_redis import get_redis_connection

from .models import User
from .serializers import UserSerializer


@api_view(['POST'])
def code2Session(request):
    appid = ''
    secret = ''
    js_code = request.data['code']
    url = 'https://api.weixin.qq.com/sns/jscode2session' + '?appid=' + appid + '&secret=' + secret + '&js_code=' + js_code + '&grant_type=authorization_code'
    response = json.loads(requests.get(url).content)  # 将json数据包转成字典
    if 'errcode' in response:
        # 有错误码
        return Response(data={'code':response['errcode'], 'msg': response['errmsg']})
    # 登陆成功
    openid = response['openid']
    session_key = response['session_key']
    # 保存openid, 须要先判断数据库中有没有这个openid
    user, created = User.objects.get_or_create(openid=openid)
    user_str = str(UserSerializer(user).data)
    # 生成自定义登陆态,返回给前端
    sha = hashlib.sha1()
    sha.update(openid.encode())
    sha.update(session_key.encode())
    digest = sha.hexdigest()
    # 将自定义登陆态保存到缓存中, 两个小时过时
    conn = get_redis_connection('default')
    conn.set(digest, user_str, ex=2*60*60)
    
    return Response(data={'code': 200, 'msg': 'ok', 'data': {'skey': digest})
复制代码

其中,redis的安装,配置与使用,能够参考这篇文档
登陆后,返回skey给小程序端,小程序保存到本地,下次请求携带skey。缓存

用户登陆认证

由于个人User类是自定义的,skey也是自定义的,没有使用token或者jwt等技术,这里就须要自定义登陆认证了,在执行视图里相应的请求处理函数前,先对skey作判断,判断经过就从skey中取得openid的值。bash

我在这里考虑了几种方法:
一、利用Django中间件,
二、利用装饰器,
三、利用rest_framework的认证类,服务器

这里先分析Django的请求处理流程:

屏幕快照 2019-07-02 上午9.27.05
从上图也能够看出,在中间件中作认证,彻底是可行的,认证不经过就能够直接返回了,不用到达路由映射表和视图。可是rest_framework中,对request进行了封装,中间件中的request是django的HttpRequest,而rest_framework将django的request封装成rest_framework的Request。

若是是装饰器的话,在本次设计中不够灵活,由于除了登陆接口,其余接口的每一个method都须要作认证。

因此综合考虑,自定义一个rest_framework的认证类是最适合此次小程序的验证的,在认证类中设置request.user,而后在视图中就能够经过request.user直接获取用户信息了。

接下来,先分析一下rest_framework的源码,看看是怎么作认证的。

认证分析

从上图源码分析中,能够看出最后是调用了认证类的认证方法:authenticator.authenticate(). 而后先看看rest_framework自带的认证类,在rest_framework.authentication中,

认证类

接下来就自定义一个适用于本次小程序设计的认证类: 新建authentication.py文件

from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from django_redis import get_redis_connection


class UserAuthentication(BaseAuthentication):
    def authenticate(self, request):
        if 'HTTP_SKEY' in request.META:
            skey = request.META['HTTP_SKEY']
            conn = get_redis_connection('default')
            if conn.exists(skey):
                user = conn.get(skey)  
                return (user, skey)
            else:
                raise exceptions.AuthenticationFailed(detail={'code': 401, 'msg': 'skey已过时'})
        else:
            raise exceptions.AuthenticationFailed(detail={'code': 400, 'msg': '缺乏skey'})

    def authenticate_header(self, request):
        return 'skey'
复制代码

最后利用全局设置DEFAULT_AUTHENTICATION_CLASSE将UserAuthentication设置为全局使用,同时登陆接口应该设计为不使用认证类,将登陆接口添加两行代码。

settings.py文件:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'note.authentication.UserAuthentication',  # 用自定义的认证类
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    ),
}
复制代码

登陆接口

import hashlib
import json
import requests
from rest_framework import status
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from django_redis import get_redis_connection

from .models import User
from .serializers import UserSerializer


@api_view(['POST'])
@authentication_classes([])  # 添加
def code2Session(request):
    appid = ''
    secret = ''
    js_code = request.data['code']
    url = 'https://api.weixin.qq.com/sns/jscode2session' + '?appid=' + appid + '&secret=' + secret + '&js_code=' + js_code + '&grant_type=authorization_code'
    response = json.loads(requests.get(url).content)  # 将json数据包转成字典
    if 'errcode' in response:
        # 有错误码
        return Response(data={'code':response['errcode'], 'msg': response['errmsg']})
    # 登陆成功
    openid = response['openid']
    session_key = response['session_key']
    # 保存openid, 须要先判断数据库中有没有这个openid
    user, created = User.objects.get_or_create(openid=openid)
    user_str = str(UserSerializer(user).data)
    # 生成自定义登陆态,返回给前端
    sha = hashlib.sha1()
    sha.update(openid.encode())
    sha.update(session_key.encode())
    digest = sha.hexdigest()
    # 将自定义登陆态保存到缓存中, 两个小时过时
    conn = get_redis_connection('default')
    conn.set(digest, user_str, ex=2*60*60)
    
    return Response(data={'code': 200, 'msg': 'ok', 'data': {'skey': digest})
复制代码

以后,在接口中经过request.user就能够取到本次请求的用户信息了。

相关文章
相关标签/搜索