Django Rest Framework + Allauth + Rest-auth 中的 Google 登录

15

我正在尝试使用 allauthrest-auth 在 django 中实现 Google 认证。经过多个小时的研究,我找到的解决方案都在我的项目中无法工作。

我基于一个 Github 问题和一篇文章编写了代码:

我还创建了一个带有 Google 客户端 ID客户端秘钥社交应用程序对象。

我的 Google 控制台项目设置:

已授权 JavaScript 起源:http://localhost:8000 已授权重定向 URI:http://localhost:8000/api/v1/users/login/google/callback/

我的代码

providers.py

from allauth.socialaccount.providers.google.provider import GoogleProvider


class GoogleProviderMod(GoogleProvider):
    def extract_uid(self, data):
        return str(data['sub'])

adapters.py:

from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from google.auth.transport import requests
from google.oauth2 import id_token

from myproj.users.providers import GoogleProviderMod


class GoogleOAuth2AdapterIdToken(GoogleOAuth2Adapter):
    provider_id = GoogleProviderMod.id

    def complete_login(self, request, app, token, **kwargs):
        idinfo = id_token.verify_oauth2_token(token.token, requests.Request(), app.client_id)
        if idinfo["iss"] not in ["accounts.google.com", "https://accounts.google.com"]:
            raise ValueError("Wrong issuer.")
        extra_data = idinfo
        login = self.get_provider().sociallogin_from_response(request, extra_data)
        return login

views.py:

from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_auth.registration.serializers import SocialLoginSerializer
from rest_auth.registration.views import SocialLoginView

from myproj.users.adapters import GoogleOAuth2AdapterIdToken


class GoogleLoginView(SocialLoginView):
    adapter_class = GoogleOAuth2AdapterIdToken
    callback_url = "http://localhost:8000/api/v1/users/login/google/callback/"
    client_class = OAuth2Client
    serializer_class = SocialLoginSerializer

urls.py:

from allauth.socialaccount.providers.oauth2.views import OAuth2CallbackView
from django.urls import path

from myproj.users.adapters import GoogleOAuth2AdapterIdToken
from myproj.users.views import GoogleLoginView

app_name = "users"
urlpatterns = [
    path(
        "login/google/",
        GoogleLoginView.as_view(),
        name="google_login"
    ),
    path(
        "login/google/callback/",
        OAuth2CallbackView.adapter_view(GoogleOAuth2AdapterIdToken),
        name="google_callback"
    ),
]

settings.py:

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "http"

当我在登录页面将从Google“登录”按钮返回的ID令牌作为代码参数传递时,发生错误:

allauth.socialaccount.providers.oauth2.client.OAuth2Error: Error retrieving access token: b'{\n "error": "invalid_grant",\n "error_description": "Malformed auth code."\n}'响应代码为400。

实际上,即使我将一些随机文本传递给代码,错误也是相同的。

感谢帮助!


我发现传递的“code”是不正确的。我从Google OAuth 2.0 Playground获得了授权代码。现在错误显示未经授权的客户端。我阅读了相关信息,看起来我需要在请求中说明“code”是从oauth playground检索到的。我该怎么做呢? 我尝试将oauth playground网址作为“redirect_uri”传递,但出现了相同的错误。 - PolishCoder
5个回答

6

我还整合了djangorestframework + django-allauth + django-rest-auth + djangorestframework-jwt

但是,我只实现了使用Google登录的功能,所以用户无法手动注册。

以下是我的代码:

views.py

from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_auth.registration.views import SocialLoginView


class GoogleLogin(SocialLoginView):
    adapter_class = GoogleOAuth2Adapter
    client_class = OAuth2Client

urls.py

...
path('auth/google', GoogleLogin.as_view(), name='google_login'),
...

settings.py

INSTALLED_APPS = [
    ...
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    ...
]
SITE_ID = 1
REST_USE_JWT = True    # this is for djangorestframework-jwt

[更新]
要在 SocialLoginView 终端使用 code 参数:

  • 创建可访问的重定向链接(我认为它在前端,或者您可以使用简单的视图)
  • 使用 https 回调(我在本地开发中使用 ngrok 完成此操作)
  • 直接访问 URL(不要使用 Google OAuth Playground 获取 code,因为 client_id 是不同的),或者只需复制 OAuth Playground 上的请求 URL 并更改 redirect_uri 和 cliend_id。这是链接: https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<https_callback>&prompt=consent&response_type=code&client_id=<cliend_id>&scope=email&access_type=offline

