2018-07-06(第三方登陸(QQ))

第三方登陸(QQ)


QQ互聯(lián)開放平臺為第三方網(wǎng)站提供了豐富的API。第三方網(wǎng)站接入QQ互聯(lián)開放平臺后,即可通過調(diào)用平臺提供的API實(shí)現(xiàn)用戶使用QQ帳號登錄網(wǎng)站功能,且可以獲取到騰訊QQ用戶的相關(guān)信息。--QQ互聯(lián)

登陸流程

1、在需要登陸的地方放置QQ登陸按鈕

這個(gè)在QQ互聯(lián)提供的文檔中獲取

2、點(diǎn)擊QQ登陸按鈕發(fā)送請求獲取授權(quán)頁面

前端按鈕綁定的JS代碼:

// qq登錄
    qq_login: function(){
        var next = this.get_query_string('next') || '/';
        axios.get(this.host + '/oauth/qq/authorization/?next=' + next, {
                responseType: 'json'
            })
            .then(response => {
                location.href = response.data.login_url;
            })
            .catch(error => {
                console.log(error.response.data);
            })
    }

host的地址為后端api域名

后端視圖 GET /oauth/qq/authorization//?next=xxx(加next是為了登陸成功后獲取以next中的地址跳轉(zhuǎn)至特定頁面,后面會(huì)用到):

添加輔助類:

class OAuthQQ(object):
# 對openid進(jìn)行加解密的安全密鑰
SECRET_KEY = settings.SECRET_KEY
# 對openid加密之后生成的access_token的有效時(shí)間
EXPIRES_IN = 10 * 60

def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None):
    # QQ網(wǎng)站應(yīng)用客戶端id
    self.client_id = client_id or settings.QQ_CLIENT_ID
    # self.client_id = client_id if client_id else settings.QQ_CLIENT_ID
    # QQ網(wǎng)站應(yīng)用客戶端安全密鑰
    self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
    # 網(wǎng)站回調(diào)url網(wǎng)址
    self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
    self.state = state or settings.QQ_STATE

def get_login_url(self):
    """
    獲取QQ的登錄網(wǎng)址:
    """
    # 組織參數(shù)
    params = {
        'response_type': 'code',
        'client_id': self.client_id,
        'redirect_uri': self.redirect_uri,
        'state': self.state,
        'scope': 'get_user_info'
    }

    # 拼接url地址
    url = 'https://graph.qq.com/oauth2.0/authorize?' + urlencode(params)

    return url

這個(gè)類可以拼接符合文檔要求的url地址,調(diào)用類中的get_login_url方法可返回地址,setting在配置項(xiàng)中配置。

后端視圖:

# GET /oauth/qq/authorization/?next=xxx
class QQAuthURLView(APIView):
"""
QQ登錄的網(wǎng)址:
"""
def get(self, request):
    next = request.query_params.get('next', '/')

# 獲取QQ登錄地址,OAuthQQ為上面類
oauth = OAuthQQ(state=next)
login_url = oauth.get_login_url()

# 返回QQ登錄地址
return Response({'login_url': login_url})

后端視圖調(diào)用后前端可接收地址跳轉(zhuǎn)至認(rèn)證頁面。

3、認(rèn)證通過后,將跳轉(zhuǎn)至回調(diào)頁面,并在redirect_uri地址后帶上Authorization Code和原始的state值。如:PC網(wǎng)站:http://graph.qq.com/demo/index.jsp?code=9A5F************************06AF&state=test。此時(shí)在頁面對應(yīng)的前端回調(diào)頁面js添加如下代碼:

mounted: function(){
    // 從路徑中獲取qq重定向返回的code
    var code = this.get_query_string('code');
    axios.get(this.host + '/oauth/qq/user/?code=' + code, {
            responseType: 'json',
        })
        .then(response => {
            if (response.data.user_id){
                // 用戶已綁定
                sessionStorage.clear();
                localStorage.clear();
                localStorage.user_id = response.data.user_id;
                localStorage.username = response.data.username;
                localStorage.token = response.data.token;
                var state = this.get_query_string('state');
                location.href = state;
            } else {
                // 用戶未綁定
                this.access_token = response.data.access_token;
                this.generate_image_code();
                this.is_show_waiting = false;
            }
        })
        .catch(error => {
            console.log(error.response.data);
            alert('服務(wù)器異常');
        })
},

回調(diào)頁面會(huì)發(fā)送請求至API GET /oauth/qq/user/?code=xxx
在輔助類中繼續(xù)添加如下方法:

