Chuyển hướng bộ điều khiển trái phép trong ASP.NET MVC


76

Tôi có một bộ điều khiển trong ASP.NET MVC mà tôi đã hạn chế ở vai trò quản trị viên:

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

Nếu người dùng không có vai trò Quản trị điều hướng đến bộ điều khiển này, họ sẽ được chào đón bằng một màn hình trống.

Điều tôi muốn làm là chuyển hướng họ đến Chế độ xem có nội dung "bạn cần ở vai trò Quản trị viên để có thể truy cập tài nguyên này."

Một cách để làm điều này mà tôi đã nghĩ đến là kiểm tra từng phương thức hành động trên IsUserInRole () và nếu không có vai trò thì hãy trả về chế độ xem thông tin này. Tuy nhiên, tôi phải đặt điều đó trong mỗi Hành động, điều này phá vỡ nguyên tắc DRY và rõ ràng là rất cồng kềnh để duy trì.

Câu trả lời:


71

Tạo thuộc tính ủy quyền tùy chỉnh dựa trên AuthorizeAttribute và ghi đè OnAuthorization để thực hiện kiểm tra theo cách bạn muốn. Thông thường, AuthorizeAttribute sẽ đặt kết quả bộ lọc thành HttpUnauthorizedResult nếu kiểm tra ủy quyền không thành công. Thay vào đó, bạn có thể đặt nó thành ViewResult (của chế độ xem Lỗi của bạn).

CHỈNH SỬA : Tôi có một vài bài đăng trên blog chi tiết hơn:

Thí dụ:

    [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 */);
        }


    }

1
Tôi không cho rằng có một liên kết mà tôi có thể truy cập để chia nhỏ điều này thành các suy luận dễ dàng hơn một chút?
Maslow

1
Có gì không rõ ràng? Trước tiên, nó sử dụng AuthorizeCore để kiểm tra xem người dùng có được ủy quyền và ở một vai trò được phép hay không. Nếu không, nếu người dùng không được xác thực, nó sẽ trả về một phản hồi Trái phép bằng cách đặt kết quả trên ngữ cảnh của bộ lọc. Nếu nó được xác thực, nó sẽ kiểm tra xem nó có trong vai trò bổ sung của "SuperUser" (một vai trò mặc định, không được chỉ định trong thuộc tính) hay không. Nếu không, nó trả về lỗi cho biết rằng trong khi được ủy quyền, người dùng không có vai trò hợp lệ cho hành động. Khi người dùng được ủy quyền và trong một vai trò hợp lệ (hoặc superuser), nó thiết lập các chính sách bộ nhớ cache để ngăn chặn bộ nhớ đệm hạ lưu
tvanfosson

Tôi tìm thấy một câu trả lời tốt hơn ở đây: stackoverflow.com/questions/1498727/...
bluee

Nó còn lại kể rằng với giải pháp này, bạn sẽ phải "decorate" lớp hoặc phương thức mà bạn muốn kiểm soát với thuộc tính này: [MasterEventAuthorizationAttribute]
netfed

@netfed, bạn cũng có thể thêm nó dưới dạng thuộc tính toàn cục, mặc dù bạn cần thêm xử lý cho AllowAnonymousAttribute (không tồn tại khi tôi viết điều này).
tvanfosson

27

Bạn có thể làm việc với phần có thể ghi đè HandleUnauthorizedRequestbên trong tùy chỉnh của mìnhAuthorizeAttribute

Như thế này:

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" }
    });
}

Bạn cũng có thể làm điều gì đó như sau:

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

Bây giờ bạn có thể sử dụng nó trong HandleUnauthorizedRequestphương pháp của mình theo cách này:

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

10

Mã của "tvanfosson" đã cho tôi "Lỗi khi thực hiện Yêu cầu con" .. Tôi đã thay đổi Ủy quyền Bật như thế này:

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");
        }
    }

Điều này hoạt động tốt và tôi hiển thị TempData trên trang lỗi. Cảm ơn "tvanfosson" cho đoạn mã. Tôi đang sử dụng xác thực windows và _isAuthorized không là gì ngoài HttpContext.User.Identity.IsAuthenticated ...


Tuy nhiên, điều này có trả về 401 trên url mà người dùng không có quyền truy cập không?
DevDave

5

