Google API V 3.0 .Net 库和 Google OAuth2 如何处理刷新令牌
Posted
技术标签:
【中文标题】Google API V 3.0 .Net 库和 Google OAuth2 如何处理刷新令牌【英文标题】:How Google API V 3.0 .Net library and Google OAuth2 Handling refresh token 【发布时间】:2014-11-23 17:37:34 【问题描述】:在我的应用程序中,我使用 Google API V 3.0 .Net 库和 Google OAuth2 来同步 Google 日历和 Outlook 日历。我正在使用下面的代码来获取 Google.Apis.Calendar.v3.CalendarService 服务对象。 在身份验证期间,我存储了 Json 文件,并从中请求 Google.Apis.Auth.OAuth2.UserCredential 对象。
private Google.Apis.Auth.OAuth2.UserCredential GetGoogleOAuthCredential()
GoogleTokenModel _TokenData = new GoogleTokenModel();
String JsonFilelocation = "jsonFileLocation;
Google.Apis.Auth.OAuth2.UserCredential credential = null;
using (var stream = new FileStream(JsonFilelocation, FileMode.Open,
FileAccess.Read))
Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.Folder = "Tasks.Auth.Store";
credential = Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.AuthorizeAsync(
Google.Apis.Auth.OAuth2.GoogleClientSecrets.Load(stream).Secrets,
new[] Google.Apis.Calendar.v3.CalendarService.Scope.Calendar ,
"user",
CancellationToken.None,
new FileDataStore("OGSync.Auth.Store")).Result;
return credential;
请求服务对象代码是:
Google.Apis.Calendar.v3.CalendarService _V3calendarService = new Google.Apis.Calendar.v3.CalendarService(new Google.Apis.Services.BaseClientService.Initializer()
HttpClientInitializer = GetGoogleOAuthCredential(),
ApplicationName = "TestApplication",
);
上面的代码可以很好地获取 Calendarservice 对象。我的问题是,我的 Json 文件有刷新和访问令牌。当访问令牌过期时,上面的代码如何处理刷新令牌以获取服务?因为我需要经常调用 Calendarservice 对象,所以我喜欢为calenderService 对象实现单例模式。如何在不经常调用 GetGoogleOAuthCredential 的情况下获取 Calendarservice?任何帮助/指导表示赞赏。
【问题讨论】:
同时检查***.com/a/24972426/833846 【参考方案1】:在过去的两天里我自己解决了这个问题。除非您指定“access_type=offline”,否则库不会自动刷新令牌。
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
我将粘贴我正在使用的代码,如果您有任何不明白的地方,请尽管问。我已经阅读了很多帖子,而且我现在几乎可以正常工作,所以有一些注释代码,它还没有被重构。我希望这会对某人有所帮助。我使用的 NuGet 包如下:
Google.Apis.Auth.MVC
Google.Apis.Calendar.v3
代码:
AuthCallbackController:
[AuthorizationCodeActionFilter]
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
protected static readonly ILogger Logger = ApplicationContext.Logger.ForType<AuthCallbackController>();
/// <summary>Gets the authorization code flow.</summary>
protected IAuthorizationCodeFlow Flow get return FlowData.Flow;
/// <summary>
/// Gets the user identifier. Potential logic is to use session variables to retrieve that information.
/// </summary>
protected string UserId get return FlowData.GetUserId(this);
/// <summary>
/// The authorization callback which receives an authorization code which contains an error or a code.
/// If a code is available the method exchange the coed with an access token and redirect back to the original
/// page which initialized the auth process (using the state parameter).
/// <para>
/// The current timeout is set to 10 seconds. You can change the default behavior by setting
/// <see cref="System.Web.Mvc.AsyncTimeoutAttribute"/> with a different value on your controller.
/// </para>
/// </summary>
/// <param name="authorizationCode">Authorization code response which contains the code or an error.</param>
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
/// <returns>
/// Redirect action to the state parameter or <see cref="OnTokenError"/> in case of an error.
/// </returns>
[AsyncTimeout(60000)]
public async override Task<ActionResult> IndexAsync(AuthorizationCodeResponseUrl authorizationCode,
CancellationToken taskCancellationToken)
if (string.IsNullOrEmpty(authorizationCode.Code))
var errorResponse = new TokenErrorResponse(authorizationCode);
Logger.Info("Received an error. The response is: 0", errorResponse);
Debug.WriteLine("Received an error. The response is: 0", errorResponse);
return OnTokenError(errorResponse);
Logger.Debug("Received \"0\" code", authorizationCode.Code);
Debug.WriteLine("Received \"0\" code", authorizationCode.Code);
var returnUrl = Request.Url.ToString();
returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?"));
var token = await Flow.ExchangeCodeForTokenAsync(UserId, authorizationCode.Code, returnUrl,
taskCancellationToken).ConfigureAwait(false);
// Extract the right state.
var oauthState = await AuthWebUtility.ExtracRedirectFromState(Flow.DataStore, UserId,
authorizationCode.State).ConfigureAwait(false);
return new RedirectResult(oauthState);
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
get return new AppFlowMetadata();
protected override ActionResult OnTokenError(TokenErrorResponse errorResponse)
throw new TokenResponseException(errorResponse);
//public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
//
// protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
//
// get return new AppFlowMetadata();
//
//
Controller调用Google API的方法
public async Task<ActionResult> GoogleCalendarAsync(CancellationToken cancellationToken)
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
//var ttt = await result.Credential.RevokeTokenAsync(cancellationToken);
//bool x = await result.Credential.RefreshTokenAsync(cancellationToken);
var service = new CalendarService(new BaseClientService.Initializer()
HttpClientInitializer = result.Credential,
ApplicationName = "GoogleApplication",
);
var t = service.Calendars;
var tt = service.CalendarList.List();
// Define parameters of request.
EventsResource.ListRequest request = service.Events.List("primary");
request.TimeMin = DateTime.Now;
request.ShowDeleted = false;
request.SingleEvents = true;
request.MaxResults = 10;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
// List events.
Events events = request.Execute();
Debug.WriteLine("Upcoming events:");
if (events.Items != null && events.Items.Count > 0)
foreach (var eventItem in events.Items)
string when = eventItem.Start.DateTime.ToString();
if (String.IsNullOrEmpty(when))
when = eventItem.Start.Date;
Debug.WriteLine("0 (1)", eventItem.Summary, when);
else
Debug.WriteLine("No upcoming events found.");
//Event myEvent = new Event
//
// Summary = "Appointment",
// Location = "Somewhere",
// Start = new EventDateTime()
//
// DateTime = new DateTime(2014, 6, 2, 10, 0, 0),
// TimeZone = "America/Los_Angeles"
// ,
// End = new EventDateTime()
//
// DateTime = new DateTime(2014, 6, 2, 10, 30, 0),
// TimeZone = "America/Los_Angeles"
// ,
// Recurrence = new String[]
// "RRULE:FREQ=WEEKLY;BYDAY=MO"
// ,
// Attendees = new List<EventAttendee>()
//
// new EventAttendee() Email = "johndoe@gmail.com"
//
//;
//Event recurringEvent = service.Events.Insert(myEvent, "primary").Execute();
return View();
else
return new RedirectResult(result.RedirectUri);
FlowMetadata的派生类
public class AppFlowMetadata : FlowMetadata
//static readonly string server = ConfigurationManager.AppSettings["DatabaseServer"];
//static readonly string serverUser = ConfigurationManager.AppSettings["DatabaseUser"];
//static readonly string serverPassword = ConfigurationManager.AppSettings["DatabaseUserPassword"];
//static readonly string serverDatabase = ConfigurationManager.AppSettings["DatabaseName"];
////new FileDataStore("Daimto.GoogleCalendar.Auth.Store")
////new FileDataStore("Drive.Api.Auth.Store")
//static DatabaseDataStore databaseDataStore = new DatabaseDataStore(server, serverUser, serverPassword, serverDatabase);
private static readonly IAuthorizationCodeFlow flow =
new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
ClientSecrets = new ClientSecrets
ClientId = "yourClientId",
ClientSecret = "yourClientSecret"
,
Scopes = new[]
CalendarService.Scope.Calendar, // Manage your calendars
//CalendarService.Scope.CalendarReadonly // View your Calendars
,
DataStore = new EFDataStore(),
);
public override string GetUserId(Controller controller)
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
//var user = controller.Session["user"];
//if (user == null)
//
// user = Guid.NewGuid();
// controller.Session["user"] = user;
//
//return user.ToString();
//var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
//var manager = new UserManager<ApplicationUser>(store);
//var currentUser = manager.FindById(controller.User.Identity.GetUserId());
return controller.User.Identity.GetUserId();
public override IAuthorizationCodeFlow Flow
get return flow;
public override string AuthCallback
get return @"/GoogleApplication/AuthCallback/IndexAsync";
实体框架 6 DataStore 类
public class EFDataStore : IDataStore
public async Task ClearAsync()
using (var context = new ApplicationDbContext())
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
await objectContext.ExecuteStoreCommandAsync("TRUNCATE TABLE [Items]");
public async Task DeleteAsync<T>(string key)
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key MUST have a value");
using (var context = new ApplicationDbContext())
var generatedKey = GenerateStoredKey(key, typeof(T));
var item = context.GoogleAuthItems.FirstOrDefault(x => x.Key == generatedKey);
if (item != null)
context.GoogleAuthItems.Remove(item);
await context.SaveChangesAsync();
public Task<T> GetAsync<T>(string key)
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key MUST have a value");
using (var context = new ApplicationDbContext())
var generatedKey = GenerateStoredKey(key, typeof(T));
var item = context.GoogleAuthItems.FirstOrDefault(x => x.Key == generatedKey);
T value = item == null ? default(T) : JsonConvert.DeserializeObject<T>(item.Value);
return Task.FromResult<T>(value);
public async Task StoreAsync<T>(string key, T value)
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key MUST have a value");
using (var context = new ApplicationDbContext())
var generatedKey = GenerateStoredKey(key, typeof(T));
string json = JsonConvert.SerializeObject(value);
var item = await context.GoogleAuthItems.SingleOrDefaultAsync(x => x.Key == generatedKey);
if (item == null)
context.GoogleAuthItems.Add(new GoogleAuthItem Key = generatedKey, Value = json );
else
item.Value = json;
await context.SaveChangesAsync();
private static string GenerateStoredKey(string key, Type t)
return string.Format("0-1", t.FullName, key);
GoogleAuthorizationCodeFlow 的派生类。启用自动“刷新”令牌的长期刷新令牌,这意味着获取新的访问令牌。
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base (initializer)
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
AccessType = "offline",
ApprovalPrompt = "force"
;
GoogleAuthItem 与 EFDataStore 一起使用
public class GoogleAuthItem
[Key]
[MaxLength(100)]
public string Key get; set;
[MaxLength(500)]
public string Value get; set;
public DbSet<GoogleAuthItem> GoogleAuthItems get; set;
【讨论】:
【参考方案2】:对我来说,客户端库没有进行刷新,甚至没有创建刷新令牌。 我检查令牌是否过期并刷新。 (令牌的生命周期为 1 小时)。 credential.Token.IsExpired 会告诉你是否过期,credential.Token.RefreshToken(userid) 会刷新必要的token。
【讨论】:
【参考方案3】:这就是客户端库的关键!这个魔法是自动为你完成的:)
UserCredential 实现了 IHttpExecuteInterceptor 和 IHttpUnsuccessfulResponseHandler。因此,每当访问令牌即将过期或已经过期时,客户端都会调用授权服务器以刷新令牌并获取新的访问令牌(在接下来的 60 分钟内有效)。
在https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#credentials了解更多信息
【讨论】:
好的,谢谢,那么每次我需要服务时,我都需要调用授权服务器。有没有办法检查令牌是否过期并在必要时调用服务器? 图书馆会为您做到这一点。 UserCredential 在使用 IMAP 客户端时不会刷新令牌。可能在使用 Google API 服务对象时会,但并非在所有情况下。以上是关于Google API V 3.0 .Net 库和 Google OAuth2 如何处理刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章
尝试保存凭据时,使用Google Drive API库和Play with Java会冻结/锁定应用程序
在 Google Contacts API 3.0 版上使用 oauth2 检索刷新令牌