QQ登录
QQ登录,亦即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目。
若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现。注册方法可参考链接http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85
成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID,创建应用的方法参考链接http://wiki.connect.qq.com/__trashed-2
QQ登录开发文档连接http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0
使用QQ登录的流程
QQ登录的大致流程为:
1.用户在浏览器点击QQ登录按钮,向后端发送请求,后端服务器根据用户传来的查询字符串中?next=xxxx(用于指定登录后跳转的页面,如果用户是通过点击个人中心来到登录页面,则登录成功后跳回用户中心),生成QQ登录页面的url并返回。
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
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
=
'/'
获取code,access_token.opnid参见
QQ登录开发文档连接
http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0
代码:
视图:
class
QQAuthURLView(APIView):
def
get(self,request):
"""
提供用于qq登录的url
"""
next
= request.query_params.get(
"next")
oauth
= OAuthQQ(state
=
next)
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")