IdentityServer 4 2.0 用户信息 "No 'Access-Control-Allow-Origin' header is present on the requested resource."

IdentityServer 4 2.0 userInfo "No 'Access-Control-Allow-Origin' header is present on the requested resource."

场景。

在 运行 本地开发机器上一切正常。但是当 CI 部署到开发服务器时,我们遇到了以下问题。

该站点正确重定向到登录,登录成功后我被重定向回客户端回调页面,该页面更新状态,然后尝试通过 userManager.getUser() 加载用户 这会导致以下情况:

错误信息:

加载失败https://dev-auth.mysite.com/connect/userinfo: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://dev-spa.mysite.com'因此不允许访问。响应具有 HTTP 状态代码 404。

日志

从 Seq Serilog 中我们看到以下其他调用成功。

请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/.well-known/openid-configuration/jwks 请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/.well-known/openid-configuration 请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/connect/authorize/callback?client_id=... 请求开始 HTTP/1.1 POST http://dev-auth.mysite.com/Account/Login application/x-www-form-urlencoded 643 请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/account/login?returnUrl=... 请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/connect/authorize?client_id=... 请求开始 HTTP/1.1 GET http://dev-auth.mysite.com/.well-known/openid-configuration

代码

项目是:

所有项目都是.net core 2.0。 spa 项目服务于使用 oidc-client 包的 React 应用程序。

auth.mysite.com Startup.cs

Startup 的身份服务器部分的 CofigureServices 是:

public void ConfigureServices(IServiceCollection aServiceCollection)
{
  // CorsPolicy.Any.Apply(aServiceCollection);
  aServiceCollection.AddMvc();
  //aServiceCollection.AddMvcCore()
  //  .AddJsonFormatters()
  //  .AddRazorViewEngine();

  aServiceCollection.AddSession();

  ConfigureServicesIdentityServer(aServiceCollection);

  aServiceCollection.Configure<RazorViewEngineOptions>(options =>
  {
    options.ViewLocationExpanders.Add(new ViewLocationExpander());
  });

  aServiceCollection.AddMediatR();
}

private static void ConfigureServicesIdentityServer(IServiceCollection aServiceCollection)
{
  aServiceCollection.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryApiResources(Resources.GetApiResources())
    .AddInMemoryClients(Clients.GetClients())
    .AddInMemoryIdentityResources(Resources.GetIdentityResources())
    // .AddJwtBearerClientAuthentication();
}

配置

public void Configure(
  IApplicationBuilder aApplicationBuilder,
  IHostingEnvironment aHostingEnvironment,
  IOptions<RequestLocalizationOptions> aRequestLocalizationOptions)
{
  Environment.UseExceptionPage(aHostingEnvironment, aApplicationBuilder);
  // aApplicationBuilder.UseCors(CorsPolicy.Any.DisplayName);
  aApplicationBuilder.UseIdentityServer();
  aApplicationBuilder.UseRequestLocalization(aRequestLocalizationOptions.Value);
  aApplicationBuilder.UseStaticFiles(); // Serve up static files from wwwroot images etc...
  aApplicationBuilder.UseSession();
  aApplicationBuilder.UseMvc();
}

客户端配置:

 new Client
        {
          ClientId = "someGUID",
          ClientName = "MySite",
          RequireClientSecret=false, // if false this is a public client.
          AllowedGrantTypes = GrantTypes.Implicit,
          AllowAccessTokensViaBrowser = true,
          AlwaysIncludeUserClaimsInIdToken = true,

          RedirectUris = {
            "http://local-spa.mysite.com:3000/callback",
            "https://dev-spa.mysite.com/callback",
          },
          PostLogoutRedirectUris = {
            "http://local-spa.mysite.com:3000/",
            "https://dev-spa.mysite.com/",
          },
          AllowedCorsOrigins = {
            "http://local-spa.mysite.com:3000",
            "https://dev-spa.mysite.com",
          },

          AllowedScopes =
          {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile,
            IdentityServerConstants.StandardScopes.Email,
            IdentityServerConstants.StandardScopes.Phone,
            Resources.WebOrderingApi,
          },

          RequireConsent = false,
        },

TypeScript 客户端:

import { Log, OidcClientSettings, UserManager, UserManagerSettings, } from 'oidc-client';

...

const protocol: string = window.location.protocol;
const hostname: string = window.location.hostname;
const port: string = window.location.port;

const oidcClientSettings: OidcClientSettings = {
  client_id: '46a0ab4a-1321-4d77-abe5-98f09310df0b',
  post_logout_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/`,
  redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
  response_type: 'id_token token',
  scope: 'openid profile email phone WebOrderingApi',
};

const userManagerSettings: UserManagerSettings = {
  ...oidcClientSettings,
  automaticSilentRenew: false,
  filterProtocolClaims: true,
  loadUserInfo: true,
  monitorSession: false,
  silent_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
};

更新

原来是开发IIS服务器不允许OPTIONS动词。这是在不需要 CORS 的生产服务器中推荐的做法,但我不建议在开发环境中这样做。

感谢大家的贡献。

此问题的一个修复方法是允许 IIS 中的 OPTIONS 动词用于身份验证服务器。

为什么这不起作用

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://dev-spa.mysite.com' is therefore not allowed access. The response had HTTP status code 404.

在跨源发出请求之前,会发出 CORS 请求,以检查是否应允许该请求,这是使用 OPTIONS 方法发送的,您的服务器以 404 响应,停止了请求

如何修复

您需要发回有效的 OPTIONS 响应,以允许请求。

我建议使用 Wireshark 或其他一些检查器来查看发送的请求及其响应,以帮助调试 CORS 响应停止请求的原因。

正如@Kirk 提到的,问题可能出在 HTTPS 上,要么是 CORS 请求,要么是整个服务器不支持 HTTPS。

资源

根据您的错误消息,我在最后注意到以下内容:

The response had HTTP status code 404.

这表明您的问题实际上可能不是 CORS 问题 - 可能更多是找不到您尝试访问的端点,从而导致 404 响应。当然,这个404响应不太可能包含相关的Access-Control-Allow-Origin header.

查看您的日志,所有对 dev-auth.mysite.com 的成功调用都使用 http 方案。但是,在引用 connect/userinfo 端点时,您的错误消息指的是 https 方案:

Failed to load https://dev-auth.mysite.com/connect/userinfo

根据您可以混合在 .NET Core CORS 中的文档,IdentityServer 会尊重它。在您的 Startup.cs 中,添加:

public void ConfigureServices(IServiceCollection services)
{
...
    services.AddCors(options =>
    {
        options.AddPolicy("WhateverNameYouWantHere",
            builder =>
            {
                builder.WithOrigins($"http://{_frontendUri}",
                                $"https://{_frontendUri}")
                    .AllowCredentials()
                    .AllowAnyMethod()
                    .AllowAnyHeader();
            });
    });
...
}

然后

public void Configure(IApplicationBuilder app)
{
    ...
    app.UseCors("WhateverNameYouWantHere");
}