Django--QQ登录

定义 QQ 登录模型类

1. 定义模型类基类

在内层 meiduo_mall 中 添加新的包 utils

在这个包中增加 BaseModel.py 文件.

在这个文件里, 添加如下的代码,

这里的代码主要作为别的模型类的基类来使用.

增加数据新建时间和更新时间:
from django.db import models

class BaseModel(models.Model):
    """为模型类补充字段"""

    # 创建时间: 
    create_time = models.DateTimeField(auto_now_add=True, 
                                       verbose_name="创建时间")
    # 更新时间: 
    update_time = models.DateTimeField(auto_now=True, 
                                       verbose_name="更新时间")

    class Meta:
        # 说明是抽象模型类(抽象模型类不会创建表)
        abstract = True
上面代码中相关参数解读:

auto_now_add:

创建或添加对象时自动添加时间, 修改或更新对象时, 不会更改时间

auto_now:

凡是对对象进行操作(创建/添加/修改/更新),时间都会随之改变

abstract:

声明该模型类仅继承使用,数据库迁移时不会创建 BaseModel 的表

2. 定义 QQ 需要使用的登录模型类

在 oauth.models.py 中添加如下模型类, 用于存放 user 和 openid:

# 导入: 
from django.db import models
from meiduo_mall.utils.BaseModel import BaseModel

# 定义QQ登录的模型类: 
class OAuthQQUser(BaseModel):
    """QQ登录用户数据"""

    # user 是个外键, 关联对应的用户
    user = models.ForeignKey('users.User', 
                             on_delete=models.CASCADE, 
                             verbose_name='用户')
    # qq 发布的用户身份id
    openid = models.CharField(max_length=64, 
                              verbose_name='openid', 
                              db_index=True)

    class Meta:
        db_table = 'tb_oauth_qq'
        verbose_name = 'QQ登录用户数据'
        verbose_name_plural = verbose_name

QQLoginTool 使用说明

1. 导入

# 使用时, 需要导入该包:
# 我们可以从下载的 QQLoginTool 中导入 OAuthQQ:
from QQLoginTool.QQtool import OAuthQQ

2. 初始化 OAuthQQ 对象

# 创建对象
# 创建对象的时候, 需要传递四个参数: 
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, 
                client_secret=settings.QQ_CLIENT_SECRET, 
                redirect_uri=settings.QQ_REDIRECT_URI, 
                state=next)
相关参数解读:

client_id: 在 QQ 互联申请到的客户端 id

client_secret: 我们申请的客户端秘钥

redirect_uri: 我们申请时添加的: 登录成功后回调的路径

state: 就是我们之前接收的 next 参数

3. 对象提供的方法一: 获取 QQ 地址.

获取 QQ 登录扫码页面,扫码后得到 Authorization Code( 特许码 )

# 调用对象的 get_qq_url() 函数, 获取对应的扫码页面: 
login_url = oauth.get_qq_url()

4. 对象提供的方法二: 获取 QQ 的 access_token.

通过上面一个函数获取的 Authorization Code 再获取 Access Token

# 调用对象的方法, 根据 code 获取 access_token: 
access_token = oauth.get_access_token(code)

5. 对象提供的方法二: 获取 QQ 的 access_token.

通过上面一个函数获取的 Access Token 再获取 OpenID

# 调用对象的方法, 根据 access_token 获取 openid: 
openid = oauth.get_open_id(access_token)

QQ 登录接口的制定

获取 QQ 登录扫码页面接口

# 导入: 
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from django import http
from django.views import View


class QQFirstView(View):
    """提供QQ登录页面网址"""

    def get(self, request):
        # next 表示从哪个页面进入到的登录页面
        # 将来登录成功后,就自动回到那个页面
        next = request.GET.get('next')

        # 获取 QQ 登录页面网址
        # 创建 OAuthQQ 类的对象
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, 
                        client_secret=settings.QQ_CLIENT_SECRET, 
                        redirect_uri=settings.QQ_REDIRECT_URI, 
                        state=next)

        # 调用对象的获取 qq 地址方法
        login_url = oauth.get_qq_url()

        # 返回登录地址
        return JsonResponse({'code': 0, 
                             'errmsg': 'OK', 
                             'login_url':login_url})

