我有一个 Angular Web 应用程序,其 API 在我的本地服务器上运行良好(以及,与任何正在开发的应用程序功能一样完美),但是当我将它及其关联的数据库迁移到 Azure 上的应用程序服务时,每个 /api 调用都会失败500。
因此,我认为问题出在数据库上,我将本地开发服务器上的连接字符串更改为指向 Azure 数据库。我通过这种方式发现了一个小问题,我在连接字符串中拼错了用户名。所以我修复了这个问题,它在访问 Azure 数据库时在我的本地服务器上完美运行,但是一旦我使用相同的连接字符串在 Azure App Service 上运行它,每次调用 /api 都会失败,并出现内部服务器错误 500。
所有常规页面都得到了完美的服务,并且 Angular 路由工作得很好。仅从数据库访问内容失败。我已经在这几天了,不知道下一步该做什么。欢迎任何建议。
我正在使用 OpenIddict 进行身份验证,因此我对其进行了标记,但无论如何我都看不到相关的内容。奇怪的是,这里有一个线索,对“/connect/token”的身份验证调用有效并返回一个有效的令牌,但“/api/...” URL 没有。
如果相关,我正在使用 Asp Net Core 2.1。
更多信息
我按照建议尝试了详细的日志,但它们几乎不详细。但我确实注意到了一件有趣的事情。在错误中有以下信息:
Requested URL: https://mysite.azuurewebsites.net/api/accounts/getusers
Physical Path: D:\home\site\wwwroot\api\accounts\getusers
现在这个应用程序正在使用 MVC,所以没有这样的物理路径。控制器装饰有:
[Route("api/accounts")]
并且动作被装饰为:
[Authorize(Roles = "Somerole")]
[HttpGet("GetUsers"), Produces("application/json")]
在我看来,路线映射失败了。但这在我的本地开发计算机上运行良好。Azure 应用服务有什么不同?我需要在门户中设置一些特殊设置以允许 MVC 吗?我无法想象门户为什么要关心这些事情。
更多信息
使用 Postman,如果我使用有效的 Bearer 令牌访问 /api/someValidUrl,我会收到 500 错误。如果我删除 Authorization 标头,则会返回 401。
我开始说我认为这与 OpenIddict 没有任何关系,但也许我错了。我的授权控制器只是创建令牌。所有的有效性检查都是由 OpenIddict 完成的。
一个巨大的线索
我添加了一个 ExceptionHandler,然后使用 Postman 发出 API 请求,并产生了以下异常:
<h1>Error: IDX20803: Unable to obtain configuration from: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'.</h1>
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
我在这里找到了解释,但我并不完全理解,但看起来 Azure 上的 MS 中间件错误地试图将其解释为 Azure AD 请求。我唯一确定的是我的项目中没有名为 IdentityModelEventSource.cs 的文件。
供参考https://mywebsite.azurewebsites.net/.well-known/openid-configuration返回:
{
"issuer": "https://mywebsite.azurewebsites.net/",
"token_endpoint": "https://mywebsite.azurewebsites.net/connect/token",
"jwks_uri": "https://mywebsite.azurewebsites.net/.well-known/jwks",
"grant_types_supported": [
"password"
],
"scopes_supported": [
"openid",
"email",
"profile",
"roles"
],
"claims_supported": [
"aud",
"exp",
"iat",
"iss",
"jti",
"sub"
],
"subject_types_supported": [
"public"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"claims_parameter_supported": false,
"request_parameter_supported": false,
"request_uri_parameter_supported": false
}
也许有了这些信息,有人可以为我指明正确的方向。
新的 Startup.cs
我接受了 Pinpoint 的建议并从 JWT 更改。新的启动如下:
using AspNet.Security.OpenIdConnect.Primitives;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SIAngular.DBContexts;
using SIAngular.Models;
using SIAngular.Services;
using OpenIddict.Abstractions;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using System;
using Microsoft.Extensions.Logging;
namespace SIAngular
{
public class Startup
{
private readonly IHostingEnvironment env;
public Startup(IHostingEnvironment env, IConfiguration configuration)
{
Configuration = configuration;
this.env = env;
SIDBConnectionString = Configuration.GetConnectionString("SIDB");
}
public static string SIDBConnectionString;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
SymmetricSecurityKey _ssk = new SymmetricSecurityKey(Convert.FromBase64String(Configuration["Jwt:Key"]));
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("SqlConnection"));
options.UseOpenIddict();
});
services.AddCors();
// Register the Identity services.
services.AddIdentityCore<ApplicationUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.Password.RequireDigit = true;
config.Password.RequiredLength = 8;
config.Password.RequireLowercase = true; config.Password.RequireNonAlphanumeric = true;
config.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddRoleValidator<RoleValidator<IdentityRole>>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddSignInManager<SignInManager<ApplicationUser>>();
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict()
// Register the OpenIddict core services.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server services.
.AddServer(options =>
{
// Register the ASP.NET Core MVC services used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.UseMvc();
// Enable the token endpoint.
options.EnableTokenEndpoint("/connect/token");
options.AcceptAnonymousClients();
options.DisableScopeValidation();
// Note: the Mvc.Client sample only uses the code flow and the password flow, but you
// can enable the other flows if you need to support implicit or client credentials.
options.AllowPasswordFlow();
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
// During development, you can disable the HTTPS requirement.
if (env.IsDevelopment())
options.DisableHttpsRequirement();
options.AddSigningKey(_ssk);
})
.AddValidation();
services.AddSingleton<IConfiguration>(Configuration);
services.AddScoped<IPasswordHasher<ApplicationUser>, SqlPasswordHasher>();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors(builder =>
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseWebpackDevMiddleware(new Microsoft.AspNetCore.SpaServices.Webpack.WebpackDevMiddlewareOptions { HotModuleReplacement = true });
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
});
}
}
}
现在问题是一个例外:
InvalidOperationException:未指定 authenticationScheme,也未找到 DefaultChallengeScheme。