網站第三方登陸介紹(QQ互聯)

客戶端                                                              web server                                                                     QQ互聯

 

可直接使用QQ登錄工具QQLoginTool

pip install QQLoginTool

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. 接收Authorization Code,OAuth2.0認證獲取openid

class QQAuthUserView(View):
    """處理QQ登陸回調:oauth_callback"""

    def get(self, request):
        """處理QQ回調的業務邏輯"""

        # 獲取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獲取access_token
            access_token = oauth.get_access_token(code)

            # 使用access_token獲取openid
            openid = oauth.get_open_id(access_token)

        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('OAuth2.0認證失敗')
        # 使用openid判斷該QQ用戶是否綁定過商城的用戶
        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已綁定商城用戶,oauth_user.user表示從QQ登陸模型類對象中找到對應的用戶模型類對象
            login(request, oauth_user.user)

            # 重定向到state:從哪來,QQ登錄完之後回哪兒去
            next = request.GET.get('state')
            response = redirect(next)

            # 將用戶名寫入到cookie中
            response.set_cookie('username', oauth_user.user.username, max_age=3600)

            # 響應QQ登陸結果
            return response

    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, access_token_openid]):
            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', {'error_sms_code': '無效的短信驗證碼'})
        if sms_code_server.decode() != sms_code_client:
            return render(request, 'oauth_callback.html', {'error_sms_code': '輸入短信驗證碼有誤'})
        # 判斷openid是否有效
        # openid使用itsdangerous簽名之後只有600秒的有效期
        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, password=password, mobile=mobile)
        else:
            # 如果手機號用戶存在,需要校驗密碼
            if not user.check_password(password):
                return render(request, 'oauth_callback.html', {'account_errmsg': '賬號或密碼錯誤'})

        # 將新建及已存在用戶綁定到openid
        #     oauth_qq_user = OAuthQQUser(user=user, openid=openid)
        #     oauth_qq_user.save()
        try:
            oauth_qq_user = OAuthQQUser.objects.create(user=user, openid=openid)
        except Exception as e:
            logger.error(e)
            return render(request, 'oauth_callback.html', {'qq_login_errmsg': 'QQ登錄失敗'})
        # 實現狀態保持
        login(request, user)
        # 重定向到state
        next = request.GET.get('state')
        response = redirect(next)

        # 將用戶名寫入到cookie中
        response.set_cookie('username', oauth_qq_user.user.username, max_age=3600)

        # 響應QQ登陸結果
        return response

3. 演示

(1) 前端用戶點擊QQ登陸按鈕,生成了login_url

(2) 用戶進入掃碼頁面,掃碼後:

由於此時用戶QQ賬號未與商城內用戶綁定,因此定義序列化方法將openid以隱藏標籤的形式存儲在前端頁面

def generate_access_token(openid):
    """
    簽名、序列化openid
    :param openid: openid明文
    :return: token(openid密文)
    """
    # 創建序列化器對象
    # s = Serializer('密鑰: 越複雜越安全', '過期時間')
    s = Serializer(settings.SECRET_KEY, constants.ACCESS_TOKEN_EXPIRES)

    # 準備待序列化的字典數據
    data = {'openid': openid}

    # 調用dumps方法進行序列化: 類型是bytes
    token = s.dumps(data)

    # 返回序列化後的數據
    return token.decode()
<input type="hidden" name="access_token_openid" value="eyJhbGciOiJIUzUxMiIsImlhdCI6MTU4MzQxNDcwNywiZXhwIjoxNTgzNDE1MzA3fQ.eyJvcGVuaWQiOiI1ODU0MDQ3Q0Q3MjhGOEQ2N0IyMERERTdDMDhCOTcwRiJ9.OkbAjloC01h3oH3YmmbW4lhwR4oCHYqCFQdY7DM-5jH0WggxUQgkQkZoLV-U_Bo7pz0BJ8PzrOQb1wKYEHN4JA">

(3) 用戶開始綁定QQ賬號和商城內賬號

當用戶點擊綁定時,後端將前端暫存的加密後的openid再次解密。

並將openid作爲字段存儲在QQ用戶認證庫中。

隨後將重定向到next字段記錄的登陸前用戶希望訪問的頁面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章