C# 使用IdentityServer4进行身份验证时重定向错误
在客户端进行身份验证后,我得到错误: Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler 消息包含错误:“”,错误描述:“错误描述为” null',error\u uri:'error\u uri为null' 失败: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[52] 消息包含错误:'(null)',错误描述:'错误描述为null',错误uri:'错误uri为null',状态 代码“500”。失败: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[17] 处理消息时发生异常。Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: 消息包含错误:“”,错误描述:“错误描述为” null',error\u uri:'error\u uri为null'。在 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ReceiveAuthorizationCodeAsync(OpenIdConnectMessage 令牌(请求)位于 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandlerEmoteAuthenticationAsync() 失败:eCleverShopSolution.WebApp.Helpers.ErrorWrappingMiddleware[0] 处理远程登录时遇到错误。System.Exception:处理远程服务器时遇到错误 登录。--> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: 消息包含错误:“”,错误描述:“错误描述为” null',error\u uri:'error\u uri为null'。在 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ReceiveAuthorizationCodeAsync(OpenIdConnectMessage 令牌(请求)位于 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandlerEmoteAuthenticationAsync() ---内部异常堆栈跟踪结束---位于Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandlerRequestAsync() 在 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 二十三 以下是客户端项目中的代码:C# 使用IdentityServer4进行身份验证时重定向错误,c#,asp.net-core,asp.net-core-mvc,identityserver4,asp.net-core-3.1,C#,Asp.net Core,Asp.net Core Mvc,Identityserver4,Asp.net Core 3.1,在客户端进行身份验证后,我得到错误: Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler 消息包含错误:“”,错误描述:“错误描述为” null',error\u uri:'error\u uri为null' 失败: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[52] 消息包含错误:'(null)',错误描述:'错误描述为n
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>();
}
public void配置服务(IServiceCollection服务)
{
services.AddHttpClient(“后端API”).ConfigurePrimaryHttpMessageHandler(()=>
{
var handler=new-HttpClientHandler();
var-environment=environment.GetEnvironmentVariable(“ASPNETCORE_-environment”);
//if(environment==Environments.Development)
//{
//handler.ServerCertificateCostomValidationCallback=(消息、证书、链、错误)=>{return true;};
//}
handler.ServerCertificateCostomValidationCallback=(消息、证书、链、错误)=>{return true;};
返回处理程序;
});
services.AddSession(选项=>
{
options.IdleTimeout=TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly=true;
});
services.AddAuthentication(选项=>
{
options.DefaultScheme=CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme=OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,选项=>
{
options.Events=新建CookieAuthenticationEvents
{
//每次cookie中间件验证cookie时都会触发此事件,
//所以基本上在每个经过身份验证的请求中
//cookie的解密已经发生,因此我们可以访问用户声明
//和cookie属性-过期等。。
OnValidatePrincipal=异步x=>
{
//因为我们的cookie生存期是基于访问令牌1的,
//检查我们是否超过了饼干寿命的一半
var now=DateTimeOffset.UtcNow;
var timeappeased=now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining=x.Properties.ExpiresUtc.Value.Subtract(现在);
如果(时间已过>剩余时间)
{
var identity=(ClaimsIdentity)x.Principal.identity;
var accessTokenClaim=identity.FindFirst(“访问令牌”);
var refreshttokenclaim=identity.FindFirst(“刷新令牌”);
//如果我们必须刷新,从声明中获取刷新令牌,然后请求
//新的访问令牌和刷新令牌
var refreshtToken=refreshtTokenClaim.Value;
var response=wait new HttpClient().RequestRefreshTokenAsync(new RefreshTokenRequest
{
地址=配置[“授权:授权URL”],
ClientId=配置[“授权:ClientId”],
ClientSecret=配置[“授权:ClientSecret”],
RefreshToken=RefreshToken
});
如果(!response.IsError)
{
//一切正常,移除旧代币并添加新代币
identity.RemoveClaim(accessTokenClaim);
标识。RemoveClaim(refreshTokenClaim);
identity.AddClaims(新[]
{
新声明(“访问令牌”,response.AccessToken),
新索赔(“刷新令牌”,分别为
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"
}
},
}
}
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("api.eclevershop", "EClever Shop API")
};