QQ 登录参数

# QQ登录参数
# 我们申请的 客户端id
QQ_CLIENT_ID = '101474184'
# 我们申请的 客户端秘钥
QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
# 我们申请时添加的: 登录成功后回调的路径
QQ_REDIRECT_URI = 'http://www.meiduo.site:8080/oauth_callback.html'

添加子路由

# 获取 QQ 扫码登录链接
re_path(r'^qq/authorization/$', views.QQFirstView.as_view()),

 第二个接口

# 导入: 
from oauth.models import OAuthQQUser
import logging
logger = logging.getLogger('django')
from django.contrib.auth import login
from oauth.utils import generate_access_token

class QQUserView(View):
    """用户扫码登录的回调处理"""

    def get(self, request):
        """Oauth2.0认证"""
        # 获取前端发送过来的 code 参数: 
        code = request.GET.get('code')

        if not code:
            # 判断 code 参数是否存在
            return http.JsonResponse({'code': 400, 
                                      'errmsg': '缺少code参数'})

        # 调用我们安装的 QQLoginTool 工具类
        # 创建工具对象
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, 
                        client_secret=settings.QQ_CLIENT_SECRET, 
                        redirect_uri=settings.QQ_REDIRECT_URI)

        try:
            # 携带 code 向 QQ服务器 请求 access_token
            access_token = oauth.get_access_token(code)

            # 携带 access_token 向 QQ服务器 请求 openid
            openid = oauth.get_open_id(access_token)

        except Exception as e:
            # 如果上面获取 openid 出错, 则验证失败
            logger.error(e)
            # 返回结果
            return http.JsonResponse({'code': 400, 
                                      'errmsg': 'oauth2.0认证失败, 即获取qq信息失败'})
        pass

给上面的接口增加子路由:

# QQ用户部分接口: 
re_path(r'^oauth_callback/$', views.QQUserView.as_view()),

openid 是否绑定用户的处理

判断 openid 是否绑定过用户

使用 openid 查询该 QQ 用户是否在美多商城中绑定过用户。

try:
    oauth_qq = OAuthQQUser.objects.get(openid=openid)
except Exception as e:
    # 如果 openid 没绑定美多商城用户
    pass
else:
    # 如果 openid 已绑定美多商城用户
    pass

openid 已绑定用户的处理

如果 openid 已绑定美多商城用户

直接生成状态保持信息,登录成功,并重定向到首页。

try:
    # 查看是否有 openid 对应的用户
    oauth_qq = OAuthQQUser.objects.get(openid=openid)

except Exception as e:
    # 如果 openid 没绑定美多商城用户
    # 请查看:  openid 未绑定用户的处理
    pass
else:
    # 如果 openid 已绑定美多商城用户
    # 根据 user 外键, 获取对应的 QQ 用户(user)
    user = oauth_qq.user

     # 实现状态保持
    login(request, user)

    # 创建重定向到主页的对象
    response = JsonResponse({'code':0,
                             'errmsg':'ok'})

    # 将用户信息写入到 cookie 中,有效期14天
    response.set_cookie('username', 
                        user.username, 
                        max_age=3600 * 24 * 14)

    # 返回响应
    return response

openid 未绑定用户的处理

为了能够在后续的绑定用户操作中前端可以使用 openid,

在这里将 openid 签名后响应给前端。

openid 属于用户的隐私信息,所以需要将 openid 签名处理,避免暴露。

try:
    # 查看是否有 openid 对应的用户
    oauth_qq = OAuthQQUser.objects.get(openid=openid)

except Exception as e:
    # 如果 openid 没绑定美多商城用户,进入这里: 

    # 调用我们自定义的方法, 对 openid 进行加密
    # 把 openid 变为 access_token
    access_token = generate_access_token(openid)

    # 把 access_token 返回给前端
    # 注意: 这里一定不能返回 0 的状态码. 否则不能进行绑定页面
    return JsonResponse({'code':300,
                         'errmsg':'ok',
                         'access_token':access_token})
else:
    ...

补充 itsdangerous 的使用

# 导入: 
from itsdangerous import TimedJSONWebSignatureSerializer
from django.conf import settings