1
你不需要在你的provider.py文件中自定义GoogleProvider类。 请在你的adapters.py文件的complete_login函数中添加extra_data['id'] = extra_data['sub']。然后你就可以在定制的GoogleOAuth2Adapter适配器中使用GoogleProvider了。
from google.oauth2 import id_token
from google.auth.transport import requests

from allauth.socialaccount.providers.oauth2.views import (
    OAuth2Adapter,
    OAuth2CallbackView,
    OAuth2LoginView,
)
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter

class GoogleOAuth2AdapterIdToken(GoogleOAuth2Adapter):

    def complete_login(self, request, app, token, **kwargs):
        idinfo = id_token.verify_oauth2_token(token.token, requests.Request(), app.client_id)
        if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
            raise ValueError('Wrong issuer.')
        extra_data = idinfo
        extra_data['id'] = extra_data['sub']
        login = self.get_provider() \
            .sociallogin_from_response(request,
                                       extra_data)
        return login

oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2AdapterIdToken)
oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2AdapterIdToken)

请参考https://gonzafirewall.medium.com/google-oauth2-and-django-rest-auth-92b0d8f70575https://github.com/pennersr/django-allauth/issues/1983

0
根据aijogja的答案,这是我的解决方案。使用django-allauth和dj-rest-auth。
在REST Google登录视图中添加了一个回调URL:
class GoogleLogin(SocialLoginView):
    """Google login endpoint"""
    adapter_class = GoogleOAuth2Adapter
    client_class = OAuth2Client
    callback_url = 'http://localhost:8000/accounts/google/login/callback/'

这是与Google API控制台一起添加为重定向URL的相同URL。我将其用于allauth提供的标准Django登录和上面的REST登录视图。

将此视图映射到google_token URL:

urlpatterns = [
    ....,
    path('google_token/', views.GoogleLogin.as_view(), name='google_token'),
    ....,
]

既然我是在为Android做这个,那我也可以加上这部分,因为它可能会对某些人有所帮助:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestServerAuthCode(serverClientId)
            .requestEmail()
            .build();

...

GoogleSignInAccount account = completedTask.getResult(ApiException.class);
String authCode = account.getServerAuthCode();

将此 authCode 发送到上面定义的端点作为代码值(视图接受代码和 authToken),就这样。 使用 IdToken 而不是 authCode 时应该有一个工作流程,但似乎 dj-rest-auth 不支持。

0
你在开发者控制台中为项目添加了UI的本地主机地址吗?我正在尝试使用Google身份验证设置类似于您的设置。最终,我在Django中创建了另一个应用程序,对于一个特定的路由免除csrf,以便在调用有效令牌时注册(更新或创建)用户。
class VerifyToken(APIView):
     permission_classes = (AllowAny,)  # maybe not needed in your case
     authentication_classes = (UnsafeSessionAuthentication,)

@csrf_exempt
def post(self, request):
    print('hitting endpoint')
    requestJson = json.loads(request.body)
    request = requests.Request()

    id_info = id_token.verify_oauth2_token(requestJson['token'], request, settings.CLIENT_ID)

    if id_info['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
        return Response("Unable to Validate User: Wrong Issuer")
        # raise ValueError('Wrong issuer.')

    if not id_info:
        return Response("Unable to Validate User: Invalid Token")
        # raise Exception("Unable to Validate Token")

    id = id_info['email']
    user_letters = id_info['given_name'][0].upper() + id_info['family_name'][0].upper()
    # In this case, if the Person already exists, its name is updated
    user, created = User.objects.update_or_create(
        email=id, defaults={
            "first_name": id_info['given_name'],
            "last_name": id_info['family_name'],
            "last_login": datetime.datetime.now(),
            "email_verified": id_info['email_verified'],
            "exp": id_info['exp'],
            "locale": id_info['locale'],
            "name": id_info['name'],
            "picture": id_info['picture'],
            "initials": user_letters,
            "username": id_info['given_name'] + " " + id_info['family_name'],
        }
    )
    if created:
        serializer = UserSerializer(user)
        return Response(serializer.data)

0

我不确定,但是你的代码有两个错误。

第一个错误: 你的适配器必须有这些行

oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2AdapterIdToken)
oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2AdapterIdToken)

其次:您必须将此行代码添加到您的提供程序中

provider_classes = [GoogleProviderMod]

你的包含prover.py文件的应用程序必须添加到设置文件中所有应用程序的末尾,这是因为在Django应用程序中有一个映射器包含所有提供程序,在它们两个(你的提供程序和来自谷歌的提供程序)中都使用'id'为'google'

我使用了你上面提到的两个链接,但是出现了一些错误。

我知道这与你提到的疑问无关,但我来到这篇文章是为了了解如何将DRF与Google登录集成,这条评论可能会帮助其他人


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接