DjangoRestFramework 使用 python-social-auth 实现第三方登录

本文为 social-auth-app-django 使用记录。(官方文档)

0. 前言

前提:有两个网站,记为网站A、网站B,两个网站有各自的用户系统,现在网站B要实现使网站A的用户无需注册直接登录自己的网站,这里就要用到第三方登录。

环境: Django == 3.x  social-auth-app-django == 4.0.0

1. 安装 

pip install social-auth-app-django

2. 使用

网站A该篇文章的 provider 项目,即 OAuth provider(提供方); 

创建 Django 项目(网站B),即 OAuth consumer(使用方),网站B目录树如下图:

├─manage.py
├─social
│  │  asgi.py
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  └─ __init__.py
└─users
   │  admin.py
   │  apps.py
   │  backends.py
   │  models.py
   │  tests.py
   │  views.py
   └─ __init__.py

并进行相关配置: 

# social/settings.py
INSTALLED_APPS = [
    ...
    'users',
    'social_django',    # add
]

SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['username', 'email']

# SOCIAL_AUTH_要使用登录模块的名称大写_KEY
# 登录模块名对应为第三步中自定义后端的 name 属性
SOCIAL_AUTH_DJANGO_KEY = 'local'
SOCIAL_AUTH_DJANGO_SECRET = 'local'

# 登录成功后跳转页面
LOGIN_REDIRECT_URL = '/admin/'        

# 获取授权码的标识
PARTIAL_PIPELINE_TOKEN_NAME = 'code'

AUTHENTICATION_BACKENDS = (
    'users.backends.djangoOauth2',                  # 自定义第三方登录认证
    'django.contrib.auth.backends.ModelBackend',    # django 默认登录认证
)

# 获取用户详细的信息
SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
)

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                # add
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect',
            ],
        },
    },
]


# social/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('social_django.urls', namespace='social'))    # add
]

 3.自定义认证后端 

配置文件 settings.py 中的 AUTHENTICATION_BACKENDS 下自定义了一个 djangoOauth2,代码实现如下:

# users/backends.py
import requests
from social_core.backends.oauth import BaseOAuth2


class djangoOauth2(BaseOAuth2):
    name = 'django'    # 调用的第三方名称, 如 google
    AUTHORIZATION_URL = 'http://127.0.0.1:8000/o/authorize/'   # 获取授权码的路由
    ACCESS_TOKEN_URL = 'http://127.0.0.1:8000/o/token/'        # 获取 token 的路由      
    ACCESS_TOKEN_METHOD = 'POST'    # 获取 token 的请求方式
    RESPONSE_TYPE = 'code'
    REDIRECT_STATE = False
    STATE_PARAMETER = False
    EXTRA_DATA = [
        ('id', 'id'),
        ('username', 'username'),
    ]

    def get_user_details(self, response):
        """Return user details from Django siteA. """
        print('get_user_details')
        return response

    def user_data(self, access_token, response=None, *args, **kwargs):
        """Return user data"""
        # 本处使用了用户id获取用户详细信息
        # 由于网站A(provider)项目中返回的 token 信息并不包含 id 信息
        # 因此这里网站A 自定义了返回 token信息(代码见下)
        url = 'http://127.0.0.1:8000/users/{}'.format(response['id'])
        headers = {'Authorization': 'Bearer ' + access_token}
        user_data = self.get_json(
            url=url,
            method='GET',
            headers=headers
        )
        print(user_data)
        return user_data

注意:此处为网站A(provider)中自定义的 token 代码:

# provider/users/views.py
import json

from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.debug import sensitive_post_parameters
from oauth2_provider.models import get_access_token_model
from oauth2_provider.signals import app_authorized
from oauth2_provider.views import TokenView


class CustomTokenView(TokenView):
    """ 自定义返回令牌 """

    @method_decorator(sensitive_post_parameters("password"))
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            body = json.loads(body)
            access_token = body.get("access_token")
            if access_token is not None:
                token = get_access_token_model().objects.get(token=access_token)
                app_authorized.send(sender=self, request=request, token=token)
                body['id'] = token.user.id
                body = json.dumps(body)
        response = HttpResponse(content=body, status=status)

        for k, v in headers.items():
            response[k] = v
        return response

# provider/urls.py
import oauth2_provider.views as oauth2_views
from django.conf import settings
from django.contrib import admin
from django.urls import path, include

from users.views import UserList, UserDetails, CustomTokenView

# OAuth2 provider endpoints
oauth2_endpoint_views = [
    path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"),
    # 这里使用自定义 token 信息
    path('token/', CustomTokenView.as_view(), name="token"),    
    path('revoke_token/', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
]

if settings.DEBUG:
    # OAuth2 Application Management endpoints
    oauth2_endpoint_views += [
        path('applications/', oauth2_views.ApplicationList.as_view(), name="list"),
        path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"),
        path('applications/<pk>/', oauth2_views.ApplicationDetail.as_view(), name="detail"),
        path('applications/<pk>/delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"),
        path('applications/<pk>/update/', oauth2_views.ApplicationUpdate.as_view(), name="update"),
    ]

    # OAuth2 Token Management endpoints
    oauth2_endpoint_views += [
        path('authorized-tokens/', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
        path('authorized-tokens/<pk>/delete/', oauth2_views.AuthorizedTokenDeleteView.as_view(),
             name="authorized-token-delete"),
    ]

urlpatterns = [
    path('admin/', admin.site.urls),
    # path('o/',  include('oauth2_provider.urls', namespace='oauth2_provider')),  # before
    path('o/', include((oauth2_endpoint_views, 'oauth2_provider'), namespace="oauth2_provider")),  # now
    path('users/', UserList.as_view()),
    path('users/<pk>/', UserDetails.as_view()),
]

 4. 在网站A中注册网站B

注意:此时网站A在8000端口运行,网站B在8001端口运行。

网站B要想调用网站A登录,则需要在网站A注册自己网站的信息,注册地址为:http://127.0.0.1:8000/o/applications/, 点击 New Applications 进行添加:

其中填写的 client_id 对应配置文件 settings.py 中的 SOCIAL_AUTH_DJANGO_KEY, client_secret 对应 SOCIAL_AUTH_DJANGO_SECRRT。

授权类型为授权码类型(authorization);

重定向地址为 http://127.0.0.1:8001/complete/django/,其中 8001 为网站B运行的端口,django 为自定义认证后端类中的 name。

5. 调用第三方登录

在浏览器中输入 http://127.0.0.1:8001/login/django/,

会发现重定向路由为 http://127.0.0.1:8000/o/authorize/?client_id=local&redirect_uri=http://127.0.0.1:8001/complete/django/&response_type=code,

输入网站A中的用户名密码,点击授权(Authorize), 

会发现网址重定向到了 http://127.0.0.1:8001/admin/login/?next=/admin/,红字表示已经使用网站A中的 admin 用户登录成功,但是该用户没有访问该页面的权限(superuser才可以访问)。

 同时Pycharm中会输出:

 创建网站B的超级用户(social)并查看 http://127.0.0.1:8001/admin 界面:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值