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设置了过期时间,时间过长即失效。。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容