博客网站上线一段时间了,发现注册用户很少,大家对于注册账号这种事都不太感兴趣,因此决定开发第三方账号登录功能,参考了相关文档实现了该功能,对整个过程以及其中遇到的问题还有解决办法做了详细的总结,避免大家踩坑。

一、什么是OAuth

1. 概念

OAuth的英文全称是Open Authorization,它是一种开放授权协议。OAuth目前共有2个版本,2007年12月的1.0版(之后有一个修正版1.0a)和2010年4月的2.0版,1.0版本存在严重安全漏洞,而2.0版解决了该问题。 OAuth简单说就是一种授权的协议,只要授权方和被授权方遵守这个协议去写代码提供服务,那双方就是实现了OAuth模式。

2. 应用场景

用户想要在某个网站应用登录使用时,又不想通过繁琐的步骤注册账号,此时就可以使用OAuth协议,调用第三方平台(如qq 微博等),完成第三方平台登录后,实现自动创建用户并登录的过程。

3. 过程解析

以博客网站使用QQ实现认证登录过程为例,详细分析具体流程

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609040146302-ed1c1edb-77f1-4beb-8936-d88ca85aaa7d.png#align=left&display=inline&height=464&margin=[object Object]&name=image.png&originHeight=464&originWidth=1165&size=113562&status=done&style=none&width=1165)

  • 第一步:点击qq登录图标

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609040263194-d9c9bd77-882d-49b0-8267-c2e6d1d45be3.png#align=left&display=inline&height=547&margin=[object Object]&name=image.png&originHeight=547&originWidth=448&size=60272&status=done&style=none&width=448) 此时f12打开调试工具,发现network发送的具体请求地址是 https://www.cuiliangblog.cn/login/login/qq 

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609040400822-f65d62d9-88e5-4e20-9529-654a5199b687.png#align=left&display=inline&height=167&margin=[object Object]&name=image.png&originHeight=167&originWidth=822&size=36462&status=done&style=none&width=822) Django服务器会响应一个重定向地址,指向qq授权登录页。https://graph.qq.com/oauth2.0/authorize?client_id=101921043&client_id=https://www.cuiliangblog.cn/login/complete/qq&state=uu25k4sabcActZYDUB2B1He8BrYS0MYu&response_type=code ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609040694692-33ab12d1-db0b-4b0b-9c6a-30a9beee2cc0.png#align=left&display=inline&height=209&margin=[object Object]&name=image.png&originHeight=209&originWidth=897&size=47938&status=done&style=none&width=897) 该请求中携带了client_id、state等信息,QQ服务器收到请求后判断来源请求认证网站信息的正确性,确认无误后跳转至登录授权页面 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609041352719-582089f5-dd23-4047-813b-892c343117c5.png#align=left&display=inline&height=224&margin=[object Object]&name=image.png&originHeight=224&originWidth=908&size=49553&status=done&style=none&width=908)

  • 第二步:qq登录页面输入用户名密码,然后点授权并登录

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609041226042-7c3dd7d7-2eb4-4d07-8f59-e1d778fb3cc3.png#align=left&display=inline&height=388&margin=[object Object]&name=image.png&originHeight=388&originWidth=935&size=71717&status=done&style=none&width=935) 用户输入账号密码点击授权并登录按钮后,访问qq服务器中校验用户名密码的方法,进行用户名密码登录校验

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609041411269-d5f70b39-57b1-4df8-9e95-90fefe674663.png#align=left&display=inline&height=477&margin=[object Object]&name=image.png&originHeight=477&originWidth=900&size=108367&status=done&style=none&width=900) 若校验成功,该方法会响应浏览器一个重定向地址,并附上一个code(授权码)和state。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609041513587-fcc1ea2b-319c-49cd-9d05-12c33150b0e1.png#align=left&display=inline&height=434&margin=[object Object]&name=image.png&originHeight=434&originWidth=904&size=85879&status=done&style=none&width=904)

  • 第三步:跳回到博客页面,成功登录

Django服务器拿到的token后,获取根据openid获取用户信息。最后将用户信息储存起来,返回给浏览器其首页的视图。到此OAuth2.0授权结束。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609044025426-d5d6a22f-218e-449c-a3a3-4109d7d0d246.png#align=left&display=inline&height=549&margin=[object Object]&name=image.png&originHeight=549&originWidth=1035&size=65282&status=done&style=none&width=1035)

二、使用OAuth

1. django项目使用OAuth

