我使用 Google 的 OAuth 身份验证创建了一个简单的 ASP.NET Core Web 应用程序。我的本地机器上运行得很好。 然而,将其作为 AppService 部署到 Azure 后,OAuth 重定向似乎变得困惑。
应用程序本身可以在这里找到:
https://gcalworkshiftui20180322114905.azurewebsites.net/
这是一个实际返回结果并显示应用程序正在运行的网址:
https://gcalworkshiftui20180322114905.azurewebsites.net/Account/Login?ReturnUrl=%2F
有时应用程序响应良好,但一旦我尝试使用 Google 登录,它就会永远加载并最终返回以下消息:
The specified CGI application encountered an error and the server terminated the process.
在幕后,身份验证回调似乎因 502.3 错误而失败:
502.3 Bad Gateway “The operation timed out”
错误跟踪可以在这里找到: https://gcalworkshiftui20180322114905.azurewebsites.net/errorlog.xml
微软的文档还没有真正帮助。
https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-overview
进一步的调查使我相信这与以下代码有关:
public GCalService(string clientId, string secret)
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/calendar-dotnet-quickstart.json");
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = clientId,
ClientSecret = secret
},
new[] {CalendarService.Scope.Calendar},
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
// Create Google Calendar API service.
_service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "gcalworkshift"
});
}
我可以想象 Azure 不支持个人文件夹?谷歌搜索并没有告诉我太多信息。
最佳答案
我关注了Facebook, Google, and external provider authentication in ASP.NET Core和 Google external login setup in ASP.NET Core创建一个带有 Google 身份验证的 ASP.NET Core Web 应用程序来检查此问题。
我也关注了.NET console application to access the Google Calendar API和 Calendar.ASP.NET.MVC5构建我的示例项目。核心代码如下,大家可以引用一下:
Startup.cs
public class Startup
{
public readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder); //C:\Users\{username}\AppData\Roaming\Google.Apis.Auth
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = "{ClientId}";
googleOptions.ClientSecret = "{ClientSecret}";
googleOptions.Scope.Add(CalendarService.Scope.CalendarReadonly); //"https://www.googleapis.com/auth/calendar.readonly"
googleOptions.AccessType = "offline"; //request a refresh_token
googleOptions.Events = new OAuthEvents()
{
OnCreatingTicket = async (context) =>
{
var userEmail = context.Identity.FindFirst(ClaimTypes.Email).Value;
var tokenResponse = new TokenResponse()
{
AccessToken = context.AccessToken,
RefreshToken = context.RefreshToken,
ExpiresInSeconds = (long)context.ExpiresIn.Value.TotalSeconds,
IssuedUtc = DateTime.UtcNow
};
await dataStore.StoreAsync(userEmail, tokenResponse);
}
};
});
services.AddMvc();
}
}
}
CalendarController.cs
[Authorize]
public class CalendarController : Controller
{
private readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
private async Task<UserCredential> GetCredentialForApiAsync()
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{ClientId}",
ClientSecret = "{ClientSecret}",
},
Scopes = new[] {
"openid",
"email",
CalendarService.Scope.CalendarReadonly
}
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
string userEmail = ((ClaimsIdentity)HttpContext.User.Identity).FindFirst(ClaimTypes.Name).Value;
var token = await dataStore.GetAsync<TokenResponse>(userEmail);
return new UserCredential(flow, userEmail, token);
}
// GET: /Calendar/ListCalendars
public async Task<ActionResult> ListCalendars()
{
const int MaxEventsPerCalendar = 20;
const int MaxEventsOverall = 50;
var credential = await GetCredentialForApiAsync();
var initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ASP.NET Core Google Calendar Sample",
};
var service = new CalendarService(initializer);
// Fetch the list of calendars.
var calendars = await service.CalendarList.List().ExecuteAsync();
return Json(calendars.Items);
}
}
在部署到 Azure Web 应用程序之前,我更改了 folder
用于构造 FileDataStore
的参数至D:\home
,但出现以下错误:
UnauthorizedAccessException: Access to the path 'D:\home\Google.Apis.Auth.OAuth2.Responses.TokenResponse-{user-identifier}' is denied.
然后,我尝试设置参数folder
至D:\home\site
并重新部署我的 Web 应用程序,发现它可以按预期工作,并且记录的用户凭据将保存在 D:\home\site
下您的 Azure Web 应用服务器。
Azure Web Apps 在称为沙箱的安全环境中运行,该环境有一些限制,您可以关注详细信息 Azure Web App sandbox 。
此外,您提到了 App Service Authentication它提供内置身份验证,无需在代码中添加任何代码。由于您已在 Web 应用程序中编写了用于身份验证的代码,因此无需设置应用服务身份验证。
要使用应用服务身份验证,您可以按照 here对于配置,那么您的 NetCore 后端可以通过 access_token
上的 HTTP GET 获取其他用户详细信息( refresh_token
、 /.auth/me
等)。端点,详细信息您可以遵循类似的 issue 。检索到登录用户的 token 响应后,您可以手动构造 UserCredential
,然后构建CalendarService
.
关于azure - 使用 OAuth2 作为 Azure Appservice 运行 asp.net core 2 应用程序会导致 502 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49572563/