Google API V 3.0 .Net库和Google OAuth2如何处理刷新令牌

在我的应用程序中,我使用带有Google OAuth2的Google API V 3.0 .Net库来同步Google日历和Outlook日历。 我使用下面的代码来获取Google.Apis.Calendar.v3.CalendarService服务对象。 在身份validation期间,我存储了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? 任何帮助/指导表示赞赏。

过去两天花了我自己搞清楚这一点。 除非您指定“access_type = offline”,否则库不会自动刷新令牌。

https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

我要粘贴我正在使用的代码,如果有任何你不理解的东西,请问。 我已经阅读了很多post,而且我现在已经开始工作,所以有一些注释代码,它还没有被重构。 我希望这会对某人有所帮助。 我正在使用的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(); /// Gets the authorization code flow. protected IAuthorizationCodeFlow Flow { get { return FlowData.Flow; } } ///  /// Gets the user identifier. Potential logic is to use session variables to retrieve that information. ///  protected string UserId { get { return FlowData.GetUserId(this); } } ///  /// 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). ///  /// The current timeout is set to 10 seconds. You can change the default behavior by setting ///  with a different value on your controller. ///  ///  /// Authorization code response which contains the code or an error. /// Cancellation token to cancel operation. ///  /// Redirect action to the state parameter or  in case of an error. ///  [AsyncTimeout(60000)] public async override Task 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(); } // } //} 

}

控制器调用Google API的方法

  public async Task 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() // { // 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(new ApplicationDbContext()); //var manager = new UserManager(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"; } } } 

entity framework6 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(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 GetAsync(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(item.Value); return Task.FromResult(value); } } public async Task StoreAsync(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 GoogleAuthItems { get; set; } 

那是客户端库的一个小问题! 这个魔法自动为你完成:)

UserCredential实现了IHttpExecuteInterceptor和IHttpUnsuccessfulResponseHandler。 因此,每当访问令牌过期或已经过期时,客户端都会调用授权服务器来刷新令牌并获得新的访问令牌(在接下来的60分钟内有效)。

有关详细信息,请访问https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#credentials

对我来说,客户端库没有进行刷新,甚至没有创建刷新令牌。
我检查令牌是否已过期并刷新。 (令牌有1小时的续航时间)。 credential.Token.IsExpired将告诉您它是否已过期,凭证.Token.RefreshToken(userid)将刷新必要的令牌。