了解上述过程后,如何在自己的项目中使用OAuth认证呢?

  • 方式一:参考文档,发送request请求api接口,完成认证授权。参考文档:https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0
  • 方式二:使用Django库,在此推荐两个库①django-allauth(大而全,从用户注册到发送邮件重置密码全过程、以及第三方登录都功能都有)②social_django(Django项目第三方登录模块)

由于我的项目已经开发了用户认证全部流程,所有在这采用social_django库实现用户第三方登录。参考文档:https://python-social-auth.readthedocs.io/en/latest/index.html

2.准备工作

使用QQ、微博的第三方登录时,需要开发者实名认证,QQ调用网站还需要进行备案。微信不支持个人身份开发者注册,需要填写企业信息。

  • QQ申请地址:https://connect.qq.com/,如果遇到无法填写信息的话,可以登陆开放平台http://wiki.open.qq.com/ 后,点管理中心-账号资料再点编辑就可以上传修改信息,提交审核即可。(QQ互联资质和腾讯开放平台资质是共用的)

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609044107013-a6a93e21-9d07-428e-9c7d-77ad1557b64f.png#align=left&display=inline&height=472&margin=[object Object]&name=image.png&originHeight=472&originWidth=1022&size=65958&status=done&style=none&width=1022)

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609044270114-b5e8752d-b5bd-4877-84f4-70532c4a3aae.png#align=left&display=inline&height=579&margin=[object Object]&name=image.png&originHeight=579&originWidth=971&size=107094&status=done&style=none&width=971)

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609044404191-1409a791-1a51-4429-ae40-f02965b4bebe.png#align=left&display=inline&height=336&margin=[object Object]&name=image.png&originHeight=336&originWidth=1029&size=42659&status=done&style=none&width=1029)

  • 安装social_django库

pip install social-auth-app-django 

  • 修改setting.py配置
INSTALLED_APPS = [
    ……
    'social_django',  # 第三方登录
    ……
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                ………
                # 第三方账号登录
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect',
                ……
            ],
        },
    },
]

# 支持邮箱登录
AUTHENTICATION_BACKENDS = (
                           'django.contrib.auth.backends.ModelBackend',  # 指定Django的modelbackend类
                           )
# 第三方登录
SOCIAL_AUTH_URL_NAMESPACE = 'social'

# 登陆成功后的回调路由
SOCIAL_AUTH_LOGIN_REDIRECT_URL = 'https://www.cuiliangblog.cn/account/'
# 使用https代理
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
  • 配置url
urlpatterns = [
    ……
    path('login/', include('social_django.urls', namespace='social')),
    # 第三方账户登录
    ……
]
  • 创建数据库相关表

python manage.py migrate 

生成如下五张表

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609045126235-23b87567-9878-4516-a7d1-782ac0e080c4.png#align=left&display=inline&height=110&margin=[object Object]&name=image.png&originHeight=110&originWidth=204&size=5586&status=done&style=none&width=204)

3. github

  • setting文件添加配置
# 登录认证后端
AUTHENTICATION_BACKENDS = (……
                           'social_core.backends.github.GithubOAuth2',  # github登录
                       		……
                           )

# 第三方Github KEY和SECRET
SOCIAL_AUTH_GITHUB_KEY = '***********'
SOCIAL_AUTH_GITHUB_SECRET = '***************'
# 使用昵称作为用户名
SOCIAL_AUTH_GITHUB_USE_OPENID_AS_USERNAME = True
  • 前端页面添加github登录跳转按钮
<button type="button" onclick="window.location.href = '{% url 'social:begin' 'github' %}'" class="layui-btn layui-btn-normal"><i class="fab fa-github fa-lg"></i></button>
  • github应用settings配置,Homepage URL和Authorization callback URL都填写域名即可

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609045468864-9882b805-e85f-4697-9bc6-ce5bfff3b766.png#align=left&display=inline&height=774&margin=[object Object]&name=image.png&originHeight=774&originWidth=668&size=82454&status=done&style=none&width=668)

  • 登录测试(如果出现redirect_uri错误提示,参考后面的注意事项)

4. 微博

  • setting文件添加配置
# 登录认证后端
AUTHENTICATION_BACKENDS = (……
                           'social_core.backends.weibo.WeiboOAuth2',  # 新浪微博登录
                       		……
                           )

# 第三方新浪微博KEY和SECRET
SOCIAL_AUTH_WEIBO_KEY = '**********'
SOCIAL_AUTH_WEIBO_SECRET = '***************'
  • 前端页面添加github登录跳转按钮
