软件开发之实现QQ登录
问题:为什么实现QQ登录?
QQ登录:即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目。
-
QQ互联开发者申请
若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现
- 相关连接:QQ互联开发者申请
-
QQ互联应用申请
成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。
- 相关连接:http://wiki.connect.qq.com/__trashed-2
-
网站对接QQ登录
QQ互联提供有开发文档,帮助开发者实现QQ登录。
-
QQ登录流程
-
定义QQ登录模型类
QQ登录成功后,我们需要将QQ用户和美多商场用户关联到一起,方便下次QQ登录时使用,所以我们选择使用MySQL数据库进行存储。
# 定义模型类基类 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 # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表 # 定义QQ登录模型类 from django.db import models from meiduo_mall.utils.models import BaseModel # Create your models here.s class OAuthQQUser(BaseModel): """QQ登录用户数据""" user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户') 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
-
迁移QQ登录模型类
python manage.py makemigrations python manage.py migrate
-
QQ登录工具QQLoginTool
-
介绍:该工具封装了QQ登录时对接QQ互联接口的请求操作。可用于快速实现QQ登录。
-
安装:pip install QQLoginTool
-
使用:
# 1.导入 from QQLoginTool.QQtool import OAuthQQ # 2.初始化 oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=next) # 3.获取QQ登录扫码页面,扫码后得到Authorization Code login_url = oauth.get_qq_url() # 4.通过Authorization Code获取Access Token access_token = oauth.get_access_token(code) # 5.通过Access Token获取OpenID openid = oauth.get_open_id(access_token)
-
-
具体实现OAuth2.0认证获取openid
-
获取QQ登录扫码页面
class QQAuthURLView(View): """提供QQ登录页面网址""" def get(self, request): # next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面 next = request.GET.get('next') # 初始化 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 http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url':login_url})
-
QQ登录扫码后,待处理的业务逻辑
# 提取code请求参数 # 使用code向QQ服务器请求access_token # 使用access_token向QQ服务器请求openid # 使用openid查询该QQ用户是否在美多商城中绑定过用户 # 如果openid已绑定美多商城用户,直接登入到美多商城系统 # 如果openid没绑定美多商城用户,创建用户并绑定到openid
-
QQ登录扫码后具体实现
class QQAuthUserView(View): """用户扫码登录的回调处理""" def get(self, request): """Oauth2.0认证""" # 提取code请求参数 code = request.GET.get('code') if not code: return http.HttpResponseForbidden('缺少code') # 创建工具对象 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: logger.error('向QQ服务器请求openid失败') return http.HttpResponseServerError('OAuth2.0认证失败') try: oauth_user = OAuthQQUser.objects.get(openid=openid) except OAuthQQUser.DoesNotExist: # 如果openid没绑定美多商城用户 access_token_openid = generate_access_token(openid) context = {'access_token_openid': access_token_openid} return render(request, 'oauth_callback.html', context) else: # 如果openid已绑定美多商城用户 # 实现状态保持 qq_user = oauth_user.user login(request, qq_user) # 响应结果 next = request.GET.get('state') response = redirect(next) # 登录时用户名写入到cookie,有效期15天 response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15) # 若用户登录成功,合并cookie中的购物车 merge_carts_cookies_redis(request=request, user=qq_user, response=response) return response
-
用户未绑定QQ的回调处理
def post(self, request): """美多商城用户绑定到openid""" # 接收参数 mobile = request.POST.get('mobile') password = request.POST.get('password') sms_code_client = request.POST.get('sms_code') access_token_openid = request.POST.get('access_token_openid') # 校验参数 # 判断参数是否齐全 if not all([mobile, password, sms_code_client]): return http.HttpResponseForbidden('缺少必传参数') # 判断手机号是否合法 if not re.match(r'^1[3-9]\d{9}$', mobile): return http.HttpResponseForbidden('请输入正确的手机号码') # 判断密码是否合格 if not re.match(r'^[0-9A-Za-z]{8,20}$', password): return http.HttpResponseForbidden('请输入8-20位的密码') # 判断短信验证码是否一致 redis_conn = get_redis_connection('verify_code') sms_code_server = redis_conn.get('sms_%s' % mobile) if sms_code_server is None: return render(request, 'oauth_callback.html', {'sms_code_errmsg': '无效的短信验证码'}) if sms_code_client != sms_code_server.decode(): return render(request, 'oauth_callback.html', {'sms_code_errmsg': '输入短信验证码有误'}) # 判断openid是否有效:错误提示放在sms_code_errmsg位置 openid = check_access_token(access_token_openid) if not openid: return render(request, 'oauth_callback.html', {'openid_errmsg': '无效的openid'}) # 保存注册数据 try: user = User.objects.get(mobile=mobile) except User.DoesNotExist: # 用户不存在,新建用户 user = User.objects.create_user(username='mobile' + mobile, password=password, mobile=mobile) else: # 如果用户存在,检查用户密码 if not user.check_password(password): return render(request, 'oauth_callback.html', {'account_errmsg': '用户名或密码错误'}) # 将用户绑定openid try: OAuthQQUser.objects.create(openid=openid, user=user) except DatabaseError: return render(request, 'oauth_callback.html', {'qq_login_errmsg': 'QQ登录失败'}) # 实现状态保持 login(request, user) # 响应绑定结果 next = request.GET.get('state') response = redirect(next) # 登录时用户名写入到cookie,有效期15天 response.set_cookie('username', user.username, max_age=3600 * 24 * 15) # 若用户登录成功,合并cookie中的购物车 merge_carts_cookies_redis(request=request, user=user, response=response) return response
-
openid签名处理
def generate_eccess_token(openid): """ 签名openid :param openid: 用户的openid :return: access_token """ serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES) data = {'openid': openid} token = serializer.dumps(data) return token.decode() def check_access_token(access_token_openid): """ 反解、反序列化access_token_openid :param access_token_openid: openid密文 :return: openid明文 """ # 创建序列化器对象:序列化和反序列化的对象的参数必须是一模一样的 s = Serialzier(settings.SECRET_KEY, constants.ACCESS_TOKEN_EXPIRES) # 反序列化openid密文 try: data = s.loads(access_token_openid) except BadData: # openid密文过期 return None else: # 返回openid明文 return data.get('openid')
-
-
注意:该代码冗余严重,具体开发当中可以多使用函数封装,以及优化代码。