【问题标题】:python-social-auth not getting correct Google OAuth2 detailspython-social-auth 未获得正确的 Google OAuth2 详细信息
【发布时间】:2015-03-30 01:07:01
【问题描述】:

我想在 Django 中使用 python-social-auth 功能登录用户以进行 Google Plus 登录。从我的网站登录时,一切正常,并且正确的详细信息已添加到数据库中。

但是,我也想通过我的 Android 应用程序进行身份验证。用户登录应用程序,然后将访问令牌发送给 django API,django API 处理登录过程如下代码,改编自the documentation

@csrf_exempt
@serengeti_api_request
@psa('social:complete')
def login_social_token(request, backend):
    # Ensure the token has been specified.
    token = request.META.get('HTTP_ACCESSTOKEN')
    if token is None:
        raise SerengetiApiRequestException('Access token is missing!')

    # Login the user for this session
    user = request.backend.do_auth(token)
    if user is None:
        raise SerengetiApiRequestException('Could not authenticate user!')

    login(request, user)

    # Store the email address if one has been specified (e.g. Twitter)
    email = request.META.get('HTTP_EMAIL')
    if email is not None:
        user.email = email
        user.save()

    # Prepare the parameters to be returned
    response = dict({
        'id': user.id,
        'first_name': user.first_name,
        'last_name': user.last_name,
        'api_key': request.session.session_key,
    })

    # Return a 200 status code to signal success.
    return HttpResponse(json.dumps(response, indent=4), status=200)

从网站登录时,social_auth_usersocialauth 表包含:

id | provider      | uid       | extra_data
==========================================
10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600}

但是,当使用上述功能从应用程序登录时,操作完成,但表中的条目如下所示:

id | provider      | uid     | extra_data
=========================================
10 | google-oauth2 | <empty> | {"access_token": "", "expires": null}

此外,auth_user 表包含一个 username(如 eeed494412obfuscated48bc47dd9b)而不是 Google Plus 用户名,email 字段为空。

我做错了什么?如何才能获得与网站上相同的功能?

我想提一下,我已经从 Android 应用程序中实现了 Facebook 和 Twitter 身份验证,它们调用了上述函数并存储了正确的详细信息,只有 Google Plus 引起了问题。

【问题讨论】:

  • 嗨,你能分享完整的代码吗

标签: django python-2.7 django-socialauth google-login python-social-auth


【解决方案1】:

只是想分享一种替代方法。此示例非常原始,并未涵盖所有情况(例如,身份验证失败)。但是,它应该对如何完成 OAuth2 身份验证提供足够的了解。

获取客户 ID

从 OAuth2 服务提供商(例如 Google)获取 CLIENT ID 并配置重定向 URL。

我假设你已经这样做了。

创建登录/注册链接

您需要在视图中生成登录/注册链接。应该是这样的:

https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URL}}&scope=email

将 {{CLIENT_ID}} 和 {{REDIRECT_URL}} 替换为您在上一步中获得的详细信息。

创建一个新视图

urls.py 中添加类似:

url(r'^oauth2/google/$', views.oauth2_google),

在你的views.py 创建一个方法:

def oauth2_google(request):

    # Get the code after a successful signing
    # Note: this does not cover the case when authentication fails
    CODE = request.GET['code']

    CLIENT_ID = 'xxxxx.apps.googleusercontent.com' # Edit this
    CLIENT_SECRET = 'xxxxx' # Edit this
    REDIRECT_URL = 'http://localhost:8000/oauth2/google' # Edit this

    if CODE is not None:
        payload = {
            'grant_type': 'authorization_code', 
            'code': CODE, 
            'redirect_uri': REDIRECT_URL, 
            'client_id': CLIENT_ID, 
            'client_secret': CLIENT_SECRET
            }

        token_details_request = requests.post('https://accounts.google.com/o/oauth2/token', data=payload)
        token_details = token_details_request.json()
        id_token = token_details['id_token']
        access_token = token_details['access_token']

        # Retrieve the unique identifier for the social media account
        decoded = jwt.decode(id_token, verify=False)
        oauth_identifier = decoded['sub']

        # Retrieve other account details
        account_details_request = requests.get('https://www.googleapis.com/plus/v1/people/me?access_token=' + access_token)
        account_details = account_details_request.json()
        avatar = account_details['image']['url']

        # Check if the user already has an account with us
        try:
            profile = Profile.objects.get(oauth_identifier=oauth_identifier)
            profile.avatar = avatar
            profile.save()
            user = profile.user
        except Profile.DoesNotExist:
            user = User.objects.create_user()           
            user.save()
            profile = Profile(user=user, oauth_identifier=oauth_identifier, avatar=avatar)
            profile.save()

        user.backend = 'django.contrib.auth.backends.ModelBackend'
        login(request, user)

        return redirect('/')

您可能需要以下导入:

from django.shortcuts import redirect
import jwt # PyJWT==0.4.1
import requests # requests==2.5.0
import json

【讨论】:

  • 我应该导入什么配置文件?
【解决方案2】:

我有一个使用 google oauth2 身份验证的项目(实际上并未运行)。我把我的配置文件留在这里,所以它可能对你有用(我只使用 oauth2,所以有些东西可能会有所不同):

