问题
I am running an Asp.Net Core 2.0 with Asp.Net Boilerplate version 3.4.0 web application.
When I have an authenticated user that does not have the required permissions to access a resource, a ChallengeResult
is set by the AbpAuthorizationFilter
via the Abp framework. This behavior results in the user being returned to the default login page. If the user is authenticated, I would like to set a ForbidResult
and redirect them to the default AccessDenied page.
After reviewing my options, I see that I have the following options:
- Add my own authorization filter and register with the
MvcOptions
prior to theAddAbp()
service configuration. The filter would conditionally set the result to Challenge or Forbid - Override the
OnRedirectToLogin
event ( i.e., customize for authenticated users ) - Override the
HandleChallengeAsync
handler ( i.e., check to see if the request is authenticated and issue ForbidResult or ChallengeResult for unauthenticated requests)
I noticed in the .Net framework version of ABP, that there are overridable methods in the AbpMvcAuthorizeFilter
(i.e., HandleUnauthorizedRequest
) ; however, this does not apply to the .Net Core version. See GitHub Issue 1256 ->
Make AbpMvcAuthorizeFilter and AbpApiAuthorizeFilter overridable
Question:
Has anyone else needed to change the default Abp behavior of returning a ChallengeResult
for unauthorized requests? If yes, what solution did you use? Did I missing something in the Abp configuration or Asp.Net Core ( other than the three options listed above) that would provide me more control of this behavior?
If I go with a workaround, it feels kind of a like a hack to control this behavior.
Option 1:
Out of the three options that I listed, option one seems the cleanest and most appropriate place to handle this logic. It's not pretty either as I would be copying the entire AbpAuthorizationFilter
to only change a few lines of code.
Current Code:
context.Result = new ChallengeResult();
Proposed Change:
if (context.HttpContext.User.Identity.IsAuthenticated)
{
//User is already logged in.. No need to redirect to the
//login page
context.Result = new ForbidResult();
}
else
{
context.Result = new ChallengeResult();
}
Full code below:
AbpAuthorizationFilter.cs See Catch block lines - 58 - 77
Option 2:
Option two feels kludgy because the logic that I would introduce into the OnRedirectToLogin
event would have to make an assumption that an authenticated user has attempted to access an unauthorized resource. Currently, I only see the Events.RedirectToLogin
being raised in the CookieAuthenticationHandler
via the HandleChallengeAsync
method. With that being said it feels safe to assume that this event would only be raised by a ChallengeResult
result. CookieAuthenticationHandler.cs
Option 3:
Option three would be the last option( i.e., avoided at all costs)
Main Goal
The main goal is to provide a better experience for authenticated users attempting to access unauthorized resources. Rather than redirecting the user to the login page, the user should be redirected to an unauthorized / forbidden page that clearly indicates that they are not authorized. This might include an option to prompt the user to provide higher privileged credentials. By prompting the user, it smells like a ChallengeResult
result flow so maybe I just answered my own question. With the current behavior, I don't have much context information on "why" the ChallengeResult
was issued. I will know that the user is logged in and that the OnRedirectToLogin
event was raised. This is probably enough info to customize the behavior of the ChallengeResult
for authenticated users. This is starting to feel like this is the correct solution. Any suggestions or feedback on using this approach?
回答1:
Out of the three options, I went with option number two ( override the OnRedirectToLogin
event) for the following reasons:
- Minimal code introduced without duplicating an entire authorization filter to only change two lines of code.
- More control of the challenge experience ( the MVC app is an OIDC client too ) with its own set of roles and permissions ( see code solution below )
- At the time of this response, the
OnRedirectToLogin
event is only raised by theHandleChallengeAsync
in the CookieAuthenticationHandler so this feels like the correct place to override theChallengeResult
behavior.
Solution:
options.Events.OnRedirectToLogin = context =>
{
if (context.HttpContext?.User?.Identity?.IsAuthenticated == false)
{
//The user is not authenticated... Use the "oidc" challenge scheme
//and send them to identity server.
var task = context.HttpContext.ChallengeAsync("oidc");
task.WaitAndUnwrapException();
return Task.CompletedTask;
}
var accessDeniedPath = BuildRedirectUri(context.HttpContext, options.AccessDeniedPath);
context.Response.Redirect(accessDeniedPath);
context.Response.StatusCode = 302;
return Task.CompletedTask;
};
Side Note:
It should be noted that the default behavior of the AbpAuthorizationFilter
does not mirror the stock behavior of Asp.Net Core MVC 2.0 AuthorizeFilter
. When authorization fails for an authenticated user, the Asp.Net Core MVC 2.0 AuthorizeFilter
returns a Forbid result.
Asp.Net Core MVC's default AuthorizeFilter
delegates the authorization to the IPolicyEvaluator
. If the authorization fails and the user is authenticated, a Forbid result is set or if the authorization fails and the user is not-authenticated, a Challenge result is set.
PolicyEvaluator.cs
var result = await _authorization.AuthorizeAsync(context.User, resource, policy);
if (result.Succeeded)
{
return PolicyAuthorizationResult.Success();
}
// If authentication was successful, return forbidden, otherwise challenge
return (authenticationResult.Succeeded)
? PolicyAuthorizationResult.Forbid()
: PolicyAuthorizationResult.Challenge();
来源:https://stackoverflow.com/questions/51027406/asp-net-boilerplate-net-core-2-0-abpauthorizationfilter-challengeresult-una