用户模块---QQ登录\

流程图

QQ登录文档:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

流程简述:

1.当点击qq登录图标时,进入生成登录url的接口

2.在前端的回调函数中跳转到qq登录的扫码页面

3.扫码登陆后,qq会携带code访问申请时指定的回调地址,

使用code获取access_token

使用access_token最终获取openid

4用来判断用户是否是第一次使用QQ登录

5.是,返回绑定/注册,页面第一次使用QQ登录,额外判断,是否已经注册账号

5.1是,统一跳转到绑定/注册界面,

5.1.1将openid和账号进行绑定/注册,手动调用jwt功能返回jwt,id,username

5.2否,返回首页,手动调用jwt功能返回jwt,id,username

具体实现:

使用到的模块:

import urllib

urllib.parse.urlencode(query)  # 将字典转换为url路径中的查询字符串

urllib.parse.parse_qs(qs)  # 将查询字符串格式数据转换为python的字典

urllib.request.urlopen(url, data=None)

在python后端发起http请求,

如果data为None,发送GET请求,

如果data不为None,发送POST请求

response.read().decode()
返回response响应对象,可以通过read()读取响应体数据,需要注意读取出的响应体数据为bytes类型

手动调用jwt生成token用于验证登录状态

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
jwt_token = jwt_encode_handler(payload)

itsdangerous模块用于生成token和解析token

from itsdangerous import TimedJSONWebSignatureSerializer

serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 300)
# 需要转换成token的数据
data = {'openid': openid} # .dumps生成token,bytes类型,需要解码
token = serializer.dumps(data).decode() # .loads将token解析为需要的数据,
openid = serializer.loads(token).get('openid')

模型类,保存QQ的openid和本站用户之间的关系:

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:
# 指定BaseModel为抽象类,不会创建实体表
abstract = True class OAuthQQUser(BaseModel):
"""QQ登录的模型"""
openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
user = models.ForeignKey('user.User', on_delete=models.CASCADE, verbose_name='用户')
objects = models.Manager() class Meta:
db_table = 'tb_oauth_qq'
verbose_name = 'QQ登录用户数据'
verbose_name_plural = verbose_name

定义一个工具类,主要负责生成token和验证token是否正确

class OAuthQQ(object):
"""用于qq登录的工具类""" def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None):
"""QQ登录开发文档中需要的参数"""
self.client_id = client_id or settings.QQ_CLIENT_ID
self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
self.state = state or settings.QQ_STATE # 用于保存登录成功后的跳转页面路径

在OAuthQQ工具类中新增生成登录url的方法


def generate_qq_login_url(self):
"""生成用于qq登录扫码的url地址"""
params = {
'response_type': 'code', # 默认值
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': self.state,
'scope': 'get_user_info', # 用户勾选的授权范围,get_user_info表示,获取登录用户的昵称、头像、性别
}
url = 'https://graph.qq.com/oauth2.0/authorize?'
# 拼接查询字符串,
url += parse.urlencode(params)
return url

定义返回扫码QQ登录url的接口

# 在点击qq登录图标时向接口发起请求
# 后端生成用于QQ扫码登录的页面的url地址
# 在前端回调函数中执行 # GET /oauth/qq/authorization/?state=xxx class QQAuthUrlView(APIView):
"""获取QQ扫码登录的网址接口""" def get(self, request):
"""
:return 扫码的url地址
"""
state = request.query_params.get('state')
oauthqq = OAuthQQ(state=state)
qq_login_url = oauthqq.generate_qq_login_url()
return Response({'qq_login_url': qq_login_url})

前端将code当参数传入后端接口,生成获取access_token的url

在OAuthQQ工具类新增,使用code请求并获取QQ的access_token,的方法

def get_qq_access_token(self, code):
"""获取access_token"""
params = {
'grant_type': 'authorization_code',
'client_id': self.client_id,
'client_secret': self.client_secret,
'code': code,
'redirect_uri': self.redirect_uri,
}
url = 'https://graph.qq.com/oauth2.0/token?'
url += parse.urlencode(params)
# 向qq方发起http请求,获取包含access_token的查询字符串
# 形式access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
try:
response = request.urlopen(url)
response_data = response.read().decode()
# 讲查询字符串转换为python中的字典,[{}]
data = parse.parse_qs(response_data)
access_token = data.get('access_token', None)[0]
except Exception as e:
logger.error(e)
raise Exception('获取access_token异常')
return access_token

根据access_token生成获取openid的url

后端发送http请求,从返回值中获取openid