Tôi gặp vấn đề tương tự. Thay vì tìm ra mã MVC, tôi đã chọn một bản hack giá rẻ có vẻ hiệu quả. Trong lớp Global.asax của tôi:

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)

2

Vấn đề này đã khiến tôi băn khoăn trong vài ngày nay, vì vậy khi tìm ra câu trả lời phù hợp với câu trả lời của tvanfosson ở trên, tôi nghĩ sẽ đáng để nhấn mạnh phần cốt lõi của câu trả lời và giải quyết một số câu trả lời có liên quan.

Câu trả lời cốt lõi là đây, ngọt ngào và đơn giản:

filterContext.Result = new HttpUnauthorizedResult();

Trong trường hợp của tôi, tôi kế thừa từ một bộ điều khiển cơ sở, vì vậy trong mỗi bộ điều khiển kế thừa từ nó, tôi ghi đè OnAuthorize:

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

Vấn đề là trong 'YourAuth', tôi đã thử hai thứ mà tôi nghĩ không chỉ hoạt động mà còn chấm dứt yêu cầu ngay lập tức. Chà, đó không phải là cách nó hoạt động. Vì vậy, đầu tiên, hai điều KHÔNG hoạt động, ngoài mong đợi:

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

Những điều đó không chỉ không hoạt động mà còn không kết thúc yêu cầu. Có nghĩa là sau:

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

Chà, ngay cả với câu trả lời đúng ở trên, dòng logic vẫn tiếp tục! Bạn vẫn sẽ nhấn DoMoreStuff ... trong OnAuthorize. Vì vậy, hãy ghi nhớ điều đó (DoMore ... nên ở trong một cái khác).

Nhưng với câu trả lời chính xác, trong khi luồng logic OnAuthorize vẫn tiếp tục cho đến cuối cùng, sau đó bạn thực sự nhận được những gì bạn mong đợi: chuyển hướng đến trang đăng nhập của bạn (nếu bạn có một bộ trong Forms auth trong webconfig của mình).

Nhưng thật bất ngờ, 1) Response.Redirect ("/ Login") không hoạt động: phương thức Hành động vẫn được gọi và 2) FormsAuthentication.RedirectToLoginPage (); làm điều tương tự: phương thức Hành động vẫn được gọi!

Điều này dường như hoàn toàn sai đối với tôi, đặc biệt là với cái sau: ai có thể nghĩ rằng FormsAuthentication.RedirectToLoginPage không kết thúc yêu cầu hoặc làm tương đương ở trên những gì filterContext.Result = new HttpUnauthorizedResult () không?


1

Bạn nên xây dựng thuộc tính Ủy quyền-bộ lọc của riêng mình.

Đây là của tôi để nghiên cứu;)

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

Điều này dường như chuyển hướng, nhưng nó cũng dường như đang chạy toàn bộ trên phương thức hành động ban đầu trước tiên.
Mike Cole

Thay vì thực hiện một chuyển hướng, bạn nên làmfilterContext.Result = new RedirectResult(loginUrl)
Mike Cole

1

Sẽ để lại điều này như một bình luận nhưng tôi cần thêm đại diện, dù sao thì tôi chỉ muốn đề cập với Nicholas Peterson rằng có lẽ việc chuyển đối số thứ hai cho lệnh gọi Chuyển hướng để yêu cầu nó kết thúc phản hồi sẽ có tác dụng. Không phải là cách dễ dàng nhất để xử lý điều này nhưng nó thực sự hoạt động.

Vì thế

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

thay vì

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

Vì vậy, bạn sẽ có cái này trong bộ điều khiển của mình:

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

1

Có lẽ bạn nhận được một trang trống khi bạn chạy từ Visual Studio trong máy chủ phát triển bằng cách sử dụng xác thực Windows ( chủ đề trước ).

Nếu bạn triển khai tới IIS, bạn có thể định cấu hình các trang lỗi tùy chỉnh cho các mã trạng thái cụ thể, trong trường hợp này là 401. Thêm httpErrors trong system.webServer:

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

Sau đó tạo phương thức ErrorController.Unauthorized và chế độ xem tùy chỉnh tương ứng.


-1

Trong tệp Startup.Auth.cs của bạn, hãy thêm dòng này:

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

Thí dụ:

// 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))
    }
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.