QQ第三方登录

准备工作

1. 成为QQ互联的开发者
参考链接:http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85

2. 审核通过后,创建应用,即获取本项目对应与QQ互联的应用ID
参考链接:http://wiki.connect.qq.com/__trashed-2

3. 在 models.py 中定义QQ身份(openid)与用户模型类User的关联关系

class OAuthQQUser(models.Model):
    """
    QQ登录用户数据
    """
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
    openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    class Meta:
        db_table = 'tb_oauth_qq'
        verbose_name = 'QQ登录用户数据'
        verbose_name_plural = verbose_name  # 单复数同名

4. QQ登录SDK使用

  • 初始化OAuthQQ对象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, 
                 client_secret=settings.QQ_CLIENT_SECRET,
                 redirect_uri=settings.QQ_REDIRECT_URI, state=next)
  • 获取QQ登录扫码页面,扫码后得到Authorization Code
login_url = oauth.get_qq_url()
  • 通过Authorization Code获取Access Token
access_token = oauth.get_access_token(code)
  • 通过Access Token获取OpenID
openid = oauth.get_open_id(access_token)

实现流程

具体流程

1. 返回QQ登录网址的视图

1. 在配置文件中添加关于QQ登录的应用开发信息

# QQ登录参数
QQ_CLIENT_ID = 'xxxx'  # ID
QQ_CLIENT_SECRET = 'xxxxxxxxxxxx'  # 密钥
QQ_REDIRECT_URI = 'http://www.xxxx.xxx/oauth_callback.html'  # 回调域

2. 接口设计

  • 请求方式:GET /oauth/qq/statues/?state=xxx
  • 请求参数:查询字符串参数
参数名 类型 是否必须 说明
state str 用户QQ登录成功后进入的网址
  • 返回数据:JSON
{
    "login_url": oauth.get_qq_url()
}
返回值 类型 是否必须 说明
login_url str qq登录网址

3. 逻辑实现

class QQAuthURLView(APIView):
    """
    提供QQ登录页面网址

    """
    def get(self, request):

        # state表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面
        state= request.query_params.get('state')
        if not state:
            state= '/'

        # 获取QQ登录页面网址
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=state)
        login_url = oauth.get_qq_url()

        return Response({'login_url': login_url})

2. OAuth2.0认证

  • 准备oauth_callback回调页,用于扫码后接受Authorization Code
  • 通过Authorization Code获取Access Token
  • 通过Access Token获取OpenID

1. 接口设计

  • 请求方式:GET /oauth/qq/users/?code=xxx
  • 请求参数: 查询字符串参数
参数名 类型 是否必须 说明
code str qq返回的授权凭证code
  • 返回数据:JSON
{
    "access_token": xxxx,
}
或
{
    "token": "xxx",
    "username": "python",
    "user_id": 1
}
返回值 类型 是否必须 说明
access_token str 用户是第一次使用QQ登录时返回,其中包含openid,用于绑定身份使用,注意这个是我们自己生成的
token str 用户不是第一次使用QQ登录时返回,登录成功的JWT token
username str 用户不是第一次使用QQ登录时返回,用户名
user_id int 用户不是第一次使用QQ登录时返回,用户id

2. 逻辑实现

  • oauth/views.py
class QQAuthUserView(GenericAPIView):
    """用户扫码登录的回调处理"""

    # 指定序列化器
    serializer_class = serializers.QQAuthUserSerializer

    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 = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
                        client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI)

        try:
            access_token = oauth.get_access_token(code)

            # 3,通过access_token获取openid
            openid = oauth.get_open_id(access_token)

            # 4,通过openid查询oauthqq对象

            try:
                oauth_qq_user = OAuthQQUser.objects.get(openid=openid)
            except OAuthQQUser.DoesNotExist:
                # ①, 没有项目用户, 也没有OAuthQQUser用户
                # ②, 有项目用户, 没有OAuthQQUser用户

                # 5,qq用户没有和项目用户绑定过,加密openid,并返回
                access_token_openid = generate_save_user_openid(openid)
                return Response({"access_token": access_token_openid})

        except Exception:
            return Response({"message": "请求qq服务器异常"},
                            status=status.HTTP_400_BAD_REQUEST)

        # 6,oauth_qq_user存在,并且绑定过了美多用户
        user = oauth_qq_user.user

        # 7,组织数据,拼接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({
            "user_id": user.id,
            "username": user.username,
            "token": token
        })
  • oauth/utils.py 中准备序列化 OpenID 的工具方法
