Program Tip

ASP.NET MVC에서 권한이없는 컨트롤러 리디렉션

programtip 2020. 10. 24. 11:39
반응형

ASP.NET MVC에서 권한이없는 컨트롤러 리디렉션


ASP.NET MVC에 관리자 역할로 제한 한 컨트롤러가 있습니다.

[Authorize(Roles = "Admin")]
public class TestController : Controller
{
   ...

관리자 역할이 아닌 사용자가이 컨트롤러로 이동하면 빈 화면이 표시됩니다.

제가하고 싶은 것은 "이 리소스에 액세스하려면 관리자 역할에 있어야합니다."라고 표시된 View로 리디렉션하는 것입니다.

이 작업을 수행하는 한 가지 방법은 IsUserInRole ()에서 각 작업 메서드를 확인하고 역할이 아닌 경우이 정보보기를 반환하는 것입니다. 그러나 DRY 원칙을 위반하고 유지 관리가 번거로운 각 Action에이를 넣어야합니다.


AuthorizeAttribute를 기반으로 사용자 지정 권한 부여 특성을 만들고 OnAuthorization을 재정 의하여 원하는 방식으로 확인합니다. 일반적으로 AuthorizeAttribute는 권한 확인이 실패하면 필터 결과를 HttpUnauthorizedResult로 설정합니다. 대신 ViewResult (오류보기의)로 설정할 수 있습니다.

편집 : 더 자세히 설명하는 블로그 게시물이 몇 개 있습니다.

예:

    [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
    public class MasterEventAuthorizationAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// The name of the master page or view to use when rendering the view on authorization failure.  Default
        /// is null, indicating to use the master page of the specified view.
        /// </summary>
        public virtual string MasterName { get; set; }

        /// <summary>
        /// The name of the view to render on authorization failure.  Default is "Error".
        /// </summary>
        public virtual string ViewName { get; set; }

        public MasterEventAuthorizationAttribute()
            : base()
        {
            this.ViewName = "Error";
        }

        protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
        {
            validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
        }

        public override void OnAuthorization( AuthorizationContext filterContext )
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException( "filterContext" );
            }

            if (AuthorizeCore( filterContext.HttpContext ))
            {
                SetCachePolicy( filterContext );
            }
            else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
            }
            else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ))
            {
                // is authenticated and is in the SuperUser role
                SetCachePolicy( filterContext );
            }
            else
            {
                ViewDataDictionary viewData = new ViewDataDictionary();
                viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
                filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
            }

        }

        protected void SetCachePolicy( AuthorizationContext filterContext )
        {
            // ** IMPORTANT **
            // Since we're performing authorization at the action level, the authorization code runs
            // after the output caching module. In the worst case this could allow an authorized user
            // to cause the page to be cached, then an unauthorized user would later be served the
            // cached page. We work around this by telling proxies not to cache the sensitive page,
            // then we hook our custom authorization code into the caching mechanism so that we have
            // the final say on whether a page should be served from the cache.
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
            cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
        }


    }

HandleUnauthorizedRequest사용자 정의 내 에서 재정의 가능한 작업을 할 수 있습니다.AuthorizeAttribute

이렇게 :

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    // Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
    filterContext.Result = new RedirectToRouteResult(
    new RouteValueDictionary 
    {
        { "action", "YourActionName" },
        { "controller", "YourControllerName" },
        { "parameterName", "YourParameterValue" }
    });
}

다음과 같이 할 수도 있습니다.

private class RedirectController : Controller
{
    public ActionResult RedirectToSomewhere()
    {
        return RedirectToAction("Action", "Controller");
    }
}

이제 다음과 같이 HandleUnauthorizedRequest방법 에서 사용할 수 있습니다 .

filterContext.Result = (new RedirectController()).RedirectToSomewhere();

"tvanfosson"의 코드는 "하위 요청을 실행하는 동안 오류가 발생했습니다."를 제공했습니다. OnAuthorization을 다음과 같이 변경했습니다.

public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if (!_isAuthorized)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") ||  filterContext.HttpContext.User.IsInRole("Manager"))
        {
            // is authenticated and is in one of the roles 
            SetCachePolicy(filterContext);
        }
        else
        {
            filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
            filterContext.Result = new RedirectResult("~/Error");
        }
    }

이것은 잘 작동하며 오류 페이지에 TempData를 표시합니다. 코드 스 니펫에 대한 "tvanfosson"에게 감사드립니다. Windows 인증을 사용하고 있으며 _isAuthorized는 HttpContext.User.Identity.IsAuthenticated ...


