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就能夠取到本次請求的用戶信息了。

相關文章
相關標籤/搜索