使用IdentityServer4进行身份验证时发生错误重定向

时间:2020-09-05 02:29:42

标签: c# asp.net-core asp.net-core-mvc identityserver4 asp.net-core-3.1

在客户端中进行身份验证后,出现错误:

Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler 消息包含错误:“”,错误说明:“ error_description is null”,error_uri:“ error_uri为null”。

失败: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler [52] 消息包含错误:“(null)”,error_description:“ error_description为空”,error_uri:“ error_uri为空”,状态 代码“ 500”。失败: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler [17] 处理消息时发生异常。 Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: 消息包含错误:“”,错误说明:“ error_description is null”,error_uri:“ error_uri为null”。在 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync() 失败:eCleverShopSolution.WebApp.Helpers.ErrorWrappingMiddleware [0] 处理远程登录时遇到错误。 System.Exception:处理遥控器时遇到错误 登录。 ---> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: 消息包含错误:“”,错误说明:“ error_description is null”,error_uri:“ error_uri为null”。在 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest) Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync() ---内部异常堆栈跟踪的结尾---在Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync() 在 Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext 上下文) eCleverShopSolution.WebApp.Helpers.ErrorWrappingMiddleware.Invoke(HttpContext 上下文)在D:\ CODE \ Web中 开发人员\ ASP.NET \ eCleverShopSolution \ src \ eCleverShopSolution.WebApp \ Helpers \ ErrorWrappingMiddleware.cs:line 23

这是客户端项目中的代码:

    public void ConfigureServices(IServiceCollection services)
    {     
        services.AddHttpClient("BackendApi").ConfigurePrimaryHttpMessageHandler(() =>
        {
            var handler = new HttpClientHandler();
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

            //if (environment == Environments.Development)
            //{
            //    handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
            //}
            handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
            return handler;
        });
        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(30);
            options.Cookie.HttpOnly = true;
        });
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                options.Events = new CookieAuthenticationEvents
                {
                    // this event is fired everytime the cookie has been validated by the cookie middleware,
                    // so basically during every authenticated request
                    // the decryption of the cookie has already happened so we have access to the user claims
                    // and cookie properties - expiration, etc..
                    OnValidatePrincipal = async x =>
                     {
                        // since our cookie lifetime is based on the access token one,
                        // check if we're more than halfway of the cookie lifetime
                        var now = DateTimeOffset.UtcNow;
                         var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
                         var timeRemaining = x.Properties.ExpiresUtc.Value.Subtract(now);

                         if (timeElapsed > timeRemaining)
                         {
                             var identity = (ClaimsIdentity)x.Principal.Identity;
                             var accessTokenClaim = identity.FindFirst("access_token");
                             var refreshTokenClaim = identity.FindFirst("refresh_token");

                            // if we have to refresh, grab the refresh token from the claims, and request
                            // new access token and refresh token
                            var refreshToken = refreshTokenClaim.Value;
                             var response = await new HttpClient().RequestRefreshTokenAsync(new RefreshTokenRequest
                             {
                                 Address = Configuration["Authorization:AuthorityUrl"],
                                 ClientId = Configuration["Authorization:ClientId"],
                                 ClientSecret = Configuration["Authorization:ClientSecret"],
                                 RefreshToken = refreshToken
                             });

                             if (!response.IsError)
                             {
                                // everything went right, remove old tokens and add new ones
                                identity.RemoveClaim(accessTokenClaim);
                                 identity.RemoveClaim(refreshTokenClaim);

                                 identity.AddClaims(new[]
                                 {
                                    new Claim("access_token", response.AccessToken),
                                    new Claim("refresh_token", response.RefreshToken)
                                  });

                                // indicate to the cookie middleware to renew the session cookie
                                // the new lifetime will be the same as the old one, so the alignment
                                // between cookie and access token is preserved
                                x.ShouldRenew = true;
                             }
                         }
                     }
                };
            })
             .AddOpenIdConnect("oidc", options =>
             {
                 options.Authority = Configuration["Authorization:AuthorityUrl"];
                 options.RequireHttpsMetadata = false;
                 options.GetClaimsFromUserInfoEndpoint = true;

                 options.ClientId = Configuration["Authorization:ClientId"];
                 options.ClientSecret = Configuration["Authorization:ClientSecret"];
                 options.ResponseType = "code";

                 options.SaveTokens = true;

                 options.Scope.Add("openid");
                 options.Scope.Add("profile");
                 options.Scope.Add("offline_access");
                 options.Scope.Add("api.eclevershop");

                 options.TokenValidationParameters = new TokenValidationParameters
                 {
                     NameClaimType = "name",
                     RoleClaimType = "role"
                 };
                 options.Events = new OpenIdConnectEvents
                 {
                     // that event is called after the OIDC middleware received the auhorisation code,
                     // redeemed it for an access token and a refresh token,
                     // and validated the identity token
                     OnTokenValidated = x =>
                      {
                         // store both access and refresh token in the claims - hence in the cookie
                         var identity = (ClaimsIdentity)x.Principal.Identity;
                          identity.AddClaims(new[]
                          {
                            new Claim("access_token", x.TokenEndpointResponse.AccessToken),
                            new Claim("refresh_token", x.TokenEndpointResponse.RefreshToken)
                          });

                         // so that we don't issue a session cookie but one with a fixed expiration
                         x.Properties.IsPersistent = true;

                         // align expiration of the cookie with expiration of the
                         // access token
                         var accessToken = new JwtSecurityToken(x.TokenEndpointResponse.AccessToken);
                          x.Properties.ExpiresUtc = accessToken.ValidTo;

                          return Task.CompletedTask;
                      }
                 };
             });

        var builder = services.AddControllersWithViews();

        var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (environment == Environments.Development)
        {
            builder.AddRazorRuntimeCompilation();
        }

        //Declare DI containers
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    }

这是API项目中的配置:

public class Config
{
    public static IEnumerable<IdentityResource> Ids =>
      new IdentityResource[]
      {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile()
      };

    public static IEnumerable<ApiResource> Apis =>
        new ApiResource[]
        {
            new ApiResource("api.eclevershop", "EClever Shop API")
        };

    public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client
            {
                ClientId = "webportal",
                ClientSecrets = { new Secret("secret".Sha256()) },

                AllowedGrantTypes = GrantTypes.Code,
                RequireConsent = false,
                RequirePkce = true,
                AllowOfflineAccess = true,

                // where to redirect to after login
                RedirectUris = { "https://localhost:5002/signin-oidc" },

                // where to redirect to after logout
                PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },

                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "api.eclevershop"
                }
             },
        }
   }

任何人都可以帮助我。非常感谢。

2 个答案:

答案 0 :(得分:0)

如果您使用的是IdentityServer4 4.0x版,则应更改此代码

public static IEnumerable<ApiResource> Apis =>
    new ApiResource[]
    {
        new ApiResource("api.eclevershop", "EClever Shop API")
    };

要改为使用ApiScopes,您可以阅读有关API范围here的更多信息。 在V4.0x中,您要求客户端提供ApiScope,而不是ApiResources。

答案 1 :(得分:0)

当 OIDC 客户端尝试交换代码以获取访问令牌但 IdentityServer4 引发内部服务器错误时,这是​​一个非常奇怪的错误消息。它需要您检查 IdentityServer4 日志文件以查看实际错误是什么。