나는 같은 문제가 있었다. MVC 코드를 알아내는 대신 작동하는 것처럼 보이는 저렴한 해킹을 선택했습니다. 내 Global.asax 클래스에서 :

member x.Application_EndRequest() =
  if x.Response.StatusCode = 401 then 
      let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
      if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
          x.Response.Redirect("/Login/Admin/" + redir)
      else
          x.Response.Redirect("/Login/Login/" + redir)

이 문제는 며칠 동안 저를 괴롭 혔으므로 위의 tvanfosson의 답변과 긍정적으로 작동하는 답변을 찾았을 때 답변의 핵심 부분을 강조하고 관련된 몇 가지 문제를 해결하는 것이 가치가 있다고 생각했습니다.

핵심 대답은 다음과 같습니다. 달콤하고 간단합니다.

filterContext.Result = new HttpUnauthorizedResult();

제 경우에는 기본 컨트롤러에서 상속하므로이를 상속하는 각 컨트롤러에서 OnAuthorize를 재정의합니다.

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    base.OnAuthorization(filterContext);
    YourAuth(filterContext); // do your own authorization logic here
}

문제는 'YourAuth'에서 작동 할뿐만 아니라 요청을 즉시 종료 할 것이라고 생각한 두 가지 시도를했습니다. 글쎄, 그것이 작동하는 방식이 아닙니다. 따라서 먼저 예기치 않게 작동하지 않는 두 가지가 있습니다.

filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!

작동하지 않을뿐만 아니라 요청도 종료하지 않습니다. 이는 다음을 의미합니다.

if (!success) {
    filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();

위의 정답이 있어도 논리의 흐름은 계속됩니다! OnAuthorize 내에서 계속 DoMoreStuff ...를 누르십시오. 그래서 그것을 명심하십시오 (따라서 DoMore ...는 else에 있어야합니다).

그러나 정답을 사용하면 OnAuthorize 논리 흐름이 끝까지 계속되는 동안 실제로 예상 한 결과를 얻을 수 있습니다. 로그인 페이지로 리디렉션합니다 (webconfig에서 Forms 인증에 하나가 설정되어있는 경우).

But unexpectedly, 1) Response.Redirect("/Login") does not work: the Action method still gets called, and 2) FormsAuthentication.RedirectToLoginPage(); does the same thing: the Action method still gets called!

Which seems totally wrong to me, particularly with the latter: who would have thought that FormsAuthentication.RedirectToLoginPage does not end the request, or do the equivalant above of what filterContext.Result = new HttpUnauthorizedResult() does?


You should build your own Authorize-filter attribute.

Here's mine to study ;)

Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
    Private _role As String

    Public Property Role() As String
        Get
            Return Me._role
        End Get
        Set(ByVal value As String)
            Me._role = value
        End Set
    End Property

    Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
        If Not String.IsNullOrEmpty(Me.Role) Then
            If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
                Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
                Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
                Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl

                filterContext.HttpContext.Response.Redirect(loginUrl, True)
            Else
                Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
                If Not hasAccess Then
                    Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
                End If
            End If
        Else
            Throw New InvalidOperationException("No Role Specified")
        End If

    End Sub
End Class

Would have left this as a comment but I need more rep, anyways I just wanted to mention to Nicholas Peterson that perhaps passing the second argument to the Redirect call to tell it to end the response would have worked. Not the most graceful way to handle this but it does in fact work.

So

filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);

instead of

filterContext.RequestContext.HttpContext.Response.Redirect("/Login);

So you'd have this in your controller:

 protected override void OnAuthorization(AuthorizationContext filterContext)
 {
      if(!User.IsInRole("Admin")
      {
          base.OnAuthorization(filterContext);
          filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
      }
 }

Perhaps you get a blank page when you run from Visual Studio under development server using Windows authentication (previous topic).

If you deploy to IIS you can configure custom error pages for specific status codes, in this case 401. Add httpErrors under system.webServer:

<httpErrors>
  <remove statusCode="401" />
  <error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>

Then create ErrorController.Unauthorized method and corresponding custom view.


In your Startup.Auth.cs file add this line:

LoginPath = new PathString("/Account/Login"),

Example:

// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
        validateInterval: TimeSpan.FromMinutes(30),
        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});

참고URL : https://stackoverflow.com/questions/977071/redirecting-unauthorized-controller-in-asp-net-mvc

반응형