python-social-auth 没有得到正确的 Google OAuth2 详细信息
python-social-auth not getting correct Google OAuth2 details
我想使用 python-social-auth
功能在 Django 中登录 Google Plus 登录用户。从我的网站登录时,一切正常,正确的详细信息已添加到数据库中。
但是,我也想从我的 Android 应用程序进行身份验证。用户登录应用程序,然后将访问令牌发送到 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
table 包含:
id | provider | uid | extra_data
==========================================
10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600}
然而,当使用上述功能从应用程序登录时,操作完成正常,但 table 中的条目如下所示:
id | provider | uid | extra_data
=========================================
10 | google-oauth2 | <empty> | {"access_token": "", "expires": null}
此外,auth_user
table 包含一个 username
类似 eeed494412obfuscated48bc47dd9b
而不是 Google Plus 用户名,并且 email
字段是空.
我哪里做错了,我怎样才能获得与网站上相同的功能?
我想提一下,我已经从 Android 应用程序实现了 Facebook 和 Twitter 身份验证,它们调用上述函数并存储正确的详细信息,只有 Google Plus 导致问题.
我有一个使用 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 键、错误的设置配置、管道..)。希望代码对您有所帮助。
我终于自己弄明白了。根据 this article in the Android's Google Plus documentation, I also need to request the plus.profile.emails.read
scope 在 Android 应用程序中提出请求时。一旦我添加了这个,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-auth
。 More details can be found here.
只是想分享一种替代方法。这个例子非常原始,并没有涵盖所有情况(例如,身份验证失败)。但是,它应该足够深入地了解如何完成 OAuth2 身份验证。
获取客户端 ID
从 OAuth2 服务提供商(例如 Google)获取客户端 ID 并配置重定向 URL。
我假设你已经这样做了。
创建登录/注册link
您需要在您的视图中生成登录/注册link。它应该是这样的:
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
我 运行 遇到了同样的问题。 google 用户的 extra_fields 没有被设置的原因是 python-social-auth 调用 google 服务器来设置这些东西,但是如果你仅使用 access_token 重新调用 Google,获取 Google 到 return refresh_token 以及所有其他与身份验证相关的字段是不够的。您可以通过手动设置来破解它,但是您最终会使用与客户端相同的访问和刷新令牌。 Google 建议您使用客户端生成一个具有您需要的任何范围的新授权令牌,然后将该授权令牌发送到服务器,然后服务器会将其转换为访问和刷新令牌。有关详细信息,请参见此处(阅读内容有点复杂):https://developers.google.com/identity/protocols/CrossClientAuth
如果您真的致力于在 python-social-auth 所做的范围内执行此操作,我建议您制作一个自定义身份验证后端,将其命名为 GoogleOAuth2AuthorizationCodeAuth
(see here for details).
更懒惰且可能容易破解和粗暴的方法是 post access_token 到我的服务器以 [=30=] 用户身份登录(你正在做的似乎是正确的),然后稍后,从客户端获取另一个 授权令牌 以便 post 到一个单独的端点,然后我将处理变成另一个凭证连接到用户配置文件的模型对象。
在 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)
我想使用 python-social-auth
功能在 Django 中登录 Google Plus 登录用户。从我的网站登录时,一切正常,正确的详细信息已添加到数据库中。
但是,我也想从我的 Android 应用程序进行身份验证。用户登录应用程序,然后将访问令牌发送到 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
table 包含:
id | provider | uid | extra_data
==========================================
10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600}
然而,当使用上述功能从应用程序登录时,操作完成正常,但 table 中的条目如下所示:
id | provider | uid | extra_data
=========================================
10 | google-oauth2 | <empty> | {"access_token": "", "expires": null}
此外,auth_user
table 包含一个 username
类似 eeed494412obfuscated48bc47dd9b
而不是 Google Plus 用户名,并且 email
字段是空.
我哪里做错了,我怎样才能获得与网站上相同的功能?
我想提一下,我已经从 Android 应用程序实现了 Facebook 和 Twitter 身份验证,它们调用上述函数并存储正确的详细信息,只有 Google Plus 导致问题.
我有一个使用 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 键、错误的设置配置、管道..)。希望代码对您有所帮助。
我终于自己弄明白了。根据 this article in the Android's Google Plus documentation, I also need to request the plus.profile.emails.read
scope 在 Android 应用程序中提出请求时。一旦我添加了这个,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-auth
。 More details can be found here.
只是想分享一种替代方法。这个例子非常原始,并没有涵盖所有情况(例如,身份验证失败)。但是,它应该足够深入地了解如何完成 OAuth2 身份验证。
获取客户端 ID
从 OAuth2 服务提供商(例如 Google)获取客户端 ID 并配置重定向 URL。
我假设你已经这样做了。
创建登录/注册link
您需要在您的视图中生成登录/注册link。它应该是这样的:
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
我 运行 遇到了同样的问题。 google 用户的 extra_fields 没有被设置的原因是 python-social-auth 调用 google 服务器来设置这些东西,但是如果你仅使用 access_token 重新调用 Google,获取 Google 到 return refresh_token 以及所有其他与身份验证相关的字段是不够的。您可以通过手动设置来破解它,但是您最终会使用与客户端相同的访问和刷新令牌。 Google 建议您使用客户端生成一个具有您需要的任何范围的新授权令牌,然后将该授权令牌发送到服务器,然后服务器会将其转换为访问和刷新令牌。有关详细信息,请参见此处(阅读内容有点复杂):https://developers.google.com/identity/protocols/CrossClientAuth
如果您真的致力于在 python-social-auth 所做的范围内执行此操作,我建议您制作一个自定义身份验证后端,将其命名为 GoogleOAuth2AuthorizationCodeAuth
(see here for details).
更懒惰且可能容易破解和粗暴的方法是 post access_token 到我的服务器以 [=30=] 用户身份登录(你正在做的似乎是正确的),然后稍后,从客户端获取另一个 授权令牌 以便 post 到一个单独的端点,然后我将处理变成另一个凭证连接到用户配置文件的模型对象。
在 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)