第三方登陆(QQ)
QQ互联开放平台为第三方网站提供了丰富的API。第三方网站接入QQ互联开放平台后,即可通过调用平台提供的API实现用户使用QQ帐号登录网站功能,且可以获取到腾讯QQ用户的相关信息。--QQ互联
登陆流程
1、在需要登陆的地方放置QQ登陆按钮
这个在QQ互联提供的文档中获取
2、点击QQ登陆按钮发送请求获取授权页面
前端按钮绑定的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中的地址跳转至特定页面,后面会用到):
添加辅助类:
class OAuthQQ(object):
# 对openid进行加解密的安全密钥
SECRET_KEY = settings.SECRET_KEY
# 对openid加密之后生成的access_token的有效时间
EXPIRES_IN = 10 * 60
def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None):
# QQ网站应用客户端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网站应用客户端安全密钥
self.client_secret = client_secret or settings.QQ_CLIENT_SECRET
# 网站回调url网址
self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
self.state = state or settings.QQ_STATE
def get_login_url(self):
"""
获取QQ的登录网址:
"""
# 组织参数
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
这个类可以拼接符合文档要求的url地址,调用类中的get_login_url方法可返回地址,setting在配置项中配置。
后端视图:
# GET /oauth/qq/authorization/?next=xxx
class QQAuthURLView(APIView):
"""
QQ登录的网址:
"""
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})
后端视图调用后前端可接收地址跳转至认证页面。
3、认证通过后,将跳转至回调页面,并在redirect_uri地址后带上Authorization Code和原始的state值。如:PC网站:http://graph.qq.com/demo/index.jsp?code=9A5F************************06AF&state=test。此时在页面对应的前端回调页面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('服务器异常');
})
},
回调页面会发送请求至API GET /oauth/qq/user/?code=xxx
在辅助类中继续添加如下方法:
def get_access_token(self, code):
"""
获取到code后拼接地址得到授权令牌,Access_Token
"""
# 组织参数
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))
# 返回数据格式如下:
# access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
# 获取响应数据并解码
res_data = response.read().decode()
# 转化成字典
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授权用户的openid:
access_token: QQ返回的access_token
"""
# 拼接url地址
url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token
try:
# 访问获取QQ授权用户的openid
response = urlopen(url)
except Exception as e:
raise QQAPIError(str(e))
# 返回数据格式如下:
# 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进行加密:
openid: QQ授权用户的openid
secret_key: 密钥
expires: token有效时间
"""
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、导入模块
from itsdangerous import TimedJSONWebSignatureSerializer
2、创建对象
serializer = TimedJSONWebSignatureSerializer(secret_key=secret_key密码, expire_time解密的有效时间))
3、加密数据,返回bytes类型
res_data=serializer.dumps(要加密的数据)
4、解密数据
res_data_=serializer.loads(res_data)
注意:加密和解密时需要密码一致,否则无法解密。
GET /oauth/qq/user/?code=xxx对应的视图:
class QQAuthUserView(APIView):
def get(self, request):
# 1. 获取QQ返回的code
code = request.query_params.get('code')
try:
# 2. 根据code获取access_token
oauth = OAuthQQ()
access_token = oauth.get_access_token(code)
# 3. 根据access_token获取授权QQ用户的openid
openid = oauth.get_openid(access_token)
except QQAPIError as e:
logger.error(e)
return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
# 4. 根据`openid`查询tb_oatu_qq表,判断是否已经绑定账号
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 如果已经绑定,生成JWT token信息
# 补充生成记录登录状态的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调用辅助类的方法获取到OpenID,此时会查询数据库,发现已经有表中存在绑定OpenID的对象,说明此用户已经绑定qq号,签发JWT,返回用户信息,前端收到Response,会跳转至next中的回调页面;如果没有查询到,则加密返回OpenID,显示绑定账号标签。
下面即对账号的验证并绑定OpenID,OpenID设置了过期时间,时间过长即失效。。。