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

第三方登陆(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设置了过期时间,时间过长即失效。。。

你可能感兴趣的:(2018-07-06(第三方登陆(QQ)))