Django_Restful Framework之QQ登录API实现(二)

  上篇已经介绍了QQ第三方登录的流程分析和模型类的创建,并且也知道了再整个过程中我们需要提供哪些API为前端提供数据。

一、提供用户登录URL的API实现

  在上篇我们已经分析了当用户点击QQ登录按钮时,后端需要为前端提供进行QQ登录的URL,可能许多人会疑惑为什么不直接由前端处理URL,直接是由该URL进行进行如QQ登入界面?

  这是由于我们需要根据QQ开发者文档提供相应的地址,和查询字符串等。

  而由于第三方登录在其他的项目中,可能也会使用到第三方登录(QQ登录),所以我们需要考虑解耦的性能,这里我们定义了一个utils.py,用来存放 获取QQ登录URL、获取授权证书sccess_token、获取QQ用户的openid、以及生成绑定用户的token、和检测绑定用户的token。其代码如下:

  QQ登录的辅助工具类

from django.conf import settings
import urllib.parse
from urllib.request import urlopen
import logging
import json
from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer, BadData

from . import constants
from .exceptions import OAuthQQAPIError

logger = logging.getLogger(‘django‘)

class OAuthQQ(object):
    """
    QQ认证辅助工具类
    """
    def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None):
        self.client_id = client_id if client_id else settings.QQ_CLIENT_ID
        self.redirect_uri = redirect_uri if redirect_uri else settings.QQ_REDIRECT_URI
        # self.state = state if state else settings.QQ_STATE
        self.state = state or settings.QQ_STATE
        self.client_secret = client_secret if client_secret else settings.QQ_CLIENT_SECRET

    def get_login_url(self):
        url = ‘https://graph.qq.com/oauth2.0/authorize?‘
        params = {
            ‘response_type‘: ‘code‘,
            ‘client_id‘: self.client_id,
            ‘redirect_uri‘: self.redirect_uri,
            ‘state‘: self.state
        }

        url += urllib.parse.urlencode(params)
        return url

    def get_access_token(self, code):
        ‘‘‘获取授权证书‘‘‘
        url = ‘https://graph.qq.com/oauth2.0/token?‘

        params = {
            ‘grant_type‘: ‘authorization_code‘,
            ‘client_id‘: self.client_id,
            ‘client_secret‘: self.client_secret,
            ‘code‘: code,
            ‘redirect_uri‘: self.redirect_uri,
        }

        url += urllib.parse.urlencode(params)

        try:
            # 发送请求
            resp = urlopen(url)

            # 读取响应体数据
            resp_data = resp.read()  # bytes
            resp_data = resp_data.decode()  # str

            # access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14

            # 解析 access_token
            resp_dict = urllib.parse.parse_qs(resp_data)
        except Exception as e:
            logger.error(‘获取access_token异常:%s‘ % e)
            raise OAuthQQAPIError
        else:
            access_token = resp_dict.get(‘access_token‘)
            return access_token[0]

    def get_openid(self, access_token):
        ‘‘‘根据授权证书去获取用户的openid‘‘‘
        url = ‘https://graph.qq.com/oauth2.0/me?access_token=‘ + access_token

        try:
            # 发送请求
            resp = urlopen(url)

            # 读取响应体数据
            resp_data = resp.read()  # bytes
            resp_data = resp_data.decode()  # str

            # callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;

            # 解析
            resp_data = resp_data[10:-4]
            resp_dict = json.loads(resp_data)
        except Exception as e:
            logger.error(‘获取openid异常:%s‘ % e)
            raise OAuthQQAPIError
        else:
            openid = resp_dict.get(‘openid‘)

            return openid

    def generate_bind_user_access_token(self, openid):
        ‘‘‘根据openid生成进入绑定用户页面的token‘‘‘
        serializer = TJWSSerializer(settings.SECRET_KEY, constants.BIND_USER_ACCESS_TOKEN_EXPIRES)
        token = serializer.dumps({‘openid‘: openid})
        return token.decode()

    @staticmethod
    def check_bind_user_access_token(access_token):
        ‘‘‘检测用户携带进行绑定操作的token‘‘‘
        serializer = TJWSSerializer(settings.SECRET_KEY, constants.BIND_USER_ACCESS_TOKEN_EXPIRES)
        try:
            data = serializer.loads(access_token)
        except BadData:
            return None
        else:
            return data[‘openid‘]

  视图

