QQ第三方登入
QQ登入
QQ登入,亦即我們所說的第三方登入,是指使用者可以不在本專案中輸入密碼,而直接通過第三方的驗證,成功登入本專案。
使用QQ登入的流程
QQ登入的大致流程為:
1.使用者在瀏覽器點選QQ登入按鈕,向後端傳送請求,後端伺服器根據使用者傳來的查詢字串中?next=xxxx(用於指定登入後跳轉的頁面,如果使用者是通過點選個人中心來到登入頁面,則登入成功後跳回使用者中心),生成QQ登入頁面的url並返回。
params = {
'response_type':'code',
'client_id':self.client_id,
'client_secret'
'redirect_uri':self.redirect_uri,
'state':self.state,
'scope':'get_user_info',
}
url = 'https://graph.qq.com/oauth2.0/authorize?'+ urlencode(params)
return url
2.前端得到qq登入頁面的url,訪問qq伺服器,qq會將使用者重定向到伺服器的callback地址,並攜帶者code和state,state是第一步中的next,即登入後跳轉的頁面。
3.前端將向伺服器傳送請求,伺服器獲取code值後,向qq伺服器傳送請求,獲取access_token,然後帶者access_token再向qq伺服器傳送請求獲取openid。得到openid後,在資料庫中查詢,是否已經繫結過帳號,如果有則簽名並登入。如果沒有,則返回加密後的openid到使用者繫結頁面。前端傳送post請求將個人資訊傳送給伺服器,伺服器後端建立使用者(如果已有則需要驗證密碼是否正確)並繫結qq。
附:
#QQ登入的引數
QQ_CLIENT_ID = 'xxxxxxx'
QQ_CLIENT_SECRET = 'xxxxx'
QQ_REDIRECT_URI = 'http://xxxxx:8080/oauth_callback.html'
QQ_STATE = '/'
程式碼:
檢視:
class QQAuthURLView(APIView):
def get(self,request):
"""
提供用於qq登入的url
"""
next = request.query_params.get("next")
oauth = OAuthQQ(state=
login_url = oauth.get_qq_login_url()
return Response({"login_url":login_url})
class QQAuthUserView(GenericAPIView):
"""
QQ登入的使用者
"""
serializer_class = OAuthQQUserSerializer
def get(self,request):
"""
獲取qq登入的使用者資料
"""
code = request.query_params.get('code')
if not code:
return Response({"message":"缺少code"},status=status.HTTP_400_BAD_REQUEST)
oauth = OAuthQQ()
#獲取openid
try:
access_token = oauth.get_access_token(code)[0]
openid = oauth.get_openid(access_token)
except Exception:
return Response({"message":"QQ伺服器錯誤"})
#判斷使用者是否存在
try:
qq_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
#使用者第一次登入
token = oauth.generate_sava_user_token(openid)
return Response({"access_token":token})
else:
#使用者不是第一次登入
user = qq_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)
return Response({
"token":token,
"user_id":user.id,
"username":user.username
})
def post(self,request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
# 生成已登入的token
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)
return Response({
'token':token,
'user_id':user.id,
'username':user.username
})
工具類:
class OAuthQQ(object):
"""
QQ認證工具類
"""
def __init__(self,client_id=None, client_secret=None,redirect_uri=None,state=None):
self.client_id = client_id or settings.QQ_CLIENT_ID
self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
self.state = state or settings.QQ_STATE#登入成功後跳轉的頁面
def get_qq_login_url(self):
"""
獲取QQ登入的網址,構造引數返回前端
:return:
"""
params = {
'response_type':'code',
'client_id':self.client_id,
'client_secret':self.client_secret,
'redirect_uri':self.redirect_uri,
'state':self.state,
'scope':'get_user_info',
}
url = 'https://graph.qq.com/oauth2.0/authorize?'+ urlencode(params)
return url
def get_access_token(self,code):
#獲取access_token
params = {
'grant_type':'authorization_code',
'client_id':self.client_id,
'client_secret':self.client_secret,
'redirect_uri':self.redirect_uri,
'code':code
}
#生成請求access_token的url
url = 'https://graph.qq.com/oauth2.0/token?'+urlencode(params)
#傳送請求
response = urlopen(url)
#返回來的值是二進位制,要進行解碼
response_data = response.read().decode()
#將查詢字串轉為字典
data = parse_qs(response_data)
#獲取access_token
access_token = data.get('access_token',None)
if access_token is None:
logger.error("code=%s msg=%s"%(data.get('code'), data.get('msg')))
raise QQAPIError
return access_token
def get_openid(self,access_token):
"""
獲取使用者的openid
:param access_token: qq提供的access_token
:return: open_id
"""
url = 'https://graph.qq.com/oauth2.0/me?access_token='+access_token
response = urlopen(url)
response_data = response.read().decode()
try:
# 返回的資料 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
data = json.loads(response_data[10:-4])
except Exception:
data = parse_qs(response_data)
logger.error("code=%s,msg=%s"%(data.get("code"),data.get('msg')))
raise QQAPIError
openid = data.get("openid")
return openid
@staticmethod
def generate_sava_user_token(openid):
"""
生成儲存使用者資料的token,加密
:param openid: 使用者的openid
:return: token
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
data = {
"openid":openid
}
token = serializer.dumps(data)
return token.decode()
@staticmethod
def check_save_user_token(token):
"""
檢驗儲存使用者資料的token
:param token: token
:return: openid or None
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
try:
data = serializer.loads(token)
except BadData:
return None
else:
return data.get("openid")