QQ第三方登录(itsdangerous生成激活token)

一.成为QQ互联的开发者

注册链接:http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85

等个3-5 天的工作日审核,审核通过就称为一个开发者了

二.创建应用

在应用管理中创建一个应用,得到APPID

填写相关资料,审核通过得到APPKEY,记录之前的回调地址

三.项目实现

浏览器--> 服务器 -- > QQ服务器

1.浏览器用Vue的axios方法,GET请求服务器获取登录QQ服务器的网址(浏览器-->服务器)

2.服务器拼接url路径返回给浏览器(服务器-->浏览器)

3.浏览器用response接收到的url路径访问QQ服务器。(浏览器-->QQ服务器)

4.QQ服务器返回code值,浏览器截取code值返回给服务器(QQ服务器-->浏览器,浏览器-->服务器)

5.服务器获取浏览器发送过来的code值,拼接字符串重新访问QQ服务器(服务器-->QQ服务器)

6.QQ服务器返回access_token给服务器(QQ服务器-->服务器)

7.服务器获取响应中的access_token值,拼接字符串给QQ服务器(服务器-->QQ服务器)

8.QQ服务器返回openid(用户唯一身份标识)给服务器(QQ服务器-->服务器)

QQ登录开发文档链接:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

oauth2.0_guid_3.png

前端代码放置QQ按钮,访问服务器

HTML

 <div class="third_party">
        <a @click="qq_login" class="qq_login">QQ</a>
        <a href="#" class="weixin_login">微信</a>
        <a href="/register.html" class="register_btn">立即注册</a>
 </div>

JS

qq_login: function(){
            var state = this.get_query_string('next') || '/';
            axios.get(this.host + '/oauth/qq/statues/?state=' + state, {
                    responseType: 'json'
                })
                .then(response => {
                    location.href = response.data.auth_url;
                })
                .catch(error => {
                    console.log(error.response.data);
                })
        },

通过前端axios请求GET方法访问服务器,服务器返回拼接好的URL

class QQAuthURLView(APIView):
    """
    请求方式: GET /oauth/qq/statues/
    """

    def get(self, request):
        # 生成auth_url
        # https://graph.qq.com/oauth2.0/authorize?xxx=xxx
        # 请求参数请包含如下内容:
        # response_type   必须      授权类型,此值固定为“code”。
        # client_id       必须      申请QQ登录成功后,分配给应用的appid。
        # redirect_uri    必须      成功授权后的回调地址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode。
        # state           必须      client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。
        # scope              可选      scope=get_user_info
        state = request.query_params.get('state')
        # 1. base_url
        # GET www.baidu.com/a.html?a=xxx&b=xxx
        # ? 是为了将路径和参数进行分割
        base_url = 'https://graph.qq.com/oauth2.0/authorize?'

        # 2. 将参数放在字典中
        params = {
            'response_type': 'code',
            'client_id': settings.QQ_APP_ID,
            'redirect_uri': settings.QQ_REDIRECT_URL,
            'state': 'test',
        }

        # 3.
        # 将query字典转换为url路径中的查询字符串
        auth_url = base_url + urlencode(params)
        return Response({'auth_url': auth_url})

前端根据返回的auth_url的response访问QQ服务器,服务器返回code给回调函数页面,前端获取code并将其传给服务器

JS

mounted: function(){
         // 从路径中获取qq重定向返回的code
        var code = this.get_query_string('code');
        axios.get(this.host + '/oauth/qq/users/?code=' + code, {
                responseType: 'json',
            })
            .then(response => {

            })
            .catch(error => {
                console.log(error.response.data);
                alert('服务器异常');
            })
    },

服务器接收到code,拼接字符串用urlopen访问QQ服务器

class QQTokenView(APIView):
    def get(self, request):
        # PC网站:https://graph.qq.com/oauth2.0/token
        # GET
        # grant_type      必须      授权类型,在本步骤中,此值为“authorization_code”。
        # client_id       必须      申请QQ登录成功后,分配给网站的appid。
        # client_secret   必须      申请QQ登录成功后,分配给网站的appkey。
        # code            必须      上一步返回的authorization
        # redirect_uri    必须      与上面一步中传入的redirect_uri保持一致。
        code = request.query_params.get('code')
        if code is None:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        # PC网站:https://graph.qq.com/oauth2.0/token
        # GET
        # grant_type      必须      授权类型,在本步骤中,此值为“authorization_code”。
        # client_id       必须      申请QQ登录成功后,分配给网站的appid。
        # client_secret   必须      申请QQ登录成功后,分配给网站的appkey。
        # code            必须      上一步返回的authorization
        # redirect_uri    必须      与上面一步中传入的redirect_uri保持一致。
        base_url = 'https://graph.qq.com/oauth2.0/token?'
        params = {
            'grant_type': 'authorization_code',
            'client_id': settings.QQ_APP_ID,
            'client_secret': settings.QQ_APP_KEY,
            'code': code,
            'redirect_uri': settings.QQ_REDIRECT_URL,
        }
        url = base_url + urlencode(params)
        response = urlopen(url)
        data = response.read().decode()
        # print(data)
        acecess_data = parse_qs(data)
        token = acecess_data.get('access_token')[0]
        print(token)
        return token