#  url(r‘^qq/authorization/$‘, views.QQAuthURLView.as_view()),
class QQAuthURLView(APIView):
    """
    获取QQ登录的url    ?next=xxx
    """
    def get(self, request):
        # 获取next参数
        next = request.query_params.get("next")

        # 拼接QQ登录的网址
        oauth_qq = OAuthQQ(state=next)
        login_url = oauth_qq.get_login_url()

        # 返回
        return Response({‘login_url‘: login_url})

  这里,通过直接获取到进行QQ登录的URL,直接返回给前端。

二、QQ登录的回调处理的API实现

  从上篇的QQ登录流程分析,我们可以知道当用户进入QQ登入页面时,进行了QQ登入认证,随后QQ服务器会根据我们提供的回调地址将页面重定向到该页面,而在进入该页面时,我们需要进行一下验证:

  1. 用户是否第一次进行QQ登入,即是否在数据库中与本项目的账号进行了绑定。
  2. 若进行了绑定,我们直接让其从哪里来回哪里去,即重定向到其进入QQ登入之前页面的HTML页面中,即在state中保存的URI中。
  3. 若没有进行绑定,则为其生成access_token,跳转到与本项目的账号进行绑定的页面。

故其业务逻辑代码如下:

  视图:请求方式 : GET /oauth/qq/user/?code=xxx

class QQAuthUserView(APIView):
    """
    QQ登录的用户  ?code=xxxx
    """
    def get(self, request):
        # 获取code
        code = request.query_params.get(‘code‘)

        if not code:
            return Response({‘message‘: ‘缺少code‘}, status=status.HTTP_400_BAD_REQUEST)

        oauth_qq = OAuthQQ()
        try:
            # 凭借code 获取access_token
            access_token = oauth_qq.get_access_token(code)   # 上述的utils.py辅助工具类中方法

            # 凭借access_token获取 openid
            openid = oauth_qq.get_openid(access_token)
        except OAuthQQAPIError:
            return Response({‘message‘: ‘访问QQ接口异常‘}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 根据openid查询数据库OAuthQQUser  判断数据是否存在
        try:
            oauth_qq_user = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 如果数据不存在,处理openid 并返回
            access_token = oauth_qq.generate_bind_user_access_token(openid)
            return Response({‘access_token‘: access_token})

        else:
            # 如果数据存在,表示用户已经绑定过身份, 签发JWT token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

            user = oauth_qq_user.user
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)

            return Response({
                ‘username‘: user.username,
                ‘user_id‘: user.id,
                ‘token‘: token
            })

  注:这里返回给前端生成的access_token,是通过Django的一个扩展类实现的 ---》

 pip install itsdangerous

  至于为什么要生成 token,而不是直接返回openid?

主要是由于:

  当用户进行绑定时,若携带的openid过来进行绑定,我们无法知道用户携带的该openid是否为其之前进行QQ登录时,后端向QQ服务器获取的openid,若用户进行篡改成别的QQ用户的openid,那么我们进行绑定时,便会将别的QQ用户的openid与本项目的账号进行了绑定。

  那么有没有解决方法呢?就是利用itsdangerous包生成的jwt_token,这种生成的token分为三个部分:header、payload(存放用户的某些信息)、以及signature,而signature是由前两个者配合本项目的secret_key进行生成的,故当用户拿到token来进行验证时,服务器这边会将token的header和payload取出并且再配合 secret_key生成 signature②,与用户携带过来的signature进行对对,这样用户若修改了token我们也可以发现。

三、绑定用户身份接口

  业务逻辑分析:

  • 用户需要填写手机号、密码、图片验证码、短信验证码、并且携带token
  • 如果用户未在本项目注册过,则会将手机号作为用户名为用户创建一个本项目的账户,并绑定用户
  • 如果用户已在本项目注册过,则检验密码后直接绑定用户

  其流程图如下:

  其代码逻辑如下:

class QQAuthUserView(CreateAPIView):
    """
    QQ登录的用户  ?code=xxxx
    """
    serializer_class = OAuthQQUserSerializer

    def get(self, request):
        # 获取code
        code = request.query_params.get(‘code‘)

        if not code:
            return Response({‘message‘: ‘缺少code‘}, status=status.HTTP_400_BAD_REQUEST)

        oauth_qq = OAuthQQ()
        try:
            # 凭借code 获取access_token
            access_token = oauth_qq.get_access_token(code)

            # 凭借access_token获取 openid
            openid = oauth_qq.get_openid(access_token)
        except OAuthQQAPIError:
            return Response({‘message‘: ‘访问QQ接口异常‘}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 根据openid查询数据库OAuthQQUser  判断数据是否存在
        try:
            oauth_qq_user = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 如果数据不存在,处理openid 并返回
            access_token = oauth_qq.generate_bind_user_access_token(openid)
            return Response({‘access_token‘: access_token})

        else:
            # 如果数据存在,表示用户已经绑定过身份, 签发JWT token
            jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
            jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

            user = oauth_qq_user.user
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)

            return Response({
                ‘username‘: user.username,
                ‘user_id‘: user.id,
                ‘token‘: token
            })

  我们本可以定义一个视图post去实现,但是这里我们修改了上述的继承类 APIView 为 CreateAPIView,关于该类便不再详细的解析,它多继承了 CreateModelMixin 、GenericAPIView,只需要定义一个指明序列化器类即可,

