第三方登陸(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í)間過長即失效。。。