def get_qq_openid(self, access_token):
"""获取openid"""
url = 'https://graph.qq.com/oauth2.0/me?access_token='
url += access_token
logger.error(url)
try:
response = request.urlopen(url)
response_data = response.read().decode()
# 返回一个字符串 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
data_dict = json.loads(response_data[10:-4])
openid = data_dict.get('openid', None)
except Exception as e:
logger.error(e)
raise Exception('获取openid异常')
return openid

根据openid去判断,该用户是否是第一次使用QQ登录功能.

定义类视图和serializers序列化器

class QQAuthUserView(GenericAPIView):
"""QQ登录后接口"""
serializer_class = OAuthQQUserSerializer def get(self, request):
"""QQ登录"""
code = request.query_params.get('code')
if not code:
return Response({'message': 'code不存在'}, 400) # 目标是通过 code获取access_token
oauthqq = OAuthQQ()
access_token = oauthqq.get_qq_access_token(code) # 通过access_token获取openid
openid = oauthqq.get_qq_openid(access_token) # 获取openid后需要判断
# oauthqquser = OAuthQQUser.get try:
oauthqquser = OAuthQQUser.objects.get(openid=openid)
except Exception as e:
logger.error('此人未绑定或未注册:%s' % e)
# 1.第一次用qq登录
# 使用openid生成记录qq身份的token,以便注册或绑定时验证身份
access_token = OAuthQQ.generate_save_user_token(openid)
return Response({'access_token': access_token})
# 1.1 已经注册本站账号--->跳转绑定界面
# 1.2 未注册本站账号--->注册并绑定 # 2.以前已经qq登录过(一定有本站账号)
else:
user = oauthqquser.user
# 生成jwt_token,用于记录登录状态
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
jwt_token = jwt_encode_handler(payload) data = {
'user_id': user.id,
'username': user.username,
'token': jwt_token
}
return Response(data=data) def post(self, request):
"""QQ账号绑定和新增功能""" serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save() jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
jwt_token = jwt_encode_handler(payload) data = {
'user_id': user.id,
'username': user.mobile,
'token': jwt_token
}
return Response(data=data)

序列化器

class OAuthQQUserSerializer(serializers.Serializer):
"""创建或绑定QQ对应的本站用户"""
access_token = serializers.CharField(label='操作凭证')
mobile = serializers.RegexField(label='手机号', regex=r'^1[3-9]\d{9}$')
password = serializers.CharField(label='密码', max_length=20, min_length=8)
sms_code = serializers.CharField(label='短信验证码') def validate(self, data):
# 检验access_token
access_token = data['access_token']
openid = OAuthQQ.check_token_by_openid(access_token)
if not openid:
raise serializers.ValidationError('access_token失效')
# 检验短信验证码
mobile = data['mobile']
sms_code = data['sms_code']
redis_conn = get_redis_connection('verify_codes')
real_sms_code = redis_conn.get('sms_%s' % mobile).decode()
if not real_sms_code:
raise serializers.ValidationError('短信验证码失效或过期') if real_sms_code != sms_code:
raise serializers.ValidationError('短信验证码错误') # 如果用户存在,检查用户密码
try:
user = User.objects.get(mobile=mobile)
except Exception as e:
logger.error('本站用户不存在,等待注册---%s' % e)
pass
else:
# 如果存在就校验密码
password = data['password']
if not user.check_password(password):
raise serializers.ValidationError('密码错误')
data['user'] = user data['openid'] = openid
return data def create(self, validated_data):
user = validated_data.get('user', None)
if not user:
# 用户不存在,先注册本站新用户
user = User.objects.create_user(
username=validated_data['mobile'],
password=validated_data['password'],
mobile=validated_data['mobile'],
)
# 新老用户都绑定QQ的openid
OAuthQQUser.objects.create(
openid=validated_data['openid'],
user=user
)
return user