<button type="button" onclick="window.location.href = '{% url 'social:begin' 'weibo' %}'" class="layui-btn layui-btn-normal"><i class="layui-icon layui-icon-login-weibo"></i></button>
  • 微博开放平台,填写应用授权回调页

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609046006820-4d354b54-0fa7-4414-bfa5-65262845f598.png#align=left&display=inline&height=390&margin=[object Object]&name=image.png&originHeight=390&originWidth=970&size=78373&status=done&style=none&width=970)

  • 登录测试(如果出现redirect_uri错误提示,参考后面的注意事项)

5. QQ

  • setting文件添加配置
# 登录认证后端
AUTHENTICATION_BACKENDS = (……
                           'social_core.backends.qq.QQOAuth2',  # qq登录
                       		……
                           )

# 第三方QQ KEY和SECRET
SOCIAL_AUTH_QQ_KEY = '***************'
SOCIAL_AUTH_QQ_SECRET = '*********'
  • 前端页面添加github登录跳转按钮
<button type="button" onclick="window.location.href = '{% url 'social:begin' 'qq' %}'" class="layui-btn layui-btn-normal"><i class="layui-icon layui-icon-login-qq"></i></button>
  • QQ开放平台,填写应用授权回调页

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609058888662-d2ad40fa-5052-4e1a-b0a0-1599411ec951.png#align=left&display=inline&height=551&margin=[object Object]&name=image.png&originHeight=551&originWidth=1002&size=38508&status=done&style=none&width=1002)

  • 登录测试(如果出现redirect_uri错误提示,参考后面的注意事项)

三、其他功能实现

使用默认配置不能获取用户头像,性别信息,微博和QQ的用户名是用户的UID,为了提高用户体验,需要在创建用户时,修改一部分代码逻辑,social_django库支持可扩展的管道机制,开发人员可以在身份验证、登录和注销期间自定义开发其他功能。参考文档:https://python-social-auth.readthedocs.io/en/latest/pipeline.html#authentication-pipeline 安装官方文档的模板开发功能,还是比较麻烦的,后期库版本更新了还得调试代码。所有我换了个思路,研究social_django库代码发现,用户其他信息都存放在AbstractUserSocialAuth模型的extra_data字段中

1. 调试测试

  • 模型文件路径:social_django > migrations > models.py
class AbstractUserSocialAuth(models.Model, DjangoUserMixin):
    """Abstract Social Auth association model"""
    ……
    extra_data = JSONField()
    ……

    def __str__(self):
        return str(self.user)

    class Meta:
        app_label = "social_django"
        abstract = True
    ……


class UserSocialAuth(AbstractUserSocialAuth):
    """Social Auth association model"""

    class Meta:
        """Meta data"""
        app_label = "social_django"
        unique_together = ('provider', 'uid')
        db_table = 'social_auth_usersocialauth'
  • 数据库查看extra_data字段信息

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609059856420-d679a1cf-b5da-488c-8851-0858c2eae347.png#align=left&display=inline&height=162&margin=[object Object]&name=image.png&originHeight=162&originWidth=1216&size=46870&status=done&style=none&width=1216)

  • python shell 调试获取用户详细信息