# 对openid加密
def generate_save_user_openid(openid):
    #1,创建TJWSSerializer对象
    serializer = TJWSSerializer(settings.SECRET_KEY,expires_in=300)

    #2,加密数据
    token = serializer.dumps({"openid":openid})

    #3,返回
    return token

3. OpenID 绑定用户

如果用户是首次使用QQ登录,则需要绑定用户

绑定用户流程

1. 接口设计

  • 请求方式:POST /oauth/qq/users/
  • 请求参数:JSON 或 表单
参数名 类型 是否必须 说明
mobile str 手机号
password str 密码
sms_code str 短信验证码
access_token str 凭据(包含openid)
  • 返回数据:JSON
返回值 类型 是否必须 说明
token str JWT token
id int 用户id
username str 用户名

2. 逻辑实现

  • oauth/views.py
def post(self, request):
        # 1,获取数据
        dict_data = request.data

        # 2,获取序列化器,校验数据
        serializer = self.get_serializer(data=dict_data)
        serializer.is_valid(raise_exception=True)

        # 3,数据入库
        oauth_qq = serializer.save()

        # 4,组织,数据返回响应
        user = oauth_qq.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)

        return Response({
            "user_id": user.id,
            "username": user.username,
            "token": token
        })
  • 新建 oauth/serializers.py 文件
from rest_framework import serializers
from .utils import check_save_user_openid
from django_redis import get_redis_connection
from users.models import User
from .models import OAuthQQUser
class QQAuthUserSerializer(serializers.Serializer):
    mobile = serializers.RegexField(label="手机号",regex=r"1[3-9]\d{9}")
    password = serializers.CharField(label="密码",min_length=8,max_length=20)
    sms_code = serializers.CharField(label="短信",min_length=6,max_length=6)
    access_token = serializers.CharField(label="token",min_length=1)

    def validate(self, attrs):
        """多字段校验"""
        #1,获取加密的openid
        access_token = attrs["access_token"]

        #2,调用方法解密openid,判断是否存在
        openid = check_save_user_openid(access_token)

        if not openid:
            raise serializers.ValidationError("openid失效")

        #3,获取redis中的短信,判断为空,正确性
        sms_code = attrs["sms_code"]
        mobile = attrs["mobile"]
        redis_conn = get_redis_connection("code")
        redis_sms_code = redis_conn.get("sms_%s"%mobile)

        if not redis_sms_code:
            raise serializers.ValidationError("短信验证码过期")

        if sms_code != redis_sms_code.decode():
            raise serializers.ValidationError("短信验证码错误")

        #4,通过手机号查询美多用户是否存在,判断密码正确性
        user = None
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            pass
        else:
            #5,表示用户存在,判断密码正确性
            if not user.check_password(attrs["password"]):
                raise serializers.ValidationError("密码错误")

        #6,返回校验之后的内容
        attrs["openid"] = openid
        attrs["user"] = user
        return attrs

    #重写create方法,创建qq用户
    def create(self, validated_data):
        """validated_data,就上面返回的attrs"""
        #1,创建qq用户
        oauth_qq = OAuthQQUser()

        #2,判断用户是否存在,如果存在设置属性,如果不存在直接创建
        user = validated_data["user"]
        if not user:
            user = User.objects.create(
                username=validated_data["mobile"],
                mobile=validated_data["mobile"],
            )
            user.set_password(validated_data["password"])
            user.save()

        #3,设置qq用户属性
        oauth_qq.openid = validated_data["openid"]
        oauth_qq.user = user
        oauth_qq.save()

        #4,返回
        return oauth_qq
  • oauth/utils.py 中准备序列化 OpenID 的工具方法
# 对openid解密
def check_save_user_openid(access_token):
    #1,创建serializer对象
    serializer = TJWSSerializer(settings.SECRET_KEY,expires_in=300)

    #2,解密openid
    dict_data = serializer.loads(access_token)

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