Django实现QQ登录

软件开发之实现QQ登录

问题:为什么实现QQ登录?

QQ登录:即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目。

  1. QQ互联开发者申请

    若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现

  2. QQ互联应用申请

    成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。

    • 相关连接:http://wiki.connect.qq.com/__trashed-2
  3. 网站对接QQ登录

    QQ互联提供有开发文档,帮助开发者实现QQ登录。

  4. QQ登录流程

在这里插入图片描述

  1. 定义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
    
  2. 迁移QQ登录模型类
    python manage.py makemigrations
    python manage.py migrate
    
  3. QQ登录工具QQLoginTool
    1. 介绍:该工具封装了QQ登录时对接QQ互联接口的请求操作。可用于快速实现QQ登录。

    2. 安装:pip install QQLoginTool

    3. 使用:

      # 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)
      
  4. 具体实现OAuth2.0认证获取openid
    1. 获取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})
      
    2. QQ登录扫码后,待处理的业务逻辑
      # 提取code请求参数
      # 使用code向QQ服务器请求access_token
      # 使用access_token向QQ服务器请求openid
      # 使用openid查询该QQ用户是否在美多商城中绑定过用户
      # 如果openid已绑定美多商城用户,直接登入到美多商城系统
      # 如果openid没绑定美多商城用户,创建用户并绑定到openid
      
    3. 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
      
    4. 用户未绑定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
      
    5. 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')
      
  5. 注意:该代码冗余严重,具体开发当中可以多使用函数封装,以及优化代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值