serializer_class = OAuthQQUserSerializer

  而其内部提供了视图方法 -- post方法,这里不再复述。随后定义一个序列化器。

from django_redis import get_redis_connection
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings

from users.models import User
from .utils import OAuthQQ
from .models import OAuthQQUser

class OAuthQQUserSerializer(serializers.ModelSerializer):
    sms_code = serializers.CharField(label=‘短信验证码‘, write_only=True)
    access_token = serializers.CharField(label=‘操作凭证‘, write_only=True)
    token = serializers.CharField(read_only=True)
    mobile = serializers.RegexField(label=‘手机号‘, regex=r‘^1[3-9]\d{9}$‘)

    class Meta:
        model = User
        fields = (‘mobile‘, ‘password‘, ‘sms_code‘, ‘access_token‘, ‘id‘, ‘username‘, ‘token‘)
        extra_kwargs = {
            ‘username‘: {
                ‘read_only‘: True
            },
            ‘password‘: {
                ‘write_only‘: True,
                ‘min_length‘: 8,
                ‘max_length‘: 20,
                ‘error_messages‘: {
                    ‘min_length‘: ‘仅允许8-20个字符的密码‘,
                    ‘max_length‘: ‘仅允许8-20个字符的密码‘,
                }
            }
        }

    def validate(self, attrs):

        # 检验access_token
        access_token = attrs[‘access_token‘]

        openid = OAuthQQ.check_bind_user_access_token(access_token)

        if not openid:
            raise serializers.ValidationError(‘无效的access_token‘)

        attrs[‘openid‘] = openid

        # 检验短信验证码
        mobile = attrs[‘mobile‘]
        sms_code = attrs[‘sms_code‘]
        redis_conn = get_redis_connection(‘verify_codes‘)
        real_sms_code = redis_conn.get(‘sms_%s‘ % mobile)
        if real_sms_code.decode() != sms_code:
            raise serializers.ValidationError(‘短信验证码错误‘)

        # 如果用户存在,检查用户密码
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            pass
        else:
            password = attrs[‘password‘]
            if not user.check_password(password):
                raise serializers.ValidationError(‘密码错误‘)

            attrs[‘user‘] = user
        return attrs

    def create(self, validated_data):

        openid = validated_data[‘openid‘]
        user = validated_data.get(‘user‘)
        mobile = validated_data[‘mobile‘]
        password = validated_data[‘password‘]

        # 判断用户是否存在
        if not user:
            user = User.objects.create_user(username=mobile, mobile=mobile, password=password)

        OAuthQQUser.objects.create(user=user, openid=openid)

        # 签发JWT 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)

        user.token = token

        return user

  

  关于QQ第三方登录便介绍到此,关键不是代码,而是业务逻辑思维~~~~~~

原文地址:https://www.cnblogs.com/littlefivebolg/p/9772958.html

时间: 2024-08-29 23:20:16

Django_Restful Framework之QQ登录API实现(二)的相关文章

Django_Restful Framework之QQ登录流程分析(一)

本篇主要介绍如何使用Django的Restful Framework提供第三方登录的API,主要介绍其流程及基本的代码实现. 在学习之前我们需要知道什么是第三方登录 -- 是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目. 实现第三方登录时,我们需要明确QQ提供的开发者文档,即 : 成为QQ的开发者,注册. 创建应用,即获取本项目对应与QQ互联的应用ID,创建应用 QQ登录开发文档,文档 一.QQ登录流程 有了上述的准备工作,我们需要明确进行第三方登录的流程,这里以QQ登

QQ登录api

