从外部 URL 重定向回导致无限循环
Redirect back from external URL resulting in endless loop
我有一个调用身份验证服务器的 Blazor 服务器端应用程序。我拥有 auth 服务器(也是一个 ASP.NET Core WebApp),它基于 OpenIdDict。我可以成功生成授权码,因为我已经使用 oidcdebugger.com.
对其进行了测试
授权服务器重定向回应用程序,以便应用程序完成授权代码流程,用户可以访问应用程序。
我做的第一件事是 MainLayout.razor
中的以下内容:
@inherits LayoutComponentBase
<AuthorizeView>
<NotAuthorized>
<GetAuthorizationCode/>
</NotAuthorized>
<Authorized>
<Header/>
<NavMenu/>
</Authorized>
</AuthorizeView>
@code {
}
GetAuthorizationCode
是一个简单的组件,它是:
public class GetAuthorizationCode: ComponentBase
{
[Inject]
protected IAuthService AuthService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
AuthService.GetAuthorizationCode();
}
}
并且服务准备要发送到身份验证服务器的请求:
public void GetAuthorizationCode()
{
var url = "urltoauthserver";
var parameters = new Dictionary<string, string>()
{
{ "client_id", "hereclientid" },
{ "redirect_uri", "hereredirecturi" },
{ "scope", "openid email profile" },
{ "response_type", "code" },
{ "response_mode", "form_post" },
{ "state", Guid.NewGuid().ToString() },
{ "nonce", "herenonce" }
};
url = QueryHelpers.AddQueryString(url, parameters);
navigationManager.NavigateTo(url);
}
重定向 uri 是 Blazor 应用程序本身内的 api 控制器(不是另一个外部 api)。我需要使用 api 控制器,因为重定向 uri 是 POST 并且看起来像这样:
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class LoginController : ControllerBase
{
[HttpPost]
public IActionResult Callback([FromForm] string code, [FromForm] string state)
{
return Ok($"Login callback. Access token: {code}");
}
}
流程应该是,一旦 auth 服务器对用户进行身份验证并将授权代码发送回重定向 uri,上面的操作应该创建一个带有 authorizatoin 代码的请求以发送到 auth 服务器并接收访问权限令牌。
在startup.cs
中我还配置了OpenIdConnect如下:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ClientId = "hereclientid";
options.ClientSecret = "hereclientsecret";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Authority = "authserver";
options.Scope.Add("email");
options.Scope.Add("profile");
options.SecurityTokenValidator = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
options.TokenValidationParameters.NameClaimType = "name";
});
问题是永远不会执行该操作,因为在重定向过程中会一遍又一遍地调用 GetAuthorizationCode
。
编辑
如果重定向是 GET,则会发生无限循环。如果重定向是 POST,我会收到 400 Bad Request,这是日志:
2021-02-04 16:56:19.628 +01:00 [INF] Request starting HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91
2021-02-04 16:56:19.628 +01:00 [INF] CORS policy execution successful.
2021-02-04 16:56:19.629 +01:00 [INF] Executing endpoint '/_Host'
2021-02-04 16:56:19.629 +01:00 [INF] Route matched with {page = "/_Host", action = "", controller = ""}. Executing page /_Host
2021-02-04 16:56:19.639 +01:00 [INF] Antiforgery token validation failed. The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".
at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
2021-02-04 16:56:19.671 +01:00 [INF] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter'.
2021-02-04 16:56:19.674 +01:00 [INF] Executing HttpStatusCodeResult, setting HTTP status code 400
2021-02-04 16:56:19.674 +01:00 [INF] Executed page /_Host in 45.2108ms
2021-02-04 16:56:19.674 +01:00 [INF] Executed endpoint '/_Host'
2021-02-04 16:56:19.675 +01:00 [INF] Request finished HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91 - 400 - - 47.2169ms
我为 POST 操作添加了 [IgnoreAntiForgeryToken]
,但它不起作用
当用户尝试访问受 Authorize 属性保护的资源时呈现该组件。组件中的代码导航到外部身份验证服务器,我认为它会生成授权代码,无论它是什么...
外部身份验证服务器应该重定向回调用代码,将授权代码传递给它...这意味着您应该使用您希望他发回的地址 (returnUrl) 调用外部 url授权码。我不确定你所说的授权码是什么意思,但说它是一个 Jwt 令牌;因此,您应该在 GetAuthorizationCode 中包含接受 Jwt 令牌的代码(通常在服务而不是组件中完成),并将其存储在本地存储中(以供将来使用)。请记住,用户正在尝试访问受保护的资源,比如说他正在尝试访问 FetchData 页面......在这种情况下,FetchData 页面中的代码应该从本地存储中读取 Jwt 令牌(授权代码)并将其添加到授权中header 的 HttpRequestMessage object 用于从 Web Api 端点请求数据。您的 Web Api 端点必须使用调用授权服务的授权属性进行保护,以检查用户是否有权访问该资源...
外部 url 永远不应 re-direct 到 Web Api 端点。它应该重定向到调用它的代码,向它传递授权代码(Jwt 令牌)。
获取此代码后,您的 Blazor 代码可能会调用 Web Api 端点并将授权代码(Jwt 令牌)传递给它
更新 1:
此代码:navigationManager.NavigateTo(url);
我认为应该是:
navigationManager.NavigateTo(url, forceLoad:true);
注意:第二个参数强制 Blazor 导航出 SPA space。尝试上面的代码并报告更改...这可能会被证明是有意义的。
注意:以下是我关于 OIDC、server-side Blazor 等的回答的链接。我建议您阅读它们,从中复制代码等。您可以从中获益良多,因为他们都经过我的测试。我有很多与 Blazor 中的身份验证、授权相关的答案,寻找它们并帮助自己。它们可以让你在丛林中的生活更加舒适...
我有一个调用身份验证服务器的 Blazor 服务器端应用程序。我拥有 auth 服务器(也是一个 ASP.NET Core WebApp),它基于 OpenIdDict。我可以成功生成授权码,因为我已经使用 oidcdebugger.com.
对其进行了测试授权服务器重定向回应用程序,以便应用程序完成授权代码流程,用户可以访问应用程序。
我做的第一件事是 MainLayout.razor
中的以下内容:
@inherits LayoutComponentBase
<AuthorizeView>
<NotAuthorized>
<GetAuthorizationCode/>
</NotAuthorized>
<Authorized>
<Header/>
<NavMenu/>
</Authorized>
</AuthorizeView>
@code {
}
GetAuthorizationCode
是一个简单的组件,它是:
public class GetAuthorizationCode: ComponentBase
{
[Inject]
protected IAuthService AuthService { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
AuthService.GetAuthorizationCode();
}
}
并且服务准备要发送到身份验证服务器的请求:
public void GetAuthorizationCode()
{
var url = "urltoauthserver";
var parameters = new Dictionary<string, string>()
{
{ "client_id", "hereclientid" },
{ "redirect_uri", "hereredirecturi" },
{ "scope", "openid email profile" },
{ "response_type", "code" },
{ "response_mode", "form_post" },
{ "state", Guid.NewGuid().ToString() },
{ "nonce", "herenonce" }
};
url = QueryHelpers.AddQueryString(url, parameters);
navigationManager.NavigateTo(url);
}
重定向 uri 是 Blazor 应用程序本身内的 api 控制器(不是另一个外部 api)。我需要使用 api 控制器,因为重定向 uri 是 POST 并且看起来像这样:
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class LoginController : ControllerBase
{
[HttpPost]
public IActionResult Callback([FromForm] string code, [FromForm] string state)
{
return Ok($"Login callback. Access token: {code}");
}
}
流程应该是,一旦 auth 服务器对用户进行身份验证并将授权代码发送回重定向 uri,上面的操作应该创建一个带有 authorizatoin 代码的请求以发送到 auth 服务器并接收访问权限令牌。
在startup.cs
中我还配置了OpenIdConnect如下:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ClientId = "hereclientid";
options.ClientSecret = "hereclientsecret";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Authority = "authserver";
options.Scope.Add("email");
options.Scope.Add("profile");
options.SecurityTokenValidator = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
options.TokenValidationParameters.NameClaimType = "name";
});
问题是永远不会执行该操作,因为在重定向过程中会一遍又一遍地调用 GetAuthorizationCode
。
编辑 如果重定向是 GET,则会发生无限循环。如果重定向是 POST,我会收到 400 Bad Request,这是日志:
2021-02-04 16:56:19.628 +01:00 [INF] Request starting HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91 2021-02-04 16:56:19.628 +01:00 [INF] CORS policy execution successful. 2021-02-04 16:56:19.629 +01:00 [INF] Executing endpoint '/_Host' 2021-02-04 16:56:19.629 +01:00 [INF] Route matched with {page = "/_Host", action = "", controller = ""}. Executing page /_Host 2021-02-04 16:56:19.639 +01:00 [INF] Antiforgery token validation failed. The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken". Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken". at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context) 2021-02-04 16:56:19.671 +01:00 [INF] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter'. 2021-02-04 16:56:19.674 +01:00 [INF] Executing HttpStatusCodeResult, setting HTTP status code 400 2021-02-04 16:56:19.674 +01:00 [INF] Executed page /_Host in 45.2108ms 2021-02-04 16:56:19.674 +01:00 [INF] Executed endpoint '/_Host' 2021-02-04 16:56:19.675 +01:00 [INF] Request finished HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91 - 400 - - 47.2169ms
我为 POST 操作添加了 [IgnoreAntiForgeryToken]
,但它不起作用
当用户尝试访问受 Authorize 属性保护的资源时呈现该组件。组件中的代码导航到外部身份验证服务器,我认为它会生成授权代码,无论它是什么...
外部身份验证服务器应该重定向回调用代码,将授权代码传递给它...这意味着您应该使用您希望他发回的地址 (returnUrl) 调用外部 url授权码。我不确定你所说的授权码是什么意思,但说它是一个 Jwt 令牌;因此,您应该在 GetAuthorizationCode 中包含接受 Jwt 令牌的代码(通常在服务而不是组件中完成),并将其存储在本地存储中(以供将来使用)。请记住,用户正在尝试访问受保护的资源,比如说他正在尝试访问 FetchData 页面......在这种情况下,FetchData 页面中的代码应该从本地存储中读取 Jwt 令牌(授权代码)并将其添加到授权中header 的 HttpRequestMessage object 用于从 Web Api 端点请求数据。您的 Web Api 端点必须使用调用授权服务的授权属性进行保护,以检查用户是否有权访问该资源...
外部 url 永远不应 re-direct 到 Web Api 端点。它应该重定向到调用它的代码,向它传递授权代码(Jwt 令牌)。 获取此代码后,您的 Blazor 代码可能会调用 Web Api 端点并将授权代码(Jwt 令牌)传递给它
更新 1:
此代码:navigationManager.NavigateTo(url);
我认为应该是:
navigationManager.NavigateTo(url, forceLoad:true);
注意:第二个参数强制 Blazor 导航出 SPA space。尝试上面的代码并报告更改...这可能会被证明是有意义的。
注意:以下是我关于 OIDC、server-side Blazor 等的回答的链接。我建议您阅读它们,从中复制代码等。您可以从中获益良多,因为他们都经过我的测试。我有很多与 Blazor 中的身份验证、授权相关的答案,寻找它们并帮助自己。它们可以让你在丛林中的生活更加舒适...