- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
这个问题类似于Not receiving Google OAuth refresh token ,但我已经按照已接受解决方案的评论中的建议指定了 access_type='offline'
。
我正在编写一个 Django 应用程序来使用 Google API 发送日历邀请,这基本上是对 https://developers.google.com/api-client-library/python/auth/web-app 中给出的 Flask 示例的改编。 ,我在其中创建了一个模型 GoogleCredentials
以将凭据持久存储在数据库中而不是 session 中。
观点如下:
import logging
from django.conf import settings
from django.shortcuts import redirect
from django.http import JsonResponse
from django.urls import reverse
from django.contrib.auth.decorators import login_required
import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery
from lucy_web.models import GoogleCredentials
logger = logging.getLogger(__name__)
# Client configuration for an OAuth 2.0 web server application
# (cf. https://developers.google.com/identity/protocols/OAuth2WebServer)
# This is constructed from environment variables rather than from a
# client_secret.json file, since the Aptible deployment process would
# require us to check that into version control, which is not in accordance
# with the 12-factor principles.
# The client_secret.json containing this information can be downloaded from
# https://console.cloud.google.com/apis/credentials?organizationId=22827866999&project=cleo-212520
CLIENT_CONFIG = {'web': {
'client_id': settings.GOOGLE_CLIENT_ID,
'project_id': settings.GOOGLE_PROJECT_ID,
'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
'client_secret': settings.GOOGLE_CLIENT_SECRET,
'redirect_uris': settings.GOOGLE_REDIRECT_URIS,
'javascript_origins': settings.GOOGLE_JAVASCRIPT_ORIGINS}}
# This scope will allow the application to manage the user's calendars
SCOPES = ['https://www.googleapis.com/auth/calendar']
API_SERVICE_NAME = 'calendar'
API_VERSION = 'v3'
@login_required
def authorize(request):
authorization_url, state = _get_authorization_url(request)
request.session['state'] = state
return redirect(to=authorization_url)
@login_required
def oauth2callback(request):
flow = _get_flow(request, state=request.session['state'])
# Note: to test this locally, set OAUTHLIB_INSECURE_TRANSPORT=1 in your .env file
# (cf. https://stackoverflow.com/questions/27785375/testing-flask-oauthlib-locally-without-https)
flow.fetch_token(authorization_response=request.get_raw_uri())
_save_credentials(user=request.user, credentials=flow.credentials)
return redirect(to=reverse('create-meeting'))
@login_required
def create_meeting(request):
# Retrieve the user's credentials from the database, redirecting
# to the authorization page if none are found
credentials = _get_credentials(user=request.user)
if not credentials:
return redirect(to=reverse('authorize'))
calendar = googleapiclient.discovery.build(
API_SERVICE_NAME, API_VERSION, credentials=credentials)
calendars = calendar.calendarList().list().execute()
return JsonResponse(calendars)
def _get_credentials(user):
"""
Retrieve a user's google.oauth2.credentials.Credentials from the database.
"""
try:
_credentials = GoogleCredentials.objects.get(user=user)
except GoogleCredentials.DoesNotExist:
return
return google.oauth2.credentials.Credentials(**_credentials.to_dict())
def _save_credentials(user, credentials):
"""
Store a user's google.oauth2.credentials.Credentials in the database.
"""
gc, _ = GoogleCredentials.objects.get_or_create(user=user)
gc.update_from_credentials(credentials)
def _get_authorization_url(request):
flow = _get_flow(request)
# Generate URL for request to Google's OAuth 2.0 server
return flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type='offline',
login_hint=settings.SCHEDULING_EMAIL,
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes='true')
def _get_flow(request, **kwargs):
# Use the information in the client_secret.json to identify
# the application requesting authorization.
flow = google_auth_oauthlib.flow.Flow.from_client_config(
client_config=CLIENT_CONFIG,
scopes=SCOPES,
**kwargs)
# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required.
flow.redirect_uri = request.build_absolute_uri(reverse('oauth2callback'))
return flow
请注意,我已将 access_type='offline'
传递给 flow.authorization_url()
。这是 GoogleCredentials
模型:
from django.db import models
from django.contrib.postgres.fields import ArrayField
from .timestamped_model import TimeStampedModel
from .user import User
class GoogleCredentials(TimeStampedModel):
"""
Model for saving Google credentials to a persistent database (cf. https://developers.google.com/api-client-library/python/auth/web-app)
The user's ID is used as the primary key, following https://github.com/google/google-api-python-client/blob/master/samples/django_sample/plus/models.py.
(Note that we don't use oauth2client's CredentialsField as that library is deprecated).
"""
user = models.OneToOneField(
User,
primary_key=True,
limit_choices_to={'is_staff': True},
# Deleting a user will automatically delete his/her Google credentials
on_delete=models.CASCADE)
token = models.CharField(max_length=255, null=True)
refresh_token = models.CharField(max_length=255, null=True)
token_uri = models.CharField(max_length=255, null=True)
client_id = models.CharField(max_length=255, null=True)
client_secret = models.CharField(max_length=255, null=True)
scopes = ArrayField(models.CharField(max_length=255), null=True)
def to_dict(self):
"""
Return a dictionary of the fields required to construct
a google.oauth2.credentials.Credentials object
"""
return dict(
token=self.token,
refresh_token=self.refresh_token,
token_uri=self.token_uri,
client_id=self.client_id,
client_secret=self.client_secret,
scopes=self.scopes)
def update_from_credentials(self, credentials):
self.token = credentials.token
self.refresh_token = credentials.refresh_token
self.token_uri = credentials.token_uri
self.client_id = credentials.client_id
self.client_secret = credentials.client_secret
self.scopes = credentials.scopes
self.save()
在开发服务器运行的情况下,如果我转到 localhost:8000/authorize
(连接到 authorize()
View )然后我检查第一个凭据,我看到 refresh_token
是 None
:
(lucy-web-CVxkrCFK) bash-3.2$ python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from lucy_web.models import *
In [2]: GoogleCredentials.objects.all()
Out[2]: <QuerySet [<GoogleCredentials: GoogleCredentials object (2154)>]>
In [3]: gc = GoogleCredentials.objects.first()
In [4]: gc.__dict__
Out[4]:
{'_state': <django.db.models.base.ModelState at 0x111f91630>,
'created_at': datetime.datetime(2018, 8, 15, 17, 58, 33, 626971, tzinfo=<UTC>),
'updated_at': datetime.datetime(2018, 8, 15, 23, 8, 38, 634449, tzinfo=<UTC>),
'user_id': 2154,
'token': 'ya29foobar6tA',
'refresh_token': None,
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'client_id': '8214foobar13-unernto9l5ievs2pi0l6fir12fus1o46.apps.googleusercontent.com',
'client_secret': 'bZt6foobarQj10y',
'scopes': ['https://www.googleapis.com/auth/calendar']}
最初,这不是问题,但过了一会儿,如果我转到 create_meeting()
View ,我会得到一个我跟踪到的 RefreshError
google.oauth2.credentials
中的这段源代码:
@_helpers.copy_docstring(credentials.Credentials)
def refresh(self, request):
if (self._refresh_token is None or
self._token_uri is None or
self._client_id is None or
self._client_secret is None):
raise exceptions.RefreshError(
'The credentials do not contain the necessary fields need to '
'refresh the access token. You must specify refresh_token, '
'token_uri, client_id, and client_secret.')
换句话说,我需要一个 refresh_token
来防止这个错误。在这种情况下,为什么 Google API 不返回一个?
最佳答案
更仔细地按照接受的答案,我发现我可以通过删除网络应用程序对我的帐户的访问并再次添加它来获取刷新 token 。我导航到 https://myaccount.google.com/permissions并删除了“Cleo”应用程序的访问权限:
然后我去localhost:8000/authorize
(链接到authorize()
View )再次查找保存的凭据,他们有一个刷新 token :
In [24]: from lucy_web.models import *
In [25]: gc = GoogleCredentials.objects.first()
In [26]: gc.__dict__
Out[26]:
{'_state': <django.db.models.base.ModelState at 0x109133e10>,
'created_at': datetime.datetime(2018, 8, 15, 17, 58, 33, 626971, tzinfo=<UTC>),
'updated_at': datetime.datetime(2018, 8, 16, 22, 37, 48, 108105, tzinfo=<UTC>),
'user_id': 2154,
'token': 'ya29.Glv6BbcPkVoFfoobarHGifJUlEKP7kvwO5G1myTDOw9UYfl1LKAGxt',
'refresh_token': '1/iafoobar4z1OxFtNljiLrmS0',
'token_uri': 'https://www.googleapis.com/oauth2/v3/token',
'client_id': '821409068013-unerntfoobarir12fus1o46.apps.googleusercontent.com',
'client_secret': 'bZt6lfoobarpI8Qj10y',
'scopes': ['https://www.googleapis.com/auth/calendar']}
关于python - 即使 access_type 为 ='offline',Google OAuth2 也不会发布刷新 token ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51883184/
我正在尝试调整一个非常简单的查询: select * from log where user_id in (...) order by id desc limit 25 我只想显示一组不同用户 ID(
我正在尝试在 MVC5 项目中使用 Microsoft.Owin.Security.Google 获取 Google 帐户的刷新 token 。要从谷歌服务器响应获取 RefreshToken,我需要
我正在尝试更改我们对 .net 客户端库的旧 rest 调用,我有两个相关的问题/问题...... 此页面上的示例应用程序 https://developers.google.com/api-clie
请求 OAuth 凭据时,我可以将 access_type 指定为 Offline 或 Online。 选择在线访问类型会强制用户在每次登录时批准对我的应用程序的访问。这是为什么?用户不是已经批准了我
我在 documentation 上看到您需要设置 access_type=offline 才能获得刷新 token 。 我确实在 OAuth url 中设置了这个值,我清楚地看到它与其他参数一起正确
我有一个已批准作为具有 openid 和 email 范围的授权 API 客户端的应用程序。我仍然看到“您的域管理员已批准访问”初始屏幕 as documented here 。解决方案是不通过 ac
这个问题类似于Not receiving Google OAuth refresh token ,但我已经按照已接受解决方案的评论中的建议指定了 access_type='offline'。 我正在编
这篇文章是 How to do OAuth-requiring operations in a GAE cron job? 的后续文章,我意识到我误用了 OAuth2DecoratorFromClie
我是一名优秀的程序员,十分优秀!