1 <?php 2 namespace Api\Member; 3 class QQConnect{ 4 /** 5 * 获取QQconnect Login 跳转到的地址值 6 * @return array 返回包含code state 7 * 8 **/ 9 public function login($app_id, $callback, $scope){ 10 $_SESSION['state'] = md5(uniqid(rand(), TRUE)); //CSRF protectio

网站集成QQ登录功能(转)

最近在做一个项目时,客户要求网站能够集成QQ登录的功能,以前没做过这方面的开发,于是去QQ的开放平台官网研究了一下相关资料,经过自己的艰苦探索,终于实现了集成QQ登录的功能,现在把相关的开发经验总结一下,希望对有这方面需求的朋友有所帮助. 一.前期准备 首先你需要登录QQ的开发平台注册一个账号,QQ互联平台官方地址:http://connect.qq.com/ 进去后注册一个开发账号,完了登录后台会有类似如下的一个后台,填好相关信息,具体可以参考下图.最后我们会有一个APP ID和APP KEY

网站集成QQ登录功能

最近在做一个项目时,客户要求网站能够集成QQ登录的功能,以前没做过这方面的开发,于是去QQ的开放平台官网研究了一下相关资料,经过自己的艰苦探索,终于实现了集成QQ登录的功能,现在把相关的开发经验总结一下,希望对有这方面需求的朋友有所帮助. 一.前期准备 首先你需要登录QQ的开发平台注册一个账号,QQ互联平台官方地址:http://connect.qq.com/ 进去后注册一个开发账号,完了登录后台会有类似如下的一个后台,填好相关信息,具体可以参考下图.最后我们会有一个APP ID和APP KEY

QQ登录集成到自己网站php代码(转载)

我们现在在各大网站论坛都可以看到点击一个QQ图标就可以利用自己的QQ号在网站进行登录了,下面我来告诉你一段QQ登录集成到自己网站php代码,有需要的朋友可参考. 1.打开open.qq.com 添加创建应用:->输入常规的数据,你会看到对应的APP ID和KEY值,这是对你身份证的唯一的验证. 2.打开 http://connect.qq.com/manage/ 点击->添加网站->输入相关信息,这里比较特别注意的是,回调地址那里填上你域名就可以了以上申核需要一到两天时间,耐心等待. 3

网站接入QQ登录

在网站接入QQ登录时遇到很多麻烦,主要是QQ互联和腾讯开放平台的关系比较乱,不知道要从哪个接入. 最终解决方式:在腾讯开放平台注册个人开发者,验证通过后,在QQ互联平台会有腾讯开放平台的注册信息,然后依次点击 个人中心->编辑->提交审核 审核完毕后应该就可以通过分配的ID信息接入了. (在最初QQ互联注册时,个人开发者总是需要提交营业执照,现在换了个qq号注册不需要了,可能是第一次QQ互联注册时JS没切过来的原因吧,应该是可以直接在QQ互联注册个人开发者的,不需要我这样绕路,(-?-;))

第三方登录之QQ登录(一)——QQ互联开放平台新建应用

现在这种第三方登录的应用很广泛也很方便.省得用户进行注册了.今天就来研究一下QQ登录API的使用. 首先说一下,腾讯提供了许多个的开放平台.差不多每个大的产品都提供了开放平台,比如微信.QQ邮箱,财付通等.这里面有2个比较易混的,一个是QQ互联,一个是腾讯开发平台.QQ互联主要是为了给第三方网站或移动应用提供登录信息.而腾讯开发平台,则是为托管型的应用(平台应用和移动应用)提供服务的.不过现在QQ互联正在往腾讯开发平台上迁移. 这是QQ互联官网中关于网站接入流程的wiki:http://wiki

日常API之QQ登录

这次的QQ登录我研究了好久惹,今天终于可以和大家分享啦! 大家都知道,QQ登录有很多方法,例如使用账号密码登录,手机版企鹅扫码登录等等(这些方法只能验证QQ是否成功登录,并没有聊天等功能) 首先就来使用账号密码登录吧(略简单) 一.准备工作 我们需要一只WebBrowser和登录API http://ui.ptlogin2.qq.com/cgi-bin/login?appid=1006102&s_url=http://id.qq.com/index.html&hide_close_icon

第三方登录之QQ登录(二)——OAuth2.0处理流程介绍(以QQ登录为例)

为了让网站更快接入,腾讯提供了JS SDK的接入方案,具体点击这里或者点击这里进行查看. 不过也可以自己写代码,当然你需要了解QQ的OAuth2.0的处理流程(Web Server Flow). OAuth2.0的处理流程:点击这里查看官方Wiki. 1.授权:获取授权码Authorization Code 说明:第一步QQ必须得到要登录系统的授权信息,如果授权全成功,则会跳转到回调地址,同时授权码以参数形式,追加回调地址上. 当然还有一些其他参数,具体详情看官方介绍. 实例: https://