目录
1、QQ登录逻辑分析
2、若想实现 QQ 登录,需要成为 QQ 互联的开发者,审核通过才可实现。<申请链接>
3、Django准备:
3.0、新建子应用apps/oauth
,并注册到配置文件settings
进入 apps 目录下:
cd apps
调用命令生成 oauth 子应用:
python …/manage.py startapp oauth
(或者通过 django-admin startapp oauth 来生成)
3.1. 定义模型类基类
- 在Django工程找一个合适的地方,新建文件
models.py
定义模型基类,以后只要是继承该基类的模型类,都会有这两个字段。
from django.db import models
class BaseModel(models.Model):
"""为模型类补充字段"""
# auto_now_add=True:表面当创建模型类对象的时候,会创建该字段的值
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
# auto_now=True:表面当更新模型类对象的时候,会更新该字段的值
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
# 说明是抽象模型类(抽象模型类不会创建表)
abstract = True
- 编辑文件
apps/oauth/models.py
新建模型类如
from django.db import models
from meiduo_mall.utils.models import BaseModel
class OAuthQQUser(BaseModel):
"""
功能:把qq用户 和 商城用户进行绑定
"""
# user 是个外键, 关联对应的用户 —— 美多商城用户
user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
# qq 布的用户身份id —— 用户使用qq扫码登陆之后,qq官方颁发的用户身份id
openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
class Meta:
db_table = 'tb_oauth_qq'
迁移建表(中间表)
生成迁移文件:
python manage.py makemigrations oauth
进行数据迁移:
python manage.py migrate oauth
4、QQLoginTool工具
该工具封装了对接 QQ 互联的请求操作.
可用于快速实现 QQ 登录的一种工具包.
安装:pip install QQLoginTool -i https://pypi.tuna.tsinghua.edu.cn/simple
使用步骤:
- 1.导包
# 使用时, 需要导入该包:
# 我们可以从下载的 QQLoginTool 中导入 OAuthQQ:
from QQLoginTool.QQtool import OAuthQQ
- 2.初始化 OAuthQQ 对象
# 创建对象
# 创建对象的时候, 需要传递四个参数:
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, # 在 QQ 互联申请到的客户端 id: app_id
client_secret=settings.QQ_CLIENT_SECRET, # 我们申请的客户端秘钥:app_key
redirect_uri=settings.QQ_REDIRECT_URI, # 我们申请时添加的: 登录成功后回调的路径
state=None # state是用户完成整个登陆流程之后,跳转到的路径
- 对象提供的方法一: 获取 QQ 地址.
获取 QQ 登录扫码页面,扫码后得到 Authorization Code( 特许码 )
# 调用对象的 get_qq_url() 函数, 获取对应的扫码页面:
login_url = oauth.get_qq_url()
- 对象提供的方法二: 获取 QQ 的 access_token.
# 调用对象的方法, 根据 code 获取 access_token:
access_token = oauth.get_access_token(code)
- 再获取 OpenID
# 调用对象的方法, 根据 access_token 获取 openid:
openid = oauth.get_open_id(access_token)
5、封装加密openID的工具
我们应该对openid 敏感数据加解密 ——使用itsdangerous
里面的TimedJSONWebSignatureSerializer
可以生成带有有效期的token
安装依赖包
pip install itsdangerous
在Django工程的合适位置新建secert.py
文件进行封装
"""
封装加密和解密openid的接口
"""
from itsdangerous import TimedJSONWebSignatureSerializer
from django.conf import settings
# 原则上一个类的封装分为2个部分:
# 1、操作所需要的属性
# 2、函数方法
class SecretOauth(object):
def __init__(self):
# 由于加密和解密都需要TimedJSONWebSignatureSerializer对象
# 就可以把该对象封装到属性中
self.serializer = TimedJSONWebSignatureSerializer(
secret_key=settings.SECRET_KEY, # 使用Django工程初始化的密钥
expires_in=24 * 15 * 60
)
# (1)、加密
def dumps(self, content_dict):
"""
功能:加密openid
参数:content_dict = {"open_id": "fnhewrbfhreubvw"}
:return: 返回加密后的access_token
"""
access_token = self.serializer.dumps(content_dict)
return access_token.decode() # 返回的是一个字符串
# (2)、解密
def loads(self, access_token):
"""
功能:解密access_token
参数:access_token = "fewbfherbgerg.regwertgt.wgtrwgtfgrew"
:return: 解密后的字典
"""
try:
data = self.serializer.loads(access_token)
except Exception as e:
# 解密/校验失败 —— 要么伪造了要么过了有效期
return None
# 校验成功返回解密后的字典{"open_id": "fnhewrbfhreubvw"}
return data
6、视图接口源码如下:
import json
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from django.contrib.auth import login
from django.http import JsonResponse
from django.views import View
from apps.users.models import User
from apps.oauth.models import OAuthQQUser
from super_mall.utils.secret import SecretOauth
# qq登陆接口1:获取扫码页面url
class QQURLView(View):
def get(self, request):
next = request.GET.get('next')
# 创建OAuthQQ一个对象oauth
oauth = OAuthQQ(
client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next
)
# 获取扫码登陆页面的url
log_url = oauth.get_qq_url()
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'login_url': log_url
})
class QQUserView(View):
# qq登陆接口2:验证code获取openid
def get(self, request):
# 1、提取参数
# 此处的code,是用户扫码验证qq身份成功之后,qq给用户
# 用户转而携带到美多后台来的
code = request.GET.get('code')
# 2、校验参数
# 2.1、必要性校验
if not code:
return JsonResponse({
'code': 400,
'errmsg': '缺少参数'
}, status=400)
# 3、数据/业务处理:验证code获取openid
# 3.1、实例化qq认证对象
oauth = OAuthQQ(
client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state='/'
)
try:
# 3.2、调用接口获取access_token
access_token = oauth.get_access_token(code)
# 3.3、调用接口获取openid
openid = oauth.get_open_id(access_token)
except Exception as e:
# 表示验证用户code失败
return JsonResponse({
'code': 400,
'errmsg': 'qq身份验证失败'
}, status=400)
# 代码走到此处,表明,美多后台已经成功获取了用户的openid
# 3.4、判读用户的"美多商城账号"和"openid"是否绑定
try:
oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist as e:
# 3.4.1、用户没有绑定qq
# 把openid加密返回给用户,由用户在页面中数据账号和密码,再进一步调用接口3来进行绑定操作
access_token = SecretOauth().dumps({
'openid': openid
})
return JsonResponse({
'code': 400, # 根据接口约定,自定义返回的code为400表示未绑定,前端就会跳转到绑定页面
'errmsg': 'ok',
'access_token': access_token
})
else:
# 3.4.2、用户绑定过qq
login(request, oauth_user.user)
response = JsonResponse({
'code': 0,
'errmsg': 'ok'
})
response.set_cookie(
'username',
oauth_user.user.username,
max_age=24 * 3600 * 14
)
return response
# qq登陆接口3:把用户的商城账号 和 用户的qq身份(openid) 进行绑定
def post(self, request):
# 提取参数
data = json.loads(request.body.decode())
mobile = data.get('mobile')
password = data.get('password')
sms_code = data.get('sms_code')
access_token = data.get('access_token')
# 校验
if not all([mobile, password, sms_code, access_token]):
return JsonResponse({
'code': 400,
'errmsg': '参数缺少'
}, status=400)
# 手机号,密码,短信格式校验,略、
token_dict = SecretOauth().loads(access_token)
if token_dict is None:
return JsonResponse({
'code': 400,
'errmsg': '无效的qq身份'
}, status=400)
openid = token_dict.get('openid')
try:
# 根据手机号查找用户,
user = User.objects.get(mobile=mobile)
except User.DoesNotExist as e:
# 没找到则新建并绑定
user = User.objects.create_user(
username=mobile,
mobile=mobile,
password=password
)
else:
# 找到则直接绑定
# 先校验密码再绑定
if not user.check_password(password):
return JsonResponse({'code': 400, 'errmsg': '密码错误!'}, status=400)
# 绑定(在中间表插入一条数据)
OAuthQQUser.objects.create(
user=user,
openid=openid
)
login(request, user)
response = JsonResponse({'code': 0, 'errmsg': 'ok'})
response.set_cookie(
'username',
user.username,
max_age=24*14*3600
)
return response