1

我有一个 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。

4

0 回答 0