[root@0da62d4c0833 myblog]# python3.9 manage.py shell
Python 3.9.0 (default, Dec 12 2020, 15:10:05) 
[GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from social_django.models import UserSocialAuth
>>> user1 = UserSocialAuth.objects.get(user_id=62)                
>>> user1.extra_data['username']
'ζั͡ޓއއ陌影'
>>> user1.extra_data['profile_image_url']
'http://thirdqq.qlogo.cn/g?b=oidb&k=B2EsdNd78qoVu4616YoNVg&s=40&t=1581912070'
>>> user1.extra_data['gender']
'女'
>>> 

调试没问题,下面开始写这部分业务逻辑的代码。

2. 获取用户头像、用户名、性别

网站的数据库设计时,有一个userinfo表,专门存放用户详细信息,整体逻辑流程就是获取UserSocialAuth当前登录用户的extra_data相关值,保存到userinfo相关字段即可

# userinfo模型详细信息
class UserInfo(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True)
    # 与自带用户表一对一
    phone = models.CharField(verbose_name='手机号', max_length=20, default="保密")
    sex_choice = [('1', '男'), ('2', '女')]
    sex = models.CharField(verbose_name='性别', max_length=1, choices=sex_choice, default=1)
    web = models.CharField(verbose_name='个人网站', max_length=50, blank=True, null=True)
    aboutme = models.TextField(verbose_name='个性签名', max_length=200, default="这个人很懒,什么都没留下!")
    photo = models.ImageField(upload_to='photo/', verbose_name='头像', default='photo/default.jpg')

    class Meta:
        verbose_name = '用户详细信息'
        verbose_name_plural = verbose_name

    def __str__(self):
        return "user:{}".format(self.user.username)

主要解决的问题有:

  • 用户头像和性别只有QQ、微博这两个平台可以授权获取,github获取不到
  • 用户名github默认获取的昵称作为用户名,而QQ和微博使用UID编码作为用户名

用户授权完成后跳转到个人中心页view部分代码

# 用户个人中心页面相关view处理逻辑
def personalCenter(request):
    try:
        userinfo = UserInfo.objects.get(user_id=request.user.id)
        # print("已注册用户登录")
    except Exception as e:
        print(e)
        # print("第三方用户第一次登录")
        social_user = UserSocialAuth.objects.get(user_id=request.user.id)
        if social_user.provider == 'github':
            # print("github登录")
            UserInfo.objects.create(user_id=request.user.id)
            user = User.objects.get(id=request.user.id)
        else:
            # print("qq或微博登录")
            username = social_user.extra_data['username'].replace(" ", "")
            photo_url = social_user.extra_data['profile_image_url']
            username = social_user.extra_data['username'].replace(" ", "")
            photo_url = social_user.extra_data['profile_image_url']
            sex = social_user.extra_data['gender']
            # QQ用户性别存储值为男、女,微博用户性别存储值为f、m
            if sex == '女' or sex == 'f':
                sex_id = 2
            else:
                sex_id = 1
            user_info = UserInfo()
            user_info.user_id = request.user.id
            user_info.photo = downloadPhoto(photo_url)
            user_info.sex = sex_id
            user_info.save()
            new_user = User.objects.get(id=request.user.id)
            new_user.username = username
            new_user.save()
            user = new_user
    ………………
    return render(request, 'layui-mini/account/index.html', locals())

四、注意事项

1. 授权回调页地址填写

  • github授权回调页填写域名即可,例如https://www.cuiliangblog.cn/
  • qq和微博的授权回调页打开浏览器调试工具,查看redirect_uri地址填写

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609061745454-23dfc7e5-5a21-4c3d-8d91-53ed4602a0ee.png#align=left&display=inline&height=166&margin=[object Object]&name=image.png&originHeight=166&originWidth=805&size=44452&status=done&style=none&width=805)

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609061930758-16a6362a-e49a-4c5e-8200-67f9a28faf53.png#align=left&display=inline&height=126&margin=[object Object]&name=image.png&originHeight=126&originWidth=973&size=32203&status=done&style=none&width=973)

2. qq回调地址问题

social_django库默认回调地址为****/login/complete/qq/,不符合qq回调页地址要求。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609061985903-bfc7d8fe-2abb-4359-932f-951e20b11b9c.png#align=left&display=inline&height=498&margin=[object Object]&name=image.png&originHeight=498&originWidth=1027&size=45994&status=done&style=none&width=1027) 此时可以修改setting配置,取消默认回调页uri后面的/

# 回调页uri后面不加/
SOCIAL_AUTH_TRAILING_SLASH = False

3. from表单redirect_uri地址异常问题

正确的form表单redirect_uri地址如下图所示

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609062869306-62cc3d4f-82ad-4adc-b0c5-966260971a72.png#align=left&display=inline&height=117&margin=[object Object]&name=image.png&originHeight=117&originWidth=496&size=16914&status=done&style=none&width=496)

主要是被代理服务器接收到的header信息不对,修改nginx配置,添加proxy_set_header 配置即可

server {
        …………
        location / {
                proxy_pass http://127.0.0.1:8000;
                proxy_set_header  Host $host;
                proxy_set_header  X-Real-IP $remote_addr;
                proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto  $scheme;
        }
 				…………
    }
  • redirect_uri地址为http://***,而不是https://***情况解决办法

修改setting配置,强制将所有http的重定向请求转为https

# 将所有非SSL请求永久重定向到SSL
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

五、效果演示

1. 登录页

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609063401781-4592c8bd-266d-4780-af77-963c635c096e.png#align=left&display=inline&height=970&margin=[object Object]&name=image.png&originHeight=970&originWidth=1920&size=3028606&status=done&style=none&width=1920)

2. 用户个人中心页

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609063568582-977775a2-dd21-41b8-858b-341bf2cd35b6.png#align=left&display=inline&height=970&margin=[object Object]&name=image.png&originHeight=970&originWidth=1920&size=175600&status=done&style=none&width=1920)

3. 后台管理用户列表

![image.png](https://cdn.nlark.com/yuque/0/2020/png/2308212/1609063666506-b42e50a5-9243-4d3a-acc1-5a30fc8a26f6.png#align=left&display=inline&height=970&margin=[object Object]&name=image.png&originHeight=970&originWidth=1920&size=247169&status=done&style=none&width=1920)