获取到QQ服务器返回的access_token值

服务器在有access_token拼接字符串访问QQ服务器得到openid

def get_openid_by_token(self, token):
        """
        PC网站:https://graph.qq.com/oauth2.0/me
        2 请求方法
        GET
        3 请求参数
        请求参数请包含如下内容:
        参数	是否必须	含义
        access_token	必须	在Step1中获取到的access token。

        """

        # 1. base_url
        base_url = 'https://graph.qq.com/oauth2.0/me?'
        # 2. 参数
        params = {
            'access_token': token
        }
        # 3. url
        url = base_url + urlencode(params)
        # 4. 根据url获取数据
        response = urlopen(url)

        data = response.read().decode()

        # print(data)
        # 5. 解析数据
        # 因为它返回的数据 不是 字典类型,我们要想获取 字典数据,需要对这个字符串进行截取
        # 'callback( {"client_id":"101474184","openid":"483C55DADEF65CC5735695CBC262F979"} );'
        try:
            openid_data = json.loads(data[10:-4])
        except Exception:
            raise Exception('数据获取错误')

        # print(openid_data)

        return openid_data['openid']

在视图函数中添加:

        try:
            qq_user = OAuthQQuser.objects.get(openid=openid)
        except OAuthQQuser.DoesNotExist:
            # 2.没有绑定过需要将openid和user信息绑定
            access_token = OAuthQQuser.generic_token_by_openid(openid)
            return Response({'access_token': access_token})

由于服务器需要向openid值,但openid是非常重要的信息,不能泄露,所以需要使用itsdangerous生成激活token

1.安装:pip install itsdangerous

2.生成用户激活token的方法封装在OAuthQQUser模型类中

  • Serializer()生成序列化器,传入混淆字符串和过期时间
  • dumps()生成openid加密后的token,传入封装openid的字典
  • 返回token字符串
  • loads()解出token字符串,得到用户id明文

class OAuthQQUser(BaseModel):
    """
    QQ登录用户数据
    """
   ...

    @staticmethod
    def generate_save_user_token(openid):

        serializer = Serializer(settings.SECRET_KEY, expires_in=3600)

        token = serializer.dumps({'openid': openid})

        return token.decode()
    
    @staticmethod
    def openid_by_token(access_token):
        serializer = Serializer(settings.SECRET_KEY, 3600)
        try:
            result = serializer.loads(access_token)
        except BadData:
            return None
        return result.get('openid')

然后开始流程的下半部分:

接上面第8步后

9.首先判断用户是不是第一次使用QQ登录,如果不是,返回用户信息和token值,如果是,生成一个token值,返回前端

# 1.根据openid来判断用户是否存在
        try:
            qq_user = OAuthQQuser.objects.get(openid=openid)
        except OAuthQQuser.DoesNotExist:
            # 2.没有绑定过需要将openid和user信息绑定
            access_token = OAuthQQuser.generic_token_by_openid(openid)
            return Response({'access_token': access_token})
        else:
            # 3.绑定过,直接返回登录的token
            from rest_framework_jwt.settings import api_settings
            # 补充生成记录登录状态的token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
            payload = jwt_payload_handler(qq_user.user)
            token = jwt_encode_handler(payload)
            return Response({
                'token': token,
                'user_id': qq_user.user.id,
                'username': qq_user.user.username,
            })

10.如果是第一次使用QQ登录,需要绑定用户信息,进入绑定用户信息页面

前端代码:

mounted: function(){
        // 从路径中获取qq重定向返回的code
        var code = this.get_query_string('code');
        axios.get(this.host + '/oauth/qq/users/?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;

                    // 从路径中取出state,引导用户进入登录成功之后的页面
                    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('服务器异常');
            })
    },

这里可以看出,location.href = state;前面获取code传到前端的state即是成功绑定信息的返回路径

业务逻辑:

  • 用户需要填写手机号、密码、图片验证码、短信验证码
  • 如果用户未在美多商城注册过,则会将手机号作为用户名为用户创建一个美多账户,并绑定用户
  • 如果用户已在美多商城注册过,则检验密码后直接绑定用户

后端接口设计

请求方式: POST /oauth/qq/user/

请求参数:

参数类型是否必须说明
mobilestr手机号
passwordstr密码
sms_codestr短信验证码
access_tokenstr凭据 (包含openid)

返回数据:

返回值类型是否必须说明
tokenstrJWT token
user_idint用户id
usernamestr用户名
def post(self,request):
        """   明确你的需求,分析已知条件, 根据已知条件,创建实现的步骤
        1. 前段应该将 短信,密码和手机号 以及 access_token(openid)的信息 传递给我们
        2. 后端接受到数据之后,对数据进行校验
        3.  user信息??? 我们根据手机号来判断
        4. 我们需要将 openid 和 user信息保存(绑定)起来
        """
        serializer = QQTokenSerializer(data=request.data)
        serializer.is_valid()
        user = serializer.save()
        from rest_framework_jwt.settings import api_settings
        # 补充生成记录登录状态的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,
        })