def generate_access_token(openid):
    """对传入的 openid 进行加密处理, 返回 token"""

        # QQ 登录保存用户数据的 token 有效期
    # settings.SECRET_KEY: 加密使用的秘钥
    # 过期时间: 600s = 10min
    serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 
                                                 expires_in=600)
    data = {'openid': openid}

    # 对 dict 进行加密
    token = serializer.dumps(data)

    # 加密完之后, 解码返回.
    return token.decode()

绑定用户接口实现

 

 

class QQUserView(View):
    """用户扫码登录的回调处理"""

    def get(self, request):
        """ Oauth2.0 认证"""
        ......

    def post(self, request):
        """美多商城用户绑定到openid"""

        # 1.接收参数
        dict = json.loads(request.body.decode())
        mobile = dict.get('mobile')
        password = dict.get('password')
        sms_code_client = dict.get('sms_code')
        access_token = dict.get('access_token')

        # 2.校验参数
        # 判断参数是否齐全
        if not all([mobile, password, sms_code_client]):
            return JsonResponse({'code':400,
                                 'errmsg':'缺少必传参数'})

        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return JsonResponse({'code':400,
                                 'errmsg':'请输入正确的手机号码'})


        # 判断密码是否合格
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return JsonResponse({'code':400,
                                 'errmsg':'请输入8-20位的密码'})

        # 3.判断短信验证码是否一致
        # 创建 redis 链接对象:
        redis_conn = get_redis_connection('verify_code')

        # 从 redis 中获取 sms_code 值:
        sms_code_server = redis_conn.get('sms_%s' % mobile)

        # 判断获取出来的有没有: 
        if sms_code_server is None:
            # 如果没有, 直接返回: 
            return JsonResponse({'code':400,
                                 'errmsg':'验证码失效'})
        # 如果有, 则进行判断: 
        if sms_code_client != sms_code_server.decode():
            # 如果不匹配, 则直接返回: 
            return JsonResponse({'code':400,
                                 'errmsg':'输入的验证码有误'})  

        # 调用我们自定义的函数, 检验传入的 access_token 是否正确:
        # 错误提示放在 sms_code_errmsg 位置
        openid = check_access_token(access_token)
        if not openid:
            return JsonResponse({'code':400,
                                 'errmsg':'缺少openid'})
        # 4.保存注册数据
        try:
            user = User.objects.get(mobile=mobile)
        except Exception as e:
            # 用户不存在,新建用户
            user = User.objects.create_user(username=mobile, 
                                            password=password, 
                                            mobile=mobile)
        else:
            # 如果用户存在,检查用户密码
            if not user.check_password(password):
               return JsonResponse({'code':400,
                                    'errmsg':'输入的密码不正确'})
        # 5.将用户绑定 openid
        try:
            OAuthQQUser.objects.create(openid=openid, 
                                       user=user)
        except Exception as e:
            return JsonResponse({'code':400,
                                 'errmsg':'往数据库添加数据出错'})
        # 6.实现状态保持
        login(request, user)

        # 7.创建响应对象: 
        response = JsonResponse({'code':0,
                                 'errmsg':'ok'})

        # 8.登录时用户名写入到 cookie,有效期14天
        response.set_cookie('username', 
                            user.username, 
                            max_age=3600 * 24 * 14)

        # 9.响应
        return response
# 导入: 
from itsdangerous import BadData


# 定义函数, 检验传入的 access_token 里面是否包含有 openid
def check_access_token(access_token):
    """
    检验用户传入的 token
    :param token: token
    :return: openid or None
    """

    # 调用 itsdangerous 中的类, 生成对象
    serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 
                                                 expires_in=600)
    try:
           # 尝试使用对象的 loads 函数
           # 对 access_token 进行反序列化( 类似于解密 )
       # 查看是否能够获取到数据:
       data = serializer.loads(access_token)

    except BadData:
        # 如果出错, 则说明 access_token 里面不是我们认可的. 
        # 返回 None
        return None
    else:
        # 如果能够从中获取 data, 则把 data 中的 openid 返回
        return data.get('openid')

 

posted @ 2020-06-04 15:20  Tracydzf  阅读(244)  评论(0编辑  收藏  举报