AUTHENTICATION_BACKENDS = (
    'social.backends.google.GoogleOAuth2',  # /google-oauth2
    'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your google oauth 2 key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your secret google oauth 2 key'

SOCIAL_AUTH_PIPELINE = (
    'social.pipeline.social_auth.social_details',
    'social.pipeline.social_auth.social_uid',
    'social.pipeline.social_auth.auth_allowed',
    'social.pipeline.social_auth.associate_by_email',
    'social.pipeline.social_auth.social_user',
    'social.pipeline.user.get_username',
    'social.pipeline.user.create_user',
    'social.pipeline.social_auth.associate_user',
    'social.pipeline.social_auth.load_extra_data',
    'social.pipeline.user.user_details'
)

我还附上了视图(请注意,我使用的是 django rest 框架)。

class ObtainAuthToken(APIView):
    permission_classes = (permissions.AllowAny,)
    serializer_class = AuthTokenSerializer
    model = Token

    # Accept backend as a parameter and 'auth' for a login / pass
    def post(self, request, backend):

        if backend == 'auth':  # For admin purposes
            serializer = self.serializer_class(data=request.DATA)
            if serializer.is_valid():
                token, created = Token.objects.get_or_create(user=serializer.object['user'])
                return Response({'token': token.key})
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        else:
            # Here we call PSA to authenticate like we would if we used PSA on server side.
            user = register_by_access_token(request, backend)

            # If user is active we get or create the REST token and send it back with user data
            if user and user.is_active:
                token, created = Token.objects.get_or_create(user=user)
                return Response({'id': user.id, 'name': user.username, 'token': token.key})
            else:
                return Response("Bad Credentials, check the Access Token and/or the UID", status=403)


@strategy('social:complete')
def register_by_access_token(request, backend):
    # This view expects an access_token GET parameter
    token = request.GET.get('access_token')
    backend = request.strategy.backend
    user = backend.do_auth(access_token=token, backend=backend)
    if user:
        # login(request, user) #Only useful for web..
        return user
    else:
        return None

在 urls.py 中:

urlpatterns = patterns(
    '',
    url(r'^login/(?P<backend>[\w-]+)$', ObtainAuthToken.as_view(), ),
)

很抱歉附上了所有这些代码,但没有提供具体答案,但需要更多数据,因为错误可能来自许多来源(错误的 api 密钥、错误的设置配置、管道..)。我希望代码有所帮助。

【讨论】:

  • 据我所知,我们正在做几乎相同的事情......当收到来自浏览器以外的其他请求时,您的代码是否正常工作?您是否在social_auth_usersocialauth 表中获得了正确的详细信息?密钥应该没问题,因为网站登录工作正常。你唯一多余的就是管道。我没有指定任何管道。这可能是问题吗?
  • 是的,它适用于 api 请求(正如我所说,我正在为移动客户端部分使用 django rest 框架)。这可能是原因之一。尝试可能值得。是的,当 some1 向 Google 注册时,我将正确的用户添加到用户表中。
【解决方案3】:

我终于自己弄明白了。根据this article in the Android's Google Plus documentation,我还需要在Android应用程序中发出请求时请求plus.profile.emails.read scope。一旦我添加了这个,python-social-auth 代码就成功地将电子邮件正确地存储在uid 字段中。这使它能够识别同一用户,无论是从网站还是应用程序登录,这正是我所需要的。这是我使用的范围字符串:

String scopes = "oauth2:" + Plus.SCOPE_PLUS_LOGIN + " https://www.googleapis.com/auth/plus.profile.emails.read";

但是,extra_data 字段仍然包含我上面提到的值。我相信这是由于还需要请求离线访问,这将允许 Google Plus 将丢失的字段传递回python-django-authMore details can be found here.

【讨论】:

  • 即使我想通了,赏金仍然对任何可以详细说明填充 extra_data 字段的人开放。
【解决方案4】:

我也遇到了同样的问题。未设置您的 google 用户的 extra_fields 的原因是因为 python-social-auth 调用 google 服务器来设置这些内容,但如果您仅使用 access_token 调用 Google,则不足以获取谷歌返回 refresh_token 和所有其他与身份验证相关的字段。您可以通过手动设置来破解它,但最终您将使用与客户端相同的访问和刷新令牌。 Google 建议您使用客户端生成具有所需范围的新授权令牌,然后将该身份验证令牌发送到服务器,然后服务器会将其转换为访问和刷新令牌。有关详细信息,请参见此处(这有点涉及阅读):https://developers.google.com/identity/protocols/CrossClientAuth

如果您真的致力于在 python-social-auth 的范围内执行此操作,我建议您制作一个自定义身份验证后端,将其命名为 GoogleOAuth2AuthorizationCodeAuth (see here for details)。

懒惰且可能容易破解和粗暴的方法是将 access_token 发布到我的服务器以以 google 用户身份登录(您似乎做得正确),然后再获取另一个 授权令牌来自客户端,以便发布到单独的端点,然后我将处理转换为连接到用户配置文件的另一个凭据模型对象。

在 DjangoRestFramework 中:

class GoogleAuthorizationCodeView(APIView):
    def post(self, request, format=None):
        credentials = flow.step2_exchange(code)
        saved_creds = GoogleCredentials.objects.create(credentials=credentials)
        return Response(status=status.HTTP_201_CREATED)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 2015-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多