重定向到 IdentityServer4 中的客户端时出现错误代码 500 "Unable to unprotect the message.State"
Error code 500 "Unable to unprotect the message.State" when redirecting to Client in IdentityServer4
我正在使用 IdentityServer4 并尝试手动验证我的 Asp.Net Core 3.1 客户端(手动创建请求以了解流程)。
这是我的客户登录信息:
[HttpGet]
public IActionResult ManualLogin()
{
var myNonce = Guid.NewGuid().ToString();
var myState = Guid.NewGuid().ToString();
var req = "https://localhost:5000/connect/authorize?" +
"client_id=mvc" +
"&redirect_uri=https://localhost:44381/signin-oidc" +
"&response_type=code id_token" +
"&scope=openid profile offline_access email" +
"&response_mode=form_post" +
$"&nonce={myNonce}" +
$"&state={myState}";
return Redirect(req);
}
这个登录方法很好用,一切正常,但我不想用它:
//[HttpGet]
//public async Task LoginAsync()
//{
// await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
// {
// RedirectUri = "https://localhost:44381/Home/external-login-callback"
// });
// }
我客户的startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// for using IHttpClientFactory
services.AddHttpClient();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
// adding Authentication services to DependencyInjection
services.AddAuthentication(config =>
{
config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, config =>
{
config.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.Authority = "https://localhost:5000";
config.ClientId = "mvc";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.UseTokenLifetime = false;
// Hybrid Flow
config.ResponseType = "code id_token";
config.Scope.Add("openid");
config.Scope.Add("offline_access");
config.Scope.Add("profile");
config.Scope.Add("email");
config.GetClaimsFromUserInfoEndpoint = true;
config.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName,
RoleClaimType = JwtClaimTypes.Role,
};
// config.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost;
});
services.AddControllersWithViews();
}
我的客户定义:
new Client
{
ClientId = "mvc",
ClientName ="My mvc client testing",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
// where to redirect to after login
RedirectUris = { "https://localhost:44381/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44381/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
AccessTokenType = AccessTokenType.Reference,
RequireConsent = false,
RequireClientSecret = true,
//AlwaysIncludeUserClaimsInIdToken = true,
RequirePkce = false,
}
我的 IS4 startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
客户端成功重定向到IS4登录页面,然后我可以对用户进行身份验证,当重定向回我客户端的signin-oidc
url时,我得到了500 Internal Server Error
:
Unable to unprotect the message.State.
Exception: Unable to unprotect the message.State.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
我没有多个oidc,只有一个!我错过了什么?
更新 1:@ToreNestenius 评论后:
我把请求的redirect_uri改成了:
&redirect_uri=https://localhost:44381/home/MyCallback"
并将回调添加到 IS4 客户端的配置中,这是我的回调:
[HttpPost]
[ActionName("mycallback")]
public async Task mycallbackAsync(
string code,
string scope,
string state,
string session_state,
string login_required)
{
var theRequest = $"https://localhost:5000/connect/token";
var client = _httpClientFactory.CreateClient();
var theContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("client_id","mvc"),
new KeyValuePair<string,string>("client_secret","secret"),
new KeyValuePair<string,string>("grant_type","hybrid"),
new KeyValuePair<string,string>("code",code),
new KeyValuePair<string,string>("redirect_uri", "https://localhost:5002/home/mycallback"),
});
theContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var base64StringUserPass = Convert.ToBase64String(Encoding.ASCII.GetBytes($"mvc:secret"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64StringUserPass);
var response = await client.PostAsync(req, theContent);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
var theAccessToken = JsonConvert.DeserializeObject<Token>(content);
// -----------------------------
// get user info
//var access_token = theAccessToken.access_token;
//string userInfo = await getUserInfoAsync(access_token);
}
现在我可以正确处理回调,然后发出 accessToken 并获取用户信息。
在您的代码中使用
"&redirect_uri=https://localhost:44381/signin-oidc" +
这意味着您正在尝试重定向回客户端中的 OpenIDConnect 身份验证 handler/scheme。但是它期望传入的请求包含它无法识别的状态和随机数值。因为初始身份验证请求不是来自该处理程序。
因为你想学习 OpenID-Connect 并手动完成(就像我学习时所做的那样)。我建议您将 redirectURi 更改为您自己的控制器中的操作方法。像 https://localhost:44381/test/callback"
我建议您在了解完整的手动流程之前避免涉及 OpenIDConnect 处理程序。
回调方法签名应如下所示:
/// <summary>
/// This method is called with the authorization code and state parameter
/// </summary>
/// <param name="code">authorization code generated by the authorization server. This code is relatively short-lived, typically lasting between 1 to 10 minutes depending on the OAuth service.</param>
/// <param name="state"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Callback(string code, string state)
{
//To be secure then the state parameter should be compared
to the state sent in the previous step
var url = new Url(_openIdSettings.token_endpoint);
//Get the tokens based on the code, using https://flurl.dev/docs/fluent-http/
var token = url.PostUrlEncodedAsync(new
{
client_id = "authcodeflowclient",
client_secret = "mysecret",
grant_type = "authorization_code",
code_verifier = code_verifier,
code = code,
redirect_uri = "https://localhost:5001/CodeFlow/callback"
}).ReceiveJson<Token>().Result;
return View(token);
}
我正在使用 IdentityServer4 并尝试手动验证我的 Asp.Net Core 3.1 客户端(手动创建请求以了解流程)。 这是我的客户登录信息:
[HttpGet]
public IActionResult ManualLogin()
{
var myNonce = Guid.NewGuid().ToString();
var myState = Guid.NewGuid().ToString();
var req = "https://localhost:5000/connect/authorize?" +
"client_id=mvc" +
"&redirect_uri=https://localhost:44381/signin-oidc" +
"&response_type=code id_token" +
"&scope=openid profile offline_access email" +
"&response_mode=form_post" +
$"&nonce={myNonce}" +
$"&state={myState}";
return Redirect(req);
}
这个登录方法很好用,一切正常,但我不想用它:
//[HttpGet]
//public async Task LoginAsync()
//{
// await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
// {
// RedirectUri = "https://localhost:44381/Home/external-login-callback"
// });
// }
我客户的startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// for using IHttpClientFactory
services.AddHttpClient();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
// adding Authentication services to DependencyInjection
services.AddAuthentication(config =>
{
config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, config =>
{
config.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.Authority = "https://localhost:5000";
config.ClientId = "mvc";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.UseTokenLifetime = false;
// Hybrid Flow
config.ResponseType = "code id_token";
config.Scope.Add("openid");
config.Scope.Add("offline_access");
config.Scope.Add("profile");
config.Scope.Add("email");
config.GetClaimsFromUserInfoEndpoint = true;
config.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName,
RoleClaimType = JwtClaimTypes.Role,
};
// config.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost;
});
services.AddControllersWithViews();
}
我的客户定义:
new Client
{
ClientId = "mvc",
ClientName ="My mvc client testing",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
// where to redirect to after login
RedirectUris = { "https://localhost:44381/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44381/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
AccessTokenType = AccessTokenType.Reference,
RequireConsent = false,
RequireClientSecret = true,
//AlwaysIncludeUserClaimsInIdToken = true,
RequirePkce = false,
}
我的 IS4 startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
客户端成功重定向到IS4登录页面,然后我可以对用户进行身份验证,当重定向回我客户端的signin-oidc
url时,我得到了500 Internal Server Error
:
Unable to unprotect the message.State.
Exception: Unable to unprotect the message.State.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
我没有多个oidc,只有一个!我错过了什么?
更新 1:@ToreNestenius 评论后:
我把请求的redirect_uri改成了:
&redirect_uri=https://localhost:44381/home/MyCallback"
并将回调添加到 IS4 客户端的配置中,这是我的回调:
[HttpPost]
[ActionName("mycallback")]
public async Task mycallbackAsync(
string code,
string scope,
string state,
string session_state,
string login_required)
{
var theRequest = $"https://localhost:5000/connect/token";
var client = _httpClientFactory.CreateClient();
var theContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("client_id","mvc"),
new KeyValuePair<string,string>("client_secret","secret"),
new KeyValuePair<string,string>("grant_type","hybrid"),
new KeyValuePair<string,string>("code",code),
new KeyValuePair<string,string>("redirect_uri", "https://localhost:5002/home/mycallback"),
});
theContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var base64StringUserPass = Convert.ToBase64String(Encoding.ASCII.GetBytes($"mvc:secret"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64StringUserPass);
var response = await client.PostAsync(req, theContent);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
var theAccessToken = JsonConvert.DeserializeObject<Token>(content);
// -----------------------------
// get user info
//var access_token = theAccessToken.access_token;
//string userInfo = await getUserInfoAsync(access_token);
}
现在我可以正确处理回调,然后发出 accessToken 并获取用户信息。
在您的代码中使用
"&redirect_uri=https://localhost:44381/signin-oidc" +
这意味着您正在尝试重定向回客户端中的 OpenIDConnect 身份验证 handler/scheme。但是它期望传入的请求包含它无法识别的状态和随机数值。因为初始身份验证请求不是来自该处理程序。
因为你想学习 OpenID-Connect 并手动完成(就像我学习时所做的那样)。我建议您将 redirectURi 更改为您自己的控制器中的操作方法。像 https://localhost:44381/test/callback"
我建议您在了解完整的手动流程之前避免涉及 OpenIDConnect 处理程序。
回调方法签名应如下所示:
/// <summary>
/// This method is called with the authorization code and state parameter
/// </summary>
/// <param name="code">authorization code generated by the authorization server. This code is relatively short-lived, typically lasting between 1 to 10 minutes depending on the OAuth service.</param>
/// <param name="state"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Callback(string code, string state)
{
//To be secure then the state parameter should be compared
to the state sent in the previous step
var url = new Url(_openIdSettings.token_endpoint);
//Get the tokens based on the code, using https://flurl.dev/docs/fluent-http/
var token = url.PostUrlEncodedAsync(new
{
client_id = "authcodeflowclient",
client_secret = "mysecret",
grant_type = "authorization_code",
code_verifier = code_verifier,
code = code,
redirect_uri = "https://localhost:5001/CodeFlow/callback"
}).ReceiveJson<Token>().Result;
return View(token);
}