def get_access_token(self, code):
"""
獲取到code后拼接地址得到授權(quán)令牌,Access_Token
"""
# 組織參數(shù)
params = {
    'grant_type': 'authorization_code',
    'client_id': self.client_id,
    'client_secret': self.client_secret,
    'code': code,
    'redirect_uri': self.redirect_uri,
}

# 拼接url地址
url = 'https://graph.qq.com/oauth2.0/token?' + urlencode(params)
try:
    # 訪問獲取accesss_token
    response = urlopen(url)
except Exception as e:
    raise QQAPIError(str(e))

# 返回?cái)?shù)據(jù)格式如下:
# access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
# 獲取響應(yīng)數(shù)據(jù)并解碼
res_data = response.read().decode()
# 轉(zhuǎn)化成字典
res_dict = parse_qs(res_data)

# 嘗試從字典中獲取access_token
access_token = res_dict.get('access_token')

if not access_token:
    # 獲取access_token失敗
    raise QQAPIError(res_dict)

# 返回access_token
return access_token[0]

def get_openid(self, access_token):
    """
    獲取QQ授權(quán)用戶的openid:
    access_token: QQ返回的access_token
    """
    # 拼接url地址
    url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token

    try:
        # 訪問獲取QQ授權(quán)用戶的openid
        response = urlopen(url)
    except Exception as e:
        raise QQAPIError(str(e))

    # 返回?cái)?shù)據(jù)格式如下:
    # callback({"client_id": "YOUR_APPID", "openid": "YOUR_OPENID"});\n
    res_data = response.read().decode()
    try:
        res_dict = json.loads(res_data[10:-4])
    except Exception as e:
        res_dict = parse_qs(res_data)
        raise QQAPIError(res_dict)

    # 獲取openid
    openid = res_dict.get('openid')
    return openid

@classmethod
def generate_save_user_token(cls, openid, secret_key=None, expires=None):
    """
    對openid進(jìn)行加密:
    openid: QQ授權(quán)用戶的openid
    secret_key: 密鑰
    expires: token有效時(shí)間
    """
    if secret_key is None:
       secret_key = cls.SECRET_KEY

    if expires is None:
       expires = cls.EXPIRES_IN

    serializer = TJWSSerializer(secret_key, expires)

    token = serializer.dumps({'openid': openid})
    return token.decode()

itsdangerous模塊的使用:

1、導(dǎo)入模塊

from itsdangerous import TimedJSONWebSignatureSerializer

2、創(chuàng)建對象

serializer = TimedJSONWebSignatureSerializer(secret_key=secret_key密碼, expire_time解密的有效時(shí)間))

3、加密數(shù)據(jù),返回bytes類型

res_data=serializer.dumps(要加密的數(shù)據(jù))

4、解密數(shù)據(jù)

res_data_=serializer.loads(res_data)

注意:加密和解密時(shí)需要密碼一致,否則無法解密。

GET /oauth/qq/user/?code=xxx對應(yīng)的視圖:

class QQAuthUserView(APIView):
def get(self, request):
    # 1. 獲取QQ返回的code
    code = request.query_params.get('code')

    try:
        # 2. 根據(jù)code獲取access_token
        oauth = OAuthQQ()
        access_token = oauth.get_access_token(code)
        # 3. 根據(jù)access_token獲取授權(quán)QQ用戶的openid
        openid = oauth.get_openid(access_token)
    except QQAPIError as e:
        logger.error(e)
        return Response({'message': 'QQ服務(wù)異常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

    # 4. 根據(jù)`openid`查詢tb_oatu_qq表,判斷是否已經(jīng)綁定賬號
    try:
        oauth_user = OAuthQQUser.objects.get(openid=openid)
    except OAuthQQUser.DoesNotExist:
        # 4.2 如果未綁定,返回token
        token = oauth.generate_save_user_token(openid)
        return Response({'access_token': token})

    else:
        # 4.1 如果已經(jīng)綁定,生成JWT token信息
        # 補(bǔ)充生成記錄登錄狀態(tài)的token
        user = oauth_user.user
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)

        response = Response({
            'token': token,
            'user_id': user.id,
            'username': user.username
        })
        return response 

類視圖QQAuthUserView調(diào)用輔助類的方法獲取到OpenID,此時(shí)會(huì)查詢數(shù)據(jù)庫,發(fā)現(xiàn)已經(jīng)有表中存在綁定OpenID的對象,說明此用戶已經(jīng)綁定qq號,簽發(fā)JWT,返回用戶信息,前端收到Response,會(huì)跳轉(zhuǎn)至next中的回調(diào)頁面;如果沒有查詢到,則加密返回OpenID,顯示綁定賬號標(biāo)簽。

下面即對賬號的驗(yàn)證并綁定OpenID,OpenID設(shè)置了過期時(shí)間,時(shí)間過長即失效。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容