DRF框架QQ登录功能的更多相关文章

  1. 网站集成QQ登录功能

    最近在做一个项目时,客户要求网站能够集成QQ登录的功能,以前没做过这方面的开发,于是去QQ的开放平台官网研究了一下相关资料,经过自己的艰苦探索,终于实现了集成QQ登录的功能,现在把相关的开发经验总结一 ...

  2. 网站集成QQ登录功能(转)

    最近在做一个项目时,客户要求网站能够集成QQ登录的功能,以前没做过这方面的开发,于是去QQ的开放平台官网研究了一下相关资料,经过自己的艰苦探索,终于实现了集成QQ登录的功能,现在把相关的开发经验总结一 ...

  3. QQ登录功能之如何获取用于本地测试的APPID

    本文主要说明一下开发者如何在QQ互联创建测试应用,从而分配给我们一套APP ID和APP KEY,在我们平时学习的时候使用. 一.QQ互联注册开发者 要想使用QQ登陆的功能,首先你必须是腾讯开发者.腾 ...

  4. DRF框架中分页功能接口

    目录 DRF框架中分页功能接口 DRF框架中分页功能接口 一.在框架中提供来三个类来实现分页功能,PageNumberPagination.LimitOffsetPagination.CursorPa ...

  5. React Native 接入微博、微信、QQ 登录功能

    在 App 开发中我们经常需要在用户登录模块接入 SNS 登录组件,这样会大大提高用户的注册体验.特别当一个不是刚性需求 App 推广的时候,这样会很大的降低用户体验的成本,没有人愿意忍受输入邮箱.手 ...

  6. jeecms框架单点登录功能的实现

    单点登录的功能实现主要原理: 1: 在点击登录按钮的时候使用reponse.addCookie()方法向浏览器发送cookie: 2: 在前段拦截器中的request.getCookie()在接收到设 ...

  7. 接入qq登录功能出现的问题

    在调用qq授权的接口时,出现以上错误. 原因是: 打包的应用签名和第一次上传包的签名不一致造成的 解决方法: 第一种方法:用上次打包apk的keystore重新打包apk,使签名一致. 第二种方法:联 ...

  8. 安卓应用使用QQ登录的申请流程

    “QQ互联”是腾讯为第三方网站.媒体.终端提供的开放平台.QQ互联拥有8个组件,提供诸如分享.登陆.like.qq提醒等能力.开发者使用QQ帐号登陆组件可以降低了用户的注册门槛,减少注册环节的用户流失 ...

  9. h5 网页版的微博微信QQ登录

    一:微博 1,先说微博吧,首先你的去http://open.weibo.com/wiki/先注册账号,通过验证审核.然后的创建网页应用.微博审核不通过的原因就是域名和网站地址,一定要按实际写的.一定要 ...

随机推荐

  1. 【转】学习Linux守护进程详细笔记

    [原文]https://www.toutiao.com/i6566814959966093837/ Linux守护进程 一. 守护进程概述 守护进程,也就是通常所说的Daemon进程,是Linux中的 ...

  2. 读高性能JavaScript编程 第三章

    第三章  DOM Scripting  最小化 DOM 访问,在 JavaScript 端做尽可能多的事情. 在反复访问的地方使用局部变量存放 DOM 引用. 小心地处理 HTML 集合,因为他们表现 ...

  3. NHibernate出现could not execute query问题

    今天在调试代码时工程总报错,提示could not execute query xxxxxxxxxxxxxxxxxxxxxxxxxxx 找了很久,最终同事发现是数据库连接配置文件的问题. <hi ...

  4. DevExpress12、DocumentManager

    DocumentManager控件 你用过Photoshop吗?里面每打开一个照片,就有一个小窗体承载这个照片,你可以在这些小窗体间切换,最小化.最大化.排列窗体, 这些操作都在Photoshop的大 ...

  5. Balanced Search Trees

    平衡搜索树 前面介绍的二叉搜索树在最坏情况下的性能还是很糟糕,而且我们不能控制操作的顺序,有时根本就不是随机的,我们希望找到有更好性能保证的算法. 2-3 search trees 于是先来了解下 2 ...

  6. mapreduce设置setMapOutputKeyClass与setMapOutputValueClass原因

    一般的mapreduce的wordcount程序如下: public class WcMapper extends Mapper<LongWritable, Text, Text, LongWr ...

  7. node.js cheerio API

    安装 npm install cheerio load var cheerio = require('cheerio'), $ = cheerio.load('<ul id=“fruits”&g ...

  8. [luogu3980] 志愿者招募

    题面 ​ 又一次考试网络流爆零...... ​ 这一题一看就是网络流, 但是要怎么构图呢? 考虑到途中的一些因素, 首先, 每一种志愿者控制的区间范围为\(S_{i}\)到\(T_{i}\), 所以, ...

  9. <数据结构与算法分析>读书笔记--函数对象

    关于函数对象,百度百科对它是这样定义的: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.又称仿函数. 听起来确实很难懂,通过搜索我找到一篇 ...

  10. leetcode367--Valid Perfect Square

    Given a positive integer num, write a function which returns True if num is a perfect square else Fa ...