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')