Django第三方登录

所有第三方登录都要先获取第三方应用的Id和秘钥
开发之前,需要前往第三方登录的开发者平台QQ、新浪微博、Github,注册账号并填写信息申请接入,成功后会给你一个ID和秘钥,以后你就通过该ID和秘钥来获取令牌,从而实现第三方登录。申请ID和秘钥时Github不需要审核,很简单,而QQ和新浪微博需要审核,稍微麻烦一点。
获得ID和秘钥后需要在setting中进行设置:
以QQ登录为例:关键点就是获取到QQ的凭证openid,然后将openid跟我们的用户模型联系起来。
若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现。
这是QQ登录的开发者文档:http://wiki.connect.qq.com/准备工作_oauth2-0 第三方登录都有其相应的开发者文档。

使用QQ登录的流程:
在这里插入图片描述
新创建一个模型类用于记录用户与第三方登录ID的关联关系:
创建模型的时候可以设置db_index=True来自动创建索引。

from django.db import models
from meiduo_mall.utils.models import BaseModel

class OAuthQQUser(BaseModel):
    """
    QQ登录用户数据
    """
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
    openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)

    class Meta:
        db_table = 'tb_oauth_qq'
        verbose_name = 'QQ登录用户数据'
        verbose_name_plural = verbose_name

其中继承的BaseModel是创建的一个模型类基类,专门用于记录模型的创建更新时间。
这种模型类基类的创建需要添加方法进行说明:

from django.db import models

class BaseModel(models.Model):
    """为模型类补充字段"""
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        abstract = True  # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表

模型类有了之后我们现在就开始完成QQ登录的具体逻辑:
按照一般第三方登录的风格需要完成三个接口的设计:

  1. 取得QQ的授权code
  2. 利用code取得唯一表示openid
  3. 将openid跟用户绑定并登录

一、返回QQ登录网址的视图
目的:访问QQ第三方的登录网址,对用户QQ身份进行校验,校验成功后返回授权code。期间需要利用next参数携带登录成功后的跳转页面的数据。

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})

python提供了标准模块urllib可以帮助我们发送HTTP请求,其中的
urllib.parse.urlencode(query):可以将query字典转换为url路径中的查询字符串,用途一般是将得到的返回结果拼接到url中。
urllib.request.urlopen(url, data=None):(爬虫要学的)发送http请求,如果data为None,发送GET请求,如果data不为None,发送POST请求
这一步逻辑是跳转到QQ的第三方登录页面,登录成功后会返回授权code并重定向到callback网址,这个callback网址是在注册QQ开发者是设置好的。

二、QQ登录回调处理
获取url中携带的参数使用“ request.query_params.get(‘参数名’) ” 例如:

    code = request.query_params.get('code')

此处完成逻辑有:

  1. 通过路由携带的参数获取code得到用户数据
  2. 得到openid
  3. 根据openid判断用户是否已经存在不存在则创建
  4. 存在就进一步判断是否已经绑定,如绑定则生成token后直接跳转到指定页面
  5. 没有绑定则跳转到绑定页面(即下一个接口)
    具体视图:
class QQAuthUserView(APIView):
    """
    QQ登录的用户
    """
    def get(self, request):
        """
        获取qq登录的用户数据
        """
        code = request.query_params.get('code')
        if not code:
            return Response({'message': '缺少code'}, status=status.HTTP_400_BAD_REQUEST)

        oauth = OAuthQQ()

        # 获取用户openid
        try:
            access_token = oauth.get_access_token(code)
            openid = oauth.get_openid(access_token)
        except QQAPIError:
            return Response({'message': 'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 判断用户是否存在
        try:
            qq_user = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 用户第一次使用QQ登录
            token = oauth.generate_save_user_token(openid)
            return Response({'access_token': token})
        else:
            # 找到用户, 生成token
            user = qq_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

三、绑定用户身份接口
主要逻辑使用序列化器实现:

class OAuthQQUserSerializer(serializers.ModelSerializer):
    """
    保存QQ用户序列化器
    """
    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_save_user_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:
            # 如果用户不存在,创建用户,绑定openid(创建了OAuthQQUser数据)
            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
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值