用一个序列化器去判断前端传过来的页面,最后返回给前端token值,user_id,username

11.判断字段

def validate(self, attrs):
        access_token = attrs.get('access_token')
        openid = OAuthQQuser.openid_by_token(access_token)
        if openid is None:
            raise serializers.ValidationError('openid发生错误')
        attrs['openid'] = openid

        mobile = attrs['mobile']
        redis_conn = get_redis_connection('code')
        redis_code = redis_conn.get('sms_%s' % mobile)
        if redis_code is None:
            raise serializers.ValidationError('短信验证码已过期')
        sms_code = attrs.get('sms_code')
        if redis_code.decode() != sms_code:
            raise serializers.ValidationError('短信验证码不一致')

        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            pass
        else:
            password = attrs.get('password')
            if not user.check_password(password):
                raise serializers.ValidationError('密码输入错误')
            attrs['user'] = user
        return attrs

1.根据openid判断用户是否绑定过

2.根据mobile和sms_code判断短信验证码是否正确

3.根据mobile判断用户是否已经注册

4.根据password判断已经注册的用户密码是否正确

5.如果没有注册,需要创建一个新用户绑定,重写create方法

def create(self, validated_data):
        # 1. 获取用户信息
        user = validated_data.get('user')
        # 2. 判断用户信息是否存在
        if user is None:
            # 不存在就创建
            user = User.objects.create(
                username=validated_data.get('mobile'),
                password=validated_data.get('password'),
                mobile=validated_data.get('mobile')
            )

            # 密码还是明文
            user.set_password(validated_data['password'])
            user.save()

        OAuthQQuser.objects.create(
            user=user,
            openid=validated_data.get('openid')
        )

        return user

补全前端代码:

 on_submit: function(){
            this.check_pwd();
            this.check_phone();
            this.check_sms_code();

            if(this.error_password == false && this.error_phone == false && this.error_sms_code == false) {
                axios.post(this.host + '/oauth/qq/users/', {
                        password: this.password,
                        mobile: this.mobile,
                        sms_code: this.sms_code,
                        access_token: this.access_token
                    }, {
                        responseType: 'json',
                    })
                    .then(response => {
                        // 记录用户登录状态

                        sessionStorage.clear();
                        localStorage.clear();
                        localStorage.token = response.data.token;
                        localStorage.user_id = response.data.user_id;
                        localStorage.username = response.data.username;
                        location.href = this.get_query_string('state');
                    })
                    .catch(error=> {
                        if (error.response.status == 400) {
                            this.error_sms_code_message = error.response.data.message;
                            this.error_sms_code = true;
                        } else {
                            console.log(error.response.data);
                        }
                    })
            }
        }

使用postman测试时,由于无法保存token信息,需要在请求头加入Authorization:JWT空格+token

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Flask 中实现 QQ 第三方登录,可以使用 Flask-OAuthlib 扩展来完成。以下是一个简单的示例: 1. 安装 Flask-OAuthlib: ```shell pip install Flask-OAuthlib ``` 2. 在 QQ 互联开放平台中创建应用并获取 App ID 和 App Key。 3. 在 Flask 应用中配置 OAuth: ```python from flask import Flask, url_for, request, redirect, session from flask_oauthlib.client import OAuth app = Flask(__name__) app.secret_key = 'secret_key' oauth = OAuth(app) qq = oauth.remote_app( 'qq', consumer_key='your-app-id', consumer_secret='your-app-key', request_token_params={ 'scope': 'get_user_info', 'response_type': 'code' }, base_url='https://graph.qq.com', request_token_url=None, access_token_method='POST', access_token_url='/oauth2.0/token', authorize_url='/oauth2.0/authorize', ) @app.route('/') def index(): if 'qq_token' in session: resp = qq.get('/oauth2.0/me') openid = resp.data.decode().split('"')[1] return 'Hello, {0}! Your QQ OpenID is {1}.'.format(session['qq_username'], openid) else: return redirect(url_for('login')) @app.route('/login') def login(): callback_url = url_for('authorized', _external=True) return qq.authorize(callback=callback_url) @app.route('/authorized') def authorized(): resp = qq.authorized_response() if resp is None: return 'Access denied: reason={0} error={1}'.format( request.args['error_reason'], request.args['error_description'] ) session['qq_token'] = (resp['access_token'], '') resp = qq.get('/oauth2.0/me') openid = resp.data.decode().split('"')[1] resp = qq.get('/user/get_user_info', data={'openid': openid}) session['qq_username'] = resp.data.decode()['nickname'] return redirect(url_for('index')) if __name__ == '__main__': app.run() ``` 4. 在 QQ 互联开放平台中将回调 URL 设置为 `http://localhost:5000/authorized`。 5. 运行 Flask 应用并访问 `http://localhost:5000/login` 即可开始 QQ 登录流程。 以上是一个简单的 Flask-OAuthlib 示例,你可以根据实际需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值